C语言双向链表

双向链表分为三个部分:

1、上一个节点地址

2、中间的数据

3、下一个节点的地址 ​
双向链表在最开始的时候,不用初始化为NULL,直接创建一个哨兵位即可

哨兵位

哨兵位,一旦创建好了就不能动了,无论什么操作,他的位置都得在第一个,你头插也不能插在哨兵位的前面。

哨兵位在传参的时候可以直接传 head ,因为不能改动哨兵位嘛,意味着不能修改 head 里面存放的这个地址,如果传的是 head 的地址,那么就可以修改 head 里面存放的地址了,

直接 head->( ) ,就能访问使用了,这样也是可以操作的,不会无效操作具体请看下面

双向链表与单向链表传参区别

双向链表在增删查改的时候传参,用的是一级指针接收。

单向链表在增删查改的时候传参,用的是二级指针接收。

原因是,我们起初给单向链表赋为NULL,你如果向增加一个节点,那么你要修改的是plist 里面存放的地址,我们想要的结果是改动这个plist 变量里面的地址,如果指示单纯传plist 是不能修改 plist 里面存放的地址的,

看官请看

plist 里面目前存放着一个地址,接下来我试图通过这个函数去修改plist里面存放的地址

走到走到这里 p 已经成功修改

但是出了函数并不能改掉 plist 里面存放的地址

而换成 &plist 并用两颗星 ** 接受才能改动 plist 存放的地址

难道一个星不能用?当然可以他虽然不能改动 plist 存放的地址,但是可以改动这个地址里面的数据,一颗星能做到的仅仅是改动节点里面的 data 和 下一个节点的地址,

目前我们动态开辟的的一个节点里面的 next 存的是随机值,当我们将这个函数走完看看是否能改动这个 next 里面的地址。

出了函数咱们可以看见成功的改动了节点里面的内容。

这也就是为什么双向链表不用传二级指针,因为双向链表的默认配置就是有一个哨兵位,因为不能改动这个哨兵位所以,只传一级指针就够用了。

而单向链表我们需要改plist 所以就需要传二级指针

关于单链表解引用一次就能访问节点内部

注意看slntail ,他已经解引用一次意味着可以修改plist ,而下面的slntail->next = list,是因为 ->本身就内涵一次解引用,所以才能访问节点里面指向的next。

申请节点

双链表在申请节点的时候,next ,和 prev,指向的地址就是本节点地址,他是自循环的,next 和 prev不放空指针的原因是,在你插入就下一个节点的时候是不能解引用的,其实不管他貌似也行。

cpp 复制代码
LTN* Buynewnode(LTNtype n)
{
	LTN* node = (LTN*)malloc(sizeof(LTN));
	
	if (node == NULL)
	{
		perror("malloc");
		exit(EOF);
	}
	node->data = n;
	node->next = node;
	node->prev = node;
	return node;
}

打印节点

前言:SB 等于哨兵位,LTN为我创建的结构体类型的重命名

1、进来先判断哨兵位有没有,和 是不是只有一个哨兵位(那还打印个 " 鲲 " )

2、然后拷贝一份第一个节点的地址(不拷贝也行反正也改不了哨兵,只是我不想,我想拷贝一份,我觉得这样舒服一点),

3、while判断语句,因为我们是从第一个有效节点开始,所以条件就是不等哨兵位就打印该节点的内容,

4、打印完该节点,下一步就是让他走到下一个节点,上来判断是不是哨兵位,不是就打印

cpp 复制代码
void Printltn(LTN* SB)
{
	if (SB == NULL||SB->next == SB)
	{
		printf("无效链表\n");
		exit(EOF);

	}
	LTN* head = SB->next;
	while (head != SB)
	{
		printf("%d->", head->data);
		head = head->next;
	}
}

尾插

先开辟一个节点,接下来就是插入的环节,SB是哨兵位

我的连接法是:先用尾结点的next连接新节点(SB->prev->next),再让新节点的prev(及上一个节点的地址)指向尾结点,再让哨兵位的 prev 指向新节点,最后新节点的next指向哨兵位。尾插完成

顺序不唯一,只要能成功链接都可以。

cpp 复制代码
void LTNPushback(LTN* SB,LTNtype x)
{
	LTN* Nnode = Buynewnode(x);

	SB->prev->next = Nnode;
	Nnode->prev = SB->prev;
	SB->prev = Nnode;
	Nnode->next = SB;
	

}

头插

我的头插逻辑是:

先让新节点的 prev 指向 哨兵,next 指向 哨兵 的下一个节点,这样新节点就暂时和两个节点连接起来了。

然后先让下一个 哨兵 的下一个节点的 prev 指向新节点,再让 哨兵 的 next 指向新节点

顺序不唯一

cpp 复制代码
void LTNPushfront(LTN* SB, LTNtype x)
{
	if (SB == NULL)
	{
		printf("无效< < < list node > > >\n");
	}
	LTN* Nnode = Buynewnode(x);
	Nnode->prev = SB;
	Nnode->next = SB->next;
	SB->next->prev = Nnode;
	SB->next = Nnode;

}

查找

查找函数主要是用来辅助 指定位置的 增加 删除

1、先判断链表是否有效,如果只有一个哨兵位也默认无效

2、拷贝第一个节点地址

3、进来判断是否等于 哨兵,不等于判断该节点的数据是不是我们要查找的,是就返回该节点地址,不是则让 Phead 走到下一位

4、如果说循环完了都没有则打印 "不存在" ,我等会儿改一下代码,找到了会提前返回的。

cpp 复制代码
 LTN* LTNFind(LTN*SB,LTNtype x)
{
	if (SB == NULL || SB->next == SB)
	{
		printf("无效< < < list node > > >\n");
		exit(EOF);

	}
	LTN* phead = SB->next;
	while (phead != SB)
	{
		if (phead->data == x)
		{
			return phead;
		}
		else
			phead = phead->next;
	}
	printf("不存在返回 " NULL " \n");
	return NULL;
}

指定位置后插入

插入的逻辑和头插的一样

cpp 复制代码
void LTInsert(LTN* pos, LTNtype x)//指定位置后插入
{
	if (pos == NULL)
	{
		printf("不存在\a\n");
		exit(EOF);
	}
	LTN* newnode = Buynewnode(x);
	newnode->prev = pos;
	newnode->next = pos->next;
	pos->next->prev = newnode;
	pos->next = newnode;
}

删除指定位置节点

上一个节点的next指向下一个节点

下一个节点的prev指向上一个节点

然后释放pos,将它置为NULL

cpp 复制代码
void LTErase(LTN* pos)//删除指定位置
{
	if (pos == NULL)
	{
		printf("不存在\a\n");
		exit(EOF);
	}

	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

销毁链表

cpp 复制代码
void LTNDestroy(LTN*SB)
{
	assert(SB);
	LTN* next = SB->next;
	while (next!=SB)
	{
		LTN* pcur = next->next;
		free(next);
		next = pcur;

	}
}
相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜9 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa10 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio