数据结构————双链表

news2024/11/6 7:24:13

目录

一、单链表的定义及其特点

定义:

特点:

双链表的优缺点

双链表的关键特性

二、双链表的实现

  准备工作:

自定义数据元素类型:

1.双链表的创建

1.1头插法介绍

1.2尾插法介绍

2.双链表的初始化

3.双链表的求表长

4.双链表的销毁

5.双链表的遍历

6.双链表的查找

6.1按值查找

6.2按序查找

7.双链表的插入

8.双链表的删除元素

全部代码(含测试):

总结:


一、单链表的定义及其特点

定义:

 双链表,顾名思义,是一种链式存储结构,其中每个节点不仅包含数据元素,还包含两个指针,分别指向其前驱和后继节点。

为什么引入双链表?

 单链表的结点中只有一个指向其后继的指针,使得单链表要访问某个结点的前驱结点时,只能从头开始遍历,访问后驱结点的复杂度为O(1),访问前驱结点的复杂度为O(n)。为了克服上述缺点,引入了双链表。

特点:

双链表的优缺点

优点

  1. 可以在O(1)时间复杂度下在已知节点位置进行插入和删除操作。
  2. 双向遍历,适用于需要频繁从两端访问数据的场景。

缺点

  1. 额外的存储空间需求,每个节点需要两个指针。
  2. 比单链表更复杂的实现和维护。

双链表的关键特性

  1. 双向性:每个节点都有两个指针,一个指向前一个节点,另一个指向后一个节点。
  2. 灵活性:可以在链表的任何位置插入或删除节点,而不仅仅是链表的尾部。
  3. 额外空间:相比单链表,双链表需要额外的空间来存储前驱指针,但这种开销通常被认为是值得的,因为它提供了额外的灵活性和性能提升。

   其它

  1. 数据安全性:由于每个节点都有指向前后节点的指针,即使某个节点的指针损坏,也能通过其他节点恢复链表结构,增加了数据的可靠性。
  2. 应用场景的拓展: 双链表因其双向性和操作效率,在文件系统、缓存管理、浏览器历史记录等领域有着广泛的应用。例如,浏览器历史记录中,用户可以方便地前后浏览,这正是利用了双链表的双向访问特性。

  3. 算法实现的简化: 在实现某些算法时,双链表可以简化代码。例如,在需要频繁进行反转、反转部分子链表或在链表中查找元素的前一个或后一个元素等操作时,双链表的双向特性可以简化算法实现,减少代码复杂度。

二、双链表的实现

  准备工作:

自定义数据元素类型:
typedef int ElemType;

1.双链表的创建

双链表的创建通常涉及两种主要的插入方法:头插法和尾插法。

1.1头插法介绍

头插法指的是在双链表的头部插入节点,

特点

  • 但链表元素的顺序与插入顺序相反,如果需要保持原有顺序,需要在插入后进行反转

代码:

//<1>头插法建立双链表
void CreateListF(DLinkNode*& L, ElemType a[], int n)
{
	DLinkNode* s;//待插入结点
	//(1)创建头结点
	L = (DLinkNode*)malloc(sizeof(DLinkNode));
	L->next = L->prior = NULL;
	//(2) 插入操作
	for (int i = 0; i < n; i++)
	{
		s= (DLinkNode*)malloc(sizeof(DLinkNode));
		s->data = a[i];
		s->next = L->next;//第一步:新结点的next指向第一个结点
		if (L->next != NULL)//第二步,第一个结点的前驱结点指向新结点
			L->next->prior = s;
		L->next = s;//第三步修改头结点的next
		s->prior = L;//最后修改新结点的前驱结点
	}
}

1.2尾插法介绍

  尾插法指的是在链表的尾部插入节点。此方法下,新节点成为链表的最后一个节点。

特点

  • 保持了数据的插入顺序,链表元素的顺序与插入顺序一致。

代码:

