一篇搞懂双向链表

根据上一道面试题的讲解,我们也在其中探讨了一下双向链表和单向链表的区别,并且通过随机链表找到了,如何复制链表的内在逻辑。如果有感到有点遗忘的小伙伴可以点击下面:

链表面试题10之随机链表的复制-CSDN博客

那么我们今天就是链表的收尾了再附带一些存储器的知识内容。

目录

1.链表的分类

1.1单向/双向

 1.2 带头/不带头

1.3 循环/不循环 

2. 双向链表常见接口实现

2.1初始化

2.2尾插

 2.3头插

 2.4尾删

2.5头删

2.6查找

2.7在指定位置插入元素

2.8指定位置删除数据


 

1.链表的分类

首先链表的结构非常多样,以下情况组合起来就有8种( 2 * 2 * 2 ) 链表结构 :

1.1单向/双向

1 . 单向 : 只能往一个方向遍历 (仅有一个指针 --> 指向下一个结点的地址), 如下图 : 只能从d1找到d2 , d2 找不到d1

2 . 双向 : 能从两个方向遍历 ( 有指向下一个结点的地址--后继,也有指向上一个结点的地址---前驱) , 如下面的第二幅图 , d2 可以找到d1 , 也可找到d3

 1.2 带头/不带头

注意 : 这里的  “带头”   跟前面我们说的   “头结点”   是两个概念!
前面讲单链表的时候,会表述   “头结点”---> 链表首结点(第一个结点),这并不严谨,只是为了更好的去理解  链表首个位置有效 的结点 , 实际上这个表述是错误的

因为链表分类中存在 一种带头链表,里面的头结点不存储任何有效元素 , 只是负责占位置(哨兵位)

"哨兵位" ---  作用 : 站在这里“放哨” ,不需要判断链表是否为空!因为在对链表进行插入等操作时,要先判断链表是否为空再执行,这样代码重复率很高,有些冗余。

如果带头链表中只有头结点 , 我们称这个链表为空

1.3 循环/不循环 

 

结合以上分析 : 我们可以推出

--------->单链表 : 单向不带头不循环链表 

在接下来学习的双链表是:

--------->双链表 : 双向带头循环链表

我们可以这么去记 , 双链表和单链表的各类的类型都相反

虽然有这么多的类型 ,但是常用的还是单链表和双链表

1 . 单链表 : 结构简单 , 一般不会单独用来存储数据 . 实际上更多的是左右数据结构的子结构 , 如哈希图 , 图的邻接表等 , 另外的这种结构在笔试面试中会出现很多

2 . 双链表 : 结构复杂,一般单独存储数据.实际中使用的链表结构,基本上都是双链表(双向带头循环链表) , 虽说结构复杂 , 但是代码实现后会发现结构会带来很多优势,实现反而简单了!

2. 双向链表常见接口实现

2.1初始化

两种方式 : 1 . 返回值的方式(返回结点) -->主要使用这种方法

2 . 传参数形式(初始化的时候 , 要保证是循环的 ----> 自己指向自己)

所以这里我们推荐用返回值的方式来返回,这样不容易错。

但是还是讲一下方式二 : 参数形式 

注意 : 初始化时 , 需要形参的改变影响实参 , 所以需要传地址 , 这里需要用到二级指针!

2.2尾插

注意 : 在进行尾插的时候 , 不需要像单链表一样使用二级指针 , 因为"哨兵位"phead的结点不会改变 , 尾插一个结点是通过访问结点的成员变量来实现的 .

1 . 创建一个新节点 : 向操作系统申请一块结点大小的空间 ---> 存储需要尾插的结点 , 在后续可能还需要再插入结点 , 这里可以先把创建一个结点的代码进行封装(buyNode)。

2 . 尾插 : 尾插时 , 先对newnode 指向进行更改 , 这样可以保证原链表的指向不被改变 ,思考不明白的 , 可以先画图 , 然后思考尾插一个结点时 , 有影响的结点有什么 ? 影响的指向是什么 ?

//申请一块结点大小的空间 --- 创建新结点
LTNode* buyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		return 1;
	}
	node->data = x;
	node->next = node->prev = node;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = buyNode(x);
	//phead phead->prev  newnode
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

 2.3头插

头插需要注意的是 : 往哪里插入结点?

==> 插入到"哨兵位"之后 , 第一个有效结点之前 !

==> 如果插入到"哨兵位"之前 , 就是尾插了!

void LTPushFront(LTNode* phead, LTDataType x)
{
	LTNode* newnode = buyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;
}

 2.4尾删

1 . 尾删时 , 先判断链表是否为空 ---> 断言(arrest) --> 调用LTEmpty 函数

2 . 明确删除尾结点所影响的结点

3 . 改变有影响结点的指向

4 . 删除尾结点 (free)

//尾删
void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->prev;
	//phead del->prv del
	del->prev->next = phead;
	phead->prev = del->prev;
 
	//删掉尾结点
	free(del);
	del = NULL;
}

2.5头删

1 . 头删时 , 先判断链表是否为空 ---> 断言 --> 调用LTEmpty 函数

2 . 明确删除头结点所影响的结点( head  del  del->next )

3 . 改变结点的指向

4 . 删除头结点( free)

 

//头删
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->next;
	//phead  del  del->next
	del->next->prev = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}

2.6查找

定义一个指针变量 (pcur) , 指向链表中第一个有效的结点( head->next) , 开始遍历 , 如果找到了 , 返回该结点 ,  没找到 , 返回NULL

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	//查找有效结点
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

2.7在指定位置插入元素

注意 : 在指定位置之前 或者指定位置之后插入数据 , 代码基本是一样的 , 因为双链表是循环的 

1 . 向操作系统申请一块结点的空间

2 . 找到在指定位置插入数据所影响的结点

3 . 改变结点的方向

//在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = buyNode(x);
	newnode->prev = pos;
	newnode->next = pos->next;
	pos->next->prev = newnode;
	pos->next = newnode;
}

2.8指定位置删除数据

//删除指定位置的数据
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
}

 再把这张图放一下,这张图我在单链表的结尾也放过。

 如果你觉得对你有帮助,可以点赞关注加收藏,感谢您的阅读,我们下一篇文章再见。

一步步来,总会学会的,首先要懂思路,才能有东西写。

 

评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值