NET 数据结构-单链表
阅读原文时间:2023年07月10日阅读:1

概念介绍:

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素

链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据

由图可知:

  1. 链表在进行添加/删除时,只需要修改 当前节点和相邻节点 的next,更新效率高
  2. 遍历数据,需要根据节点按顺序访问,导致查询速度慢,时间复杂度为O(n)
  3. 每个节点中都保存了下一个节点的指针【Next,最后一个节点的next为null】,所以长度是动态变化的,且不是连续内存空间

相关代码:

MyLinkedListNode:自定义链表节点类

 /// <summary>  
 /// 自定义链表节点类: 单链表  
 /// </summary>  
 public class MyLinkedListNode<T>  
 {  
     /// <summary>  
     /// 当前节点  
     /// </summary>  
     public T Node { get; set; }

     /// <summary>  
     /// 下一个节点  
     /// </summary>  
     public MyLinkedListNode<T> Next { get; set; }

     /// <summary>  
     /// 构造函数: 无参构造函数  
     /// </summary>  
     /// <param name="Node"></param>  
     public MyLinkedListNode()  
     {  
         this.Node = default;  
         this.Next = null;  
     }

     /// <summary>  
     /// 构造函数: 指定当前节点,常用于 新增根节点和最后一个节点  
     /// </summary>  
     /// <param name="node"></param>  
     public MyLinkedListNode(T node)  
     {  
         this.Node = node;  
         this.Next = null;  
     }

     /// <summary>  
     /// 构造函数: 指定当前节点和下一节点,常用于 新增内部节点/确定下一节点 的情况  
     /// </summary>  
     /// <param name="next"></param>  
     /// <param name="Next"></param>  
     public MyLinkedListNode(T node, MyLinkedListNode<T> next)  
     {  
         this.Node = node;  
         this.Next = next;  
     }  
 }

