当前位置: 首页 > news >正文

数据结构与算法——双链表的详解以及增、插、删、查、印、毁的实现

文章目录

  • 一、前言
  • 二、链表的分类
    • 2.1是否带头
    • 2.2单向或者双向
    • 2.3循环或者不循环
  • 三、双链表
    • 3.1双链表的概念
    • 3.2双链表的结构
  • 四、双链表各功能的实现
    • 4.1双链表结点的创建
    • 4.2双链表的初始化
      • 4.2.1第一种初始化方式
      • 4.2.2第二种初始化方式
    • 4.3双链表判断是否为空
    • 4.4双链表的尾、头及pos位置插入
      • 4.4.1尾插
      • 4.4.2头插
      • 4.4.3pos位置之后插入
      • 4.4.4pos位置之前插入
    • 4.5双链表的尾、头及pos位置删除
      • 4.5.1尾删
      • 4.5.2头删
      • 4.5.3pos位置结点删除
    • 4.6双链表特定元素的查找
    • 4.7双链表元素的打印
    • 4.8双链表的销毁
      • 4.8.1第一种销毁方式
      • 4.8.2第二种销毁方式
    • 4.9双链表的传参
  • 五、双链表整体代码分析
    • 5.1头文件——List.h及代码展示
    • 5.2主体代码文件——List.c及代码展示
    • 5.3测试文件——test.c及代码展示
      • 5.3.1尾插演示
      • 5.3.2头插演示
      • 5.3.3尾删演示
      • 5.3.4头删演示
      • 5.3.5特定元素查找演示
      • 5.3.6pos位置后插入演示
      • 5.3.7pos位置前插入演示
      • 5.3.8pos结点删除演示
      • 5.3.9链表销毁演示
  • 六、总结

一、前言

hello啊,各位宝子们。今天的你是在继续选择躺平,还是在持之以恒地努力学习呢?躺平呢有躺平的安逸,努力呢也有努力的收获。也希望大家平衡好这两者之间的得失关系。言归正传,今天咱们继续深入翱翔数据结构与算法的海洋,进一步领悟其独特的魅力。,up今天所要给大家介绍的数据结构就是——双链表。希望大家努力学习,耐心阅读。

二、链表的分类

这时候有的宝子可能就比较困惑。up我们之前不是才学习了单链表的数据结构吗?而今天又要开始学习双链表,那么链表到底又是怎么一个分类法呢。别急,up这就带领大家解开这层神秘的面纱。
链表的结构非常多样化,具体分为三个大方面——是否带头、方向指向以及是否循环。这样的话由于每个大方面存在两种情况,所以组合情况就有8(222)种链表结构。
在这里插入图片描述

2.1是否带头

在这里插入图片描述

2.2单向或者双向

在这里插入图片描述

2.3循环或者不循环

在这里插入图片描述
到这里,大家的困惑相信也就烟消云散了吧。那我们之前所学习的单链表,它的全称就是不带头单向不循环链表。而我们这次所要学习的双链表,全称则就是带头双向循环链表。虽然链表的结构分类可达8种之多,但是最常见的就是单链表和双链表这两种结构

三、双链表

3.1双链表的概念

双链表是一种数据结构,它的每个节点除了存储数据外,还有两个指针,分别指向前一个节点和后一个节点。这种结构使得双链表在进行插入和删除操作时更为高效,因为可以直接访问任何节点的前驱和后继节点。

3.2双链表的结构

在这里插入图片描述

四、双链表各功能的实现

4.1双链表结点的创建

在这里插入图片描述
代码展示:

ListNode* Listbuynode(ListDataType x)//申请结点
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		printf("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->pre = newnode->next = newnode;
	return newnode;
}

4.2双链表的初始化

4.2.1第一种初始化方式

代码展示:

void ListInit1(ListNode** pphead)//第一种初始化
{
	assert(pphead);
	*pphead = Listbuynode(-1);
}

4.2.2第二种初始化方式

ListNode*  ListInit2()//第二种初始化
{
	ListNode* phead = Listbuynode(-1);
	return phead;
}

以上两种初始化的方式具体的不同在于:初始化1采用的是void类型,不需要返回值;初始化二采用的是ListNode*型,需要返回值。

4.3双链表判断是否为空

代码展示:

bool ListEmpty(ListNode* phead)//判空
{
	assert(phead);
	return phead->next == phead;
}

4.4双链表的尾、头及pos位置插入

4.4.1尾插

在这里插入图片描述

尾插的实质:在判断链表不为空的前提下,先让新结点的newnode->pre和newnode->next分别指向原链表的phead->pre和phead,最后再让原链表中的phead->pre->next和phead->pre再分别指向新节点newnode.
代码展示:

