深入理解双向链表的设计与实现
双向链表(Doubly Linked List)是一种常见的数据结构,它在每个节点中包含两个指针,分别指向前一个节点和后一个节点。相比于单向链表,双向链表的优势在于可以在O(1)时间内进行前向和后向遍历。本文将详细介绍如何设计一个支持插入、删除和查找操作的双向链表,并分析其实现细节。
一、双向链表的基本概念
双向链表是一种线性数据结构,其中每个节点包含三个部分:
- 数据域:存储节点的数据。
- 前驱指针:指向前一个节点。
- 后继指针:指向后一个节点。
双向链表的结构如下图所示:
NULL <- [prev | data | next] <-> [prev | data | next] <-> ... <-> [prev | data | next] -> NULL
二、双向链表的节点设计
在C++中,我们可以使用类来定义双向链表的节点。每个节点包含一个数据域和两个指针,分别指向前一个节点和后一个节点。以下是节点类的定义:
class Node {
public:
int data;
Node* prev;
Node* next;
Node(int val) : data(val), prev(nullptr), next(nullptr) {}
};
三、双向链表的类设计
双向链表类需要包含头指针和尾指针,以便于进行插入、删除和查找操作。以下是双向链表类的定义:
class DoublyLinkedList {
private:
Node* head;
Node* tail;
public:
DoublyLinkedList() : head(nullptr), tail(nullptr) {}
// 插入操作
void insertAtBeginning(int val);
void insertAtEnd(int val);
void insertAfter(Node* prevNode, int val);
// 删除操作
void deleteNode(Node* delNode);
// 查找操作
Node* search(int val);
// 打印链表
void printList();
};
四、插入操作的实现
双向链表的插入操作可以分为三种情况:在链表的开头插入、在链表的末尾插入和在指定节点之后插入。
- 在链表的开头插入:
void DoublyLinkedList::insertAtBeginning(int val) {
Node* newNode = new Node(val);
if (head == nullptr) {
head = tail = newNode;
} else {
newNode->next = head;
head->prev = newNode;
head = newNode;
}
}
- 在链表的末尾插入:
void DoublyLinkedList::insertAtEnd(int val) {
Node* newNode = new Node(val);
if (tail == nullptr) {
head = tail = newNode;
} else {
newNode->prev = tail;
tail->next = newNode;
tail = newNode;
}
}
- 在指定节点之后插入:
void DoublyLinkedList::insertAfter(Node* prevNode, int val) {
if (prevNode == nullptr) return;
Node* newNode = new Node(val);
newNode->next = prevNode->next;
newNode->prev = prevNode;
if (prevNode->next != nullptr) {
prevNode->next->prev = newNode;
} else {
tail = newNode;
}
prevNode->next = newNode;
}
五、删除操作的实现
双向链表的删除操作需要考虑节点的前驱和后继指针的调整。以下是删除操作的实现:
void DoublyLinkedList::deleteNode(Node* delNode) {
if (delNode == nullptr) return;
if (delNode == head) {
head = delNode->next;
}
if (delNode == tail) {
tail = delNode->prev;
}
if (delNode->next != nullptr) {
delNode->next->prev = delNode->prev;
}
if (delNode->prev != nullptr) {
delNode->prev->next = delNode->next;
}
delete delNode;
}
六、查找操作的实现
查找操作需要遍历链表,找到指定值的节点。以下是查找操作的实现:
Node* DoublyLinkedList::search(int val) {
Node* current = head;
while (current != nullptr) {
if (current->data == val) {
return current;
}
current = current->next;
}
return nullptr;
}
七、打印链表
为了方便调试和验证,我们可以实现一个打印链表的函数,输出链表中的所有节点值:
void DoublyLinkedList::printList() {
Node* current = head;
while (current != nullptr) {
cout << current->data << " ";
current = current->next;
}
cout << endl;
}
八、完整代码示例
以下是完整的双向链表实现代码:
#include <iostream>
using namespace std;
class Node {
public:
int data;
Node* prev;
Node* next;
Node(int val) : data(val), prev(nullptr), next(nullptr) {}
};
class DoublyLinkedList {
private:
Node* head;
Node* tail;
public:
DoublyLinkedList() : head(nullptr), tail(nullptr) {}
void insertAtBeginning(int val);
void insertAtEnd(int val);
void insertAfter(Node* prevNode, int val);
void deleteNode(Node* delNode);
Node* search(int val);
void printList();
};
void DoublyLinkedList::insertAtBeginning(int val) {
Node* newNode = new Node(val);
if (head == nullptr) {
head = tail = newNode;
} else {
newNode->next = head;
head->prev = newNode;
head = newNode;
}
}
void DoublyLinkedList::insertAtEnd(int val) {
Node* newNode = new Node(val);
if (tail == nullptr) {
head = tail = newNode;
} else {
newNode->prev = tail;
tail->next = newNode;
tail = newNode;
}
}
void DoublyLinkedList::insertAfter(Node* prevNode, int val) {
if (prevNode == nullptr) return;
Node* newNode = new Node(val);
newNode->next = prevNode->next;
newNode->prev = prevNode;
if (prevNode->next != nullptr) {
prevNode->next->prev = newNode;
} else {
tail = newNode;
}
prevNode->next = newNode;
}
void DoublyLinkedList::deleteNode(Node* delNode) {
if (delNode == nullptr) return;
if (delNode == head) {
head = delNode->next;
}
if (delNode == tail) {
tail = delNode->prev;
}
if (delNode->next != nullptr) {
delNode->next->prev = delNode->prev;
}
if (delNode->prev != nullptr) {
delNode->prev->next = delNode->next;
}
delete delNode;
}
Node* DoublyLinkedList::search(int val) {
Node* current = head;
while (current != nullptr) {
if (current->data == val) {
return current;
}
current = current->next;
}
return nullptr;
}
void DoublyLinkedList::printList() {
Node* current = head;
while (current != nullptr) {
cout << current->data << " ";
current = current->next;
}
cout << endl;
}
int main() {
DoublyLinkedList dll;
dll.insertAtBeginning(10);
dll.insertAtEnd(20);
dll.insertAtEnd(30);
dll.insertAfter(dll.search(20), 25);
cout << "链表内容: ";
dll.printList();
dll.deleteNode(dll.search(25));
cout << "删除25后的链表内容: ";
dll.printList();
return 0;
}
九、总结
本文详细介绍了双向链表的设计与实现,包括插入、删除和查找操作。通过这些操作,我们可以灵活地管理链表中的数据。双向链表在实际应用中具有广泛的用途,例如实现LRU缓存、双端队列等。希望本文对你理解双向链表的实现有所帮助,并能在面试中展示你的编程能力和对C++语言特性的理解。
如果你有其他问题或需要进一步的帮助,请随时告诉我!😊