C# 单向链表实现

10
在尝试了解C#中如何实现单向链表时,我发现了下面的链接:Creating a very simple linked list
然而,由于我是C#的新手,我被上述讨论初始部分列出的语法所困惑了。一个名为Node的类被声明,并且在类内又有一个被声明为“public Node next”的语句。这个语句是构造函数吗?请帮忙解答。
public class Node {
    public Node next;
    public Object data;
 }
4个回答

18
在简单的单向链表实现中,Node 类型包含对列表中下一项的引用,这就是您发布的 Node 类型中的 next 字段所表示的内容。该引用用于允许迭代列表。
封装的 LinkedList 类(或您想要称呼它的任何名称)将包含对列表中第一项的单个 Node 引用。从第一个节点开始,您可以通过获取 next 字段来遍历整个列表。当 next 为 null 时,您已经到达了列表的末尾。
以此代码为例:
public class LinkedList
{
    public class Node
    {
        // link to next Node in list
        public Node next = null;
        // value of this Node
        public object data;
    }

    private Node root = null;

    public Node First { get { return root; } }

    public Node Last 
    {
        get
        {
            Node curr = root;
            if (curr == null)
                return null;
            while (curr.next != null)
                curr = curr.next;
            return curr;
        }
    }
}

First 属性只是返回根节点,也就是列表中的第一个节点。而 Last 属性则从根节点开始遍历整个列表,直到找到一个其 next 属性为 null 的节点,表示已经到达了列表的末尾。

这使得向列表中添加元素变得非常简单:

public void Append(object value)
{
    Node n = new Node { data = value };
    if (root == null)
        root = n;
    else
        Last.next = n;
}

要删除一个节点,你需要找到它在列表中的前一个节点,然后更新该节点的next链接,使其指向要删除的节点的下一个节点:

public void Delete(Node n)
{
    if (root == node) 
    {
        root = n.next;
        n.next = null;
    }
    else
    {
        Node curr = root;
        while (curr.next != null)
        {
            if (curr.next == n)
            {
                curr.next = n.next;
                n.next = null;
                break;
            }
            curr = curr.next;
        }
    }
}

除了在列表中插入值,交换节点等操作之外,您还可以执行一些其他操作。在节点后插入速度很快,而在节点前插入速度较慢,因为您需要定位到前一个节点。如果您真的希望实现快速的“插入前”操作,则需要使用双向链表,其中Node类型具有nextprevious链接。


针对您在评论中提出的问题,我想进一步解释一下...

C# 中的所有类型都可以分为两类:值类型和引用类型。这些名称反映了它们在代码块之间传递的方式:值类型是按值传递的(将值复制到新变量中),而引用类型是按引用传递的(将引用/指针复制到新变量中)。区别在于,对值类型参数的更改不会影响调用者对该值的副本,而对引用类型参数的更改将反映在调用者对该引用的副本中。

对变量赋值时也是如此。在下面的示例中,当更改 b 时,a 的值不会改变:

int a = 0;
int b = a;
b = 1;

这相当直观。可能会让你困惑的是,在C#中,struct也是值类型:

public struct test
{
    public string value;
}

static void Main()
{
    test a;
    a.value = "a";
    test b = a;
    b.value = "b";

    Console.WriteLine("{0} {1}", a.value, b.value);
}

以上代码将输出 a b,因为当你将 a 赋值给 b 时,会创建一个副本。但是如果我们将结构体改为类:
public class test
{
    public string value;
}

static void Main()
{
    test a = new test(); // Note the 'new' keyword to create a reference type instance
    a.value = "a";
    test b = a;
    b.value = "b";
    Console.WriteLine("{0} {1}", a.value, b.value);
}

因为变量b是对与变量a引用相同的对象的引用,所以这里的输出将是b b。这两个变量引用同一个对象

如果您来自C/C++或其他类似的语言,您可以将引用类型变量视为指针。虽然不完全相同,但C#确实有指针(它们被隐藏在正常托管代码中),但它足够接近。在将其指向类型的实例之前,它并不完全可用。就像在C/C++中的char*在指向某个地方之前并不特别有用。

