带头双向循环链表
- 一,前言
- 二,正文
- 1.申请一个节点
- 2初始化链表
- 3.链表打印
- 4.链表查找
- 5.链表在pos位置之后插入x
- 6.链表的头插
- 7.链表尾插
- 8.链表删除pos位置的值
- 9.链表的尾删
- 10.链表头删
- 11.销毁链表
- 三,结语
一,前言
带头双向循环链表作为单链表的升级版,有着更强的实用性,更少的特殊情况。其结构如图
如图,我们先定义结构体,每个节点都有头(prev)尾(rear)两个指针指向该节点的前后节点。
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* prev;
struct SListNode* next;
}SListNode;
在实际操作中,我们要设置一个该节点类型的指针,再初始化使其指向一个不存储数据的节点(将其称为头节点(如图中的head节点)),进行完该操作才可以进行其他操作。
二,正文
1.申请一个节点
因为后面的初始化,插入,头插操作都需要malloc一个节点,所以我们单独写一个申请节点的函数,使代码更简练
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
2初始化链表
初始化链表即将主函数设置的空指针指向一个动态申请的头节点,并将头节点的头尾指针指向自己
这里因为要改变plist指向的地址,所以要用二级指针。这里我将头节点的data赋值为-1。如果看不懂二级指针,可以用另一种写法。在初始化函数里定义一个指针,申请一个节点,使该指针指向该节点,再返回该指针
void InitSListNode(SListNode** pplist)
{
*pplist = BuySListNode(-1);
(*pplist)->prev = *pplist;
(*pplist)->next = *pplist;
}
3.链表打印
这部分唯一要这样的就是判断打印的条件
void SListPrint(SListNode* plist)
{
assert(plist);
SListNode* cur = plist->next;
if (cur == plist)
{
printf("链表为空");
}
while(cur != plist)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
4.链表查找
一般查找是伴随着插入或删除使用的,这里未查找到返回空指针,会触发在插入或删除中的断言(assert),表示未查找到数据。
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
SListNode* cur = plist->next;
while (cur != plist)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
5.链表在pos位置之后插入x
先判断pos是不是空指针,来验证查找是否成功。然后申请一个节点,将它插在pos指向的节点后面。注意插入操作的顺序,要先处理新节点的头尾指针,再处理pos的下一个节点的头指针,最后是pos的尾指针。不用考虑链表只有头节点的特殊情况。
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* newnode = BuySListNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
6.链表的头插
头插就是在头节点后插入一个节点,可以把头节点地址传到在pos位置之后插入x的函数中,也可以将插入操作的逻辑重新写一遍
void SListPushFront(SListNode* plist, SLTDateType x)
{
assert(plist);
SListInsertAfter(plist, x);
/*SListNode* newnode = BuySListNode(x);
newnode->next = plist->next;
newnode->prev = plist;
plist->next->prev = newnode;
plist->next = newnode;*/
}
7.链表尾插
和头插一样的逻辑,也可以传plist->prev的地址到pos之后插入x的函数,这里就不写了。pos位置的删除函数一样可以用的头删和尾删里去。
void SListPushBack(SListNode* plist, SLTDateType x)
{
assert(plist);
SListNode* newnode = BuySListNode(x);
newnode->next = plist;
newnode->prev = plist->prev;
plist->prev->next = newnode;
plist->prev = newnode;
}
8.链表删除pos位置的值
删除和插入逻辑相反,要先处理前后的节点,再处理pos指向的节点。要注意的是要提前判断链表是否为空。
void SListEraseAfter(SListNode* pos)
{
assert(plist);
if (plist->next == plist && plist->prev == plist)
{
printf("链表为空,删除失败\n");
return;
}
SListNode* posnext = pos->next;
SListNode* posprev = pos->prev;
posprev->next = posnext;
posnext->prev = posprev;
free(pos);
pos = NULL;
}
9.链表的尾删
先定义cur指针指向倒数第二个节点,将cur->next指向的节点释放后再进行下一步操作
void SListPopBack(SListNode* plist)
{
assert(plist);
assert(plist->next != plist && plist->prev != plist);
SListNode* cur = plist->prev->prev;
free(cur->next);
cur->next = plist;
plist->prev = cur;
}
10.链表头删
与尾删逻辑基本一样,但我在这里定义的cur表示要删掉的节点
void SListPopFront(SListNode* plist)
{
assert(plist);
if (plist->next == plist && plist->prev == plist)
{
printf("链表为空,删除失败\n");
return;
}
SListNode* cur = plist->next;
plist->next = cur->next;
cur->next->prev = plist;
free(cur);
cur = NULL;
}
11.销毁链表
这里我的思路是设置一个循环,不断头删至只剩头节点,再单独释放头节点
void SLTDestroy(SListNode* plist)
{
assert(plist);
while (plist->next!=plist)
{
SListPopFront(plist);
}
free(plist);
plist = NULL;
}
三,结语
每一种操作都有不同代码编辑方法,操作的目的使为了解决问题,没必要全部按网上的代码写。以上就是带头双向循环链表的基本操作,代码仅供参考,如果有错误还请大家指出,谢谢