定义

链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。
链表和数组比较,不用事先确定存储空间,而是根据需要开辟内存单元

表现示例

image.png
第0个结点称为头结点,它存放有第一个结点的首地址,它没有数据,只是一个指针变量。
以下的每个结点都分为两个域。

  • 一个是数据域,存放各种实际的数据, 如学号num, 姓名name, 性别sex和成绩score等。
  • 一个域为指针域, 存放下一结点的首地址。链表中的每一个结点都是同一种结构类型

    优势

    链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。

链表都有一个头指针, 一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。

名称注释

首节点

存放第一个有效数据的节点头节点:在单链表的第一个结点之前附设一个结点,它没有直接前驱,称之为头结点,头结点的数据域可以不存储任何信息,指针域指向第一个节点(首节点)的地址。头结点的作用是使所有链表(包括空表)的头指针非空

头指针

指向头节点的指针

尾节点

存放最后一个有效数据的节点

尾指针

指向尾节点的指针

链表就如同车链子一样, head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”) , 链表到此结束。

作为有强大功能的链表,对他的操作当然有许多,比如:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。

操作

链表中添加结点

链表头部插入结点(头插法)

假设有以下链表
image.png
建立一个新结点,它的下一结点指向null
Node node = new Node(5);
QQ截图20220419094434.png
把新结点的下一结点指向head结点
node.next = head;
image.png
更新头结点,head指向新结点
head = node;
完成往链表头部插入结点

往链表中间或尾部插入结点

image.png
假设往结点1后插入新结点6,找到结点1的下一结点指向6,6的下一结点指向结点2即可
image.png

  1. Node pre = head;
  2. for(int i = 0; i < index - 1; i++)
  3. {
  4. pre = pre.next;
  5. }

image.png

  1. Node node = new Node(6);
  2. node.next = pre.next;

pre的下一结点指向6
pre.next = node;
image.png

查询链表中的结点

image.png
利用临时变量cur
Node cur = head;
image.png

  1. for(int i = 0; i < index; i++)
  2. cur = cur.next;

image.png

删除链表中的结点

删除链表头部结点

image.png
删除结点5,只需把头结点指向结点5的下一结点
head = head.next;
image.png
结点5没有引用会被回收。

删除链表中间或尾部的结点

假设要删除索引位置3的结点
image.png

  1. Node pre=head;
  2. for(int i = 0; i < index - 1; i++)
  3. {
  4. pre = pre.next;
  5. }
  1. Node delNode = pre.next;
  2. pre.next = delNode.next;

image.png

删除链表中指定的结点

假如删除结点6
image.png
临时变量cur结点指向head,临时pre结点指向null
image.png

  1. pre = cur;
  2. cur = cur.next;

image.png

