【数据结构】链表补充——静态链表、循环链表、双向链表与双向循环链表
链表是数据结构中非常重要的一种线性结构,相比顺序表(数组),链表具有灵活的内存分配和动态大小的优点。链表的常见变种包括:静态链表、循环链表、双向链表,以及它们的组合——双向循环链表。在本篇博客中,我们将一步步深入探索这些链表结构的实现原理、优缺点与应用场景。
一、什么是链表?
链表(Linked List)是一种线性存储结构,它由若干个节点组成。
每个节点包含两个部分:
- 数据域(存储数据元素);
- 指针域(存储下一个节点的地址)。
相比顺序表,链表的元素不需要在内存中连续存储,这使得链表在插入和删除元素时表现得更加高效。链表的节点通过指针连接,形成一个“链条”。
二、静态链表
📦 静态链表的定义
静态链表是一种使用数组模拟链表结构的方式。
它不同于传统的链表(使用指针),静态链表采用了数组的方式来存储节点,并通过数组中的索引来模拟指针的作用。每个元素不仅存储数据,还存储指向下一个元素的数组下标。
🌿 静态链表的结构
#include <iostream>
using namespace std;#define MAX_SIZE 100 // 假设链表最多有100个元素typedef int ElemType;struct StaticList {ElemType data; // 存储数据元素int next; // 存储下一个元素在数组中的下标
};class StaticLinkedList {
private:StaticList arr[MAX_SIZE]; // 数组模拟链表int head; // 头指针,指向链表的第一个元素int size; // 当前链表的长度public:StaticLinkedList() {head = -1; // 初始为空链表size = 0;}// 插入元素void insert(int pos, ElemType value) {if (size == MAX_SIZE) {cout << "链表已满!" << endl;return;}int newNode = size++; // 使用size作为新的节点下标arr[newNode].data = value; // 存储数据// 找到插入位置if (pos == 0) {arr[newNode].next = head;head = newNode;} else {int prev = head;for (int i = 0; i < pos - 1; i++) {prev = arr[prev].next;}arr[newNode].next = arr[prev].next;arr[prev].next = newNode;}}// 打印链表void print() {int curr = head;while (curr != -1) {cout << arr[curr].data << " ";curr = arr[curr].next;}cout << endl;}
};
🔍 静态链表分析
- 优点:使用数组模拟链表,可以避免动态内存分配带来的开销。每个节点之间通过数组下标来连接,比较节省内存。
- 缺点:一旦数组满了,无法动态扩展,且对内存的利用不如动态链表灵活。
三、循环链表
📦 循环链表的定义
循环链表是一种特殊的链表,它的最后一个节点的next指针指向头节点,形成一个环形结构。循环链表可以是单向循环链表(只包含一个方向的指针)或者双向循环链表(包含两个方向的指针)。
🌿 单向循环链表的实现
#include <iostream>
using namespace std;struct Node {int data;Node* next;
};class CircularLinkedList {
private:Node* head; // 头节点指针public:CircularLinkedList() {head = nullptr;}// 插入元素void insert(int value) {Node* newNode = new Node();newNode->data = value;if (!head) {head = newNode;newNode->next = head; // 自己指向自己} else {Node* temp = head;while (temp->next != head) {temp = temp->next; // 遍历到链表的最后一个节点}temp->next = newNode; // 将新节点接到链表末尾newNode->next = head; // 新节点指向头节点,形成环}}// 打印链表void print() {if (!head) return;Node* temp = head;do {cout << temp->data << " ";temp = temp->next;} while (temp != head); // 循环直到回到头节点cout << endl;}
};
🔍 循环链表分析
- 优点:通过将尾节点指向头节点,实现了循环结构,能够更加方便地从尾部回到头部,适用于循环调度等应用场景。
- 缺点:结构上比普通链表复杂,且循环链表无法轻易地插入或删除某个元素的前一个节点。
四、双向链表
📦 双向链表的定义
双向链表是指每个节点除了有指向下一个节点的指针外,还有一个指向前一个节点的指针。这样可以使得链表的操作更加灵活,支持双向遍历。
🌿 双向链表的实现
#include <iostream>
using namespace std;struct Node {int data;Node* next;Node* prev;
};class DoublyLinkedList {
private:Node* head; // 头节点Node* tail; // 尾节点public:DoublyLinkedList() {head = nullptr;tail = nullptr;}// 插入元素void insert(int value) {Node* newNode = new Node();newNode->data = value;newNode->next = nullptr;newNode->prev = tail;if (tail) {tail->next = newNode;} else {head = newNode; // 第一个节点}tail = newNode;}// 打印链表(从头到尾)void print() {Node* temp = head;while (temp) {cout << temp->data << " ";temp = temp->next;}cout << endl;}// 打印链表(从尾到头)void printReverse() {Node* temp = tail;while (temp) {cout << temp->data << " ";temp = temp->prev;}cout << endl;}
};
🔍 双向链表分析
- 优点:支持双向遍历,删除、插入操作非常方便,尤其是在已知某个节点的情况下,可以快速地进行前向或后向操作。
- 缺点:比单向链表占用更多的内存空间,因为每个节点需要额外存储一个指向前节点的指针。
五、双向循环链表
📦 双向循环链表的定义
双向循环链表结合了双向链表和循环链表的特点:既有指向前后节点的指针,又形成了一个循环结构。这样,既可以双向遍历链表,又可以在尾部和头部之间无缝切换。
🌿 双向循环链表的实现
#include <iostream>
using namespace std;struct Node {int data;Node* next;Node* prev;
};class DoublyCircularLinkedList {
private:Node* head; // 头节点public:DoublyCircularLinkedList() {head = nullptr;}// 插入元素void insert(int value) {Node* newNode = new Node();newNode->data = value;if (!head) {head = newNode;newNode->next = head;newNode->prev = head;} else {Node* tail = head->prev;tail->next = newNode;newNode->prev = tail;newNode->next = head;head->prev = newNode;}
}// 打印链表(从头到尾)void print() {if (!head) return;Node* temp = head;do {cout << temp->data << " ";temp = temp->next;} while (temp != head); // 循环直到回到头节点cout << endl;}
};
🔍 双向循环链表分析
- 优点:支持双向遍历,且头尾连接成循环结构,适用于需要从任意位置循环访问的场景。
- 缺点:需要存储两个指针(前后节点指针),因此内存开销较大,且比普通循环链表或双向链表更复杂。
六、小结
| 链表类型 | 特点 | 优点 | 缺点 |
|---|---|---|---|
| 静态链表 | 使用数组模拟链表结构 | 内存利用相对较高,操作简单 | 不能动态扩展,灵活性差 |
| 单向循环链表 | 最后一个节点指向头节点,形成循环结构 | 支持循环遍历,适合循环调度等应用 | 插入删除操作较麻烦 |
| 双向链表 | 每个节点有两个指针,分别指向前后节点 | 支持双向遍历,插入删除操作方便 | 每个节点需要额外的空间来存储前指针 |
| 双向循环链表 | 双向链表和循环链表结合,头尾连接 | 支持双向遍历,适用于循环访问的场景 | 存储空间开销较大,操作复杂 |
