一、引言
链表是一种常见的数据结构,它允许动态地分配内存空间,并通过指针(或引用)将数据元素连接在一起。双向链表作为链表的一种,除了拥有普通链表的特性外,每个节点还包含两个指针:一个指向前一个节点(prev),另一个指向后一个节点(next)。这种结构使得双向链表在插入、删除以及遍历等操作上都具有独特的优势。
二、双向链表的基本结构
双向链表的基本结构通常包括一个节点(Node)类和一个双向链表(DoublyLinkedList)类。节点类用于存储数据元素以及指向前后节点的指针,而双向链表类则用于管理这些节点,提供插入、删除、遍历等操作的方法。
1. 节点类(Node)
节点类通常包含两个指针(prev和next)和一个数据元素(data)。在C++中,节点类可以定义如下:
template <typename T>
class Node {
public:T data;Node* prev;Node* next;Node(T data) : data(data), prev(nullptr), next(nullptr) {}
};
这里使用了模板类(template class),使得节点类可以存储任意类型的数据。
2. 双向链表类(DoublyLinkedList)
双向链表类需要包含一些基本操作,如插入节点、删除节点、遍历链表等。以下是一个简单的双向链表类的实现:
template <typename T>
class DoublyLinkedList {
private:Node<T>* head; // 指向头节点的指针Node<T>* tail; // 指向尾节点的指针int size; // 链表的大小// 辅助函数,用于在指定位置插入节点void insertAt(int position, T data) {// ... 实现细节将在后文给出 ...}// 辅助函数,用于删除指定位置的节点void deleteAt(int position) {// ... 实现细节将在后文给出 ...}public:DoublyLinkedList() : head(nullptr), tail(nullptr), size(0) {}// 插入节点到链表末尾void append(T data) {insertAt(size, data);}// 插入节点到链表开头void prepend(T data) {insertAt(0, data);}// 删除指定位置的节点void removeAt(int position) {if (position < 0 || position >= size) {throw std::out_of_range("Invalid position");}deleteAt(position);}// 删除指定值的节点(如果存在多个,只删除第一个)void remove(T data) {Node<T>* current = head;while (current != nullptr) {if (current->data == data) {removeAt(currentIndex(current));return;}current = current->next;}}// 遍历链表并打印节点数据void printList() {Node<T>* current = head;while (current != nullptr) {std::cout << current->data << " ";current = current->next;}std::cout << std::endl;}// 获取链表大小int getSize() const {return size;}// 检查链表是否为空bool isEmpty() const {return size == 0;}// ... 其他可能的方法,如查找、反转等 ...
};
三、双向链表的操作实现
接下来,我们将详细实现双向链表类中的一些关键操作。
1. 插入节点(insertAt)
在指定位置插入节点时,我们需要考虑几种情况:插入到链表开头、插入到链表末尾以及插入到链表中间。以下是insertAt方法的实现:
template <typename T>
void DoublyLinkedList<T>::insertAt(int position, T data) {if (position < 0 || position > size) {throw std::out_of_range("Invalid position");}Node<T>* newNode = new Node<T>(data);if (size == 0) { // 链表为空,新节点既是头节点也是尾节点head = tail = newNode;} else if (position == 0) { // 插入到链表开头newNode->next = head;head->prev = newNode;head = newNode;} else if (position == size) { // 插入到链表末尾newNode->prev = tail;tail->next = newNode;tail = newNode;} else { // 插入到链表中间Node<T>* current = head;for (int i = 0; i < position - 1; ++i) {current = current->next;}newNode->prev = current;newNode->next = current->next;current->next->prev = newNode;current->next = newNode;}++size;
}
2. 删除节点(deleteAt)
删除指定位置的节点时,我们需要考虑删除头节点、删除尾节点以及删除中间节点的情况。以下是deleteAt方法的实现:
template <typename T>
void DoublyLinkedList<T>::deleteAt(int position) {if (position < 0 || position >= size) {throw std::out_of_range("Invalid position");}Node<T>* target = head;if (position == 0) { // 删除头节点head = head->next;if (head != nullptr) {head->prev = nullptr;} else {tail = nullptr; // 如果链表为空,则尾节点也为空}} else if (position == size - 1) { // 删除尾节点target = tail;tail = tail->prev;if (tail != nullptr) {tail->next = nullptr;} else {head = nullptr; // 如果链表为空,则头节点也为空}} else { // 删除中间节点for (int i = 0; i < position; ++i) {target = target->next;}target->prev->next = target->next;target->next->prev = target->prev;}delete target;--size;
}
四、测试代码
下面是一个简单的测试程序,用于演示双向链表的用法:
#include <iostream>int main() {DoublyLinkedList<int> list;list.append(1);list.append(2);list.prepend(0);list.insertAt(1, 100);list.printList(); // 输出: 0 100 1 2std::cout << "Size: " << list.getSize() << std::endl; // 输出: 4std::cout << "Is empty? " << (list.isEmpty() ? "Yes" : "No") << std::endl; // 输出: Nolist.removeAt(1); // 删除索引为1的节点(值为100)list.printList(); // 输出: 0 1 2list.remove(1); // 删除值为1的节点list.printList(); // 输出: 0 2return 0;
}
五、总结
本文详细介绍了双向链表的基本结构、操作实现以及C++代码示例。双向链表通过引入两个指针(prev和next)来实现对前后节点的访问,从而在插入、删除和遍历等操作上提供了更高的灵活性。在实际应用中,双向链表常用于需要频繁进行节点插入和删除的场景,如LRU(最近最少使用)缓存淘汰算法等。通过掌握双向链表的基本原理和实现方法,读者可以更好地理解和应用这种数据结构。