MyLinkedList:自定义链表

 /// <summary>  
 /// 自定义链表  
 /// 功能:  
 ///     1.添加: 添加到集合最后面  
 ///     2.添加: 添加到集合最前面  
 ///     3.添加: 添加索引后面  
 ///     4.添加: 添加索引前面  
 ///     5.删除: 删除T  
 ///     6.删除: 删除指定索引  
 ///     7.删除: 删除第一个  
 ///     8.删除: 删除最后一个  
 ///     9.删除: 删除所有  
 /// </summary>  
 public class MyLinkedList<T>  
 {  
     /// <summary>  
     /// 存储链表集合-根节点:  
     /// 框架自带了双向链表 System.Collections.Generic.LinkedList,链表集合保存在 MyLinkedListNode 中  
     /// 考虑到存在clear方法,链表默认值为null更方便  
     /// </summary>  
     private MyLinkedListNode<T> \_rootNode = null; // { get; set; }

     /// <summary>  
     /// 索引索引器,从0开始,根据索引返回指定索引节点信息  
     /// </summary>  
     /// <param name="index"></param>  
     /// <returns></returns>  
     public T this\[int index\]  
     {  
         get  
         {  
             var node = GetNodeAt(index).Node;  
             Console.WriteLine($"this\[int {index}\] = {node}\\r\\n");  
             return node;  
         }  
     }

     #region 查询方法

     /// <summary>  
     /// 根据索引返回指定索引节点信息  
     /// </summary>  
     /// <param name="index">索引,从0开始</param>  
     /// <returns></returns>  
     private MyLinkedListNode<T> GetNodeAt(int index) => GetNodeTupleAt(index)?.Item1;

     /// <summary>  
     /// 根据索引返回指定索引:节点元组  
     /// </summary>  
     /// <param name="index">索引,从0开始</param>  
     /// <returns>item1:当前节点;item2:上一节点;item3:下一节点;</returns>  
     private Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>> GetNodeTupleAt(int index)  
     {  
         if (index < ) return null;  
         if (\_rootNode == null) throw new Exception("自定义链表为空!");

         var num = ;  
         // 当前节点  
         MyLinkedListNode<T> currentNode = \_rootNode;  
         // 上一节点  
         MyLinkedListNode<T> prevNode = \_rootNode;  
         // while循环会在  currentNode == 倒数第二个节点时就会停止循环,所以while后面需要再做一次判断  
         while (currentNode.Next != null)  
         {  
             // 如果当前索引 和 查找索引相同,则返回担负起节点  
             if (num == index) return GetValidNodeTuple(index, currentNode, prevNode);  
             // 重置:上一节点  
             prevNode = currentNode;  
             // 重置:当前节点  
             currentNode = currentNode.Next;  
             num++;  
         }  
         if (num < index) throw new Exception("索引超过链表长度!");  
         return num == index ? GetValidNodeTuple(index, currentNode, prevNode) : null;  
     }

     /// <summary>  
     /// 获取有效的节点元组  
     /// </summary>  
     /// <param name="index">索引</param>  
     /// <param name="currentNode">当前节点</param>  
     /// <param name="prevNode">上一节点【如果索引 == 0 ? null :上一节点 】</param>  
     /// <returns>item1:当前节点;item2:上一节点;item3:下一节点;</returns>  
     private Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>> GetValidNodeTuple(int index, MyLinkedListNode<T> currentNode, MyLinkedListNode<T> prevNode)  
     {  
         return new Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>>(currentNode, index ==  ? null : prevNode, currentNode.Next);  
     }

     #endregion

     #region 添加方法

     /// <summary>  
     /// 1.添加: 添加到集合最后面  
     /// </summary>  
     /// <param name="item"></param>  
     public void Append(T item)  
     {  
         MyLinkedListNode<T> node = new MyLinkedListNode<T>(item);  
         // 如果链表集合为空,则讲当前 元素当作跟节点  
         if (\_rootNode == null)  
         {  
             \_rootNode = node;  
             return;  
         }

         // 循环得到最末节点  
         MyLinkedListNode<T> currentNode = \_rootNode;  
         while (currentNode.Next != null) currentNode = currentNode.Next;

         // 添加到集合最后面  
         currentNode.Next = node;  
     }

     /// <summary>  
     /// 2.添加: 添加到集合最前面  
     /// </summary>  
     /// <param name="item"></param>  
     public void AddFirst(T item)  
     {  
         MyLinkedListNode<T> node = new MyLinkedListNode<T>(item);  
         // 如果链表集合为空,则讲当前 元素当作跟节点  
         if (\_rootNode == null)  
         {  
             \_rootNode = node;  
             return;  
         }

         \_rootNode = new MyLinkedListNode<T>(item, \_rootNode);

         // 显示链表中的所有数据  
         Console.Write($"AddFirst({item})\\t");  
         Show();  
     }

     /// <summary>  
     /// 3.添加: 在索引后面添加  
     /// 3.1.获取到当前索引的节点  
     /// 3.2.根据item创建新节点,把 当前节点的 下一节点指给 新节点的下一节点  
     /// 3.3.把新节点当作当前节点的下一节点  
     /// </summary>  
     /// <param name="index"></param>  
     /// <param name="item"></param>  
     public void AddAtAfter(int index, T item)  
     {  
         MyLinkedListNode<T> node = new MyLinkedListNode<T>(item);  
         // 如果链表集合为空,则讲当前 元素当作跟节点  
         if (\_rootNode == null)  
         {  
             \_rootNode = node;  
             return;  
         }  
         // 3.1.获取到当前索引的节点  
         var currentNode = GetNodeAt(index);  
         // 如果链表集合为空,则讲当前 元素当作跟节点  
         if (currentNode == null)  
         {  
             \_rootNode = node;  
             return;  
         }

         // 3.2.根据item创建新节点  
         var newNode = new MyLinkedListNode<T>(item, currentNode.Next);

         // 3.3.把新节点当作当前节点的下一节点  
         currentNode.Next = newNode;

         // 显示链表中的所有数据  
         Console.Write($"AddAtAfter(int {index},T {item})\\t");  
         Show();  
     }

     /// <summary>  
     /// 4.添加: 在索引前面添加  
     /// 4.1.获取到 当前索引 和 上一索引 的节点  
     /// 4.2.根据item创建新节点,把当前节点当作新节点的下一节点  
     /// 4.3.把新节点当作上一节点的下一节点  
     /// </summary>  
     /// <param name="index"></param>  
     /// <param name="item"></param>  
     public void AddAtBefore(int index, T item)  
     {  
         var nodeTuple = GetNodeTupleAt(index);  
         if (nodeTuple == null) throw new Exception("索引超过链表长度!");

         // 4.1.获取到 当前索引 和 上一索引 的节点  
         var currentNode = nodeTuple.Item1;  
         var prevtNode = nodeTuple.Item2;

         // 4.2.根据item创建新节点,把当前节点当作新节点的下一节点  
         var newNode = new MyLinkedListNode<T>(item, currentNode);

         // 4.3.把新节点当作上一节点的下一节点:如果索引是0,则新节点作为链接根节点  
         if (index == ) \_rootNode = newNode;  
         else prevtNode.Next = newNode;

         // 显示链表中的所有数据  
         Console.Write($"AddAtBefore(int {index},T {item})\\t");  
         Show();  
     }

     #endregion

     #region 删除方法

     /// <summary>  
     /// 5.删除: 删除T  
     /// 5.1.得到 当前节点/上一节点/下一节点  
     /// 5.2.把 上一节点的下一节点 更新为 下一节点  
     /// </summary>  
     /// <param name="item"></param>  
     public void Remove(T item)  
     {  
         if (\_rootNode == null) return;  
         // 当前节点  
         var currentNode = \_rootNode;  
         // 上一节点  
         MyLinkedListNode<T> prevNode = null;  
         while (currentNode.Next != null)  
         {  
             if (currentNode.Node.Equals(item))  
             {  
                 // 根据 当前节点 的 上一节点和下一节点 删除 当前节点  
                 Remove(prevNode, currentNode.Next);

                 // 显示链表中的所有数据  
                 Console.Write($"Remove({item})\\t");  
                 Show();  
                 return;  
             }  
             // 重置 上一节点 和 当前节点  
             prevNode = currentNode;  
             currentNode = currentNode.Next;  
         }  
         // 如果需要删除的是最后一个节点,则while循环在  currentNode == 倒数第二个节点时就会停止循环  
         Remove(prevNode, null);

         // 显示链表中的所有数据  
         Console.Write($"Remove({item})\\t");  
         Show();  
     }

     /// <summary>  
     /// 根据 当前节点 的 上一节点和下一节点 删除 当前节点:把上一节点的next 指向 下一节点  
     /// </summary>  
     /// <param name="prevNode"></param>  
     /// <param name="nextNode"></param>  
     private void Remove(MyLinkedListNode<T> prevNode, MyLinkedListNode<T> nextNode)  
     {  
         if (prevNode == null) \_rootNode = nextNode;  
         else prevNode.Next = nextNode;  
     }

     /// <summary>  
     /// 6.删除: 删除指定索引  
     /// 6.1.得到 当前/上一/下一节点  
     /// 6.2.把当前节点的下一节点 更新为 下一节点  
     /// </summary>  
     /// <param name="index"></param>  
     public void RemoveAt(int index)  
     {  
         var nodeTuple = GetNodeTupleAt(index);

         // 上一节点  
         var prevNode = nodeTuple.Item2;  
         // 判断上一节点是不是null ? 当前节点为根节点,需要把下一节点更新为更节点  
         if (prevNode == null) \_rootNode = nodeTuple.Item3;  
         else prevNode.Next = nodeTuple.Item3;

         // 显示链表中的所有数据  
         Console.Write($"RemoveAt({index})\\t");  
         Show();  
     }

     /// <summary>  
     /// 7.删除: 删除第一个  
     /// </summary>  
     public void RemoveFirst()  
     {  
         if (\_rootNode == null) return;  
         \_rootNode = \_rootNode.Next;

         // 显示链表中的所有数据  
         Console.Write($"RemoveFirst()\\t");  
         Show();  
     }

     /// <summary>  
     /// 8.删除: 删除最后一个  
     /// </summary>  
     public void RemoveLast()  
     {  
         if (\_rootNode == null) return;  
         // 如果链表只存在根节点,则把根节点删除  
         if (\_rootNode.Next == null)  
         {  
             \_rootNode = null;  
             return;  
         }  
         // while循环获得最后一个节点  
         var currentNode = \_rootNode;  
         // 上一节点/倒数第二个节点  
         MyLinkedListNode<T> prevNode = null;  
         while (currentNode.Next != null)  
         {  
             prevNode = currentNode;  
             currentNode = currentNode.Next;  
         }  
         prevNode.Next = null;

         // 显示链表中的所有数据  
         Console.Write($"RemoveLast()\\t");  
         Show();  
     }

     /// <summary>  
     /// 9.删除: 删除所有  
     /// </summary>  
     public void Clear()  
     {  
         \_rootNode = null;

         // 显示链表中的所有数据  
         Console.Write($"Clear()\\t");  
         Show();  
     }

     #endregion

     /// <summary>  
     /// 显示链表中的所有数据  
     /// </summary>  
     public void Show()  
     {  
         if (\_rootNode == null)  
         {  
             Console.WriteLine($"链表中的数据为空!\\r\\n");  
             return;  
         }  
         StringBuilder builder = new StringBuilder();

         MyLinkedListNode<T> currentNode = \_rootNode;  
         while (currentNode.Next != null)  
         {  
             builder.Append($"{currentNode.Node}\\t");  
             currentNode = currentNode.Next;  
         }  
         // 最后一个节点next为null,不会进入while  
         builder.Append($"{currentNode.Node}\\t");

         Console.WriteLine($"链表中的数据为:\\r\\n{builder.ToString()}\\r\\n");  
     }  
 }

