双向链表的初步练习

!!‧✧̣̥̇‧✦‧✧̣̥̇‧✦ ‧✧̣̥̇:  Solitary-walk

      ⸝⋆   ━━━┓
     - 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━  ➴ ⷯ

本人座右铭 :   欲达高峰,必忍其痛;欲戴王冠,必承其重。

 


     希望在看完我的此篇博客后可以对你有帮助哟

   此外,希望各位大佬们在看完后,可以互赞互关一下,看到必回
  

目录

 


1. 双向链表的初始化

2. 双向链表的销毁

3. 双向链表的尾插

4. 双向链表的头插

5. 双向链表的尾删

6.双向链表的头删

7. 双向链表的指定位置之后的插入

8. 双向链表的指定位置的删除
9. 双向链表的按值查找

10.链表打印


首先在我们进行各项具体任务之前,咱还是需要把前期工作准备好的 

1.先进行自定义链表的声明

2.一些函数的声明

3.必要头文件的引入

首先我们要知道什么是双向链表,顾名思义,对于一个结点而言既有指向自己的指针,也有指向其他结点的指针域

如图所示:

双向链表的初步练习_第1张图片

链表结构体类型的定义代码如下:

typedef struct ListNode 
{
	DataType data;//数据域
	struct ListNode* pre;//前驱域
	struct ListNode*next;//后继域
}ListNode;

 1.初始化

 注意这里的带头双向链表不同于前面单链表的头节点

为了好区分,这里我们称之为"哨兵位"

哨兵位只是占一个位置,并不存储任何有效的数据

 当我们只有哨兵位这个结点,思考一下,如何变成一个双向循环链表

双向链表的初步练习_第2张图片

你想对了吗??

双向链表的初步练习_第3张图片

	phead->pre = phead->next = phead;//让他变成双向循环链表

初始化代码之不采用 传参的方法

ListNode* Init()//初始化,采用不传参的形式,但是需把哨兵位这个结点返回
{
	//自己需要创建一个哨兵位
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	assert(phead);//可能开辟失败
	phead->data = -1;
	phead->pre = phead->next = phead;//记住这里要让他变成双向循环链表
	return phead;
}

初始化代码之采用 传参的方法

双向链表的初步练习_第4张图片

注意这里我们形参是用二级指针来接收的

因为实参我们需要传链表的地址,否则我们是无法完成初始化的

因为函数形参只是实参的一份临时拷贝,对形参的临时修改并不会影响我们的实参

void Init(ListNode** pphead)//初始化,采用传参的形式
{
	//注意一下操作都是基于 phead是哨兵位来进行的 他只占一个位置,并不存储任何有效数据
	assert(pphead);
	*pphead = (ListNode*)malloc(sizeof(ListNode));
	if (*pphead == NULL)
	{
		perror("malloc fail\n");
		return 1;//既然开辟失败,没必要执行下面代码
	}

	(*pphead)->data = -1;//注意优先级顺序
	(*pphead)->next = (*pphead)->pre = NULL;//构建一个双向循环链表
}
2.销毁

说白了,其实就是一个结点一个结点进行删除

这自然就需要遍历了

我们在删除之前需要先找到删除结点后一个结点

关键是我们实参到底是指针还是指针地址

 让要删除的结点为 del = phead->next;

那么要删除的结点下一个结点为 del->next

我们不妨试一下~~~

实参为指针

void Destroy(ListNode* phead)//链表销毁;想一下,传一级指针还是二级?  在这里我们传入一级指针,为了保持接口一致性
{
	//销毁我们是一个一个进行删除,自然就需要遍历
	assert(phead);
	ListNode* del = phead->next;
	while (del != phead)
	{
		ListNode* next = del->next;
		free(del);
		/*del = NULL;*/    //  ?
		del = next;
	}
	//来到这说明,此时只有一个哨兵位
	free(phead)
    phead = NULL;

}

 注意当我们只是简单调用销毁函数的时候,代码并不是我们所设想的一样

正常来说,我们的plist的值应该为NULL

双向链表的初步练习_第5张图片 

为了解决这个问题我们就需要在传一级指针的时候,手动把plist 置为空 

 实参为指针地址

 代码如下,但是问题又来了

