在上一篇中,我们学习了线性表最基础的表现形式-顺序表,但是其存在一定缺点:必须占用一整块事先分配好的存储空间,在插入和删除操作上需要移动大量元素(即操作不方便),于是不受固定存储空间限制并且可以进行比较快捷地插入和删除操作的链表横空出世,所以我们就来复习一下链表。
1.1 单链表的节点结构
在链表中,每个节点由两部分组成:数据域和指针域。
1.2 单链表的总体结构
链表就是由N个节点链接而成的线性表,如果其中每个节点只包含一个指针域那么就称为单链表,如果含有两个指针域那么就称为双链表。
PS:在线性表的链式存储结构中,为了便于插入和删除操作的实现,每个链表都带有一个头指针(或尾指针),通过头指针可以唯一标识该链表。从头指针所指向的节点出发,沿着节点的链可以访问到每个节点。
二、单链表实现
2.1 单链表节点的定义
public class Node<T> { // 数据域 public T Item { get; set; } // 指针域 public Node<T> Next { get; set; } public Node() { } public Node(T item) { this.Item = item; } }
此处定义Node类为单链表的节点,其中包括了一个数据域Item与一个指针域Next(指向后继节点的位置)。
2.2 单链表节点的新增
①默认在尾节点后插入新节点
public void Add(T value) { Node<T> newNode = new Node<T>(value); if (this.head == null) { // 如果链表当前为空则置为头结点 this.head = newNode; } else { Node<T> prevNode = this.GetNodeByIndex(this.count - 1); prevNode.Next = newNode; } this.count++; }
首先判断头结点是否为空,其次依次遍历各节点找到尾节点的前驱节点,然后更改前驱节点的Next指针指向新节点即可。
②指定在某个节点后插入新节点
public void Insert(int index, T value) { Node<T> tempNode = null; if (index < 0 || index > this.count) { throw new ArgumentOutOfRangeException("index", "索引超出范围"); } else if (index == 0) { if (this.head == null) { tempNode = new Node<T>(value); this.head = tempNode; } else { tempNode = new Node<T>(value); tempNode.Next = this.head; this.head = tempNode; } } else { Node<T> prevNode = GetNodeByIndex(index - 1); tempNode = new Node<T>(value); tempNode.Next = prevNode.Next; prevNode.Next = tempNode; } this.count++; }
这里需要判断是否是在第一个节点进行插入,如果是则再次判断头结点是否为空。
2.3 单链表节点的移除
public void RemoveAt(int index) { if (index == 0) { this.head = this.head.Next; } else { Node<T> prevNode = GetNodeByIndex(index - 1); if (prevNode.Next == null) { throw new ArgumentOutOfRangeException("index", "索引超出范围"); } Node<T> deleteNode = prevNode.Next; prevNode.Next = deleteNode.Next; deleteNode = null; } this.count--; }
移除某个节点只需将其前驱节点的Next指针指向要移除节点的后继节点即可。
至此,关键部分的代码已介绍完毕,下面给出完整的单链表模拟实现代码:
/// <summary> /// 单链表模拟实现 /// </summary> public class MySingleLinkedList<T> { private int count; // 字段:当前链表节点个数 private Node<T> head; // 字段:当前链表的头结点 // 属性:当前链表节点个数 public int Count { get { return this.count; } } // 索引器 public T this[int index] { get { return this.GetNodeByIndex(index).Item; } set { this.GetNodeByIndex(index).Item = value; } } public MySingleLinkedList() { this.count = 0; this.head = null; } // Method01:根据索引获取节点 private Node<T> GetNodeByIndex(int index) { if (index < 0 || index >= this.count) { throw new ArgumentOutOfRangeException("index", "索引超出范围"); } Node<T> tempNode = this.head; for (int i = 0; i < index; i++) { tempNode = tempNode.Next; } return tempNode; } // Method02:在尾节点后插入新节点 public void Add(T value) { Node<T> newNode = new Node<T>(value); if (this.head == null) { // 如果链表当前为空则置为头结点 this.head = newNode; } else { Node<T> prevNode = this.GetNodeByIndex(this.count - 1); prevNode.Next = newNode; } this.count++; } // Method03:在指定位置插入新节点 public void Insert(int index, T value) { Node<T> tempNode = null; if (index < 0 || index > this.count) { throw new ArgumentOutOfRangeException("index", "索引超出范围"); } else if (index == 0) { if (this.head == null) { tempNode = new Node<T>(value); this.head = tempNode; } else { tempNode = new Node<T>(value); tempNode.Next = this.head; this.head = tempNode; } } else { Node<T> prevNode = GetNodeByIndex(index - 1); tempNode = new Node<T>(value); tempNode.Next = prevNode.Next; prevNode.Next = tempNode; } this.count++; } // Method04:移除指定位置的节点 public void RemoveAt(int index) { if (index == 0) { this.head = this.head.Next; } else { Node<T> prevNode = GetNodeByIndex(index - 1); if (prevNode.Next == null) { throw new ArgumentOutOfRangeException("index", "索引超出范围"); } Node<T> deleteNode = prevNode.Next; prevNode.Next = deleteNode.Next; deleteNode = null; } this.count--; } } }