概念介绍:
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。
链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据
由图可知:
- 链表在进行添加/删除时,只需要修改 当前节点和相邻节点 的next,更新效率高
- 遍历数据,需要根据节点按顺序访问,导致查询速度慢,时间复杂度为O(n)
- 每个节点中都保存了下一个节点的指针【Next,最后一个节点的next为null】,所以长度是动态变化的,且不是连续内存空间
相关代码:
MyLinkedListNode:自定义链表节点类
1 ///2 /// 自定义链表节点类: 单链表 3 /// 4 public class MyLinkedListNode 5 { 6 /// 7 /// 当前节点 8 /// 9 public T Node { get; set; } 10 11 /// 12 /// 下一个节点 13 /// 14 public MyLinkedListNode Next { get; set; } 15 16 /// 17 /// 构造函数: 无参构造函数 18 /// 19 /// 20 public MyLinkedListNode() 21 { 22 this.Node = default; 23 this.Next = null; 24 } 25 26 /// 27 /// 构造函数: 指定当前节点,常用于 新增根节点和最后一个节点 28 /// 29 /// 30 public MyLinkedListNode(T node) 31 { 32 this.Node = node; 33 this.Next = null; 34 } 35 36 /// 37 /// 构造函数: 指定当前节点和下一节点,常用于 新增内部节点/确定下一节点 的情况 38 /// 39 /// 40 /// 41 public MyLinkedListNode(T node, MyLinkedListNode next) 42 { 43 this.Node = node; 44 this.Next = next; 45 } 46 }
MyLinkedList:自定义链表
1 ///2 /// 自定义链表 3 /// 功能: 4 /// 1.添加: 添加到集合最后面 5 /// 2.添加: 添加到集合最前面 6 /// 3.添加: 添加索引后面 7 /// 4.添加: 添加索引前面 8 /// 5.删除: 删除T 9 /// 6.删除: 删除指定索引 10 /// 7.删除: 删除第一个 11 /// 8.删除: 删除最后一个 12 /// 9.删除: 删除所有 13 /// 14 public class MyLinkedList 15 { 16 /// 17 /// 存储链表集合-根节点: 18 /// 框架自带了双向链表 System.Collections.Generic.LinkedList,链表集合保存在 MyLinkedListNode 中 19 /// 考虑到存在clear方法,链表默认值为null更方便 20 /// 21 private MyLinkedListNode _rootNode = null; // { get; set; } 22 23 /// 24 /// 索引索引器,从0开始,根据索引返回指定索引节点信息 25 /// 26 /// 27 /// 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 /// 41 /// 根据索引返回指定索引节点信息 42 /// 43 /// 索引,从0开始 44 /// 45 private MyLinkedListNode GetNodeAt(int index) => GetNodeTupleAt(index)?.Item1; 46 47 /// 48 /// 根据索引返回指定索引:节点元组 49 /// 50 /// 索引,从0开始 51 /// item1:当前节点;item2:上一节点;item3:下一节点; 52 private Tuple , MyLinkedListNode , MyLinkedListNode > 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 currentNode = _rootNode; 60 // 上一节点 61 MyLinkedListNode 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 /// 78 /// 获取有效的节点元组 79 /// 80 /// 索引 81 /// 当前节点 82 /// 上一节点【如果索引 == 0 ? null :上一节点 】 83 /// item1:当前节点;item2:上一节点;item3:下一节点; 84 private Tuple , MyLinkedListNode , MyLinkedListNode > GetValidNodeTuple(int index, MyLinkedListNode currentNode, MyLinkedListNode prevNode) 85 { 86 return new Tuple , MyLinkedListNode , MyLinkedListNode >(currentNode, index == 0 ? null : prevNode, currentNode.Next); 87 } 88 89 #endregion 90 91 #region 添加方法 92 93 /// 94 /// 1.添加: 添加到集合最后面 95 /// 96 /// 97 public void Append(T item) 98 { 99 MyLinkedListNode node = new MyLinkedListNode (item); 100 // 如果链表集合为空,则讲当前 元素当作跟节点 101 if (_rootNode == null) 102 { 103 _rootNode = node; 104 return; 105 } 106 107 // 循环得到最末节点 108 MyLinkedListNode currentNode = _rootNode; 109 while (currentNode.Next != null) currentNode = currentNode.Next; 110 111 // 添加到集合最后面 112 currentNode.Next = node; 113 } 114 115 /// 116 /// 2.添加: 添加到集合最前面 117 /// 118 /// 119 public void AddFirst(T item) 120 { 121 MyLinkedListNode node = new MyLinkedListNode (item); 122 // 如果链表集合为空,则讲当前 元素当作跟节点 123 if (_rootNode == null) 124 { 125 _rootNode = node; 126 return; 127 } 128 129 _rootNode = new MyLinkedListNode (item, _rootNode); 130 131 // 显示链表中的所有数据 132 Console.Write($"AddFirst({item})\t"); 133 Show(); 134 } 135 136 /// 137 /// 3.添加: 在索引后面添加 138 /// 3.1.获取到当前索引的节点 139 /// 3.2.根据item创建新节点,把 当前节点的 下一节点指给 新节点的下一节点 140 /// 3.3.把新节点当作当前节点的下一节点 141 /// 142 /// 143 /// 144 public void AddAtAfter(int index, T item) 145 { 146 MyLinkedListNode node = new MyLinkedListNode (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 (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 /// 174 /// 4.添加: 在索引前面添加 175 /// 4.1.获取到 当前索引 和 上一索引 的节点 176 /// 4.2.根据item创建新节点,把当前节点当作新节点的下一节点 177 /// 4.3.把新节点当作上一节点的下一节点 178 /// 179 /// 180 /// 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 (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 /// 208 /// 5.删除: 删除T 209 /// 5.1.得到 当前节点/上一节点/下一节点 210 /// 5.2.把 上一节点的下一节点 更新为 下一节点 211 /// 212 /// 213 public void Remove(T item) 214 { 215 if (_rootNode == null) return; 216 // 当前节点 217 var currentNode = _rootNode; 218 // 上一节点 219 MyLinkedListNode 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 /// 245 /// 根据 当前节点 的 上一节点和下一节点 删除 当前节点:把上一节点的next 指向 下一节点 246 /// 247 /// 248 /// 249 private void Remove(MyLinkedListNode prevNode, MyLinkedListNode nextNode) 250 { 251 if (prevNode == null) _rootNode = nextNode; 252 else prevNode.Next = nextNode; 253 } 254 255 /// 256 /// 6.删除: 删除指定索引 257 /// 6.1.得到 当前/上一/下一节点 258 /// 6.2.把当前节点的下一节点 更新为 下一节点 259 /// 260 /// 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 /// 278 /// 7.删除: 删除第一个 279 /// 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 /// 291 /// 8.删除: 删除最后一个 292 /// 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 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 /// 319 /// 9.删除: 删除所有 320 /// 321 public void Clear() 322 { 323 _rootNode = null; 324 325 // 显示链表中的所有数据 326 Console.Write($"Clear()\t"); 327 Show(); 328 } 329 330 #endregion 331 332 /// 333 /// 显示链表中的所有数据 334 /// 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 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 ///2 /// 测试单链表 3 /// 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 }