void LTDestroy(ListNode** pphead) 
{
		assert(pphead && *pphead);
		ListNode* cur = (*pphead)->next;
		while ( cur!=*pphead )
		{
			ListNode* next = cur->next;
			free(cur);
			cur = next;
		}
		free(*pphead);
		*pphead = NULL;
	}

双向链表的初步练习_第6张图片

 对于这个问题容本up主先买个关子,欲知后事如何 且听下回分解

为了保持接口一致性,我们这里就使用一级指针来接收

3.尾插

顾名思义,是在尾结点后面进行插入,注意这里在哨兵位的前面进行插入也是可以滴~~~

接下来就是改变指针的指向

1)首先既然是尾插就需要为要插进来的数据开辟结点

这里涉及到创建结点的函数

2)其次是处理指针指向

3)先处理node的pre ,next

4)处理 原来尾结点,哨兵位

 尾插草图如下:

双向链表的初步练习_第7张图片

 结点创建函数的代码如下:

ListNode* ListBuyNode(x)//创建结点
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc fail\n");
		return;
	}
	//来到这,说明空间开辟成功
	node->data = x;
	node->next = node->pre = NULL;
	return node;
}

 

 双向链表的初步练习_第8张图片

 尾插完整代码:

void PushBack(ListNode* phead,DataType x)//尾插
{
	assert(phead);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//先处理node 的前驱,后继
	node->pre = phead->pre; //  phead->pre->next原来尾结点的后继,node->pre = phead->pre->next这里出现了野指针的问题 phead->pre->next没有具体指向
	node->next = phead;
	//接下来在处理 原来尾结点,哨兵位
	/*,这样写是错的,以下2句话不可以颠倒
	已经把node这个结点给覆盖掉了,在执行65行代码时,node 是未知的,因为这个结点已经被覆盖了,phead->pre->next此时他的指向也就是未知的,调试时不会报错,但是遍历读取他就会报错了,就像你在打印函数里进行打印时,出现野指针
	phead->pre = node;
	phead->pre->next = node;*/

	phead->pre->next = node;
	phead->pre = node;//别忘了,每尾插进来的结点最终都是一个新的尾结点
}

4.头插

首先是在哨兵位是后面进行的,每插入进来应该结点,此节点就会成为一个新的头节点

同上,需要先创建一个结点

其次找到原来头节点 phead->next 

 头插草图如下:

双向链表的初步练习_第9张图片

头插完整代码如下:

void PushFront(ListNode* phead,DataType x)//头插
{
	//头插是在哨兵位的后面进行插入
	assert(phead);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//处理node 的后继,前驱
	node->pre = phead;
	node->next = phead->next;
	//处理phead ,phead->next 
	/*这样写也是对的
	phead->next->pre = node;*/
	phead->next = node;//头节点要进行更新
	phead->next->pre = node;

}

5.尾删

注意我们不能上来就行删除

需要先找到要删除结点 (del)前一个也就是 del->pre

这里我们一定要注意结点的先后顺序

完整代码如下:

void PopBack(ListNode* phead)//尾删
{
	//注意不能直接删除,需要先找到尾结点的前一个结点
	assert(phead);
	//先判断以下是否为空,有2种写法
	/*if (phead->pre == phead)
	{
		return 9;
	}*/
	assert(phead->next != phead);//是否为空
	ListNode* del = phead->pre;//把要删除的结点先保存起来
	//删除之前,先要构成一个新的双向循环链表
	del->pre->next = phead;
	phead->pre = del->pre;
	/*错误的,顺序不能颠倒,因为此时 del->pre已经被覆盖了
	phead->pre = del->pre;//新的尾结点
	del->pre->next = phead;*/

	free(del);
	del = NULL;

}

 

7. 指定位置之后的插入
草图如下:

双向链表的初步练习_第10张图片

 

1)要为插入的数据创建结点

2)找到指定位置(pos)之后的结点 (pos->next)和之前的结点(pos->pre

3)  改变指针指向

指定位置之后插入对应完整代码:

void InsertAfter(ListNode* pos, DataType x)//指定位置之后插入
{
	assert(pos);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//处理 node 的前驱,后继
	node->next = pos->next;
	node->pre = pos;
	//处理 pos 和 pos->next的pre
	pos->next = node;
	pos->next->pre = node;

}
8. 双向链表的指定位置的删除

对应草图如下:

双向链表的初步练习_第11张图片

 直接进行删除是不行滴~~~

先要找到指定位置(pos)之后的结点 (pos->next)和之前的结点(pos->pre

其次改变指针走向

void Earse(ListNode* pos)//指定位置删除
{
	assert(pos);
	//需要找到pos之前的一个结点和之后的一个结点
	pos->next->pre = pos->pre;
	pos->pre->next = pos->next;
	free(pos);
	pos = NULL;
}
9. 双向链表的按值查找

 这里自然就需要一个一个进行遍历了

按值查找对应完整代码:

ListNode* Find(ListNode* phead, DataType x)//在链表中按值查找,若是找到则返回对应的结点
{
	assert(phead);
	ListNode* pcur = phead->next;//定义一个用来循环遍历的指针
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;//直接返回对应结点
		}
		pcur = pcur->next;
	}
	printf("没有找到\n");
}

 10链表打印

方法同上,对链表进行遍历

void Print(ListNode* phead)//链表打印
{
	assert(phead);
	ListNode* pcur = phead->next;//定义一个用来 遍历的指针,注意他初始值是 phead->next
	while (pcur != phead)
	{
		printf("%d-> ", pcur->data);
		pcur = pcur->next;//记得更新
	}
	printf("\n");
}

 


List.c对应完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

//void Init(ListNode** pphead)//初始化,采用传参的形式
//{
//	//注意一下操作都是基于 phead是哨兵位来进行的 他只占一个位置,并不存储任何有效数据
//	assert(pphead);
//	*pphead = (ListNode*)malloc(sizeof(ListNode));
//	if (*pphead == NULL)
//	{
//		perror("malloc fail\n");
//		return 1;//既然开辟失败,没必要执行下面代码
//	}
//
//	(*pphead)->data = -1;//注意优先级顺序
//	(*pphead)->next = (*pphead)->pre = NULL;//构建一个双向循环链表
//}

ListNode* Init()//初始化,采用不传参的形式,但是需把哨兵位这个结点返回
{
	//自己需要创建一个哨兵位
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	assert(phead);//可能开辟失败
	phead->data = -1;
	phead->pre = phead->next = phead;//记住这里要让他变成双向循环链表
	return phead;
}
ListNode* ListBuyNode(x)//创建结点
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc fail\n");
		return;
	}
	//来到这,说明空间开辟成功
	node->data = x;
	node->next = node->pre = NULL;
	return node;
}
void Print(ListNode* phead)//链表打印
{
	assert(phead);
	ListNode* pcur = phead->next;//定义一个用来 遍历的指针,注意他初始值是 phead->next
	while (pcur != phead)
	{
		printf("%d-> ", pcur->data);
		pcur = pcur->next;//记得更新
	}
	printf("\n");
}