void ListPushBack(ListNode* phead,ListDataType x)//尾插
{
	assert(phead);
	ListNode* newnode = Listbuynode(x);
	newnode->pre = phead->pre;
	newnode->next = phead;
	phead->pre->next = newnode;
	phead->pre = newnode;

}

4.4.2头插

在这里插入图片描述
头插的实质:在判断链表不为空的情况下,先让新节点的newnode->pre和newnode->next分别指向原链表的phead和phead->next,最后再让原链表中的phead->next->pre和phead->next再分别指向新节点newnode。
注意事项:头插的位置是是在头结点之后,如果插入在头结点之前,实际效果是尾插。
代码展示:

void ListPushFront(ListNode* phead, ListDataType x)//头插
{
	assert(phead);
	ListNode* newnode = Listbuynode(x);
	newnode->next = phead->next;
	newnode->pre = phead;
	phead->next->pre = newnode;
	phead->next = newnode;
}

4.4.3pos位置之后插入

在这里插入图片描述
pos位置后插入的实质:在pos结点不为空的情况下,先让新结点newnode->next和newnode->pre分别指向原链表中的pos->next和pos,最后再让原链表中的pos->next->pre和pos->next指向新节点newnode。
代码展示:

void ListInsertBack(ListNode* pos, ListDataType x)//在pos位置后插入结点
{
	assert(pos);
	ListNode* newnode = Listbuynode(x);
	newnode->next = pos->next;
	newnode->pre = pos;
	pos->next->pre = newnode;
	pos->next = newnode;

}

4.4.4pos位置之前插入

在这里插入图片描述
pos位置之前插入的实质:pos结点不为空的情况下先让新节点newnode->next和newnode->pre分别指向原链表中的pos和pos->pre,最后再让原链表中的pos->pre->next和pos->pre分别指向新节点newnode。
代码展示:

void ListInsertFront(ListNode* pos, ListDataType x)//在pos位置前插入结点
{

	assert(pos);
	ListNode* newnode = Listbuynode(x);
	newnode->next = pos;
	newnode->pre = pos->pre;
	pos->pre->next = newnode;
	pos->pre = newnode;
}

4.5双链表的尾、头及pos位置删除

4.5.1尾删

在这里插入图片描述
尾删的实质:在判断链表非空的情况下,先把尾结点用del保存下来方便后续操作,再让del结点的del->pre->next指向phead,并且让phead->pre指向del->pre,最后再把del结点释放掉并且置为空即可。
代码展示:

void ListPopBack(ListNode* phead)//尾删
{
	assert(!ListEmpty(phead));
	ListNode* del = phead->pre;
	del->pre->next = phead;
	phead->pre = del->pre;
	free(del);
	del = NULL;
}

4.5.2头删

在这里插入图片描述
头删的实质:在判断链表非空的情况下,先把头删结点用del保存下来方便后续操作,接着再让del结点的del->next->pre指向phead,并且让phead->next指向del->next,最后再把del结点释放掉并且置为空即可。
注意事项: 头删删除的是头结点的下一个结点也就是11结点,并非是头结点,因为在双链表中头结点是不能删除和销毁的。

代码展示:

void ListPopFront(ListNode* phead)//头删
{
	assert(!ListEmpty(phead));
	ListNode* del = phead->next;
	del->next->pre = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}

4.5.3pos位置结点删除

在这里插入图片描述
pos位置删除实质:在判断pos结点不为空的情况下,先让pos->next->pre指向pos->pre,接着让pos->pre->next指向pos->next,最后将pos结点释放掉并且置为空即可。
代码展示:

void ListErase(ListNode* pos)//删除pos位置结点
{
	pos->next->pre = pos->pre;
	pos->pre->next = pos->next;
	free(pos);
	pos = NULL;
}

4.6双链表特定元素的查找

查找的实质:在判断链表不为空的情况下,定义一个开始结点指针pcur从phead->next结点开始,遍历整个链表,如果找到了则return pcur,没找到则return NULL,直到pcur = phead为止跳出循环。
代码展示:

ListNode* ListFind(ListNode* phead, ListDataType x)//查找特定元素
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)//找到了
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;//未找到
}

4.7双链表元素的打印

打印的实质:在判断链表不为空的情况下,定义一个开始结点指针pcur从phead->next结点开始,遍历整个链表,逐一打印元素,直到pcur=phead为止跳出循环。

void ListPrint(ListNode* phead)//双链表的打印
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

4.8双链表的销毁

4.8.1第一种销毁方式

void ListDestroy1(ListNode** pphead)//第一种销毁方法
{
	assert(pphead);
	ListNode* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(*pphead);
	*pphead = NULL;
}

4.8.2第二种销毁方式

void ListDestroy(ListNode* phead)//第二种销毁方法
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

销毁的实质:在判断链表不为空的情况下,定义一个开始结点指针pcur从pcur->next结点开始,遍历整个链表,逐一销毁,直到pcur = phead为止跳出循环,最后再把头结点销毁掉在置为空即可。

4.9双链表的传参

上面所有功能对已经实现完毕了,但是你可能会懵逼——为什么有的函数传参传的是一级指针,而又有的是传的二级指针呢?这里up给大家统一回答:关于传参到底是传一级指针还是二级指针,是看头结点是否发生改变,如果头结点不发生改变就传一级指针,相反如果头结点发生改变就传二级指针。

五、双链表整体代码分析

5.1头文件——List.h及代码展示

在这里插入图片描述
代码展示:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int ListDataType;//重命名
typedef struct  ListNode//结点的结构体创建
{
	ListDataType data;
	struct ListNode* pre;
	struct ListNode* next;
}ListNode;
ListNode* Listbuynode(ListDataType x);//申请结点
void ListInit1(ListNode** pphead);//第一种初始化
ListNode* ListInit2();//第二种初始化
bool ListEmpty(ListNode* phead);//判空
void ListPushBack(ListNode* phead, ListDataType x);//尾插
void ListPushFront(ListNode* phead, ListDataType x);//头插
void ListPopBack(ListNode* phead);//尾删
void ListPopFront(ListNode* phead);//头删
void ListInsertBack(ListNode* pos, ListDataType x);//在pos位置后插入结点
void ListInsertFront(ListNode* pos, ListDataType x);//在pos位置前插入结点
void ListErase(ListNode* pos);//删除pos位置结点
ListNode* ListFind(ListNode* phead, ListDataType x);//查找特定元素
void ListPrint(ListNode* phead);//双链表的打印
void ListDestroy1(ListNode** pphead);//第一种销毁方法
void ListDestroy(ListNode* phead);//第二种销毁方法


5.2主体代码文件——List.c及代码展示

在这里插入图片描述
代码展示:

#include "List.h"
ListNode* Listbuynode(ListDataType x)//申请结点
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		printf("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->pre = newnode->next = newnode;
	return newnode;
}
void ListInit1(ListNode** pphead)//第一种初始化
{
	assert(pphead);
	*pphead = Listbuynode(-1);
}
ListNode*  ListInit2()//第二种初始化
{
	ListNode* phead = Listbuynode(-1);
	return phead;
}
bool ListEmpty(ListNode* phead)//判空
{
	assert(phead);
	return phead->next == phead;
}
void ListPushBack(ListNode* phead,ListDataType x)//尾插
{
	assert(phead);
	ListNode* newnode = Listbuynode(x);
	newnode->pre = phead->pre;
	newnode->next = phead;
	phead->pre->next = newnode;
	phead->pre = newnode;
}
void ListPushFront(ListNode* phead, ListDataType x)//头插
{
	assert(phead);
	ListNode* newnode = Listbuynode(x);
	newnode->next = phead->next;
	newnode->pre = phead;
	phead->next->pre = newnode;
	phead->next = newnode;
}
void ListPopBack(ListNode* phead)//尾删
{
	assert(!ListEmpty(phead));
	ListNode* del = phead->pre;
	del->pre->next = phead;
	phead->pre = del->pre;
	free(del);
	del = NULL;
}
void ListPopFront(ListNode* phead)//头删
{
	assert(!ListEmpty(phead));
	ListNode* del = phead->next;
	del->next->pre = phead;
	phead->next = del->next;
	free(del);
	del = NULL;
}
void ListInsertBack(ListNode* pos, ListDataType x)//在pos位置后插入结点
{
	assert(pos);
	ListNode* newnode = Listbuynode(x);
	newnode->next = pos->next;
	newnode->pre = pos;
	pos->next->pre = newnode;
	pos->next = newnode;

}
void ListInsertFront(ListNode* pos, ListDataType x)//在pos位置前插入结点
{

	assert(pos);
	ListNode* newnode = Listbuynode(x);
	newnode->next = pos;
	newnode->pre = pos->pre;
	pos->pre->next = newnode;
	pos->pre = newnode;
}
void ListErase(ListNode* pos)//删除pos位置结点
{
	pos->next->pre = pos->pre;
	pos->pre->next = pos->next;
	free(pos);
	pos = NULL;
}
ListNode* ListFind(ListNode* phead, ListDataType x)//查找特定元素
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)//找到了
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;//未找到
}
void ListPrint(ListNode* phead)//双链表的打印
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}
void ListDestroy1(ListNode** pphead)//第一种销毁方法
{
	assert(pphead);
	ListNode* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(*pphead);
	*pphead = NULL;
}
void ListDestroy(ListNode* phead)//第二种销毁方法
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

5.3测试文件——test.c及代码展示

5.3.1尾插演示

