链表粗解
1. 链表简介
链表是一种线性数据结构,由一系列节点组成。每个节点包含数据和指向下一个节点的引用,形成节点之间的链式连接。与数组相比,链表具有以下特点:
- 动态内存管理:
可根据需要动态分配内存,避免预先分配固定大小。 - 高效插入删除:
在任意位置(已知前驱节点)插入或删除节点时,只需要更新指针,操作复杂度为 O(1)。 - 顺序访问:
由于节点不连续存储,访问链表中任意位置的数据需从头开始遍历,时间复杂度为 O(n)。
常见链表类型:
- 单向链表 (Singly Linked List)
- 双向链表 (Doubly Linked List)
- 循环链表 (Circular Linked List)
2. 链表的数据结构原理
- 基本概念
- 节点(Node): 链表的最基本的元素,每个节点包含数据部分(如代码中的 Val)和指向下一个节点的引用(Next)。
- 头结点(Head): 链表的开始节点。部分实现中使用哨兵节点(dummy node),如本示例中 _head 始终存在但实际数据存放在 _head.Next 中。
- 尾结点(Tail): 指向链表中最后一个节点。便于在尾部添加新节点时快速定位,无需从头开始遍历链表。
- 优缺点分析
优点:
- 动态内存分配: 插入和删除不需要移动其他元素,仅需调整指针,操作复杂度为 O(1)(在已知位置时)。
- 节省空间: 不必预先定义固定大小,能根据数据量动态增长。
缺点:
- 顺序存储: 链表不支持高效的随机访问。例如,获取第 i 个元素需要从头顺序查找,时间复杂度为 O(n)。
- 额外空间: 每个节点需要额外存储一个或多个指针,增加内存开销。
- 内存碎片化: 链表节点在内存中不是连续分配的,可能导致内存局部性较差。
3. 使用C#实现链表
- 单向链表(Singly Linked List)
1.1 类结构
- 节点Node:
public class Node
{
public T Val { get; set; } //存储节点数据
public Node? Next { get; set; } //指向下一个节点
public Node(T val)
{
Val = val;
Next = null;
}
}
- 私有成员变量
private readonly Node _head; //哨兵头节点,不直接存储数据,实际数据存放在 _head.Next
private Node _tail; //指向最后一个节点,便于尾部添加新节点
private int _size; //维护链表中节点的数量
- 接口实现: 实现了 IEnumerable
接口,用于支持迭代器模式,使得链表可以使用 foreach 进行遍历。
1.2 主要操作解析:
- 头部插入:
①. 新节点的 Next 指向当前 _head.Next(原首节点)。
②. 哨兵节点 _head 的 Next 更新为新节点。
③. 处理空链表,更新 _tail 指向新节点。
public void AddFirst(T val) {
var newNode = new Node(val);
newNode.Next = _head.Next; // ① 新节点指向原首节点
_head.Next = newNode; // ② 哨兵指向新节点
if (_size == 0) // ③ 处理空链表
_tail = newNode;
_size++;
}
- 任意位置插入
a. 检查索引是否合法(索引值允许在 [0, _size] 之间)。
b. 若索引为 0,则直接头部插入;若等于 _size,则直接尾部插入。
c. 否则,定位到目标位置前一个节点,再将新节点插入到正确位置。
public void Insert(int index, T val) {
CheckPositionIndex(index);
// 头部插入
if (index == 0) {
AddFirst(val);
return;
}
// 尾部插入
if (index == _size) {
AddLast(val);
return;
}
Node prev = GetNode(index - 1); // ① 找到前驱节点
var newNode = new Node(val);
newNode.Next = prev.Next; // ② 新节点指向后续节点
prev.Next = newNode; // ③ 前驱节点指向新节点
_size++;
}
- 尾部删除
a. 验证链表是否为空,否则抛出异常。
b. 通过遍历找到倒数第二个节点。
c. 将倒数第二个节点的 Next 设为 null,并更新 _tail 指向该节点。
d. 更新链表节点计数并返回被删除节点的数据。
public T RemoveLast() {
if (IsEmpty)
throw new InvalidOperationException();
Node prev = _head;
while (prev.Next != _tail) // ① 定位到倒数第二节点
prev = prev.Next;
T val = _tail.Val;
prev.Next = null; // ② 断开尾节点
_tail = prev; // ③ 更新尾指针
_size--;
return val;
}
- 索引器访问
a. 允许直接通过 list[index] 语法获取或设置指定位置的节点数据
public T this[int index] {
get {
Node node = GetNode(index);
return node.Val;
}
set {
Node node = GetNode(index);
node.Val = value;
}
}
1.3 完整代码:
public class LinkedList<T> : IEnumerable<T>
{
public class Node
{
public T Val { get; set; }
public Node? Next { get; set; }
public Node(T val)
{
Val = val;
Next = null;
}
}
private readonly Node _head; //哨兵头节点,不直接存储数据,实际数据存放在 _head.Next
private Node _tail; //指向最后一个节点,便于尾部添加新节点
private int _size; //维护链表中节点的数量
public LinkedList()
{
_head = new Node(default);
_tail = _head;
_size = 0;
}
public void AddFirst(T val)
{
var newNode = new Node(val);
newNode.Next = _head.Next;
_head.Next = newNode;
if (_size == 0)
{
_tail = newNode;
}
_size++;
}
public void AddLast(T val)
{
var newNode = new Node(val);
_tail.Next = newNode;
_tail = newNode;
_size++;
}
public void Add(int index, T val)
{
CheckPositionIndex(index);
if (index == 0)
{
AddFirst(val);
return;
}
if (index == _size)
{
AddLast(val);
return;
}
var prev = GetNode(index - 1);
var newNode = new Node(val);
newNode.Next = prev.Next;
prev.Next = newNode;
_size++;
}
public T RemoveFirst()
{
if (IsEmpty)
{
throw new IndexOutOfRangeException("No elements to remove");
}
var first = _head.Next;
_head.Next = first.Next;
if (_size == 1)
{
_tail = _head;
}
_size--;
return first.Val;
}
public T RemoveLast()
{
if (IsEmpty)
{
throw new IndexOutOfRangeException("No elements to remove");
}
var prev = _head;
while (prev.Next != _tail)
{
prev = prev.Next;
}
T val = _tail.Val;
prev.Next = null;
_tail = prev;
_size--;
return val;
}
public T Remove(int index)
{
CheckElementIndex(index);
if (index == 0)
{
return RemoveFirst();
}
var prev = GetNode(index - 1);
var nodeToRemove = prev.Next;
prev.Next = nodeToRemove.Next;
if (nodeToRemove == _tail)
{
_tail = prev;
}
_size--;
return nodeToRemove.Val;
}
public T this[int index]
{
get => GetNode(index).Val;
set => GetNode(index).Val = value;
}
public T GetFirst()
{
if (IsEmpty)
{
throw new IndexOutOfRangeException("No elements in the list");
}
return _head.Next.Val;
}
public T GetLast()
{
if (IsEmpty)
{
throw new IndexOutOfRangeException("No elements in the list");
}
return _tail.Val;
}
public T Get(int index)
{
CheckElementIndex(index);
var p = GetNode(index);
return p.Val;
}
public T Set(int index, T val)
{
CheckElementIndex(index);
var p = GetNode(index);
var oldVal = p.Val;
p.Val = val;
return oldVal;
}
public override string ToString()
{
if (_size == 0)
{
return "No elements in the list";
}
StringBuilder sb = new();
sb.AppendLine($"Size = {_size}");
var curr = _head.Next;
while (curr != null)
{
sb.Append(curr.Val);
if (curr.Next != null)
{
sb.Append(" -> ");
}
curr = curr.Next;
}
return sb.ToString();
}
public int Count => _size;
public bool IsEmpty => _size == 0;
private Node GetNode(int index)
{
CheckElementIndex(index);
Node curr = _head.Next;
for (int i = 0; i < index; i++)
{
curr = curr.Next;
}
return curr;
}
/// <summary>
/// 检查用于获取、删除或更新节点时的索引是否在有效范围 [0, _size-1] 内。
/// </summary>
private void CheckElementIndex(int index)
{
if (index < 0 || index >= _size)
throw new IndexOutOfRangeException($"Index: {index}, _size: {_size}");
}
/// <summary>
/// 检查用于添加节点时的索引是否在有效范围 [0, _size] 内。
/// </summary>
private void CheckPositionIndex(int index)
{
if (index < 0 || index > _size)
throw new IndexOutOfRangeException($"Index: {index}, _size: {_size}");
}
public IEnumerator<T> GetEnumerator()
{
Node curr = _head.Next;
while (curr != null)
{
yield return curr.Val;
curr = curr.Next;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}