• .NET源码中的链表


    .NET中自带的链表是LinkedList类,并且已经直接实现成了双向循环链表。

    其节点类LinkedListNode的数据结构如下,数据项包括指示到某个链表的引用,以及左,右节点和值。

    [html] view plain copy
     
    1. public sealed class LinkedListNode<T>  
    2. {  
    3.   internal LinkedList<T> list;  
    4.   internal LinkedListNode<T> next;  
    5.   internal LinkedListNode<T> prev;  
    6.   internal T item;  
    7. }  


    另外,获取前一个节点和后一个节点的实现如下:注意:里面的if-else结构的意义是当前一个(后一个)节点不为空且不是头节点时才不返回null,这样做的意义是当链表内只有1个节点时,其prev和next是指向自身的。

    [csharp] view plain copy
     
    1. [__DynamicallyInvokable]  
    2.    public LinkedListNode<T> Next  
    3.    {  
    4.      [__DynamicallyInvokable] get  
    5.      {  
    6.        if (this.next != null && this.next != this.list.head)  
    7.          return this.next;  
    8.        else  
    9.          return (LinkedListNode<T>) null;  
    10.      }  
    11.    }  
    12.   
    13.    [__DynamicallyInvokable]  
    14.    public LinkedListNode<T> Previous  
    15.    {  
    16.      [__DynamicallyInvokable] get  
    17.      {  
    18.        if (this.prev != null && this != this.list.head)  
    19.          return this.prev;  
    20.        else  
    21.          return (LinkedListNode<T>) null;  
    22.      }  
    23.    }  

    过有一个把链表置为无效的方法定义如下:

    [csharp] view plain copy
     
    1.     internal void Invalidate()  
    2.     {  
    3.       this.list = (LinkedList<T>) null;  
    4.       this.next = (LinkedListNode<T>) null;  
    5.       this.prev = (LinkedListNode<T>) null;  
    6.     }  

    而LinkedList的定义如下:主要的两个数据是头节点head以及长度count。

    [csharp] view plain copy
     
    1. public class LinkedList<T> : ICollection<T>, IEnumerable<T>, ICollection, IEnumerable, ISerializable, IDeserializationCallback  
    2.   {  
    3.     internal LinkedListNode<T> head;  
    4.     internal int count;  
    5.     internal int version;  
    6.     private object _syncRoot;  
    7.     private SerializationInfo siInfo;  
    8.     private const string VersionName = "Version";  
    9.     private const string CountName = "Count";  
    10.     private const string ValuesName = "Data";  

    而对此链表的主要操作,包括:

    • 插入节点到最后,Add(),也是AddLast()。
    • 在某个节点后插入,AddAfter(Node, T)。
    • 在某个节点前插入,AddBefore(Node, T)。
    • 插入到头节点之前,AddFirst(T)。
    • 清除所有节点,Clear()。
    • 是否包含某个值,Contains(T),也就是Find()。
    • 查找某个节点的引用,Find()和FindLast()。
    • 复制到数组,CopyTo(Array)
    • 删除某个节点,Remove(T)。
    另外有几个内部方法,用来支撑复用插入和删除操作:
    • 内部插入节点,InternalInsertNodeBefore()
    • 内部插入节点到空链表,InternalInsertNodeToEmptyList()
    • 内部删除节点,InternalRemoveNode()
    • 验证新节点是否有效,ValidateNewNode()
    • 验证节点是否有效,ValidateNode()
     
    插入新节点到链表最后的代码如下:
    [csharp] view plain copy
     
    1. public void AddLast(LinkedListNode<T> node)  
    2. {  
    3.   this.ValidateNewNode(node);  
    4.   if (this.head == null)  
    5.     this.InternalInsertNodeToEmptyList(node);  
    6.   else  
    7.     this.InternalInsertNodeBefore(this.head, node);  
    8.   node.list = this;  
    9. }  
    插入操作的第一步是验证节点是否有效,即节点不为null,且节点不属于其他链表。
    [csharp] view plain copy
     
    1. internal void ValidateNewNode(LinkedListNode<T> node)  
    2.    {  
    3.      if (node == null)  
    4.        throw new ArgumentNullException("node");  
    5.      if (node.list != null)  
    6.        throw new InvalidOperationException(SR.GetString("LinkedListNodeIsAttached"));  
    7.    }  

    如果头节点为空,则执行插入到空链表的操作:将节点的next和prev都指向为自己,并作为头节点。
    [csharp] view plain copy
     
    1. private void InternalInsertNodeToEmptyList(LinkedListNode<T> newNode)  
    2.    {  
    3.      newNode.next = newNode;  
    4.      newNode.prev = newNode;  
    5.      this.head = newNode;  
    6.      ++this.version;  
    7.      ++this.count;  
    8.    }  
    如果头节点不为空,则执行插入到头节点之前(注:因为是双向链表,所以插到头节点之前就相当于插到链表的最后了),具体的指针指向操作如下:
    [csharp] view plain copy
     
    1. private void InternalInsertNodeBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)  
    2. {  
    3.   newNode.next = node;  
    4.   newNode.prev = node.prev;  
    5.   node.prev.next = newNode;  
    6.   node.prev = newNode;  
    7.   ++this.version;  
    8.   ++this.count;  
    9. }  

    而插入新节点到指定节点之后的操作如下:同样还是调用的内部函数,把新节点插入到指定节点的下一个节点的之前。有点绕,但确实让这个内部函数起到多个作用了。
    [csharp] view plain copy
     
    1.     public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode)  
    2.     {  
    3.       this.ValidateNode(node);  
    4.       this.ValidateNewNode(newNode);  
    5.       this.InternalInsertNodeBefore(node.next, newNode);  
    6.       newNode.list = this;  
    7.     }  

    而插入新节点到指定节点之前的操作如下:直接调用插入新节点的内部函数,另外还要判断指定的节点是否是头节点,如果是的话,要把头节点变成新的节点。
    [csharp] view plain copy
     
    1. public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode)  
    2.     {  
    3.       this.ValidateNode(node);  
    4.       this.ValidateNewNode(newNode);  
    5.       this.InternalInsertNodeBefore(node, newNode);  
    6.       newNode.list = this;  
    7.       if (node != this.head)  
    8.         return;  
    9.       this.head = newNode;  
    10.     }  

    把新链表插入到第一个节点(也就是变成头节点)的操作如下:如果链表为空就直接变成头节点,否则就插入到头节点之前,取代头节点。
    [csharp] view plain copy
     
    1. public void AddFirst(LinkedListNode<T> node)  
    2.     {  
    3.       this.ValidateNewNode(node);  
    4.       if (this.head == null)  
    5.       {  
    6.         this.InternalInsertNodeToEmptyList(node);  
    7.       }  
    8.       else  
    9.       {  
    10.         this.InternalInsertNodeBefore(this.head, node);  
    11.         this.head = node;  
    12.       }  
    13.       node.list = this;  
    14.     }  

    查找链表中某个值的操作如下:注意直接返回null的条件是头节点为空。然后就是遍历了,因为是双向链表,所以要避免死循环(遍历到头节点时跳出)。
    [csharp] view plain copy
     
    1. public LinkedListNode<T> Find(T value)  
    2.     {  
    3.       LinkedListNode<T> linkedListNode = this.head;  
    4.       EqualityComparer<T> @default = EqualityComparer<T>.Default;  
    5.       if (linkedListNode != null)  
    6.       {  
    7.         if ((object) value != null)  
    8.         {  
    9.           while (!@default.Equals(linkedListNode.item, value))  
    10.           {  
    11.             linkedListNode = linkedListNode.next;  
    12.             if (linkedListNode == this.head)  
    13.               goto label_8;  
    14.           }  
    15.           return linkedListNode;  
    16.         }  
    17.         else  
    18.         {  
    19.           while ((object) linkedListNode.item != null)  
    20.           {  
    21.             linkedListNode = linkedListNode.next;  
    22.             if (linkedListNode == this.head)  
    23.               goto label_8;  
    24.           }  
    25.           return linkedListNode;  
    26.         }  
    27.       }  
    28. label_8:  
    29.       return (LinkedListNode<T>) null;  
    30.     }  

    删除某个节点的操作如下:
    [csharp] view plain copy
     
    1. public void Remove(LinkedListNode<T> node)  
    2. {  
    3.   this.ValidateNode(node);  
    4.   this.InternalRemoveNode(node);  
    5. }  

    同样,内部删除节点的实现如下:如果节点指向自己,说明是头节点,所以直接把头节点置null。然后就是指针的指向操作了。
    [csharp] view plain copy
     
    1. internal void InternalRemoveNode(LinkedListNode<T> node)  
    2.     {  
    3.       if (node.next == node)  
    4.       {  
    5.         this.head = (LinkedListNode<T>) null;  
    6.       }  
    7.       else  
    8.       {  
    9.         node.next.prev = node.prev;  
    10.         node.prev.next = node.next;  
    11.         if (this.head == node)  
    12.           this.head = node.next;  
    13.       }  
    14.       node.Invalidate();  
    15.       --this.count;  
    16.       ++this.version;  
    17.     }  

    而清空链表的操作如下:遍历链表,逐个设置为无效,最后将内部的头节点也置为null。
    [csharp] view plain copy
     
    1. public void Clear()  
    2. {  
    3.   LinkedListNode<T> linkedListNode1 = this.head;  
    4.   while (linkedListNode1 != null)  
    5.   {  
    6.     LinkedListNode<T> linkedListNode2 = linkedListNode1;  
    7.     linkedListNode1 = linkedListNode1.Next;  
    8.     linkedListNode2.Invalidate();  
    9.   }  
    10.   this.head = (LinkedListNode<T>) null;  
    11.   this.count = 0;  
    12.   ++this.version;  
    13. }  
     
    链表转数组的实现如下:首先判断入参的有效性,然后从头节点开始遍历,依次复制到数组中,直到头结点(尽头)。
    [csharp] view plain copy
     
    1. public void CopyTo(T[] array, int index)  
    2.    {  
    3.      if (array == null)  
    4.        throw new ArgumentNullException("array");  
    5.      if (index < 0 || index > array.Length)  
    6.      {  
    7.        throw new ArgumentOutOfRangeException("index", SR.GetString("IndexOutOfRange", new object[1]  
    8.        {  
    9.          (object) index  
    10.        }));  
    11.      }  
    12.      else  
    13.      {  
    14.        if (array.Length - index < this.Count)  
    15.          throw new ArgumentException(SR.GetString("Arg_InsufficientSpace"));  
    16.        LinkedListNode<T> linkedListNode = this.head;  
    17.        if (linkedListNode == null)  
    18.          return;  
    19.        do  
    20.        {  
    21.          array[index++] = linkedListNode.item;  
    22.          linkedListNode = linkedListNode.next;  
    23.        }  
    24.        while (linkedListNode != this.head);  
    25.      }  
    26.    }  

    以上。
  • 相关阅读:
    爱奇艺笔试题 输出01020304... ...
    ThreadPoolExecutor 中为什么WorkQueue会在corePoolSize满了之后入队
    jvisualvm 的使用
    连续子数组的最大和
    最长连续子序列
    leetcode 需要了解的知识点储备
    java String
    mysql MVCC
    java 批量导出(zip文件)
    java 中接口调用
  • 原文地址:https://www.cnblogs.com/yoga21/p/9227555.html
Copyright © 2020-2023  润新知