文章目录
双向带头循环链表结构如图
![双向带头循环链表](https://i-blog.csdnimg.cn/blog_migrate/0a87fe65e2b3153a52d88482332a88f5.png)
双向循环链表结构定义
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* prev;
struct ListNode* next;
}LTNode;
注意,结构定义并不体现链表是否带环。
双向带头循环链表需要实现的接口有:
- 创建链表并返回头节点(初始化)
- 销毁
- 打印
- 尾插
- 头插
- 尾删
- 头删
- 查找
- 在pos前一个位置插入
- 删除pos节点
创建链表并初始化
注意这里创建了一个头节点(哨兵节点),不存数据。
LTNode* IistInit()
{
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
开辟新节点
LTNode* BuyListNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
打印双向链表
注意循环结束条件是cur=phead
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while(cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
双向链表尾插
时间复杂度为O(1),比单链表效率高,和顺序表一样
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = BuyListNode(x);
tail->next = newnode;
newnode->prev = tail;
phead->prev = newnode;
newnode->next = phead;
}
双向链表尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
双向链表头插
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyLsitNode(x);
LTNode* next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
双向链表头删
删除前提是链表不为空
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* next = phead->next;
LTNode* nextNext = next->next;
phead->next = nextNext;
nextNext->prev = phead;
free(next);
}
双向链表查找
返回值为x的节点
找不到就返回NULL
LTNode* ListFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while(cur != phead)
{
if(cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
在pos前一个位置插入
默认pos在链表中
这里如果不是双向链表是无法实现的(但是没有头节点也可以实现)
void ListInsert(LTNode* phead, LTDataType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = BuyListNode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
删除pos节点
这里默认pos在链表中,并且pos不能是哨兵节点,那么链表里至少有phead和pos
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
销毁链表
free前仍需要记录下一个节点,于单链表不同之处在于循环结束条件,并且需要free哨兵结点,需要注意的是,phead并没有被置成空,需要使用者在函数外将phead置空。类似free。
void LsitDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while(cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
复用ListInsert实现头插尾插
头插
void ListPushFront(LTNode* phead, LTDataType x)
{
ListInsert(phead->next, x);
}
尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
ListInsert(phead, x);
}
复用LsitErase实现头删尾删
头删
需要注意,断言需要保留。
之前的头删中
assert(phead->next != phead);
用来确保链表不为空,在这里确保了传给ListErase的pos不为phead
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next !=phead);
ListErase(phead->next);
}
尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListErase(phead->prev);
}
总结
双向循环链表无论是头插尾插、中间插入删除时间复杂度都是O(1),可以说充分展现了链表的优点。
除了不能随机访问,其余效率都比顺序表高。
由于可以访问前一个节点,尾插、中间插入、尾删、中间删除都比单链表简洁。