双向链表
- 一、双向链表是什么?
- 二、双向链表的特点
- 三、双向链表的操作
-
- (1)初始化双向链表
- (2)插入操作
-
- ① 在链表头部插入节点
- ② 在链表尾部插入节点
- 指定位置插入
- (3)删除操作
-
- ① 在链表头部删除节点
- ② 在链表尾部删除节点
- 指定位置删除
- (4)销毁链表
- (5)链表的打印
- 四、总结
在数据结构的世界中,链表是一种非常基础且重要的结构。今天,我们来深入探讨一种特殊的链表------双向链表。它不仅继承了单链表的灵活性,还通过增加反向链接,极大地提升了操作效率。本文将详细介绍双向链表的结构、操作、优势以及应用场景,帮助你更好地理解和使用这种强大的数据结构。
一、双向链表是什么?
双向链表是一种线性数据结构,它由一系列节点组成,每个节点包含三个部分:
- 数据域:存储实际数据。
- 指向前一个节点的指针(prev):指向当前节点的前驱节点。
- 指向后一个节点的指针(next):指向当前节点的后继节点。
与单链表相比,双向链表的每个节点都可以方便地访问其前驱和后继节点,这使得它在某些操作上更加高效。
c
typedef struct Node {
int data; // 数据域
struct Node* prev; // 指向前一个节点的指针
struct Node* next; // 指向后一个节点的指针
} Node;
二、双向链表的特点
(1)灵活性高
双向链表可以方便地向前或向后遍历。这种双向遍历的特性使得它在某些场景下比单链表更加灵活。
(2)插入和删除操作高效
在已知节点位置的情况下,插入和删除操作的时间复杂度为 O(1)。这是因为可以直接通过前驱和后继指针调整链接关系,而不需要像单链表那样从头开始遍历。
(3)空间开销较大
每个节点需要额外存储两个指针,相比单链表,空间开销更大。因此,在内存资源有限的情况下,需要权衡使用。
三、双向链表的操作
(1)初始化双向链表
创建一个双向链表时,通常会引入一个头节点(哨兵节点),它不存储实际数据,但可以简化插入和删除操作。
c
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
(2)插入操作
① 在链表头部插入节点
c
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
② 在链表尾部插入节点
c
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* tail = phead->prev;
ListNode* newnode = BuyListNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
指定位置插入
c
// pos位置之前插入x
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
(3)删除操作
① 在链表头部删除节点
c
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
② 在链表尾部删除节点
c
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;
}
指定位置删除
c
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
(4)销毁链表
c
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
(5)链表的打印
c
void ListPrint(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
四、总结
双向链表是一种非常强大的数据结构,它通过增加反向链接,极大地提升了操作的灵活性和效率。虽然它比单链表占用更多空间,但在需要频繁插入、删除或双向遍历的场景中,双向链表无疑是更好的选择。