在这里插入图片描述
`
代码展示;

ListPushBack(plist, 1);//尾插
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);

5.3.2头插演示

在这里插入图片描述
在这里插入图片描述
代码展示;

ListPushFront(plist, 5);//头插
ListPushFront(plist, 6);

5.3.3尾删演示

在这里插入图片描述
在这里插入图片描述
代码展示:

	ListPopBack(plist);//尾删
	ListPopBack(plist);

5.3.4头删演示

在这里插入图片描述
在这里插入图片描述

ListPopFront(plist);//头删
ListPopFront(plist);

5.3.5特定元素查找演示

在这里插入图片描述
在这里插入图片描述
代码展示:

	ListNode* find = ListFind(plist, 4);//特定位置元素查找
	if (find != NULL)
	{
		printf("找到了!");
	}
	else
	{
		printf("未找到!");
	}

5.3.6pos位置后插入演示

在这里插入图片描述
在这里插入图片描述
代码展示:

	ListInsertBack(find, 4);//pos后插入结点

5.3.7pos位置前插入演示

在这里插入图片描述
在这里插入图片描述
代码展示:

	ListInsertFront(find, 3);//pos前插入结点

5.3.8pos结点删除演示

在这里插入图片描述
在这里插入图片描述

	ListErase(find);//删除pos结点

5.3.9链表销毁演示

在这里插入图片描述
在这里插入图片描述
代码展示:

ListDestroy(plist);//链表的销毁
plist = NULL;
ListPrint(plist);
#include "List.h"
void test01()
{
	//ListNode* plist = NULL;//初始化1
	//ListInit1(&plist);
	ListNode* plist = ListInit2();//初始化2
	ListPushBack(plist, 1);//尾插
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushFront(plist, 5);//头插
	ListPushFront(plist, 6);
	//ListPopBack(plist);//尾删
	//ListPopBack(plist);
	//ListPopFront(plist);//头删
	//ListPopFront(plist);
	//ListNode* find = ListFind(plist, 4);//特定位置元素查找
	//if (find != NULL)
	//{
	//	printf("找到了!");
	//}
	//else
	//{
	//	printf("未找到!");
	//}
	//ListInsertBack(find, 4);//pos后插入结点
	//ListInsertFront(find, 3);//pos前插入结点
	//ListErase(find);//删除pos结点
	ListDestroy(plist);//链表的销毁
	plist = NULL;
	ListPrint(plist);

}
int main()
{
	test01();
	return 0;
}

六、总结

OK啦宝子们,到这里,数据结构——双链表以及所有功能的实现就讲述完毕了。怎么样大家的功力是否又蹭蹭蹭地往上涨了呢?经过这么久的努力学习,相信大家已然成为了一个高手了吧,哈哈哈。不过不要骄傲,我相信大家一定可以越来越NB。最后呢,祝大家在学习的道路上一帆风顺,终成大神。记得三连O。

只有不断学习,才能不断进步,才能在激烈的竞争中立于不败之地。
在这里插入图片描述

相关文章:

  • 紧急任务插入,如何不影响原计划
  • Koji/OBS编译节点OS版本及工具版本管理深度实践指南
  • 链式多分支规则树模型的应用
  • 【KEIL5】HardFault问题DEBUG排查方式
  • 爱普生FC1610AN5G手机中替代传统晶振的理想之选
  • Hyperlane 文件分块上传服务端
  • 解决java使用easyexcel填充模版后,高度不一致问题
  • 【人工智能】DeepSeek 的上下文窗口扩展:解锁长文本处理的理论与实践
  • 【力扣hot100题】(088)最长有效括号
  • VS Code 的 .S 汇编文件里面的注释不显示绿色
  • 在spark中,窄依赖算子map和filter会组合为一个stage,这种情况下,map和filter是在一个task内进行的吗?
  • 玄机靶场-webshell查杀WP
  • viewmodel协程中执行耗时操作,导致viewmodel创建两次,导致observer失效
  • Linux 网络基础知识总结
  • 供应S620 支持 PD 的多协议双向快充移动电源解决方案
  • 保护PCBA的不同方法:喷三防漆 vs 镀膜
  • Ajax------免刷新地前后端交互
  • 力扣DAY46-50 | 热100 | 二叉树:展开为链表、pre+inorder构建、路径总和、最近公共祖先、最大路径和
  • 英伟达开源253B语言模型:Llama-3.1-Nemotron-Ultra-253B-v1 模型情况
  • #Hash 模式 vs History 模式
  • 学校网站模板 html/百度收录什么意思
  • 武汉网站建设哪家强/信息流优化师怎么入行
  • 济南外贸网站/今日山东新闻头条
  • 如何自己免费做网站/推广软件是什么工作
  • wordpress css sprite/关键词排名优化公司推荐
  • 做家装的网站/aso优化方案