- C++
C++双向循环链表:带哨兵节点的实现
- 2025-6-9 22:31:41 @
C++双向循环链表教程:带哨兵节点的实现
一、什么是哨兵节点?
哨兵节点(Sentinel Node)是一种特殊的节点,它不存储实际数据,仅作为链表的边界标记。在双向循环链表中使用头尾哨兵节点可以简化插入和删除操作,避免处理边界条件。
二、双向循环链表的结构设计
下面是带哨兵节点的双向循环链表的基本结构:
#include <iostream>
template<typename T>
struct Node {
T data; // 数据域
Node* prev; // 指向前驱节点的指针
Node* next; // 指向后继节点的指针
// 构造函数
Node(const T& value = T()) : data(value), prev(nullptr), next(nullptr) {}
};
template<typename T>
class CircularDoublyLinkedList {
private:
Node<T>* head; // 头哨兵节点
Node<T>* tail; // 尾哨兵节点
int count; // 链表中实际元素的数量
public:
// 构造函数
CircularDoublyLinkedList();
// 析构函数
~CircularDoublyLinkedList();
// 判断链表是否为空
bool empty() const;
// 返回链表中元素的数量
int size() const;
// 在链表头部插入元素
void push_front(const T& value);
// 在链表尾部插入元素
void push_back(const T& value);
// 删除链表头部元素
void pop_front();
// 删除链表尾部元素
void pop_back();
// 获取链表头部元素
T& front();
// 获取链表尾部元素
T& back();
// 在指定位置插入元素
void insert(int index, const T& value);
// 删除指定位置的元素
void erase(int index);
// 清空链表
void clear();
// 打印链表中的所有元素
void print() const;
};
三、哨兵节点的初始化与链表构造
在构造函数中,我们需要初始化头尾哨兵节点并建立循环连接:
template<typename T>
CircularDoublyLinkedList<T>::CircularDoublyLinkedList() {
// 创建头哨兵节点
head = new Node<T>();
// 创建尾哨兵节点
tail = new Node<T>();
// 初始化循环连接
head->next = tail;
tail->prev = head;
// 初始元素数量为0
count = 0;
}
链表初始状态下,头哨兵节点的next
指向尾哨兵节点,尾哨兵节点的prev
指向头哨兵节点,形成一个空的循环。
四、基本操作实现
下面是链表的一些基本操作的实现:
// 判断链表是否为空
template<typename T>
bool CircularDoublyLinkedList<T>::empty() const {
return head->next == tail; // 当且仅当head的next是tail时链表为空
}
// 返回链表中元素的数量
template<typename T>
int CircularDoublyLinkedList<T>::size() const {
return count;
}
// 在链表头部插入元素
template<typename T>
void CircularDoublyLinkedList<T>::push_front(const T& value) {
Node<T>* newNode = new Node<T>(value);
// 连接新节点和头哨兵节点之后的节点
newNode->next = head->next;
newNode->prev = head;
// 更新头哨兵节点和原第一个节点
head->next->prev = newNode;
head->next = newNode;
// 增加元素计数
count++;
}
// 在链表尾部插入元素
template<typename T>
void CircularDoublyLinkedList<T>::push_back(const T& value) {
Node<T>* newNode = new Node<T>(value);
// 连接新节点和尾哨兵节点之前的节点
newNode->prev = tail->prev;
newNode->next = tail;
// 更新尾哨兵节点和原最后一个节点
tail->prev->next = newNode;
tail->prev = newNode;
// 增加元素计数
count++;
}
五、删除操作实现
删除操作同样需要注意维护循环结构:
// 删除链表头部元素
template<typename T>
void CircularDoublyLinkedList<T>::pop_front() {
if (empty()) return; // 链表为空时不执行操作
Node<T>* toDelete = head->next; // 要删除的节点
// 更新指针,跳过要删除的节点
head->next = toDelete->next;
toDelete->next->prev = head;
// 删除节点并减少计数
delete toDelete;
count--;
}
// 删除链表尾部元素
template<typename T>
void CircularDoublyLinkedList<T>::pop_back() {
if (empty()) return; // 链表为空时不执行操作
Node<T>* toDelete = tail->prev; // 要删除的节点
// 更新指针,跳过要删除的节点
tail->prev = toDelete->prev;
toDelete->prev->next = tail;
// 删除节点并减少计数
delete toDelete;
count--;
}
六、访问操作实现
通过头尾哨兵节点,我们可以方便地访问链表的首尾元素:
// 获取链表头部元素
template<typename T>
T& CircularDoublyLinkedList<T>::front() {
if (empty()) throw std::out_of_range("List is empty");
return head->next->data; // 返回头哨兵节点之后的节点数据
}
// 获取链表尾部元素
template<typename T>
T& CircularDoublyLinkedList<T>::back() {
if (empty()) throw std::out_of_range("List is empty");
return tail->prev->data; // 返回尾哨兵节点之前的节点数据
}
七、任意位置操作实现
在指定位置插入和删除元素:
// 在指定位置插入元素
template<typename T>
void CircularDoublyLinkedList<T>::insert(int index, const T& value) {
if (index < 0 || index > count) throw std::out_of_range("Index out of range");
Node<T>* current = head;
// 移动到插入位置的前一个节点
for (int i = 0; i < index; i++) {
current = current->next;
}
// 创建新节点并插入
Node<T>* newNode = new Node<T>(value);
newNode->next = current->next;
newNode->prev = current;
current->next->prev = newNode;
current->next = newNode;
// 增加计数
count++;
}
// 删除指定位置的元素
template<typename T>
void CircularDoublyLinkedList<T>::erase(int index) {
if (index < 0 || index >= count) throw std::out_of_range("Index out of range");
Node<T>* current = head->next;
// 移动到要删除的节点
for (int i = 0; i < index; i++) {
current = current->next;
}
// 更新指针,跳过要删除的节点
current->prev->next = current->next;
current->next->prev = current->prev;
// 删除节点并减少计数
delete current;
count--;
}
八、清理和打印操作
// 清空链表
template<typename T>
void CircularDoublyLinkedList<T>::clear() {
while (!empty()) {
pop_front();
}
}
// 打印链表中的所有元素
template<typename T>
void CircularDoublyLinkedList<T>::print() const {
Node<T>* current = head->next;
while (current != tail) {
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
}
// 析构函数
template<typename T>
CircularDoublyLinkedList<T>::~CircularDoublyLinkedList() {
clear();
delete head;
delete tail;
}
九、使用示例
下面是一个使用这个双向循环链表的简单示例:
int main() {
CircularDoublyLinkedList<int> list;
// 在尾部插入元素
list.push_back(10);
list.push_back(20);
list.push_back(30);
// 打印链表
std::cout << "链表内容: ";
list.print(); // 输出: 10 20 30
// 在头部插入元素
list.push_front(5);
// 打印链表
std::cout << "插入头部元素后的链表: ";
list.print(); // 输出: 5 10 20 30
// 删除尾部元素
list.pop_back();
// 打印链表
std::cout << "删除尾部元素后的链表: ";
list.print(); // 输出: 5 10 20
// 在位置1插入元素
list.insert(1, 15);
// 打印链表
std::cout << "在位置1插入元素后的链表: ";
list.print(); // 输出: 5 15 10 20
// 删除位置2的元素
list.erase(2);
// 打印链表
std::cout << "删除位置2元素后的链表: ";
list.print(); // 输出: 5 15 20
return 0;
}
十、哨兵节点的优势
使用哨兵节点的主要优势:
- 简化边界条件:不需要特殊处理空链表或头尾节点的插入删除
- 代码简洁:减少了条件判断,使代码更易读和维护
- 避免空指针:永远不会有空指针引用,所有节点都有前驱和后继
这种结构在实现高级数据结构(如STL的list)时非常有用,能够提高代码的健壮性和性能。
3 条评论
-
admin SU @ 2025-6-9 22:50:24
#include <iostream> template<typename T> struct Node { T data; // 数据域 Node* prev; // 指向前驱节点的指针 Node* next; // 指向后继节点的指针 // 构造函数 Node(const T& value = T()) : data(value), prev(nullptr), next(nullptr) {} }; template<typename T> class CircularDoublyLinkedList { private: Node<T>* head; // 头哨兵节点 Node<T>* tail; // 尾哨兵节点 int count; // 链表中实际元素的数量 public: // 构造函数 CircularDoublyLinkedList(); // 析构函数 ~CircularDoublyLinkedList(); // 判断链表是否为空 bool empty() const; // 返回链表中元素的数量 int size() const; // 在链表头部插入元素 void push_front(const T& value); // 在链表尾部插入元素 void push_back(const T& value); // 删除链表头部元素 void pop_front(); // 删除链表尾部元素 void pop_back(); // 获取链表头部元素 T& front(); // 获取链表尾部元素 T& back(); // 在指定位置插入元素 void insert(int index, const T& value); // 删除指定位置的元素 void erase(int index); // 清空链表 void clear(); // 打印链表中的所有元素 void print() const; }; template<typename T> CircularDoublyLinkedList<T>::CircularDoublyLinkedList() { // 创建头哨兵节点 head = new Node<T>(); // 创建尾哨兵节点 tail = new Node<T>(); // 初始化循环连接 head->next = tail; tail->prev = head; // 初始元素数量为0 count = 0; } // 判断链表是否为空 template<typename T> bool CircularDoublyLinkedList<T>::empty() const { return head->next == tail; // 当且仅当head的next是tail时链表为空 } // 返回链表中元素的数量 template<typename T> int CircularDoublyLinkedList<T>::size() const { return count; } // 在链表头部插入元素 template<typename T> void CircularDoublyLinkedList<T>::push_front(const T& value) { Node<T>* newNode = new Node<T>(value); // 连接新节点和头哨兵节点之后的节点 newNode->next = head->next; newNode->prev = head; // 更新头哨兵节点和原第一个节点 head->next->prev = newNode; head->next = newNode; // 增加元素计数 count++; } // 在链表尾部插入元素 template<typename T> void CircularDoublyLinkedList<T>::push_back(const T& value) { Node<T>* newNode = new Node<T>(value); // 连接新节点和尾哨兵节点之前的节点 newNode->prev = tail->prev; newNode->next = tail; // 更新尾哨兵节点和原最后一个节点 tail->prev->next = newNode; tail->prev = newNode; // 增加元素计数 count++; } // 删除链表头部元素 template<typename T> void CircularDoublyLinkedList<T>::pop_front() { if (empty()) return; // 链表为空时不执行操作 Node<T>* toDelete = head->next; // 要删除的节点 // 更新指针,跳过要删除的节点 head->next = toDelete->next; toDelete->next->prev = head; // 删除节点并减少计数 delete toDelete; count--; } // 删除链表尾部元素 template<typename T> void CircularDoublyLinkedList<T>::pop_back() { if (empty()) return; // 链表为空时不执行操作 Node<T>* toDelete = tail->prev; // 要删除的节点 // 更新指针,跳过要删除的节点 tail->prev = toDelete->prev; toDelete->prev->next = tail; // 删除节点并减少计数 delete toDelete; count--; } // 获取链表头部元素 template<typename T> T& CircularDoublyLinkedList<T>::front() { if (empty()) throw std::out_of_range("List is empty"); return head->next->data; // 返回头哨兵节点之后的节点数据 } // 获取链表尾部元素 template<typename T> T& CircularDoublyLinkedList<T>::back() { if (empty()) throw std::out_of_range("List is empty"); return tail->prev->data; // 返回尾哨兵节点之前的节点数据 } // 在指定位置插入元素 template<typename T> void CircularDoublyLinkedList<T>::insert(int index, const T& value) { if (index < 0 || index > count) throw std::out_of_range("Index out of range"); Node<T>* current = head; // 移动到插入位置的前一个节点 for (int i = 0; i < index; i++) { current = current->next; } // 创建新节点并插入 Node<T>* newNode = new Node<T>(value); newNode->next = current->next; newNode->prev = current; current->next->prev = newNode; current->next = newNode; // 增加计数 count++; } // 删除指定位置的元素 template<typename T> void CircularDoublyLinkedList<T>::erase(int index) { if (index < 0 || index >= count) throw std::out_of_range("Index out of range"); Node<T>* current = head->next; // 移动到要删除的节点 for (int i = 0; i < index; i++) { current = current->next; } // 更新指针,跳过要删除的节点 current->prev->next = current->next; current->next->prev = current->prev; // 删除节点并减少计数 delete current; count--; } // 清空链表 template<typename T> void CircularDoublyLinkedList<T>::clear() { while (!empty()) { pop_front(); } } // 打印链表中的所有元素 template<typename T> void CircularDoublyLinkedList<T>::print() const { Node<T>* current = head->next; while (current != tail) { std::cout << current->data << " "; current = current->next; } std::cout << std::endl; } // 析构函数 template<typename T> CircularDoublyLinkedList<T>::~CircularDoublyLinkedList() { clear(); delete head; delete tail; } int main() { CircularDoublyLinkedList<int> list; // 在尾部插入元素 list.push_back(10); list.push_back(20); list.push_back(30); // 打印链表 std::cout << "链表内容: "; list.print(); // 输出: 10 20 30 // 在头部插入元素 list.push_front(5); // 打印链表 std::cout << "插入头部元素后的链表: "; list.print(); // 输出: 5 10 20 30 // 删除尾部元素 list.pop_back(); // 打印链表 std::cout << "删除尾部元素后的链表: "; list.print(); // 输出: 5 10 20 // 在位置1插入元素 list.insert(1, 15); // 打印链表 std::cout << "在位置1插入元素后的链表: "; list.print(); // 输出: 5 15 10 20 // 删除位置2的元素 list.erase(2); // 打印链表 std::cout << "删除位置2元素后的链表: "; list.print(); // 输出: 5 15 20 return 0; }
-
2025-6-9 22:36:06@
C++ 双向循环链表(包含头尾哨兵节点)教程
简介
双向循环链表是一种特殊的链表结构,它的每个节点都有两个指针:
prev
指向前一个节点next
指向后一个节点
在这个实现中,我们会使用头尾哨兵节点(Sentinel Nodes),它们是不存储实际数据的特殊节点:
- 头哨兵节点始终位于链表的最前面
- 尾哨兵节点始终位于链表的最后面
这种设计可以简化边界条件的处理,让代码更加简洁。
节点类定义
template<typename T> class Node { public: T data; // 存储的数据 Node* prev; // 指向前一个节点 Node* next; // 指向后一个节点 // 构造函数 Node(const T& data = T(), Node* prev = nullptr, Node* next = nullptr) : data(data), prev(prev), next(next) {} };
链表类定义
template<typename T> class DoublyCircularLinkedList { private: Node<T>* head; // 头哨兵节点 Node<T>* tail; // 尾哨兵节点 int size; // 链表的实际元素个数(不含哨兵) public: // 构造函数 - 初始化空链表(只有哨兵节点) DoublyCircularLinkedList() { // 创建头尾哨兵节点 head = new Node<T>(); // 不存储实际数据 tail = new Node<T>(); // 不存储实际数据 // 初始时,头尾哨兵节点互相指向对方 head->next = tail; head->prev = tail; tail->prev = head; tail->next = head; size = 0; } // 析构函数 - 释放所有节点内存 ~DoublyCircularLinkedList() { clear(); // 清空链表 delete head; // 删除头哨兵 delete tail; // 删除尾哨兵 } // 在指定位置插入新节点 bool insert(int index, const T& data) { if (index < 0 || index > size) { return false; // 插入位置非法 } Node<T>* newNode = new Node<T>(data); // 找到插入位置前后的节点 Node<T>* current = head->next; for (int i = 0; i < index; ++i) { current = current->next; } // 插入新节点 newNode->prev = current->prev; newNode->next = current; current->prev->next = newNode; current->prev = newNode; ++size; return true; } // 在头部添加元素 void push_front(const T& data) { insert(0, data); } // 在尾部添加元素 void push_back(const T& data) { insert(size, data); } // 删除指定位置的节点 bool remove(int index) { if (index < 0 || index >= size) { return false; // 删除位置非法 } // 找到要删除的节点 Node<T>* current = head->next; for (int i = 0; i < index; ++i) { current = current->next; } // 修改前后节点的指针 current->prev->next = current->next; current->next->prev = current->prev; // 释放内存 delete current; --size; return true; } // 删除头部元素 void pop_front() { if (!empty()) { remove(0); } } // 删除尾部元素 void pop_back() { if (!empty()) { remove(size - 1); } } // 获取指定位置的元素 bool get(int index, T& data) const { if (index < 0 || index >= size) { return false; // 位置非法 } // 找到对应节点 Node<T>* current = head->next; for (int i = 0; i < index; ++i) { current = current->next; } data = current->data; return true; } // 查找元素的位置 int find(const T& data) const { Node<T>* current = head->next; int index = 0; while (current != tail) { if (current->data == data) { return index; } current = current->next; ++index; } return -1; // 未找到 } // 修改指定位置的元素值 bool set(int index, const T& data) { if (index < 0 || index >= size) { return false; // 位置非法 } // 找到对应节点 Node<T>* current = head->next; for (int i = 0; i < index; ++i) { current = current->next; } current->data = data; return true; } // 判断链表是否为空 bool empty() const { return size == 0; } // 获取链表长度 int getSize() const { return size; } // 清空链表 void clear() { Node<T>* current = head->next; // 释放所有实际数据节点 while (current != tail) { Node<T>* next = current->next; delete current; current = next; } // 重置哨兵节点 head->next = tail; tail->prev = head; size = 0; } // 打印链表内容 void print() const { std::cout << "链表内容: "; Node<T>* current = head->next; while (current != tail) { std::cout << current->data << " "; current = current->next; } std::cout << std::endl; } // 从尾部打印链表内容 void printReverse() const { std::cout << "逆序链表内容: "; Node<T>* current = tail->prev; while (current != head) { std::cout << current->data << " "; current = current->prev; } std::cout << std::endl; } };
使用示例
#include <iostream> using namespace std; // 上面的Node和DoublyCircularLinkedList类定义放在这里 int main() { DoublyCircularLinkedList<int> list; // 添加元素 list.push_back(10); list.push_back(20); list.push_front(5); list.insert(2, 15); // 在索引2处插入15 // 打印链表 list.print(); // 输出: 5 10 15 20 list.printReverse(); // 输出: 20 15 10 5 int value; if (list.get(2, value)) { cout << "索引2处的值为: " << value << endl; // 输出: 15 } // 修改元素 list.set(1, 25); // 删除元素 list.pop_back(); // 删除尾部元素 list.pop_front(); // 删除头部元素 list.remove(1); // 删除索引1处的元素 // 清空链表 list.clear(); return 0; }
总结
这个双向循环链表的主要特点包括:
- 头尾哨兵节点:简化了边界条件的处理
- 双向性:可以通过
prev
和next
在两个方向上遍历 - 循环性:尾节点的
next
指向头节点,头节点的prev
指向尾节点 - 常用操作:实现了插入、删除、查找、修改等基本功能
使用头尾哨兵节点的设计可以让代码更清晰、减少边界条件判断,并且更容易实现循环特性。
-
2025-6-9 22:32:59@
下面是完整的带哨兵节点的双向循环链表实现程序,包含详细注释和使用示例:
#include <iostream> #include <stdexcept> // 链表节点结构 template<typename T> struct Node { T data; // 数据域 Node* prev; // 指向前驱节点的指针 Node* next; // 指向后继节点的指针 // 构造函数 Node(const T& value = T()) : data(value), prev(nullptr), next(nullptr) {} }; // 双向循环链表类 template<typename T> class CircularDoublyLinkedList { private: Node<T>* head; // 头哨兵节点 Node<T>* tail; // 尾哨兵节点 int count; // 链表中实际元素的数量 public: // 构造函数 CircularDoublyLinkedList() { // 创建头哨兵节点 head = new Node<T>(); // 创建尾哨兵节点 tail = new Node<T>(); // 初始化循环连接 head->next = tail; tail->prev = head; // 初始元素数量为0 count = 0; } // 析构函数 ~CircularDoublyLinkedList() { clear(); delete head; delete tail; } // 判断链表是否为空 bool empty() const { return head->next == tail; // 当且仅当head的next是tail时链表为空 } // 返回链表中元素的数量 int size() const { return count; } // 在链表头部插入元素 void push_front(const T& value) { Node<T>* newNode = new Node<T>(value); // 连接新节点和头哨兵节点之后的节点 newNode->next = head->next; newNode->prev = head; // 更新头哨兵节点和原第一个节点 head->next->prev = newNode; head->next = newNode; // 增加元素计数 count++; } // 在链表尾部插入元素 void push_back(const T& value) { Node<T>* newNode = new Node<T>(value); // 连接新节点和尾哨兵节点之前的节点 newNode->prev = tail->prev; newNode->next = tail; // 更新尾哨兵节点和原最后一个节点 tail->prev->next = newNode; tail->prev = newNode; // 增加元素计数 count++; } // 删除链表头部元素 void pop_front() { if (empty()) return; // 链表为空时不执行操作 Node<T>* toDelete = head->next; // 要删除的节点 // 更新指针,跳过要删除的节点 head->next = toDelete->next; toDelete->next->prev = head; // 删除节点并减少计数 delete toDelete; count--; } // 删除链表尾部元素 void pop_back() { if (empty()) return; // 链表为空时不执行操作 Node<T>* toDelete = tail->prev; // 要删除的节点 // 更新指针,跳过要删除的节点 tail->prev = toDelete->prev; toDelete->prev->next = tail; // 删除节点并减少计数 delete toDelete; count--; } // 获取链表头部元素 T& front() { if (empty()) throw std::out_of_range("List is empty"); return head->next->data; // 返回头哨兵节点之后的节点数据 } // 获取链表尾部元素 T& back() { if (empty()) throw std::out_of_range("List is empty"); return tail->prev->data; // 返回尾哨兵节点之前的节点数据 } // 在指定位置插入元素 void insert(int index, const T& value) { if (index < 0 || index > count) throw std::out_of_range("Index out of range"); Node<T>* current = head; // 移动到插入位置的前一个节点 for (int i = 0; i < index; i++) { current = current->next; } // 创建新节点并插入 Node<T>* newNode = new Node<T>(value); newNode->next = current->next; newNode->prev = current; current->next->prev = newNode; current->next = newNode; // 增加计数 count++; } // 删除指定位置的元素 void erase(int index) { if (index < 0 || index >= count) throw std::out_of_range("Index out of range"); Node<T>* current = head->next; // 移动到要删除的节点 for (int i = 0; i < index; i++) { current = current->next; } // 更新指针,跳过要删除的节点 current->prev->next = current->next; current->next->prev = current->prev; // 删除节点并减少计数 delete current; count--; } // 清空链表 void clear() { while (!empty()) { pop_front(); } } // 打印链表中的所有元素 void print() const { Node<T>* current = head->next; while (current != tail) { std::cout << current->data << " "; current = current->next; } std::cout << std::endl; } }; // 使用示例 int main() { CircularDoublyLinkedList<int> list; // 在尾部插入元素 list.push_back(10); list.push_back(20); list.push_back(30); // 打印链表 std::cout << "链表内容: "; list.print(); // 输出: 10 20 30 // 在头部插入元素 list.push_front(5); // 打印链表 std::cout << "插入头部元素后的链表: "; list.print(); // 输出: 5 10 20 30 // 删除尾部元素 list.pop_back(); // 打印链表 std::cout << "删除尾部元素后的链表: "; list.print(); // 输出: 5 10 20 // 在位置1插入元素 list.insert(1, 15); // 打印链表 std::cout << "在位置1插入元素后的链表: "; list.print(); // 输出: 5 15 10 20 // 删除位置2的元素 list.erase(2); // 打印链表 std::cout << "删除位置2元素后的链表: "; list.print(); // 输出: 5 15 20 return 0; }
这个程序实现了一个完整的带哨兵节点的双向循环链表,包括以下功能:
- 链表的创建和销毁
- 元素的插入(头部、尾部、任意位置)
- 元素的删除(头部、尾部、任意位置)
- 元素的访问(头部、尾部)
- 链表状态检查(是否为空、大小)
- 链表内容的打印
你可以直接编译运行这个程序,也可以根据需要扩展更多功能,如迭代器支持、反转链表、查找元素等操作。
- 1