接上节实现的单链表,今天更新带头循环双向链表,这个的实现还是比较简单的。
首先带头循环双向链表的接口函数其实和单链表是差不多的
首先看一下带头双向循环链表的结构是什么样的
给出接口函数,首先要定义数据类型,一个数据加两个指针,组成结构体作为链表的元素。
接下来分别是初始化,打印,尾插,头插,尾删,头删,插入和删除
#include
#include
#include
#include
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
//void ListInit(LTNode** pphead);
LTNode* ListInit();
void ListPrint(LTNode* phead);
void ListPushBack(LTNode* phead, LTDataType x);
void ListPushFront(LTNode* phead, LTDataType x);
void ListPopBack(LTNode* phead);
void ListPopFront(LTNode* phead);
// 在pos位置之前插入x
void ListInsert(LTNode* pos, LTDataType x);
// 删除pos位置的节点
void ListErase(LTNode* pos);
接下来一个一个实现:
首先应该有一个创建元素类型的函数,用来实现后续的一切操作:动态开辟在头文件定义的元素类型的一个元素,赋值给data,两个指针置空
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
这里初始化其实就是创建一个哨兵位节点,元素和链表内的元素是一样的,只不过里面的数据可给可不给。
这里还有一点就是使用返回值来初始化可以避免传二级指针。
创建一个哨兵位,将两个指针都指向自己。
LTNode* ListInit()
{
LTNode* phead = BuyListNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
这里要注意的就是判断停下来的条件,因为是需要遍历的,其实就是指向phead就要停下来。
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead;
while (cur->next != phead)
{
printf("%d", cur->data);
cur = cur->next;
}
printf("\n");
}
思路非常简单啊,记住原来的尾,依次连接即可
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
phead->prev = newnode;
newnode->next = phead;
}
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
LTNode* next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
这里需要注意的就是断言的第二句,哨兵位的头节点可不能删掉
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 ListPopFront(LTNode* phead)
{
assert(plist->next);//链表不能为空
LTNode* next = phead->next;
phead->next = next->next;//1
next->next->prev = phead;//2
free(next);
next = NULL;
}
这个函数就是在pos位置之前插入,所以还要记住未插入前的pos的前一个位置是谁
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
// prve newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
删除pos位置的节点,需要记住前后两个节点,然后链接即可
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
写到最后,其实可以这样总结,如果有人让你短时间写一个链表出来,首先定义带头循环双向链表的节点,然后直接写出insert和erase两个函数,因为带头循环双向链表良好的结构,他的头插尾插,头删尾删通过insert和erase都能轻松实现,只需要再写一个初始化函数和打印函数即可。