测试代码:

     /// <summary>  
     /// 测试单链表  
     /// </summary>  
     public static void RunLinkedList()  
     {  
         Console.WriteLine("======= 链表测试 Start =======");

         MyLinkedList<string> myLinkedList = new MyLinkedList<string>();  
         myLinkedList.Append("张三");  
         myLinkedList.Append("李四");  
         myLinkedList.Append("王五");  
         myLinkedList.Append("六麻子");  
         myLinkedList.Append("田七");  
         myLinkedList.Show(); // 张三    李四    王五    六麻子  田七

         // 异常测试  
         var a = myLinkedList\[\]; // 张三  
         a = myLinkedList\[\]; // 六麻子  
         //a = myLinkedList\[11\];

         // 测试添加功能  
         myLinkedList.AddFirst("郭大爷"); // 郭大爷  张三    李四    王五    六麻子  田七  
         myLinkedList.AddAtAfter(, "海大爷"); // 郭大爷  海大爷  张三    李四    王五    六麻子  田七  
         myLinkedList.AddAtBefore(, "Robot"); // Robot   郭大爷  海大爷  张三    李四    王五    六麻子  田七  
         myLinkedList.AddAtBefore(, "Robot"); // Robot   郭大爷  Robot   海大爷  张三    李四    王五    六麻子  田七  
         myLinkedList.AddAtBefore(, "Robot"); // Robot   郭大爷  Robot   海大爷  Robot   张三    李四    王五    六麻子  田七

         // 测试删除功能  
         myLinkedList.Remove("Robot"); // 郭大爷  Robot   海大爷  Robot   张三    李四    王五    六麻子  田七  
         myLinkedList.Remove("Robot"); // 郭大爷  海大爷  Robot   张三    李四    王五    六麻子  田七  
         myLinkedList.Remove("田七"); // 郭大爷  海大爷  Robot   张三    李四    王五    六麻子  
         myLinkedList.RemoveAt(); // 海大爷  Robot   张三    李四    王五    六麻子  
         myLinkedList.RemoveAt(); // 海大爷  张三    李四    王五    六麻子  
         myLinkedList.RemoveFirst(); // 张三    李四    王五    六麻子  
         myLinkedList.RemoveFirst(); // 李四    王五    六麻子  
         myLinkedList.RemoveLast(); // 李四    王五  
         myLinkedList.RemoveLast(); // 李四  
         myLinkedList.Clear(); // 链表中的数据为空!

         Console.WriteLine("======= 链表测试 End =======");  
     }