//<2>尾插法建立双链表
void CreateListR(DLinkNode*& L, ElemType a[], int n)
{
	DLinkNode* s, * r;
	L = (DLinkNode*)malloc(sizeof(DLinkNode));
	L->next = L->prior = NULL;
	r = L;//r始终指向尾结点,开始指向L
	for (int i = 0; i < n; i++)
	{
		s= (DLinkNode*)malloc(sizeof(DLinkNode));
		s->data = a[i];
		r->next = s;//第一步将尾部节点的后继指针指向新结点
		s->prior = r;//第二步将新结点的前驱指针指向尾部节点
		r = s;//最后移动尾指针
	}
	r->next = NULL;
}

2.双链表的初始化

  创建一个头结点,并将其头结点的next域置空

void InitList(DLinkNode*& L)
{
	L = (DLinkNode*)malloc(sizeof(DLinkNode));
	L->next = L->prior = NULL;
}

3.双链表的求表长

 用p指向头结点,用n来累计数据节点的个数,n初始值为0;

//【4】求表长
int  ListLength(DLinkNode* L)
{
	int j = 1;
	DLinkNode* p = L->next;
	while (p->next!=NULL)
	{
		j++;
		p = p->next;
	}
	return j;
}

4.双链表的销毁

1.  pre 和 p指向相邻的节点,初始pre 指向头结点,p指向首个数据节点。

2.p不为空时循环,释放pre节点,然后pre 和p 俩个节点后移。

3. 循环结束后pre指向尾节点,将其释放

代码:

//【3】双链表的销毁
void DeleteList(DLinkNode*& L)
{
	DLinkNode* pre, * p;
	pre = L; p = L->next;
	while (p != NULL)
	{
		free(pre);
		pre = p;
		p = p->next;
	}
	free(pre);
}

5.双链表的遍历

 遍历双链表,p指向L的第一个结点(L->next)

代码:

void PrintList(DLinkNode* L)
{
	DLinkNode* p = L->next;
	while (p)
	{
		printf("%d->",p->data);
		p = p->next;
	}
	printf("end\n");
}

6.双链表的查找

6.1按值查找

 与单链表的按值查找是一致的,在L中查找第一个出现元素为e的结点。

代码:

//<1>按值查找 查找值e在L中的第一个出现位置
DLinkNode* LocateElem(DLinkNode* L, ElemType e,int& n)
{
	n = 1;
	DLinkNode* p = L->next;
	while (p!=NULL&&p->data!=e)
	{
		n++;
		p =p->next;
	}
	if (p==NULL)
	{
		return NULL;
	}
	else
	{
		return p;
	}
}
6.2按序查找

 与单链表的按序查找是一致的,在L中查找序号为i的结点,并返回该结点。

代码:

//<2>按序查找
DLinkNode* GetElem(DLinkNode* L, int i) 
{
	int j = 0;
	DLinkNode* p = L->next;
	if (i <= 0)return NULL;
	while (j < i - 1&&p!=NULL)
	{
		j++;
		p = p->next;
	}
	if (p != NULL)
	{
		return p;
	}
	else
	{
		return NULL;
	}
}

7.双链表的插入

 给定双链表L ,在第i个位置插入节点s(即放在i-1个结点的前面),值为e。

  我们定义结点 p指针用于找到第i-1个结点,s是待插入的结点。

实现示意图如下:

 值得注意的是,当我们插入的结点是最后一个结点的后面时,我们要判断p的后继结点是否存在

  代码:

//【8】插入 
bool ListInsert(DLinkNode*& L, int i, ElemType e)
{
	DLinkNode* s, * p = L;
	int j = 0; 
	if (i <= 0)return NULL;
	//第一步,找到第i-1个结点
	while (p != NULL && j < i - 1)
	{
		j++;
		p = p->next;
	}
	if (p == NULL)
	{
		return false;
	}
	else//插入操作
	{
		s = (DLinkNode*)malloc(sizeof(DLinkNode));
		s->data = e;
		s->next = p->next;//1
		if(p->next!=NULL)//如果p的后继结点存在,允许修改前驱指针(处理插入尾结点后面的情况)
			p->next->prior = s;//2
		s->prior = p;//3
		p->next = s;//4
		return true;
	}
}

