Bootstrap

数据结构漫游记:动态带头双向循环链表

嘿,各位技术潮人!好久不见甚是想念。生活就像一场奇妙冒险,而编程就是那把超酷的万能钥匙。此刻,阳光洒在键盘上,灵感在指尖跳跃,让我们抛开一切束缚,给平淡日子加点料,注入满满的passion。准备好和我一起冲进代码的奇幻宇宙了吗?Let's go!

我的博客:yuanManGan

我的专栏:C++入门小馆 C言雅韵集 数据结构漫游记  闲言碎语小记坊 题山采玉

目录

双向循环链表:

双向链表的定义:

申请结点:

初始化:

尾插:

打印

头插

尾删:

头删:

插入:

删除:

销毁链表:


我们之前学习了单链表,但我们没有对链表进行分类,我们先对链表进行一下分类: 

我们就得到了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);
}

 注意要在测试函数里面将头结点,自己指向空。

完结撒花!!!!! 

;