NET 数据结构-单链表
概念介绍:
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。
链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据
由图可知:
- 链表在进行添加/删除时,只需要修改 当前节点和相邻节点 的next,更新效率高
- 遍历数据,需要根据节点按顺序访问,导致查询速度慢,时间复杂度为O(n)
- 每个节点中都保存了下一个节点的指针【Next,最后一个节点的next为null】,所以长度是动态变化的,且不是连续内存空间
相关代码:
MyLinkedListNode:自定义链表节点类

1 /// <summary> 2 /// 自定义链表节点类: 单链表 3 /// </summary> 4 public class MyLinkedListNode<T> 5 { 6 /// <summary> 7 /// 当前节点 8 /// </summary> 9 public T Node { get; set; } 10 11 /// <summary> 12 /// 下一个节点 13 /// </summary> 14 public MyLinkedListNode<T> Next { get; set; } 15 16 /// <summary> 17 /// 构造函数: 无参构造函数 18 /// </summary> 19 /// <param name="Node"></param> 20 public MyLinkedListNode() 21 { 22 this.Node = default; 23 this.Next = null; 24 } 25 26 /// <summary> 27 /// 构造函数: 指定当前节点,常用于 新增根节点和最后一个节点 28 /// </summary> 29 /// <param name="node"></param> 30 public MyLinkedListNode(T node) 31 { 32 this.Node = node; 33 this.Next = null; 34 } 35 36 /// <summary> 37 /// 构造函数: 指定当前节点和下一节点,常用于 新增内部节点/确定下一节点 的情况 38 /// </summary> 39 /// <param name="next"></param> 40 /// <param name="Next"></param> 41 public MyLinkedListNode(T node, MyLinkedListNode<T> next) 42 { 43 this.Node = node; 44 this.Next = next; 45 } 46 }
MyLinkedList:自定义链表