8.双链表的删除元素

  给定双链表L,删除第i个节点,并将删除节点的值带回,p指向第i-1个节点,q为第i个节点

示意图:

同样,我们也因该考虑删除最后一个结点时,由于要修改第i个结点的后继的结点的前驱指针(上图的2号操作),我们要判断q的后继结点是否存在

代码:

//【9】删除 删除第 i 个结点
bool  ListDelete(DLinkNode*& L, int i, ElemType& e)
{
	if (i <= 0)return false;
	DLinkNode* p=L;//p用于指向i-1个结点
	DLinkNode* q;// q用于指向第i个结点
	int j = 0;
	while (j < i - 1 && p != NULL)
	{
		j++;
		p = p->next;
	}
	if (p == NULL)
	{
		return false;
	}
	else
	{
		q = p->next;
		if (q == NULL)return false;  //不存在第i个结点
		e =q->data;
		if(q->next!=NULL)//如果q的后继结点存在,修改后继结点的前驱指针
			q->next->prior = p;//1
		p->next = q->next;//2
		free(q);
		return true;
	}
}

全部代码(含测试):

#include<stdio.h>
#include<stdlib.h>

typedef  int  ElemType;
typedef struct DNode {
	ElemType data;//数据域
	struct DNode* prior,* next;//前驱指针和后驱指针
}DLinkNode,*DLinkList;
/*【1】双链表的建立*/
//<1>头插法建立双链表
void CreateListF(DLinkNode*& L, ElemType a[], int n)
{
	DLinkNode* s;//待插入结点
	//(1)创建头结点
	L = (DLinkNode*)malloc(sizeof(DLinkNode));
	L->next = L->prior = NULL;
	//(2) 插入操作
	for (int i = 0; i < n; i++)
	{
		s= (DLinkNode*)malloc(sizeof(DLinkNode));
		s->data = a[i];
		s->next = L->next;//第一步:新结点的next指向第一个结点
		if (L->next != NULL)//第二步,第一个结点的前驱结点指向新结点
			L->next->prior = s;
		L->next = s;//第三步修改头结点的next
		s->prior = L;//最后修改新结点的前驱结点
	}
}
//<2>尾插法建立双链表
void CreateListR(DLinkNode*& L, ElemType a[], int n)
{
	DLinkNode* s, * r;
	L = (DLinkNode*)malloc(sizeof(DLinkNode));
	L->next = L->prior = NULL;
	r = L;//r始终指向尾结点,开始指向L
	for (int i = 0; i < n; i++)
	{
		s= (DLinkNode*)malloc(sizeof(DLinkNode));
		s->data = a[i];
		r->next = s;//第一步将尾部节点的后继指针指向新结点
		s->prior = r;//第二步将新结点的前驱指针指向尾部节点
		r = s;//最后移动尾指针
	}
	r->next = NULL;
}

/*双链表的基本运算*/

//【2】双链表的初始化

