初阶数据结构之双向链表详解

news2024/11/25 0:31:10

目录

一:双向链表的概念

1.什么是双向链表?

2.双向链表的优点

3.双向链表的结构

二:双向链表的实现

1.定义链表结点

2.初始化双向链表

3.添加结点

4.尾插

5.头插

6.打印双向链表

7.查找链表结点

8.在指定结点后插入新结点

9.删除指定链表结点

10.头删

11.尾删

12.销毁双向链表

三:代码综合

1.List.h:

2.List.c

3.test.c


开门见山,直接开始讲解。

如果内容对你有所帮助,不妨点个赞再走。(如果有错误请指出,一定改正)

                     

一:双向链表的概念

1.什么是双向链表?

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表,因此,本节的双向链表也是双向循环链表。

2.双向链表的优点

1、双向性:双链表支持在每个节点上存储前驱节点和后继节点的指针,使得在任何节点上都可以方便地找到其前驱节点和后继节点。而单链表只能通过遍历整个链表来查找特定节点的下一个节点或上一个节点,效率较低。

2、插入和删除操作更高效:由于双链表支持双向链接,因此在插入和删除操作中,双链表只需要重新调整相关的指针即可,而不需要像单链表那样需要遍历整个链表。这使得双链表的插入和删除操作更高效。

3、内存利用率更高:双向链表每个节点存储了前驱节点和后继节点的指针,因此可以更充分地利用内存空间。相比之下,单链表每个节点只存储了一个指向下一个节点的指针,内存利用率相对较低。

3.双向链表的结构

图示:

头结点head的prev的指针指向d3,d3的next指针指向head。

同时,无头单向非循环链表和带头双向循环链表也是我们平时最常用的链表结构。

接下来,我们来聊聊双向链表的实现

二:双向链表的实现

在进行链表的实现时,我们把函数的声明放在List.h中,在List.c中实现,在test.c中调用。

1.定义链表结点

在List.h中定义链表结点,data的类型设为DataType,方便以后使用其他类型的数据。

typedef int DataType;
typedef struct ListNode
{
	DataType data;
	struct ListNode* prev;
	struct ListNode* next;

}ListNode;

2.初始化双向链表

初始化链表就是为链表设置一个头结点,头结点内不存储有效数据,只在链表中充当哨兵位的作用,然后把头结点的的prev指针和next指针都指向自己

传递二级指针的原因是需要把头结点指针的指向改为指向新开辟空间的地址,因为是对地址进行操作,因此要传递二级指针。

//双向链表初始化
void InitList(ListNode** head)
{
	*head = (ListNode*)malloc(sizeof(ListNode));
	if (*head == NULL)
	{
		perror("malloc");
		exit(1);
	}
	(*head)->data = 1;
	(*head)->prev = *head;
	(*head)->next = *head;
}

 运行结果:

3.添加结点

添加链表结点添加链表结点其实就是把非结点结构的数据转换为结点类型的数据,在链表插入新节点时使用。

//添加结点
ListNode* BuyNode(DataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc");
		return NULL;
	}
	newnode->data = x;
	newnode->prev = newnode;
	newnode->next = newnode;
	return newnode;
}

4.尾插

代码展示:

//尾插
void PushBack(ListNode* head, DataType x)
{
	ListNode* newnode = BuyNode(x);
	newnode->next = head;
	newnode->prev = head->prev;
	head->prev->next = newnode;
	head->prev = newnode;
}

当传过来的x为2时,运行结果:

5.头插

代码展示:

//头插
void PushFront(ListNode* head, DataType x)
{
	ListNode* newnode = BuyNode(x);
	newnode->next = head->next;
	newnode->prev = head;
	head->next->prev = newnode;
	head->next = newnode;

}

6.打印双向链表

代码展示:

//打印双向链表
void PrintList(ListNode* head)
{
	assert(head && head->next);//判断链表的哨兵位和下一个结点是否为空
	ListNode* tail = head->next;
	while (tail != head)
	{
		printf("%d->", tail->data);
		tail = tail->next;
	}
	printf("\n");
}

 在上面我们已经进行过尾插和头插的操作,让我们打印出来此时链表中的内容为:

7.查找链表结点

//查找结点
ListNode* FindNode(ListNode* head, DataType x)
{
	assert(head && head->next);
	ListNode* tail = head->next;
	while (tail != head)
	{
		if (tail->data == x)
		{
			return tail;
		}
		tail = tail->next;
	}
}

8.在指定结点后插入新结点

需要先使用FindNode函数查找指定结点,再把x转换为链表类型,最后插入。

