简述:有了前篇单链表的增删查改的基础 也是出于带头双向循环链表本身的优势 双链表的增删就变得格外简单。让我们一起来体验一下。
目录
带头双向循环链表图解:
带头双向循环链表基本结构:
typedef struct ListNode
{
LTDataType _data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
带头双向循环链表的实现:
函数实现基本框架的创建不再详述...
初始化:
与无头单向不循环链表不同的是 这里需要创建哨兵位 即所谓的头
这里有两种方式:第一种是传二级指针 另一种是返回值法 我们来了解一下
void ListInit(LTNode** phead)//用二级指针的方法
{
*phead = (LTNode*)malloc(sizeof(LTNode));//要发生对phead的修改 所以这里传二级指针
(*phead)->next = *phead;//调整存放的前后指针
(*phead)->prev = *phead;
}
LTNode* ListInit(LTNode* phead)//返回值法
{
phead = (LTNode*)malloc(sizeof(LTNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
两种方法均可以实现 但是返回值法要注意记得接收返回值
创建新节点:
因为后面尾插、头插、指定位置前插节点都要创建新节点 因此我们这里将其包装成一个函数
prev、next前后设空是个好习惯
LTNode* BuyListNode(LTDateType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
newnode->val = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
尾插:
assert也是个好习惯
链接思路看图解
void ListPushBack(LTNode* phead, LTDateType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
//链接
LTNode* tail = phead->prev;
phead->prev = newnode;//哨兵的头
newnode->next = phead;//新节点的尾
tail->next = newnode;
newnode->prev = tail;
//ListInsert(phead,x);//后面就知道啦
}
打印:
与单向不循环链表思路相同 不再详述
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->val);
cur = cur->next;
}
printf("\n");
}
尾删:
值得注意得是 我们这里的头结点不存储任何有效数据 作为哨兵位
要设置条件避免误删 我们这里可以设置assert(phead->next!=phead)
void ListPopBcak(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//不注意会把哨兵位也删掉
LTNode* tail = phead->prev;
tail->prev->next = phead;
phead->prev = tail->prev;
free(tail);
//ListErase(phead->prev);//后面就知道啦
}
头插:
void ListPushFront(LTNode* phead,LTDateType x)
{
assert(phead);
LTNode* next = phead->next;
LTNode* newnode = BuyListNode(x);
phead->next = newnode;
newnode->next = next;
next->prev = newnode;
newnode->prev = phead;
//ListInsert(phead->next, x);//后面就知道啦
}
头删:
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//不注意会把哨兵位也删掉
LTNode* next = phead->next;
phead->next = next->next;
next->next->prev = next->prev;
free(next);
//ListErase(phead->next);//后面就知道啦
}
查找:
与无头单向不循环链表相似
但是需要注意的是停止循环的条件: 如果还是到next==NULL停止 那么就死循环了
同时避免他一遍又一遍的找 多找几遍效果也是一样的
因此这里我们设置当他循环回哨兵位的时候 循环结束
LTNode* ListFind(LTNode* phead, LTDateType x)
{
assert(phead);
LTNode* next = phead->next;
while (next != phead)
{
if (next->val == x)
return next;
next = next->next;
}
return NULL;
}
在某位置之前插入节点:
来解密了:前面的《后面就知道啦》
void ListInsert(LTNode* pos, LTDateType x)
{
assert(pos);
LTNode* newnode = BuyListNode(x);
LTNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
这个函数是可以应用在之前的头插尾插函数中的。
删除某位置的节点:
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
删除链表
void Destroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
需要注意的是这里的phead=NULL 并不能实际改变phead的值 (传值操作)
但是为了保持接口的一致性 因此需要在函数之外 给phead=NULL