void InitList(DLinkNode*& L)
{
	L = (DLinkNode*)malloc(sizeof(DLinkNode));
	L->next = L->prior = NULL;
}
//【3】双链表的销毁
void DeleteList(DLinkNode*& L)
{
	DLinkNode* pre, * p;
	pre = L; p = L->next;
	while (p != NULL)
	{
		free(pre);
		pre = p;
		p = p->next;
	}
	free(pre);
}
//【4】求表长
int  ListLength(DLinkNode* L)
{
	int j = 1;
	DLinkNode* p = L->next;
	while (p->next!=NULL)
	{
		j++;
		p = p->next;
	}
	return j;
}
//【5】判断是否为空
bool ListEmpty(DLinkNode*L)
{
	return (L->next == NULL);
}
//【6】遍历双链表
void PrintList(DLinkNode* L)
{
	DLinkNode* p = L->next;
	while (p)
	{
		printf("%d->",p->data);
		p = p->next;
	}
	printf("end\n");
}
//【7】查找
//<1>按值查找 查找值e在L中的第一个出现位置
DLinkNode* LocateElem(DLinkNode* L, ElemType e,int& n)
{
	n = 1;
	DLinkNode* p = L->next;
	while (p!=NULL&&p->data!=e)
	{
		n++;
		p =p->next;
	}
	if (p==NULL)
	{
		return NULL;
	}
	else
	{
		return p;
	}
}
//<2>按序查找
DLinkNode* GetElem(DLinkNode* L, int i)
{
	int j = 0;
	DLinkNode* p = L->next;
	if (i <= 0)return NULL;
	while (j < i - 1&&p!=NULL)
	{
		j++;
		p = p->next;
	}
	if (p != NULL)
	{
		return p;
	}
	else
	{
		return NULL;
	}
}
//【8】插入 
bool ListInsert(DLinkNode*& L, int i, ElemType e)
{
	DLinkNode* s, * p = L;
	int j = 0; 
	if (i <= 0)return NULL;
	//第一步,找到第i-1个结点
	while (p != NULL && j < i - 1)
	{
		j++;
		p = p->next;
	}
	if (p == NULL)
	{
		return false;
	}
	else//插入操作
	{
		s = (DLinkNode*)malloc(sizeof(DLinkNode));
		s->data = e;
		s->next = p->next;//1
		if(p->next!=NULL)//如果p的后继结点存在,允许修改前驱指针
			p->next->prior = s;//2
		s->prior = p;//3
		p->next = s;//4
		return true;
	}
}
//【9】删除 删除第 i 个结点
bool  ListDelete(DLinkNode*& L, int i, ElemType& e)
{
	if (i <= 0)return false;
	DLinkNode* p=L;//p用于指向i-1个结点
	DLinkNode* q;// q用于指向第i个结点
	int j = 0;
	while (j < i - 1 && p != NULL)
	{
		j++;
		p = p->next;
	}
	if (p == NULL)
	{
		return false;
	}
	else
	{
		q = p->next;
		if (q == NULL)return false;  //不存在第i个结点
		e =q->data;
		if(q->next!=NULL)//如果q的后继结点存在,修改后继结点的前驱指针
			q->next->prior = p;//1
		p->next = q->next;//2
		free(q);
		return true;
	}
}

int main()
{
	//尾插法建立双链表,并遍历单链表
	DLinkList L1 ,L2 ,L3;
	InitList(L3);//初始化
	int a[5] = { 1,2,3,4,5 };
	int b[5] = { 11 ,66, 12,44,55 };
	CreateListF(L1, a, 5);//头插法建立链表L1
	CreateListR(L2, b, 5);//尾插法建立链表L2

	if (ListEmpty(L3))//判空
		printf("L3为空\n");
	//输出
	PrintList(L1);
	PrintList(L2);
	//按值查找
	DLinkNode* p;
	//按值查找
	int n;
	p = LocateElem(L1, 2,n);
	if(p!=NULL)
	{ 
	printf("值为2的结点的序号为是:%d\n",n);
	printf("值为2的结点的下一个结点值是:%d\n" , p->next->data);
	printf("值为2的结点的上一个结点值是:%d\n", p->prior->data);
	}
	else
	{
		printf("无效查找\n");
	}
	//按位查找
	p = GetElem(L2, 3);
	if (p != NULL)
	{
		printf("L2第三个结点值是:%d\n", p->data);
	}
	else
	{
		printf("无效查找\n");
	}
	//插入操作
	ListInsert(L1,6,99);
	printf( "在第6个结点插入值为99后L1: ");
	PrintList(L1);
	//删除操作
	int e;
	ListDelete(L1,5,e);
	printf( "删除第五个结点后L1: ");
	PrintList(L1);
	printf("删除的值为%d\n", e);

	//求表长
	printf("L1长为:%d" ,ListLength(L1) );
	printf("\n");
	printf("L2长为:%d", ListLength(L2));
	printf("\n");
	
	
	DeleteList(L1);
	DeleteList(L2);
	
	return 0;
}

