我们本次来完成双向带头循环链表,与之前的内容相比增加了些许细节,大家也可以看看之前写的
(1条消息) 链表---双向带头循环链表及其多种接口的实现(完美结构)-CSDN博客
目录
链表结构
尾插
创建新节点
初始化
打印数据
判断是否为空
尾删
头插
头删
pos位置之前插入
删除pos位置节点
查找节点
销毁
全部代码
typedef int LTDataType;
typedef struct ListNode {struct ListNode* next;struct ListNode* prev;LTDataType data;
}LTNode;
由于是双向链表,所以相比单链表,多了一个指针prev用来指向前一个节点
void LTPushBack(LTNode* phead, LTDataType x) {assert(phead);LTNode* newnode = BuyListNode(x);LTNode* tail = phead->prev;tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}
双向循环链表的尾插非常简单,我们要找到链表的尾节点,只需找头的prev即可,接着改变指向即可,这里对比单链表发现,这里不需要二级指针,我们这里改的都是结构体的变量,要修改结构体的变量,使用结构体的指针即可,这也是哨兵位头节点的优势,我们还要进行断言,因为存在哨兵位的头节点,所以是不可能为空,所以需要断言
LTNode* BuyListNode(LTDataType x) {LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL) {perror("malloc fail");return NULL;}node->next = NULL;node->prev = NULL;node->data = x;return node;
}
在尾插里我们使用了BuyListNode函数,所以我们这里进行实现,我们将新节点的next和prev都置为空,防止出现问题,并且我们发现,这个函数是有返回值的,这里如果不用返回值就会出现和单链表那里一样的问题,即形参和实参,那样就需要使用二级指针,所以我们这里用返回值解决即可
LTNode* LTInit() {LTNode* phead = BuyListNode(-1);phead->next = phead;phead->prev = phead;return phead;
}
初始化的问题是一样的,我们使用返回值来解决,初始化是创建哨兵位的头节点,我们让他的next和prev都指向自己,这样最初的带头双向循环链表就完成了,哨兵位的头节点不存储数据,我们这里随便给个-1
void LTPrint(LTNode* phead) {assert(phead);printf("<-head<->");LTNode* cur = phead->next;while (cur != phead) {printf("%d<->", cur->data);cur = cur->next;}printf("\n");
}
既然链表是带哨兵位的,那么说明绝对不会为空,所以这些函数都需要断言,之后就不再说了,打印数据我们要从phead的next开始打印,这是因为哨兵位的头节点是不存储数据的,接着我们进行打印即可,因为是循环链表,当cur等于phead时,所有的数据也就被打印完了
有了这几个函数,我们简单测试一下