Joseph Albahari(撰写了一篇关于值类型和引用类型的优秀文章:C#概念:值类型与引用类型。这非常值得一读,他写的大部分内容都很好。我还强烈建议您考虑购买他的C# in a Nutshell书籍之一。


节点 curr = 根; 对不起,如果这是一个愚蠢的问题,但我只是想变得更擅长 C#,并且不想做任何假设。当您进行此声明时,我是否正确地理解您正在实例化类型为 Node 的对象?如果是这样,让我困惑的是内存空间将如何分配,因为在执行“节点 curr”时还有另一个声明“公共节点下一个”? - user3011489
不,声明是一个可以保存引用的变量,在概念上类似于C或C++中的指针。所有类变量都是如此。要创建类对象的实例,您需要使用new关键字。 - Corey

3

创建单向链表很简单。让我们尝试理解其概念。如果概念清晰,那么你就能理解逻辑本身。单向链表具有两个部分的节点。一个部分包含数据值,另一个部分包含下一个节点的引用地址。看一下以下代码:

首先,我们需要创建链表节点类。

/// <summary>
/// Creating the Real World Entity of Linked List Node
/// </summary>
public class LinkedListNode
{
    public Object Value { get; set; }

    public LinkedListNode Next { get; set; }

}

这里的类有“Value”和“Holder”,用于持有序列中下一个节点的引用。接下来,我们需要创建链表本身。

/// <summary>
/// Creating the Linked List Class Itself. It defines the First and Last Nodes of Linked List
/// </summary>
public class LinkedList
{
    public LinkedListNode First { get; set; }
    public LinkedListNode Last { get; set; }

    /// <summary>
    /// Method to Add items into the Linked List
    /// </summary>
    /// <param name="_value"></param>
    public void AddToLinkedList(object _value)
    {
        LinkedListNode node = new LinkedListNode();
        node.Value = _value;

        if (First == null)
        {
            First = node;
            Last = node;
        }
        else
        {
            Last.Next = node;
            Last = node;
        }
    }

    /// <summary>
    /// Method to display all items. We can further implement the IEnumerable interface
    /// to Yield IEnumerator Interface.
    /// </summary>
    public void DisplayAllItems()
    {
        LinkedListNode current = First;
        while (current != null)
        {
            Console.WriteLine(current.Value);
            current = current.Next;
        }
    }
}

在这里,关键是向链表中添加项目。首先,我们需要检查链表是否存在。我们检查链表中的第一个或头结点。如果它为空,我们将节点分配为第一个入口点。此时,最后一项就是第一项本身。

现在,我们来看看如何添加和显示项目

class Program
{
    static void Main(string[] args)
    {
        LinkedList singlyLinkedList = new LinkedList();
        singlyLinkedList.AddToLinkedList(4);
        singlyLinkedList.AddToLinkedList(5);
        singlyLinkedList.AddToLinkedList(7);
        singlyLinkedList.AddToLinkedList(2);
        singlyLinkedList.AddToLinkedList(1);
        singlyLinkedList.AddToLinkedList(10);

        singlyLinkedList.DisplayAllItems();
        Console.ReadLine();
    }
}

如果有任何问题,请告诉我 :)


1

0

请记住,链表不仅保存数据,还保存指向列表中下一个节点的引用/指针。由于在C#中类是引用类型,因此您不会看到在C或C++中可能看到的任何特殊语法。

struct Node
{
    int Number;
    struct Node* next; /* this points to the next node in the linked list */
};

谢谢大家的帮助。在阅读了微软网站上的字段描述后,我的理解更加清晰了。不过,我有一个问题:如果字段声明与类名相同,是否有一些术语来定义这种类型的字段? - user3011489
@user3011489 我相信一定有一个术语来描述这个问题,因为似乎对于所有事情都有一个术语...但我不知道它是什么,或者我暂时想不起来了。我认为,当使用高级语言抽象开发人员的许多内容时,实现解决方案到像链表、双向链表等问题会更加复杂。 - Inisheer
感谢您的帮助。 - user3011489

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接