总结:

  双链表是一种在许多数据结构和算法中都十分有用的工具,特别是在需要高效插入和删除操作的场景下。双链表与单链表在某些操作上几乎是相同的,比如按位查找,按值查找,求表长,打印链表等等,主要有区别的是创建双链表,插入,删除等操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2134785.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TDengine 与 SCADA 强强联合:提升工业数据管理的效率与精准

随着时序数据库&#xff08;Time Series Database&#xff09;的日益普及&#xff0c;越来越多的工业自动化控制&#xff08;工控&#xff09;人员开始认识到其强大能力。然而&#xff0c;时序数据库在传统实时数据库应用领域&#xff0c;特别是在过程监控层的推广仍面临挑战&a…

cmd命令

常用命令 查看电脑名称&#xff1a; hostname 查看网卡信息&#xff1a; ipconfig 快速打开网络设置界面&#xff1a; control.exe netconnections 或 rundll32.exe shell32.dll,Control_RunDLL ncpa.cpld 打开防火墙设置&#xff1a; wf.msc 指定网卡设置IP地址&#…

linux_L1_linux重启服务器

使用putty登录到linux服务器切换到管理员账号 sudo -s重启命令 reboot

Unity全面取消Runtime费用 安装游戏不再收版费

Unity宣布他们已经废除了争议性的Runtime费用&#xff0c;该费用于2023年9月引入&#xff0c;定于1月1日开始收取。Runtime费用起初是打算根据使用Unity引擎安装游戏的次数收取版权费。2023年9月晚些时候&#xff0c;该公司部分收回了计划&#xff0c;称Runtime费用只适用于订阅…

ROS1录包偶现一次崩溃问题定位

现象&#xff1a;崩到了mogo_reporter里面 堆栈&#xff1a;crash里面同时存在两个主线程的堆栈 代码 #include "boost/program_options.hpp" #include <signal.h> #include <string> #include <sstream> #include <iostream> #include <…

【PSINS】ZUPT代码解析(PSINS_SINS_ZUPT)|MATLAB