具体代码实现

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. namespace LinkedList
  5. {
  6. class LinkedList1<E> // 泛型链表类
  7. {
  8. // 结点内部类
  9. private class Node
  10. {
  11. public E e; // 存储的元素e
  12. public Node next; // 当前结点的下一个结点
  13. // 构造函数初始化
  14. public Node(E e, Node next)
  15. {
  16. this.e = e;
  17. this.next = next;
  18. }
  19. // 没有下一结点的构造
  20. public Node(E e)
  21. {
  22. this.e = e;
  23. this.next = null;
  24. }
  25. // 打印输出
  26. public override string ToString()
  27. {
  28. return e.ToString();
  29. }
  30. }
  31. // 链表头部
  32. private Node head;
  33. // 存储的元素个数
  34. private int N;
  35. // 构造函数
  36. public LinkedList1()
  37. {
  38. head = null;
  39. N = 0;
  40. }
  41. // 属性:链表元素个数
  42. public int Count
  43. {
  44. get { return N; }
  45. }
  46. // 属性:是否为空
  47. public bool IsEmpty
  48. {
  49. get { return N == 0; }
  50. }
  51. // 插入结点
  52. public void Add(int index, E e)
  53. {
  54. if (index < 0 || index > N)
  55. {
  56. throw new ArgumentException("非法索引");
  57. }
  58. // 往头部插入结点
  59. if (index == 0)
  60. {
  61. Node node = new Node(e); // 创建新结点
  62. node.next = head; // 新结点next指向头结点
  63. head = node; // 更新头结点
  64. //head = new Node(e, head); 这里复用了之前写的public Node(E e,Node next)构造方法,等同于上面三行代码一样
  65. }
  66. // 往中间或尾部插入
  67. else
  68. {
  69. Node pre = head; // 临时结点pre
  70. for (int i = 0; i < index - 1; i++) // 找到要插入的索引位置的结点的前一个结点
  71. {
  72. pre = pre.next;
  73. }
  74. Node node = new Node(e); // 创建新结点
  75. node.next = pre.next; // 新结点指向临时结点下一结点
  76. pre.next = node; // 更新临时结点下一结点指向新结点
  77. //pre.next = new Node(e, pre.next); 同理等同于上面三句代码
  78. }
  79. N++;// 链表元素个数更新
  80. }
  81. // 头部插入
  82. public void AddFirst(E e)
  83. {
  84. Add(0, e);
  85. }
  86. // 尾部插入
  87. public void AddLast(E e)
  88. {
  89. Add(N, e);
  90. }
  91. // 查询结点
  92. public E Get(int index)
  93. {
  94. if (index < 0 || index >= N)
  95. {
  96. throw new ArgumentException("非法索引");
  97. }
  98. Node cur = head; // 临时变量结点cur指向结点head
  99. // 找到对应索引结点
  100. for (int i = 0; i < index; i++)
  101. {
  102. cur = cur.next;
  103. }
  104. return cur.e;
  105. }
  106. public E GetFirst()
  107. {
  108. return Get(0);
  109. }
  110. public E GetLast()
  111. {
  112. return Get(N - 1);
  113. }
  114. // 修改结点元素
  115. public void Set(int index, E newE)
  116. {
  117. if (index < 0 || index >= N)
  118. throw new ArgumentException("非法索引");
  119. Node cur = head; // 临时变量结点cur指向结点head
  120. // 找到对应索引结点
  121. for (int i = 0; i < index; i++)
  122. {
  123. cur = cur.next;
  124. }
  125. cur.e = newE;
  126. }
  127. // 判断包含
  128. public bool Contains(E e)
  129. {
  130. Node cur = head;
  131. while (cur != null)
  132. {
  133. // 如果结点与元素相同返回true
  134. if (cur.e.Equals(e))
  135. {
  136. return true;
  137. }
  138. cur = cur.next;
  139. }
  140. return false;
  141. }
  142. public E RemoveAt(int index)//删除
  143. {
  144. if (index < 0 || index >= N)
  145. throw new ArgumentException("非法索引");
  146. //删除头结点
  147. if (index == 0)
  148. {
  149. Node delNode = head; // delNode指向head结点
  150. head = head.next; // 更新头结点
  151. N--;
  152. return delNode.e; // 返回被删除的结点元素
  153. }
  154. else
  155. {
  156. Node pre = head;
  157. // 找到删除索引结点的前一个结点
  158. for (int i = 0; i < index - 1; i++)
  159. pre = pre.next;
  160. Node delNode = pre.next; // delNode指向pre结点的下一个结点
  161. pre.next = delNode.next; // pre下一个结点指向delNode的下一个结点
  162. N--;
  163. return delNode.e;
  164. }
  165. }
  166. public E RemoveFirst()
  167. {
  168. return RemoveAt(0);
  169. }
  170. public E RemoveLast()
  171. {
  172. return RemoveAt(N - 1);
  173. }
  174. public void Remove(E e)
  175. {
  176. // 头结点空,说明链表没有结点
  177. if (head == null)
  178. {
  179. return;
  180. }
  181. // 删除的元素是头结点
  182. if (head.e.Equals(e))
  183. {
  184. head = head.next;
  185. N--;
  186. }
  187. else
  188. {
  189. Node cur = head;
  190. Node pre = null;
  191. // 查找要删除的元素的结点
  192. while (cur != null)
  193. {
  194. // 找到元素退出循环
  195. if (cur.e.Equals(e))
  196. {
  197. pre.next = cur.next; // pre下一结点指向cur的下一结点
  198. break;
  199. }
  200. pre = cur;
  201. cur = cur.next;
  202. }
  203. //if (cur != null)
  204. //{
  205. // pre.next = pre.next.next; // pre下一结点指向cur的下一结点
  206. // N--;
  207. //}
  208. }
  209. }
  210. // 打印输出
  211. public override string ToString()
  212. {
  213. StringBuilder res = new StringBuilder();
  214. Node cur = head;
  215. while (cur != null)
  216. {
  217. res.Append(cur + "->");
  218. cur = cur.next;
  219. }
  220. res.Append("Null");
  221. return res.ToString();
  222. }
  223. }
  224. }
  1. using System;
  2. namespace LinkedList
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. // 创建链表
  9. LinkedList1<int> l = new LinkedList1<int>();
  10. for (int i = 0; i < 5; i++)
  11. {
  12. l.AddFirst(i); // 头部插入
  13. Console.WriteLine(l);
  14. }
  15. l.AddLast(60); // 尾部插入
  16. Console.WriteLine("尾部插入");
  17. Console.WriteLine(l);
  18. l.Add(2, 99);
  19. Console.WriteLine("索引2位置插入99结点");
  20. Console.WriteLine(l);
  21. l.Set(1, 100);
  22. Console.WriteLine("修改索引1位置的结点元素");
  23. Console.WriteLine(l);
  24. l.RemoveAt(2);
  25. Console.WriteLine("删除索引2位置的结点");
  26. Console.WriteLine(l);
  27. l.RemoveFirst();
  28. Console.WriteLine("删除头结点");
  29. Console.WriteLine(l);
  30. l.RemoveLast();
  31. Console.WriteLine("删除尾部结点");
  32. Console.WriteLine(l);
  33. l.Remove(0);
  34. Console.WriteLine("删除结点0");
  35. Console.WriteLine(l);
  36. Console.Read();
  37. }
  38. }
  39. }