4

嗨,我正在尝试使用链接列表进行一些练习。

我定义了一个名为的对象类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)
}

我需要使用“链表数据结构类型”来组织这些数据。

包含linkedlist的所有方法作为我学到的添加节点到列表->(插入),从列表中删除节点,正如我所学到的-->((删除),遍历我学到的列表- ->((PrintList),根据我的学习在列表中找到一个节点->((Find , FindPrevious) 我正在自学的问题,我试图搜索网络并从愚蠢的 C# 中阅读更多内容是一场灾难。我做了太多,以至于我很难过,以至于我不知道如何完成它。

我正在努力使用这些类来编写可执行程序并对其进行测试。

如果你不想帮助完成这个程序(希望不是),至少给我一些真实的例子或想法,毕竟我是一个自学极客 :-)

4

8 回答 8

14
列表的头部。
 (第 1 项
   元素:student1
   下一个------------> ( item2
  ) 元素:student2
                        下一个------------> ( item3
                      ) 元素:student3
                                             下一个:空
                                           )
                                           列表的尾部。

首先,为了能够编写 StudentList 类,您需要先编写客户端代码。客户端代码是使用您的学生列表的代码。另外,不要一次只写一件事然后扔掉。而是编写一大堆 [测试] 案例来练习与 StudentList 交互所需的不同方式。也写特例。但是不要试图写一把瑞士军刀,因为它可以做任何事情。编写完成工作的最少代码量。

您需要如何使用该类将在很大程度上决定该类的构造方式。这是 TDD 或测试驱动设计的本质。

我可以看到你最大的问题是你不知道你想如何使用这个类。所以让我们先这样做。

// 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 从不透露列表是如何实现的。实现隐藏是一个重要的面向对象概念。

如果我们确实允许客户端代码直接操作列表,那么将 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 非常基本。但是代码只做我的第一个单元测试需要的,仅此而已。您可以进行更多优化,并且还有其他构建列表的方法。

展望未来:你需要做的是首先为你的代码需要做的事情创建[单元]测试,然后添加你需要的实现。


* 仅供参考,我还重写了 Student 类。来自 C# 透视的错误命名和奇怪的大小写,更不用说您提供的代码无法编译。我更喜欢_作为领导者而不是私有成员变量。有些人不喜欢这样,但是你是新手,所以我会把它们留在里面,因为它们很容易被发现。

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; } }
}
  • 检查参数
  • 注意属性、类和变量的不同大小写。
  • 隐藏默认构造函数。为什么我要创建没有真实数据的学生?
  • 提供一些只读属性。
    • 这个类在书面上是不可变的(即一旦你创建了一个学生,你就不能改变它)。
于 2008-10-30T00:18:44.980 回答
2

我给你做一个!您需要将每个节点绘制为一个框,并计算出您需要使用哪些代码来更改每个操作的列表。看看这个以获得一些灵感:

http://en.wikipedia.org/wiki/Linked_list

那里的图表没有将主列表类显示为您应该拥有的框,其中有两个箭头用于标题和尾部。

在 Insert 方法中为这两种情况绘制一些图表,以了解发生了什么。一个图表表示列表中没有任何内容且标题为空,另一个图表表示列表中已有内容。然后从那里进行其他操作。

public class S_LinkedList {

    protected S_Node header = null;

    protected S_Node tail = null;

    public S_LinkedList()
    {
    }

    // METHODS which i don't know how to do it (never use linkedlist before)
    void Insert(Student s)
    {
        if( header == null )
        {
            header = new S_Node(s);
            tail = header;
        }
        else
        {
            tail.Link = new S_Node(s);
            tail = tail.Link;
        }
    }
}
于 2008-10-29T19:30:17.560 回答
2

您缺少的操作是:

添加:

将尾节点的链接设置为添加节点,将尾节点设置为新节点。

删除/删除:

如果您没有双向链表但使用单链表从头开始遍历列表,直到找到需要将前一个节点保存在单独变量中的节点,这有点棘手。当您找到要删除的节点时,将前一个节点的链接设置为该节点链接。优化可能是检查它是否不是您要查找的链接。或者使它成为一个双向链表,您不需要跟踪前一个节点。

寻找:

从一个节点到另一个节点遍历列表,直到找到您要查找的那个。

阅读这篇维基百科文章了解更多信息。

于 2008-10-29T19:30:28.543 回答
2

看看这个...

虽然如果你真的想学习如何做到这一点,你应该至少用 c 或 c++ 编写它,然后你会做一些有用的事情......

于 2008-10-29T19:37:08.810 回答
1

实际上,我没有理由用 C# 编写自己的链表(除了学习它是如何工作的),因为 .NET 已经包含 LinkedList 泛型类。

于 2008-10-29T19:13:13.397 回答
1

链表的概念并不难理解。另一方面,实施......可能会有点棘手。

我也可以理解您尝试在网络上查找有关它的信息的挫败感。我以前在你的船上,一切都因地点而异。您可能真的想投资一本数据结构书,因为我认为您找到的信息将比您在野外找到的大多数信息更加清晰和有用。

如果您以前从未使用过 ll,在 Java/C# 中实现链表会容易得多。然而,一旦你对它有了更好的感觉,你就会通过在 C/C++ 中创建它们来更好地理解 ll。

从上面的代码中,您最好将每个 S_Node 视为包含 Student 对象的常规节点,而不是将其视为 Student 节点(希望这是有道理的)。相同的规则适用于您的 S_LinkedList 类。链表是节点的列表模式。这些节点包含学生对象。

希望这可以帮助。

于 2008-10-29T20:16:23.987 回答
1

试试这个作为你的学生课程。

public class Student
{
    protected string Name;
    protected int ID;
    protected int Mark;
    protected char Grade;

    public Student()  // default Constructor
    {
        Name = "";
        ID = 0;
        Mark = 0;
        Grade = '';
    }

    public Student(string Name, int ID, int Mark, char Grade) // Constructor
    {
        this.Name = Name;
        this.ID = ID;
        this.Mark = Mark;
        this.Grade = Grade;
    }
}
于 2008-10-29T20:22:05.567 回答
0

你的问题,正如我所读的那样,太模糊了。我会从谷歌搜索“链接列表”或拿起一本关于“数据结构”的书开始。当您遇到特定问题时,请在此处提出,我相信有人会帮助您。

于 2008-10-29T19:14:26.407 回答