1 /// <summary> 2 /// 自定义链表 3 /// 功能: 4 /// 1.添加: 添加到集合最后面 5 /// 2.添加: 添加到集合最前面 6 /// 3.添加: 添加索引后面 7 /// 4.添加: 添加索引前面 8 /// 5.删除: 删除T 9 /// 6.删除: 删除指定索引 10 /// 7.删除: 删除第一个 11 /// 8.删除: 删除最后一个 12 /// 9.删除: 删除所有 13 /// </summary> 14 public class MyLinkedList<T> 15 { 16 /// <summary> 17 /// 存储链表集合-根节点: 18 /// 框架自带了双向链表 System.Collections.Generic.LinkedList,链表集合保存在 MyLinkedListNode 中 19 /// 考虑到存在clear方法,链表默认值为null更方便 20 /// </summary> 21 private MyLinkedListNode<T> _rootNode = null; // { get; set; } 22 23 /// <summary> 24 /// 索引索引器,从0开始,根据索引返回指定索引节点信息 25 /// </summary> 26 /// <param name="index"></param> 27 /// <returns></returns> 28 public T this[int index] 29 { 30 get 31 { 32 var node = GetNodeAt(index).Node; 33 Console.WriteLine($"this[int {index}] = {node}\r\n"); 34 return node; 35 } 36 } 37 38 #region 查询方法 39 40 /// <summary> 41 /// 根据索引返回指定索引节点信息 42 /// </summary> 43 /// <param name="index">索引,从0开始</param> 44 /// <returns></returns> 45 private MyLinkedListNode<T> GetNodeAt(int index) => GetNodeTupleAt(index)?.Item1; 46 47 /// <summary> 48 /// 根据索引返回指定索引:节点元组 49 /// </summary> 50 /// <param name="index">索引,从0开始</param> 51 /// <returns>item1:当前节点;item2:上一节点;item3:下一节点;</returns> 52 private Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>> GetNodeTupleAt(int index) 53 { 54 if (index < 0) return null; 55 if (_rootNode == null) throw new Exception("自定义链表为空!"); 56 57 var num = 0; 58 // 当前节点 59 MyLinkedListNode<T> currentNode = _rootNode; 60 // 上一节点 61 MyLinkedListNode<T> prevNode = _rootNode; 62 // while循环会在 currentNode == 倒数第二个节点时就会停止循环,所以while后面需要再做一次判断 63 while (currentNode.Next != null) 64 { 65 // 如果当前索引 和 查找索引相同,则返回担负起节点 66 if (num == index) return GetValidNodeTuple(index, currentNode, prevNode); 67 // 重置:上一节点 68 prevNode = currentNode; 69 // 重置:当前节点 70 currentNode = currentNode.Next; 71 num++; 72 } 73 if (num < index) throw new Exception("索引超过链表长度!"); 74 return num == index ? GetValidNodeTuple(index, currentNode, prevNode) : null; 75 } 76 77 /// <summary> 78 /// 获取有效的节点元组 79 /// </summary> 80 /// <param name="index">索引</param> 81 /// <param name="currentNode">当前节点</param> 82 /// <param name="prevNode">上一节点【如果索引 == 0 ? null :上一节点 】</param> 83 /// <returns>item1:当前节点;item2:上一节点;item3:下一节点;</returns> 84 private Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>> GetValidNodeTuple(int index, MyLinkedListNode<T> currentNode, MyLinkedListNode<T> prevNode) 85 { 86 return new Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>>(currentNode, index == 0 ? null : prevNode, currentNode.Next); 87 } 88 89 #endregion 90 91 #region 添加方法 92 93 /// <summary> 94 /// 1.添加: 添加到集合最后面 95 /// </summary> 96 /// <param name="item"></param> 97 public void Append(T item) 98 { 99 MyLinkedListNode<T> node = new MyLinkedListNode<T>(item); 100 // 如果链表集合为空,则讲当前 元素当作跟节点 101 if (_rootNode == null) 102 { 103 _rootNode = node; 104 return; 105 } 106 107 // 循环得到最末节点 108 MyLinkedListNode<T> currentNode = _rootNode; 109 while (currentNode.Next != null) currentNode = currentNode.Next; 110 111 // 添加到集合最后面 112 currentNode.Next = node; 113 } 114 115 /// <summary> 116 /// 2.添加: 添加到集合最前面 117 /// </summary> 118 /// <param name="item"></param> 119 public void AddFirst(T item) 120 { 121 MyLinkedListNode<T> node = new MyLinkedListNode<T>(item); 122 // 如果链表集合为空,则讲当前 元素当作跟节点 123 if (_rootNode == null) 124 { 125 _rootNode = node; 126 return; 127 } 128 129 _rootNode = new MyLinkedListNode<T>(item, _rootNode); 130 131 // 显示链表中的所有数据 132 Console.Write($"AddFirst({item})\t"); 133 Show(); 134 } 135 136 /// <summary> 137 /// 3.添加: 在索引后面添加 138 /// 3.1.获取到当前索引的节点 139 /// 3.2.根据item创建新节点,把 当前节点的 下一节点指给 新节点的下一节点 140 /// 3.3.把新节点当作当前节点的下一节点 141 /// </summary> 142 /// <param name="index"></param> 143 /// <param name="item"></param> 144 public void AddAtAfter(int index, T item) 145 { 146 MyLinkedListNode<T> node = new MyLinkedListNode<T>(item); 147 // 如果链表集合为空,则讲当前 元素当作跟节点 148 if (_rootNode == null) 149 { 150 _rootNode = node; 151 return; 152 } 153 // 3.1.获取到当前索引的节点 154 var currentNode = GetNodeAt(index); 155 // 如果链表集合为空,则讲当前 元素当作跟节点 156 if (currentNode == null) 157 { 158 _rootNode = node; 159 return; 160 } 161 162 // 3.2.根据item创建新节点 163 var newNode = new MyLinkedListNode<T>(item, currentNode.Next); 164 165 // 3.3.把新节点当作当前节点的下一节点 166 currentNode.Next = newNode; 167 168 // 显示链表中的所有数据 169 Console.Write($"AddAtAfter(int {index},T {item})\t"); 170 Show(); 171 } 172 173 /// <summary> 174 /// 4.添加: 在索引前面添加 175 /// 4.1.获取到 当前索引 和 上一索引 的节点 176 /// 4.2.根据item创建新节点,把当前节点当作新节点的下一节点 177 /// 4.3.把新节点当作上一节点的下一节点 178 /// </summary> 179 /// <param name="index"></param> 180 /// <param name="item"></param> 181 public void AddAtBefore(int index, T item) 182 { 183 var nodeTuple = GetNodeTupleAt(index); 184 if (nodeTuple == null) throw new Exception("索引超过链表长度!"); 185 186 // 4.1.获取到 当前索引 和 上一索引 的节点 187 var currentNode = nodeTuple.Item1; 188 var prevtNode = nodeTuple.Item2; 189 190 // 4.2.根据item创建新节点,把当前节点当作新节点的下一节点 191 var newNode = new MyLinkedListNode<T>(item, currentNode); 192 193 // 4.3.把新节点当作上一节点的下一节点:如果索引是0,则新节点作为链接根节点 194 if (index == 0) _rootNode = newNode; 195 else prevtNode.Next = newNode; 196 197 // 显示链表中的所有数据 198 Console.Write($"AddAtBefore(int {index},T {item})\t"); 199 Show(); 200 } 201 202 #endregion 203 204 #region 删除方法 205 206 207 /// <summary> 208 /// 5.删除: 删除T 209 /// 5.1.得到 当前节点/上一节点/下一节点 210 /// 5.2.把 上一节点的下一节点 更新为 下一节点 211 /// </summary> 212 /// <param name="item"></param> 213 public void Remove(T item) 214 { 215 if (_rootNode == null) return; 216 // 当前节点 217 var currentNode = _rootNode; 218 // 上一节点 219 MyLinkedListNode<T> prevNode = null; 220 while (currentNode.Next != null) 221 { 222 if (currentNode.Node.Equals(item)) 223 { 224 // 根据 当前节点 的 上一节点和下一节点 删除 当前节点 225 Remove(prevNode, currentNode.Next); 226 227 // 显示链表中的所有数据 228 Console.Write($"Remove({item})\t"); 229 Show(); 230 return; 231 } 232 // 重置 上一节点 和 当前节点 233 prevNode = currentNode; 234 currentNode = currentNode.Next; 235 } 236 // 如果需要删除的是最后一个节点,则while循环在 currentNode == 倒数第二个节点时就会停止循环 237 Remove(prevNode, null); 238 239 // 显示链表中的所有数据 240 Console.Write($"Remove({item})\t"); 241 Show(); 242 } 243 244 /// <summary> 245 /// 根据 当前节点 的 上一节点和下一节点 删除 当前节点:把上一节点的next 指向 下一节点 246 /// </summary> 247 /// <param name="prevNode"></param> 248 /// <param name="nextNode"></param> 249 private void Remove(MyLinkedListNode<T> prevNode, MyLinkedListNode<T> nextNode) 250 { 251 if (prevNode == null) _rootNode = nextNode; 252 else prevNode.Next = nextNode; 253 } 254 255 /// <summary> 256 /// 6.删除: 删除指定索引 257 /// 6.1.得到 当前/上一/下一节点 258 /// 6.2.把当前节点的下一节点 更新为 下一节点 259 /// </summary> 260 /// <param name="index"></param> 261 public void RemoveAt(int index) 262 { 263 var nodeTuple = GetNodeTupleAt(index); 264 265 // 上一节点 266 var prevNode = nodeTuple.Item2; 267 // 判断上一节点是不是null ? 当前节点为根节点,需要把下一节点更新为更节点 268 if (prevNode == null) _rootNode = nodeTuple.Item3; 269 else prevNode.Next = nodeTuple.Item3; 270 271 272 // 显示链表中的所有数据 273 Console.Write($"RemoveAt({index})\t"); 274 Show(); 275 } 276 277 /// <summary> 278 /// 7.删除: 删除第一个 279 /// </summary> 280 public void RemoveFirst() 281 { 282 if (_rootNode == null) return; 283 _rootNode = _rootNode.Next; 284 285 // 显示链表中的所有数据 286 Console.Write($"RemoveFirst()\t"); 287 Show(); 288 } 289 290 /// <summary> 291 /// 8.删除: 删除最后一个 292 /// </summary> 293 public void RemoveLast() 294 { 295 if (_rootNode == null) return; 296 // 如果链表只存在根节点,则把根节点删除 297 if (_rootNode.Next == null) 298 { 299 _rootNode = null; 300 return; 301 } 302 // while循环获得最后一个节点 303 var currentNode = _rootNode; 304 // 上一节点/倒数第二个节点 305 MyLinkedListNode<T> prevNode = null; 306 while (currentNode.Next != null) 307 { 308 prevNode = currentNode; 309 currentNode = currentNode.Next; 310 } 311 prevNode.Next = null; 312 313 // 显示链表中的所有数据 314 Console.Write($"RemoveLast()\t"); 315 Show(); 316 } 317 318 /// <summary> 319 /// 9.删除: 删除所有 320 /// </summary> 321 public void Clear() 322 { 323 _rootNode = null; 324 325 // 显示链表中的所有数据 326 Console.Write($"Clear()\t"); 327 Show(); 328 } 329 330 #endregion 331 332 /// <summary> 333 /// 显示链表中的所有数据 334 /// </summary> 335 public void Show() 336 { 337 if (_rootNode == null) 338 { 339 Console.WriteLine($"链表中的数据为空!\r\n"); 340 return; 341 } 342 StringBuilder builder = new StringBuilder(); 343 344 MyLinkedListNode<T> currentNode = _rootNode; 345 while (currentNode.Next != null) 346 { 347 builder.Append($"{currentNode.Node}\t"); 348 currentNode = currentNode.Next; 349 } 350 // 最后一个节点next为null,不会进入while 351 builder.Append($"{currentNode.Node}\t"); 352 353 Console.WriteLine($"链表中的数据为:\r\n{builder.ToString()}\r\n"); 354 } 355 }
测试代码:
1 /// <summary> 2 /// 测试单链表 3 /// </summary> 4 public static void RunLinkedList() 5 { 6 Console.WriteLine("======= 链表测试 Start ======="); 7 8 MyLinkedList<string> myLinkedList = new MyLinkedList<string>(); 9 myLinkedList.Append("张三"); 10 myLinkedList.Append("李四"); 11 myLinkedList.Append("王五"); 12 myLinkedList.Append("六麻子"); 13 myLinkedList.Append("田七"); 14 myLinkedList.Show(); // 张三 李四 王五 六麻子 田七 15 16 // 异常测试 17 var a = myLinkedList[1]; // 张三 18 a = myLinkedList[3]; // 六麻子 19 //a = myLinkedList[11]; 20 21 // 测试添加功能 22 myLinkedList.AddFirst("郭大爷"); // 郭大爷 张三 李四 王五 六麻子 田七 23 myLinkedList.AddAtAfter(0, "海大爷"); // 郭大爷 海大爷 张三 李四 王五 六麻子 田七 24 myLinkedList.AddAtBefore(0, "Robot"); // Robot 郭大爷 海大爷 张三 李四 王五 六麻子 田七 25 myLinkedList.AddAtBefore(2, "Robot"); // Robot 郭大爷 Robot 海大爷 张三 李四 王五 六麻子 田七 26 myLinkedList.AddAtBefore(4, "Robot"); // Robot 郭大爷 Robot 海大爷 Robot 张三 李四 王五 六麻子 田七 27 28 // 测试删除功能 29 myLinkedList.Remove("Robot"); // 郭大爷 Robot 海大爷 Robot 张三 李四 王五 六麻子 田七 30 myLinkedList.Remove("Robot"); // 郭大爷 海大爷 Robot 张三 李四 王五 六麻子 田七 31 myLinkedList.Remove("田七"); // 郭大爷 海大爷 Robot 张三 李四 王五 六麻子 32 myLinkedList.RemoveAt(0); // 海大爷 Robot 张三 李四 王五 六麻子 33 myLinkedList.RemoveAt(1); // 海大爷 张三 李四 王五 六麻子 34 myLinkedList.RemoveFirst(); // 张三 李四 王五 六麻子 35 myLinkedList.RemoveFirst(); // 李四 王五 六麻子 36 myLinkedList.RemoveLast(); // 李四 王五 37 myLinkedList.RemoveLast(); // 李四 38 myLinkedList.Clear(); // 链表中的数据为空! 39 40 Console.WriteLine("======= 链表测试 End ======="); 41 }
分类:
AspNet
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!