嗨,我正在尝试使用链接列表进行一些练习。
我定义了一个名为 Student
的对象类:
public class Student
{
protected string Student_Name;
protected int Student_ID;
protected int Student_Mark;
protected char Student_Grade;
public Student() // default Constructor
{
Student_Name = " ";
Student_ID = 0;
Student_Mark = 0;
Student_Grade = ' ';
}
public Student(string Sname, int Sid, int Smark, char Sgrade) // Constructor
{
int len = sname.Length;
Student_Name = sname.Substring(0, len);
//Student_Name = sname.Substring(0, sname.Length);
Student_ID = Sid;
Student_Mark = Smark;
Student_Grade = Sgrade;
}
}
然后是
Node
类:public class S_Node
{
public Student Element;
public S_Node Link;
public S_Node()
{
Element = new Student();
Link = null;
}
public Node(Student theElement)
{
Element = theElement;
Link = null;
}
}
和
LinkedList
:public class S_LinkedList
{
protected S_Node header;
protected S_Node tail;
public S_LinkedList()
{
header = new S_Node();
Tail = new S_Node();
header.Link = Tail;
}
// METHODS which i don't know how to do it (never use linkedlist before)
}
我需要使用“链表数据结构类型”来组织这些数据。
包含链表的所有方法,如我所学的将节点添加到列表中 -->(Insert),从列表中删除节点,如我所学 -->(((Remove),遍历我所学的列表 - ->((PrintList),在我学到的列表中找到一个节点 -->((Find , FindPrevious) 我正在自学的问题,我试图搜索网络并从愚蠢的 C# 中阅读更多是一场灾难。
我做了太多,我很难过,我不知道如何完成它。
我正在努力使用此类来编写可执行程序并对其进行测试。
如果你不想帮助完成这个程序(希望不是)至少给我看一些真实的例子或想法,毕竟我是一个自学极客:-)
最佳答案
the head of the list. ( item1 Element: student1 Next ------------> ( item2 ) Element: student2 Next ------------> ( item3 ) Element: student3 Next: null ) the tail of the list.
First of all, for you to be able to write the StudentList class, you need to write the client code first. Client code is the code that uses your student list. Also, don't just write one thing at a time and throw it away. Instead write a whole bunch of [test] cases that exercise the different ways you need to interact with the StudentList. Write exceptional cases too. But don't be tempted to write a swiss-army knife of a class that does everything just because it can. Write the minimal amount of code that gets the job done.
How you need to use the class will heavily dictate how the class is constructed. This is the essence of TDD or Test Driven Design.
Your biggest problem that I can see is you have no idea how you want to use the class. So lets do that first.
// create a list of students and print them back out.
StudentList list = new StudentList();
list.Add( new Student("Bob", 1234, 2, 'A') );
list.Add( new Student("Mary", 2345, 4, 'C') );
foreach( Student student in list)
{
Console.WriteLine(student.Name);
}
我将学生添加到列表中,然后将它们打印出来。
我不需要我的客户端代码来查看 StudentList 中的内容。因此 StudentList 隐藏了它如何实现链表。让我们写出StudentList的基础知识。
public class StudentList
{
private ListNode _firstElement; // always need to keep track of the head.
private class ListNode
{
public Student Element { get; set; }
public ListNode Next { get; set; }
}
public void Add(Student student) { /* TODO */ }
}
StudentList 是非常基本的。它在内部跟踪第一个或头节点。显然总是需要跟踪第一个节点。
您可能还想知道为什么在 StudentList 中声明 ListNode。发生的情况是 ListNode 类只能被 StudentList 类访问。这样做是因为 StudentList 不想将详细信息透露给它的内部实现,因为它控制着对列表的所有访问。 StudentList 从不透露列表是如何实现的。实现隐藏是一个重要的 OO 概念。
如果我们确实允许客户端代码直接操作列表,那么将 StudentList 放在第一位就没有意义了。
让我们继续执行 Add() 操作。
public void Add(Student student)
{
if (student == null)
throw new ArgumentNullException("student");
// create the new element
ListNode insert = new ListNode() { Element = student };
if( _firstElement == null )
{
_firstElement = insert;
return;
}
ListNode current = _firstElement;
while (current.Next != null)
{
current = current.Next;
}
current.Next = insert;
}
Add 操作必须找到列表中的最后一项,然后将新的 ListNode 放在最后。虽然效率不高。它目前是 O(N) 并且随着列表变长,添加会变慢。
让我们稍微优化一下插入并重写 Add 方法。为了使 Add 更快,我们需要做的就是让 StudentList 跟踪列表中的最后一个元素。
private ListNode _lastElement; // keep track of the last element: Adding is O(1) instead of O(n)
public void Add(Student student)
{
if( student == null )
throw new ArgumentNullException("student");
// create the new element
ListNode insert = new ListNode() { Element = student };
if (_firstElement == null)
{
_firstElement = insert;
_lastElement = insert;
return;
}
// fix up Next reference
ListNode last = _lastElement;
last.Next = insert;
_lastElement = insert;
}
现在,当我们添加时,我们不进行迭代。我们只需要跟踪头部和尾部引用。
接下来:foreach 循环。 StudentList 是一个集合,作为一个集合,我们要枚举它并使用 C# 的
foreach
。 C# 编译器不能神奇地迭代。为了使用 foreach 循环,我们需要为编译器提供一个枚举器来使用,即使我们编写的代码没有显式地出现使用枚举器。不过,首先,让我们重新审视我们如何迭代链表。
// don't add this to StudentList
void IterateOverList( ListNode current )
{
while (current != null)
{
current = current.Next;
}
}
好的。所以让我们进入 C# 的 foreach 循环并返回一个枚举器。为此,我们更改 StudentList 以实现 IEnumerable。这有点先进,但你应该能够弄清楚发生了什么。
// StudentList now implements IEnumerable<Student>
public class StudentList : IEnumerable<Student>
{
// previous code omitted
#region IEnumerable<Student> Members
public IEnumerator<Student> GetEnumerator()
{
ListNode current = _firstElement;
while (current != null)
{
yield return current.Element;
current = current.Next;
}
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
您应该能够在那里发现链表迭代。不要被
yield
关键字抛出。 yield 所做的就是将当前学生返回到 foreach 循环。枚举器在到达链表末尾时停止返回学生。就是这样!代码按照我们希望的方式工作。
* 这绝不是实现列表的唯一方法。我选择将列表逻辑放在 StudentList 中,并保持 ListNode 非常基本。但是代码只做我第一个单元测试需要的东西,仅此而已。您可以进行更多优化,还有其他构建列表的方法。
展望 future :您需要做的是首先为您的代码需要做的事情创建 [单元] 测试,然后添加您需要的实现。
* 仅供引用,我还重写了 Student 类。来自 C# 的错误命名和奇怪的大小写,更不用说您提供的代码无法编译。我更喜欢
_
作为私有(private)成员变量的领导者。有些人不喜欢那样,但是你是新手,所以我会把它们留在里面,因为它们很容易被发现。public class Student
{
private string _name;
private int _id;
private int _mark;
private char _letterGrade;
private Student() // hide default Constructor
{ }
public Student(string name, int id, int mark, char letterGrade) // Constructor
{
if( string.IsNullOrEmpty(name) )
throw new ArgumentNullException("name");
if( id <= 0 )
throw new ArgumentOutOfRangeException("id");
_name = name;
_id = id;
_mark = mark;
_letterGrade = letterGrade;
}
// read-only properties - compressed to 1 line for SO answer.
public string Name { get { return _name; } }
public int Id { get { return _id; } }
public int Mark { get { return _mark; } }
public char LetterGrade { get { return _letterGrade; } }
}