bool LTEmpty(LTNode* phead) {assert(phead);return phead->next == phead;
}
判断链表是否为空的条件是phead的next或者prev是否等于自己
void LTPopBack(LTNode* phead) {assert(phead);assert(!LTEmpty(phead));LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;tailPrev->next = phead;phead->prev = tailPrev;free(tail);tail = NULL;
}
尾删我们还要判断链表是否为空,链表为空时我们不能进行尾删,我们可以用严厉的检查也可以用温柔的,这里大家自行选择,我们使用两个指针找到尾节点和尾节点的prev,接着改变指向,释放尾节点即可
void LTPushFront(LTNode* phead, LTDataType x) {assert(phead);LTNode* newnode = BuyListNode(x);/*newnode->next = phead->next;newnode->next->prev = newnode;newnode->prev = phead;phead->next = newnode;*/LTNode* first = phead->next;phead->next = newnode;newnode->prev = phead;first->prev = newnode;newnode->next = first;
}
我们使用一个变量first记录phead的next,接着改变指向即可
屏蔽的代码是另一种写法,不过这种写法不如用一个新变量直观
void LTPopFront(LTNode* phead) {assert(phead);assert(!LTEmpty(phead));LTNode* first = phead->next;LTNode* second = first->next;second->prev = phead;phead->next = second;free(first);first = NULL;
}
头删与尾删类似,我们使用两个变量就可以轻松解决,改变指向后释放空间即可
void LTInsert(LTNode* pos, LTDataType x) {assert(pos);LTNode* newnode = BuyListNode(x);LTNode* prev = pos->prev;newnode->next = pos;pos->prev = newnode;prev->next = newnode;newnode->prev = prev;
}
我们用一个指针指向pos的prev,接着改变指向即可,有了这个函数,我们就可以进行复用,比如尾插,头插等等,我们都可以用这个函数来进行代替,比如头插我们就可以这样写
void LTPushFront(LTNode* phead, LTDataType x) {assert(phead);//LTNode* newnode = BuyListNode(x);/*newnode->next = phead->next;newnode->next->prev = newnode;newnode->prev = phead;phead->next = newnode;*//*LTNode* first = phead->next;phead->next = newnode;newnode->prev = phead;first->prev = newnode;newnode->next = first;*/LTInsert(phead->next, x);
}
尾插直接传phead即可,因为phead的prev就是尾
void LTPushBack(LTNode* phead, LTDataType x) {assert(phead);/*LTNode* newnode = BuyListNode(x);LTNode* tail = phead->prev;tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;*/LTInsert(phead,x);
}
void LTErase(LTNode* pos) {assert(pos);LTNode* prev = pos->prev;LTNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);
}
用两个指针改变指向,释放空间即可
同理,有了Erase,头删和尾删我们就可以进行复用
void LTPopFront(LTNode* phead) {assert(phead);assert(!LTEmpty(phead));/*LTNode* first = phead->next;LTNode* second = first->next;second->prev = phead;phead->next = second;free(first);first = NULL;*/LTErase(phead->next);
}
void LTPopBack(LTNode* phead) {assert(phead);assert(!LTEmpty(phead));/*LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;tailPrev->next = phead;phead->prev = tailPrev;free(tail);tail = NULL;*/LTErase(phead->prev);
}
LTNode* LTFind(LTNode* phead, LTDataType x) {assert(phead);LTNode* cur = phead->next;while (cur != phead) {if (cur->data == x) {return cur;}cur = cur->next;}return NULL;
}
我们对链表进行遍历查找即可,没有找到返回NULL
void LTDestroy(LTNode* phead) {assert(phead);LTNode* cur = phead->next;while (cur != phead) {LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}
使用两个指针记录遍历,最后释放哨兵位节点即可,我们发现,phead最后没置为空,这也是二级指针问题,我们让使用的人置为空即可
以上即为本期全部内容,希望大家可以有所收获,最后附上全部代码
#include
#include
#include
#include
typedef int LTDataType;
typedef struct ListNode {struct ListNode* next;struct ListNode* prev;LTDataType data;
}LTNode;
LTNode* BuyListNode(LTDataType x);
LTNode* LTInit();
void LTPrint(LTNode* phead);
void LTDestroy(LTNode* phead);
bool LTEmpty(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
#include"List.h"LTNode* BuyListNode(LTDataType x) {LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL) {perror("malloc fail");return NULL;}node->next = NULL;node->prev = NULL;node->data = x;return node;
}LTNode* LTInit() {LTNode* phead = BuyListNode(-1);phead->next = phead;phead->prev = phead;return phead;
}
void LTDestroy(LTNode* phead) {assert(phead);LTNode* cur = phead->next;while (cur != phead) {LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}
void LTPrint(LTNode* phead) {assert(phead);printf("<-head<->");LTNode* cur = phead->next;while (cur != phead) {printf("%d<->", cur->data);cur = cur->next;}printf("\n");
}bool LTEmpty(LTNode* phead) {assert(phead);return phead->next == phead;
}void LTPushBack(LTNode* phead, LTDataType x) {assert(phead);/*LTNode* newnode = BuyListNode(x);LTNode* tail = phead->prev;tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;*/LTInsert(phead,x);
}
void LTPopBack(LTNode* phead) {assert(phead);assert(!LTEmpty(phead));/*LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;tailPrev->next = phead;phead->prev = tailPrev;free(tail);tail = NULL;*/LTErase(phead->prev);
}void LTPushFront(LTNode* phead, LTDataType x) {assert(phead);//LTNode* newnode = BuyListNode(x);/*newnode->next = phead->next;newnode->next->prev = newnode;newnode->prev = phead;phead->next = newnode;*//*LTNode* first = phead->next;phead->next = newnode;newnode->prev = phead;first->prev = newnode;newnode->next = first;*/LTInsert(phead->next, x);
}
void LTPopFront(LTNode* phead) {assert(phead);assert(!LTEmpty(phead));/*LTNode* first = phead->next;LTNode* second = first->next;second->prev = phead;phead->next = second;free(first);first = NULL;*/LTErase(phead->next);
}
void LTInsert(LTNode* pos, LTDataType x) {assert(pos);LTNode* newnode = BuyListNode(x);LTNode* prev = pos->prev;newnode->next = pos;pos->prev = newnode;prev->next = newnode;newnode->prev = prev;
}
void LTErase(LTNode* pos) {assert(pos);LTNode* prev = pos->prev;LTNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);
}
LTNode* LTFind(LTNode* phead, LTDataType x) {assert(phead);LTNode* cur = phead->next;while (cur != phead) {if (cur->data == x) {return cur;}cur = cur->next;}return NULL;
}
如有错误,还请指正