嘿,各位技术潮人!好久不见甚是想念。生活就像一场奇妙冒险,而编程就是那把超酷的万能钥匙。此刻,阳光洒在键盘上,灵感在指尖跳跃,让我们抛开一切束缚,给平淡日子加点料,注入满满的passion。准备好和我一起冲进代码的奇幻宇宙了吗?Let's go!
我的博客:yuanManGan
目录
我们之前学习了单链表,但我们没有对链表进行分类,我们先对链表进行一下分类:
我们就得到了2×2×2种链表,带头链表是什么意思呢?所谓的带头链表,就是有个不存储有效信息的头结点,也称为哨兵位。
双向循环链表:
我们先来解决一下双向,双向指的是能从一个结点找到他的前驱结点和后继结点,这就意味着,我们在定义结构体时应该在结构体内部定义两个指针,一个指向前驱,一个指向后继。
那循环要怎么解决呢,我们定义单链表时最后一个结点指向NULL,如果我们让尾结点指向首结点,不就实现了双向循环链表了吗。
闲话少叙,干!
双向链表的定义:
typedef int SLTData;
typedef struct SlistNode
{
SLTData data;
struct SlistNode* prev;
struct SlistNode* next;
}SlistNode;
我们既然有头结点,那么我们进行关于链表接下来的操作时,要先初始化一个头结点,而创建一个结点一定会向内存申请空间,我们先分装一个申请结点的函数吧
申请结点:
SlistNode* BuySlistNode(int x)
{
SlistNode* newnode = (SlistNode*)malloc(sizeof(SlistNode));
assert(newnode);
newnode->data = x;
return newnode;
}
然后就是初始化操作,我们继续初始化需要传入什么参数吗,我们不如就在初始化操作里创建好然后返回指针,这就省略了传参数的工作了。既然它是第一个结点,他的指针肯定都指向自己,才能实现循环操作。
初始化:
SlistNode* SlistUnit()
{
SlistNode* head = BuySlistNode(0);
head->next = head;
head->prev = head;
return head;
}
该操作定义的哨兵位因为它存储的数据没有意义,所以这里就随便给了一个0值。
进行完初始化我们来进行下尾插操作
进行尾插操作时我们还需不需要遍历一下链表找到最后一个结点呢?不需要!因为我们的哨兵位的前驱指针指向的就是尾结点。
我们 只需要将尾结点与哨兵位的指针弄到新结点上去就可以解决了,与单链表一样的问题,我们要先处理那个结点,我们选择先处理对链表影响不大的结点,也就是新结点,然后对比其他结点,发现如果我们先处理哨兵位就找不到尾结点了。
尾插:
我们创建这个函数时我们需要传入什么参数呢,有同学学过单链表的说我们得传入二级指针,因为得改变传入的参数啊,传入的如果是一级指针的话形参的改变不会改变实参,这一堆背的滚瓜烂熟,但你还是没太了解指针啊大兄弟,我们画个图细说:
请您仔细看看,plist指针指向的始终是哨兵位结点的指针,没有发生过变化,我们这里发生改变的是plist指向的结点的前驱和后继指针,跟plist没有一点关系。我们就写出一下代码:
void SlistPushBack(SlistNode* phead, SLTData x)
{
// phead->prev new phead
assert(phead);
SlistNode* newnode = BuySlistNode(x);
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
实现了插入我们就得实现一个打印来验证是否正确,
单链表的打印是在cur指针指向NULL的时候,但我们实现的循环链表不会出现NULL,那在什么时候结束呢,对在cur==head的时候结束,我们要注意的是我们不会打印哨兵位的数据所以,我们的cur将会初始化为head->next.
打印
void SlistPrint(SlistNode* phead)
{
assert(phead);
SlistNode* cur = phead->next;
while (cur != phead)
{
printf("%d->", cur->data);
cur = cur->next;
}
puts("");
}
我们来测试一下:
void test1()
{
SlistNode* plist = SlistUnit();
SlistPushBack(plist, 1);
SlistPushBack(plist, 2);
SlistPushBack(plist, 3);
SlistPushBack(plist, 4);
SlistPrint(plist);
}
进行了尾插逃不掉的是头插操作,但头插时,我们不是将新结点插入哨兵位的前面,插入了就成了这个样子:
看起来没什么问题,但当我们进行打印操作时,这不就成尾插了吗?所以我们得在哨兵位的后面进行插入操作。
头插
void SlistPushFront(SlistNode* phead, SLTData x)
{
// head newnode head->next
assert(phead);
SlistNode* newnode = BuySlistNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
然后就进入到我们的删除操作
尾删:
尾插的话比起单链表就要多处理几个指针而已,
很简单记得不要忘记free,然后如果只有一个哨兵位的话肯定不能进行操作,我们加个assert就好了。
void SlistPopBack(SlistNode* phead)
{
//phead->prev->prev del phead
assert(phead && phead->next);
SlistNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;
free(del);
}
实现头删也是同样的实路大体是一样的,我们飞速的过掉
头删:
void SlistPopFront(SlistNode* phead)
{
//phead del del->next;
assert(phead && phead->next);
SlistNode* del = phead->next;
phead->next = del->next;
del->next->prev = phead;
free(del);
}
接下来是查找操作,还是和之前的一模一样
SlistNode* ListFind(SlistNode* phead, SLTData x)
{
SlistNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x) return cur;
cur = cur->next;
}
return NULL;
}
剩下插入和删除操作就很简单了
插入:
void SlistInsertBefore(SlistNode* pos, SLTData x)
{
assert(pos);
SlistNode* newnode = BuySlistNode(x);
newnode->next = pos;
newnode->prev = pos->prev;
pos->prev->next = newnode;
pos->prev = newnode;
}
void SlistInsertAfter(SlistNode* pos, SLTData x)
{
//pos new pos->next
assert(pos);
SlistNode* newnode = BuySlistNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
删除:
void SlistErace(SlistNode* pos)
{
assert(pos);
SlistNode* del = pos;
del->prev->next = del->next;
del->next->prev = del->prev;
free(del);
}
最后一个
销毁链表:
void SlistDestroy(SlistNode* phead)
{
SlistNode* cur = phead;
while (cur != phead)
{
SlistNode* next = cur->next;
free(cur);
cur = next;
}
free(cur);
}
注意要在测试函数里面将头结点,自己指向空。
完结撒花!!!!!