//在指定结点后面插入结点
void InsertNode(ListNode* head, DataType a, DataType x)
{
	assert(head && head->next);
	ListNode* pos = FindNode(head,a);
	ListNode* newnode = BuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

假设a为3,x为6时,运行结果为:

9.删除指定链表结点

删除链表结点要先用FindNode查找结点的地址,然后再进行删除。

//删除指定结点
void DelNode(ListNode* head, DataType x)
{
	assert(head && head->next);
	ListNode* pos = FindNode(head,x);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

假设删除的数据x为3,则运行结果为:

事实证明,我们删除成功了。

10.头删

代码展示:

//头删
void DelFront(ListNode* head)
{
	assert(head && head->next);
	ListNode* first = head->next;
	first->next->prev = head;
	head->next = first->next;
	free(first);
	first = NULL;
}

运行结果:

 头删成功。

11.尾删

//尾删
void DelBack(ListNode* head)
{
	assert(head && head->next);
	ListNode* back = head->prev;
	back->prev->next = head;
	head->prev = back->prev;
}

运行结果:

此时我们的链表中已经没有任何数据了。

12.销毁双向链表

销毁双向链表我们也要传递二级指针,因为在函数操作中也会改变头结点的地址。

//销毁双向链表
void DelList(ListNode** head)
{
	assert(head && *head);
	ListNode* phead = (*head)->next;
	while (phead!=*head)
	{
		ListNode* next = phead->next;创建next指针用来存储下一个结点的地址
		free(phead);
		phead = next;
	}
    free(*head);
	*head=NULL;
}

至此,双向链表的实现全部完成。

三:代码综合

1.List.h:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int DataType;
typedef struct ListNode
{
	DataType data;
	struct ListNode* prev;
	struct ListNode* next;

}ListNode;

//双向链表初始化
void InitList(ListNode** head);

//添加结点
ListNode* BuyNode(DataType x);

//尾插结点
void PushBack(ListNode*head,DataType x);

//头插结点
void PushFront(ListNode* head, DataType x);

//打印双向链表
void PrintList(ListNode* head);

//查找结点
ListNode* FindNode(ListNode* head, DataType x);

//指定结点后面插入结点
void InsertNode(ListNode* head,DataType a, DataType x);

//删除指定结点
void DelNode(ListNode* head, DataType);

//头删
void DelFront(ListNode* head);

//尾删
void DelBack(ListNode* head);

//销毁双向链表
void DelList(ListNode** head);

2.List.c

#include"List.h"

//双向链表初始化
void InitList(ListNode** head)
{
	*head = (ListNode*)malloc(sizeof(ListNode));
	if (*head == NULL)
	{
		perror("malloc");
		exit(1);
	}
	(*head)->data = 1;
	(*head)->prev = *head;
	(*head)->next = *head;
}

//添加结点
ListNode* BuyNode(DataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc");
		return NULL;
	}
	newnode->data = x;
	newnode->prev = newnode;
	newnode->next = newnode;
	return newnode;
}

//尾插
void PushBack(ListNode* head, DataType x)
{
	ListNode* newnode = BuyNode(x);
	newnode->next = head;
	newnode->prev = head->prev;
	head->prev->next = newnode;
	head->prev = newnode;

}

//头插
void PushFront(ListNode* head, DataType x)
{
	ListNode* newnode = BuyNode(x);
	newnode->next = head->next;
	newnode->prev = head;
	head->next->prev = newnode;
	head->next = newnode;

}

//打印双向链表
void PrintList(ListNode* head)
{
	assert(head && head->next);
	ListNode* tail = head->next;
	while (tail != head)
	{
		printf("%d->", tail->data);
		tail = tail->next;
	}
	printf("\n");
}

//查找结点
ListNode* FindNode(ListNode* head, DataType x)
{
	assert(head && head->next);
	ListNode* tail = head->next;
	while (tail != head)
	{
		if (tail->data == x)
		{
			return tail;
		}
		tail = tail->next;
	}
}

//在指定结点后面插入结点
void InsertNode(ListNode* head, DataType a, DataType x)
{
	assert(head && head->next);
	ListNode* pos = FindNode(head,a);
	ListNode* newnode = BuyNode(x);
	newnode->next = pos->next;
	newnode->prev = pos;
	pos->next->prev = newnode;
	pos->next = newnode;
}

//删除指定结点
void DelNode(ListNode* head, DataType x)
{
	assert(head && head->next);
	ListNode* pos = FindNode(head,x);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

//头删
void DelFront(ListNode* head)
{
	assert(head && head->next);
	ListNode* first = head->next;
	first->next->prev = head;
	head->next = first->next;
	free(first);
	first = NULL;
}

//尾删
void DelBack(ListNode* head)
{
	assert(head && head->next);
	ListNode* back = head->prev;
	back->prev->next = head;
	head->prev = back->prev;
}

//销毁双向链表
void DelList(ListNode** head)
{
	assert(head && *head);
	ListNode* phead = (*head)->next;
	while (phead!=*head)
	{
		ListNode* next = phead->next;
		free(phead);
		phead = next;
	}
	free(*head);
	*head =NULL;
}

3.test.c

#include"List.h"
int main()
{
	ListNode* head=NULL;
	//初始化
	InitList(&head);

	//尾插
	PushBack(head, 2);
	printf("%d", head->next->data);

	//头插
	PushFront(head, 3);
	printf("%d\n", head->next->data);

	//打印链表
	PrintList(head);

	//查找结点
	ListNode* node = FindNode(head, 3);

	//指定结点后面插入新节点
	InsertNode(head, 3, 6);
	PrintList(head);

	//删除指定结点
	DelNode(head, 3);
	PrintList(head);

	//头删
	DelFront(head);
	PrintList(head);

	//尾删
	DelBack(head);
	PrintList(head);

	//销毁双向链表
	DelList(&head);
	PrintList(head);

}

完结撒花。

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

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

相关文章

KMP算法【C++】

KMP算法测试 KMP 算法详解 根据解释写出对应的C代码进行测试&#xff0c;也可以再整理成一个函数 #include <iostream> #include <vector>class KMP { private:std::string m_pat;//被匹配的字符串std::vector<std::vector<int>> m_dp;//状态二维数组…

【iceberg】数据湖与iceberg调研与实战

文章目录 一. 为什么现在要强调数据湖1. 大数据架构发展历史2. Lambda架构与kappa架构3. 数据湖所具备的能力 二. iceberg是数据湖吗1. iceberg的诞生2. iceberg设计之table format从如上iceberg的数据结构可以知道&#xff0c;iceberg在数据查询时&#xff0c;1.查找文件的时间…

三、自定义信号和槽函数(无参和有参)

需求&#xff1a; 下班后&#xff0c;小明说请小红吃好吃的&#xff0c;随便吃&#xff0c;吃啥买啥 无参&#xff1a;小红没有提出吃啥 有参&#xff1a;小红提出自己想吃的东西&#xff0c;吃啥取决于一时兴起&#xff08;emit触发&#xff09; 思路&#xff1a; 1&#xff…

【数据结构】排序详解(希尔排序,快速排序,堆排序,插入排序,选择排序,冒泡排序)

目录 0. 前情提醒&#xff1a; 1. 插入排序 1.1 基本思想&#xff1a; 1.2 直接插入排序 实现步骤&#xff1a; 动图演示&#xff1a; 特性总结&#xff1a; 代码实现&#xff1a; 1.3 希尔排序&#xff08;缩小增量排序&#xff09; 基本思想&#xff1a; 步骤演示&…

谷歌上架,个人号比企业号好上?“14+20”封测如何解决,你知道了吗

在Google Play上架应用&#xff0c;对开发者而言&#xff0c;既是挑战也是机遇。随着谷歌政策的不断更新&#xff0c;特别是要求2023年11月13日后注册的个人开发者账号在发布正式版应用前&#xff0c;必须经过20人连续14天的封闭测试。 这一政策的改变使得许多开发者开始考虑使…

适合小白入门的AI扩图(创成式填充)工具

近期&#xff0c;发现许多人对AI扩图工具的需求比较大&#xff0c;为了满足大家的需求&#xff0c;本期天祺为大家整理了一些好用的AI扩图工具&#xff0c;各个设配的扩图工具都有介绍哦&#xff0c;电脑&#xff0c;手机端都能用&#xff0c;大家可以根据自己的喜好和需求进行…

Linux程序开发(十):文件分类器趣味设计

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

搜索插入位置 ---- 二分查找

题目链接 题目: 分析: 因为数排序数组, 所以具有"二段性", 可以使用二分查找题目中, 我们如果找到目标值 , 则返回下标, 如果没找到目标值, 应该返回的是>target的第一个位置, 所以应该将数组分成< target 和 > target当<target时, 应该移动left, left…

Marin说PCB之POC电路layout设计仿真案例---03

今天中午午休我刚要打开手机的准备刷抖音看无忧传媒的学生们的“学习资料”的时候&#xff0c;看到CSDN -APP上有提醒&#xff0c;一看原来是一位道友发的一个问题&#xff1a; 本来小编最近由于刚刚从国外回来&#xff0c;手上的项目都已经结束了&#xff0c;这周开始学习仿真…

Xcode=> 安装 simulator

XCode xcode中下载 simulator 点击加号➕&#xff0c;选择对应的版本&#xff0c;即可下载 下载完成&#xff1a; 其他下载办法 因为使用上述下载&#xff0c;会经常性的出现断开&#xff0c;再次下载又是从头开始&#xff0c;太费时费力。下面使用下载地址&#xff0c;然后用…

软考中级-软件设计师-真题详解【2023年上半年】

2023上半年真题记忆点详解 本片不涉及解题法&#xff0c;只整理记忆背诵点&#xff0c;记住即可拿分。 上午题部分&#xff1a; 片内总线&#xff1a;用于芯片内部各主要部件连接&#xff1b; 系统总线&#xff1a;用于CPU、主存、外设见的数据传输&#xff1b; 通讯总线&…

百度信息流 - 成本保障未生效?

今天创建百度信息流单元时&#xff0c;发现一个细节&#xff0c;创建好后&#xff0c;成本保障未生效&#xff08;“保”字没有出现&#xff09; 过了一会&#xff0c;再进来看&#xff0c;成本保障生效了。 分析原因 &#xff1a; 展现为 1 &#xff0c;也就是说&#xff0c;一…

建筑施工突发事故应急处置vr安全培训平台

在不断发展的时代背景下&#xff0c;掌握必要的应急安全知识已成为我们生活中不可或缺的一部分。由央企携手我们华锐推出的3D线上应急宣教虚拟体验馆&#xff0c;标志着民众应急安全教育的全新里程碑&#xff0c;不仅突破了传统学习模式的局限&#xff0c;还让每个人都能在灵活…

GpuMall智算云:Ubuntu 实例桌面版

基于 ubuntu18.04 安装的桌面版本&#xff0c;桌面使用 xfce4 &#xff0c;集成了 Pytorch2.3.0、cuda11.8、Python3.10、VNC、noVNC、VSCode-Server。 在 镜像市场 选择xfce4-desktop镜像&#xff0c;然后进行创建实例 GpuMall智算云 | 省钱、好用、弹性。租GPU就上GpuMall…

这八个步骤,有效进行防错管理

导读 在产品实际的生产过程中&#xff0c;因零件相似而错装、因零件又小又多而漏装等现象时有发生&#xff0c;需要防止或尽可能避免错误发生。 试想&#xff0c;一个操作人员每天进行同样的装配工作上百次千次甚至上万次&#xff0c;如果产品设计和过程设计开发不能防止提前预…

vue.js状态管理和服务端渲染

状态管理 vuejs状态管理的几种方式 组件内管理状态&#xff1a;通过data&#xff0c;computed等属性管理组件内部状态 父子组件通信&#xff1a;通过props和自定义事件实现父子组件状态的通信和传递 事件总线eventBus&#xff1a;通过new Vue()实例&#xff0c;实现跨组件通…

Pytorch入门(7)—— 梯度累加(Gradient Accumulation)

1. 梯度累加 在训练大模型时&#xff0c;batch_size 最大值往往受限于显存容量上限&#xff0c;当模型非常大时&#xff0c;这个上限可能小到不可接受。梯度累加&#xff08;Gradient Accumulation&#xff09;是一个解决该问题的 trick梯度累加的思想很简单&#xff0c;就是时…

第12周作业--HLS入门

目录 一、HLS入门 二、HLS入门程序编程 创建项目 1、点击Vivado HLS 中的Create New Project 2、设置项目名 3、加入文件 4、仿真 3、综合 一、HLS入门 1. HLS是什么&#xff1f;与VHDL/Verilog编程技术有什么关系? HLS&#xff08;High-Level Synthesis&#xff0c…

自动化重置数据库功能的探索与实践

1、简介 在现代软件开发中&#xff0c;尤其是涉及到数据驱动的应用程序时&#xff0c;开发和测试环境中数据库的管理是至关重要的一环。为了确保开发和测试环境中的数据库始终处于一致的状态&#xff0c;自动化重置数据库成为了一种常见的实践。本文旨在介绍如何通过Shell脚本…

打印9*9乘法表(递归或压缩矩阵)python

打印9*9表def print_multiplication_table(row, col):if row > 10:return # 递归结束条件if col row:print() # 换行print_multiplication_table(row 1, 1) # 递归调用下一行else:print(f"{row-1} * {col} {(row-1) * col}\t", end"") # 打印乘法…