void PushBack(ListNode* phead,DataType x)//尾插
{
	assert(phead);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//先处理node 的前驱,后继
	node->pre = phead->pre; //  phead->pre->next原来尾结点的后继,node->pre = phead->pre->next这里出现了野指针的问题 phead->pre->next没有具体指向
	node->next = phead;
	//接下来在处理 原来尾结点,哨兵位
	/*,这样写是错的,以下2句话不可以颠倒
	已经把node这个结点给覆盖掉了,在执行65行代码时,node 是未知的,因为这个结点已经被覆盖了,phead->pre->next此时他的指向也就是未知的,调试时不会报错,但是遍历读取他就会报错了,就像你在打印函数里进行打印时,出现野指针
	phead->pre = node;
	phead->pre->next = node;*/

	phead->pre->next = node;
	phead->pre = node;//别忘了,每尾插进来的结点最终都是一个新的尾结点
}
void PushFront(ListNode* phead,DataType x)//头插
{
	//头插是在哨兵位的后面进行插入
	assert(phead);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//处理node 的后继,前驱
	node->pre = phead;
	node->next = phead->next;
	//处理phead ,phead->next 
	/*这样写也是对的
	phead->next->pre = node;*/
	phead->next = node;//头节点要进行更新
	phead->next->pre = node;

}
void PopBack(ListNode* phead)//尾删
{
	//注意不能直接删除,需要先找到尾结点的前一个结点
	assert(phead);
	//先判断以下是否为空,有2种写法
	/*if (phead->pre == phead)
	{
		return 9;
	}*/
	assert(phead->next != phead);//是否为空
	ListNode* del = phead->pre;//把要删除的结点先保存起来
	//删除之前,先要构成一个新的双向循环链表
	del->pre->next = phead;
	phead->pre = del->pre;
	/*错误的,顺序不能颠倒,因为此时 del->pre已经被覆盖了
	phead->pre = del->pre;//新的尾结点
	del->pre->next = phead;*/

	free(del);
	del = NULL;

}
void PopFront(ListNode* phead)//头删
{
	assert(phead);
	assert(phead->next != phead);//确保不为空
	ListNode* del = phead->next;
	//以下2句没有先后顺序之分
	del->next->pre = phead;

	phead->next = del->next;
	free(del);
	del = NULL;

}
void InsertAfter(ListNode* pos, DataType x)//指定位置之后插入
{
	assert(pos);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//处理 node 的前驱,后继
	node->next = pos->next;
	node->pre = pos;
	//处理 pos 和 pos->next的pre
	pos->next = node;
	pos->next->pre = node;

}
void Earse(ListNode* pos)//指定位置删除
{
	assert(pos);
	//需要找到pos之前的一个结点和之后的一个结点
	pos->next->pre = pos->pre;
	pos->pre->next = pos->next;
	free(pos);
	pos = NULL;
}
ListNode* Find(ListNode* phead, DataType x)//在链表中按值查找,若是找到则返回对应的结点
{
	assert(phead);
	ListNode* pcur = phead->next;//定义一个用来循环遍历的指针
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;//直接返回对应结点
		}
		pcur = pcur->next;
	}
	printf("没有找到\n");
}
void Destroy(ListNode* phead)//链表销毁;想一下,传一级指针还是二级?  在这里我们传入一级指针,为了保持接口一致性
{
	//销毁我们是一个一个进行删除,自然就需要遍历
	assert(phead);
	ListNode* del = phead->next;
	while (del != phead)
	{
		ListNode* next = del->next;
		free(del);
		/*del = NULL;*/    //  ?
		del = next;
	}
	//来到这说明,此时只有一个哨兵位
	free(phead);
	phead = NULL;

}
void LTDestroy(ListNode** pphead) 
{
		assert(pphead && *pphead);
		ListNode* cur = (*pphead)->next;
		while ( cur!=*pphead )
		{
			ListNode* next = cur->next;
			free(cur);
			cur = next;
		}
		free(*pphead);
		*pphead = NULL;
	}

 List.h对应完整代码

#pragma once
#include
#include
#include

#include
//双向链表的实现
typedef int DataType;
typedef struct ListNode {

	DataType data;//数据域
	struct ListNode* pre;//前驱域
	struct ListNode*next;//后继域
}ListNode;

//接口函数的声明
//void Init(ListNode** pphead);//初始化,采用传参的形式
ListNode* Init();//初始化,采用不传参的形式,但是需把哨兵位这个结点返回
void Print(ListNode* phead);//链表打印
void PushBack(ListNode* phead,DataType x);//尾插,这里传入一级指针即可,因为返回的头节点我们不需要进行更改
void PushFront(ListNode* phead, DataType x);//头插
void PopBack(ListNode* phead);//尾删
void PopFront(ListNode* phead);//头删
void InsertAfter(ListNode* pos,DataType x);//指定位置之后插入
void Earse(ListNode* pos);//指定位置删除
ListNode* Find(ListNode*phead,DataType x);//按值查找,若是找到则返回对应的结点
void Destroy(ListNode* phead);//链表销毁;想一下,传一级指针还是二级?为了保持接口一致性我们传入一级指针


ok,以上就是我要为大家进行share的一些基本内容,都来到这里了,要是感觉我写的还不错的话,各位大佬烦劳点个赞,互关以下呗~~~

你可能感兴趣的:(开发语言,ecmascript,数据结构,链表)