这篇文章写关于PSINS_SINS_ZUPT的相关解析。【值得注意的是】:例程里面给的这个m文件的代码,并没有使用ZUPT的相关技术,只是一个速度观测的EKF 简述程序作用 主要作用是进行基于零速更新(ZUPT)的惯性导航系统(INS)仿真和滤波 什么是ZUPT ZUPT是Zero Velocity Update(…

828华为云征文 | 使用华为云Flexus云服务器X安装搭建crmeb多门店商城教程

&#x1f680;【商城小程序&#xff0c;加速启航&#xff01;华为云Flexus X服务器助力您的业务腾飞】&#x1f680; 1、点击链接进入华为云官网&#xff0c;页面如下&#xff1a; 华为云Flexus云服务器X选购页面 https://www.huaweicloud.com/product/flexus-x.html 2、进…

Uniapp + Vue3 + Vite +Uview + Pinia 实现提交订单以及支付功能(最新附源码保姆级)

Uniapp Vue3 Vite Uview Pinia 实现提交订单以及支付功能&#xff08;最新附源码保姆级&#xff09; 1 效果展示2 提交订单2.1 cart.js2.2 submit-order.vue 3、支付页面order-pay.vue 1 效果展示 2 提交订单 2.1 cart.js // src/pages/store/cart/cart.js import {defineS…

【最新华为OD机试E卷】报文响应时间(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

mybatis获取参数的5种情况

Mybatis获取参数值的两种方式 mybatis获取参数值的方式有两种: ${} 和 #{} ${} 这个的本质就是字符串拼接 这个无法避免sql注入攻击 #{} 这个的本质就是占位符(尽量使用 #{} 的方式) 可以避免sql注入 mybatis获取参数值的情况 1.mapper接口方法的参数为单个字面量类型…

solidity-20-sendeth

发送ETH 这章的标题让我觉得奇怪&#xff0c;因为先前我也发送ETH&#xff0c;如上一篇提到的recieve和fallback函数。 重现了教程中的代码 // SPDX-License-Identifier: MIT pragma solidity ^0.8.21;contract sendeth{// 这个事件是为了打log,记录收到的eth和剩余的gas fee…

echarts中tooptips提示框超出了怎么解决

我们在制作echarts表格时&#xff0c;有时候会遇到提示框内容较多&#xff0c;会让提示框超出&#xff0c;展示不全数据&#xff0c;如下&#xff1a; 这种情况下需要在tooltips下增加一些属性&#xff1a; 1.confine: true&#xff1a;这个配置的作用是让提示框&#xff08;t…

Docker笔记-容器数据卷

Docker笔记-容器数据卷 docker的理念将运行的环境打包形成容器运行&#xff0c;运行可以伴随容器&#xff0c;但是我们对数据的要求是希望持久化&#xff0c;容器 之间可以共享数据&#xff0c;Docker容器产生的数据&#xff0c;如果不通过docker commit生成新的镜像&#xf…

大数据新视界 --大数据大厂之数据挖掘入门:用 R 语言开启数据宝藏的探索之旅

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

类型转换等 面试真题

题目1 请问哪个结果为NaN A. 123null B. 123‘1’ C. 123/0 D. 123undefined 在这四个表达式中&#xff0c;只有D. 123 undefined 的结果是 NaN&#xff0c;原因如下&#xff1a; A. 123 null 结果是&#xff1a;123原因&#xff1a;null 在数值运算中会被自动转换为 0&a…

mac上什么压缩软件没有广告,苹果电脑解压软件BetterZip有广告吗

mac上有很多压缩软件&#xff0c;可以帮助用户压缩或解压各种格式的文件&#xff0c;如zip、rar、7z等。但是&#xff0c;有些压缩软件会在使用过程中弹出广告&#xff0c;影响用户的体验和效率。那么&#xff0c;mac上什么压缩软件没有广告呢&#xff1f;苹果电脑解压软件Bett…

一步步教你利用大模型开发个性化AI应用,告别‘人工智障’!

为了回答这个问题&#xff0c;我用说人话的方式拿gpts创建了一个“我”&#xff0c;然后让她来回答这个问题。&#xff08;确认过眼神&#xff0c;我是懂套娃的&#xff09; 接下来我会先展示下整个定制过程&#xff1b;然后我们一起看一下她能把题答到什么程度&#xff1b;最后…

UnrealEngine 打包Android平台应用

虚幻引擎 支持将项目发布到 安卓&#xff08;Android&#xff09; 移动设备上&#xff0c;并且提供了若干功能帮你将项目发布到 谷歌游戏商店。本节包含了如何设置Android开发环境、如何使用Android功能和服务、以及如何为发布游戏做准备相关的指南。 当前SDK要求 当前UE版本…

JavaSE篇之内部类和图书系统

1.内部类(类中类) 在Java中&#xff0c;将一个类定义在另一个类内部&#xff0c;前者称为内部类&#xff0c;后者称为外部类。 注意事项&#xff1a; 1. 1.静态内部类&#xff08;被static修饰的内部类&#xff09; 1.在静态内部类的方法中不能直接引用外部类的成员变量&…

中国农业银行——轻量式云原生应用平台(轻云平台)

2021年10月&#xff0c;中国人民银行等联合发布了《关于规范金融业开源技术应用与发展的意见》&#xff08;银办发〔2021〕146 号&#xff09;&#xff0c;规范金融机构合理应用开源技术&#xff0c;提高应用水平和自主可控能力&#xff0c;促进开源技术健康可持续发展。前期&a…