C语言双向链表

1. 链表的分类

链表的种类很多,主要由三个要素决定:是否带头,单向还是双向,是否循环

根据这三个要素的组合,共可得到8(2*2*2)种链表

 

而我们常用的链表有两种:

 1. 单链表:不带头单向不循环链表。 

 2. 双向链表:带头双向循环链表。

 之前我们已经实现过单链表C语言单链表-CSDN博客

单链表在三个元素的选择中,选择的都是较为简单的选项,结构简单那就意味着我们在对其进行操作时所能用的现成条件较少,操作函数的实现就较为复杂。

双向链表作为单链表的另一个极端,每个元素都选择了较为复杂的那个,而将结构设计的复杂的目的,就是为了解决一些操作实现起来很麻烦的问题。

带头的作用

在单链表的实现中,当链表为空时,我们常常要对其进行单独的处理。

例如单链表的尾插和尾删:

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* ptail = *pphead;
		while (ptail->next->next != NULL)
		{
			ptail = ptail->next;
		}
		free(ptail->next);
		ptail->next = NULL;
	}
}

应对这种情况常常需要对头结点进行修改,所以我们仅仅为了这么一种单独的情况,写了一种应对方法并将函数的参数改为了"SLTNode**"类型的指针。

带头,即使得链表自带一个没有意义的结点,始终保持链表中有结点。

这样一来,else部分的代码就对所有情况适用了。

使链表带头很简单,只需要在初始化链表时为其申请一个存储无意义数据的结点即可。

在之后对链表的操作中,要确保不访问这个头结点的数据。

这个头结点通常也被叫做是哨兵位。

//初始化
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

双向的作用

由于单链表只能单向遍历,我们在遇到很多问题时不得不多次遍历链表(查找链表的倒数第k个结点),或者定义一个参数(prev)来存储前一个结点的地址。

例如在指定位置之前插入数据:

//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
		return;
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = SLTBuyNode(x);
		newnode->data = x;
		newnode->next = prev->next;
		prev->next = newnode;
	}
}

这样会极大的降低我们程序运行的效率。

双向,即每个结点不仅包含了指向后一个数据的指针,还包含了指向前一个数据的指针。

//双向链表
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

这样,我们就可以更加自由地去查找结点,提高运行效率。

当然,运行效率的提升一般都是靠空间的消耗来换取的。

循环的作用

在单链表中,我们常常会为了寻找到尾结点而大动干戈地遍历整个链表。

就例如上面给出的尾插和尾删的代码。

循环,即使得尾结点的next指针指向头结点,并让头结点的prev指针指向尾结点,将整个链首尾相连形成一个环。

这样一来,只需要通过头结点的prev指针就可以查找到尾结点,通过尾结点的next指针就可以找到头结点。

将两个本来相隔最远的结点链接在一起,就使得某些算法的复杂度大幅下降。

双向链表的结构

2. 双向链表的实现

 2.1 头文件

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

typedef int LTDataType;

//双向链表
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

//初始化
LTNode* LTInit();
//销毁
void LTDestroy(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//打印
void LTPrint(LTNode* phead);
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//删除pos结点
void LTErase(LTNode* pos);

//LTErase和LTDestroy为了保持接口一致性而传入一级指针,未将pos或phead置为空

2.2 源文件

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"

LTNode* LTBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	node->data = x;
	node->next = node->prev = node;

	return node;
}

//初始化
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

//销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}

	free(phead);
	phead = NULL;
}

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead && phead->prev != phead);

	LTNode* del = phead->prev;
	phead->prev->prev->next = phead;
	phead->prev = phead->prev->prev;

	free(del);
	del = NULL;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);

	LTNode* del = phead->next;
	phead->next->next->prev = phead;
	phead->next = phead->next->next;

	free(del);
	del = NULL;
}

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("head\n");
}

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

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);
	newnode->prev = pos;
	newnode->next = pos->next;

	pos->next->prev = newnode;
	pos->next = newnode;
}

//删除pos结点
void LTErase(LTNode* pos)
{
	//pos理论上来说不能是哨兵位
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

可见,对双向链表进行操作的各种函数要比对单链表进行操作的各种函数简单的多。

但是相对的,双向链表会占用更多的空间。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙原生应用元服务-访问控制(权限)开发Stage模型向用户申请授权

一、向用户申请授权 当应用需要访问用户的隐私信息或使用系统能力时&#xff0c;例如获取位置信息、访问日历、使用相机拍摄照片或录制视频等&#xff0c;应该向用户请求授权。这需要使用 user_grant 类型权限。在此之前&#xff0c;应用需要进行权限校验&#xff0c;以判断当前…

02_按键控制LED

按键控制LED 按键控制LED 按键控制LED while (1){//按键控制LEDif(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5)GPIO_PIN_RESET)//读取PC5引脚状态&#xff0c;即检测按键是否按下{while(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5)GPIO_PIN_RESET);//松手检测HAL_GPIO_WritePin(GPIOA,GPIO_PI…

【JavaWeb】Day45.Mybatis——入门程序

什么是MyBatis? MyBatis是一款优秀的持久层框架&#xff0c;用于简化JDBC的开发。 &#xff08;持久层&#xff1a;指的是就是数据访问层(dao)&#xff0c;是用来操作数据库的。&#xff09; &#xff08;框架&#xff1a;是一个半成品软件&#xff0c;是一套可重用的、通用…

【软考】UML中的图之类图

目录 1. 说明2. 图示3. 类图使用方式3.1 对系统的词汇建模3.2 对简单的协作建模3.3 对逻辑数据库模式建模 1. 说明 1.类图&#xff08;Class Diagram&#xff09;展现了一组对象、接口、协作和它们之间的关系。2.在面向对象系统的建模中所建立的最常见的图是类图。3.类图给出系…

Openwrt21.02支持SKW78(MT7621)

1.获取SDK 1.下载Openwrt源码 下载链接&#xff1a; git clone --branch openwrt-21.02 https://gitee.com/cocos_yang/openwrt.git 下载完后&#xff0c;会有一个openwrt目录&#xff0c;进入openwrt目录 cd openwrt 修改feeds.conf.default的内容&#xff0c;如下所示&#x…

操作系统(1)计算机存储结构

文章目录 一、计算机存储结构1、基本概念2、计算机存储结构的组成部分3、局部性原理4、高速缓存4.1、基本概念4.2、工作原理4.3、层次结构4.4、对系统性能的影响4.5、一致性问题 5、寄存器5.1&#xff0c;种类5.2&#xff0c;特点5.3、作用5.4、寄存器与缓存的差异 前言 计算机…

MybatisPlus实现数据权限隔离

引言 Mybatis Plus对Mybatis做了无侵入的增强&#xff0c;非常的好用&#xff0c;今天就给大家介绍它的其中一个实用功能&#xff1a;数据权限插件。 数据权限插件的应用场景和多租户的动态拦截拼接SQL一样。建议点赞收藏关注&#xff0c;方便以后复习查阅。 依赖 首先导入M…

【网安小白成长之路】7.burp基本使用

&#x1f42e;博主syst1m 带你 acquire knowledge&#xff01; ✨博客首页——syst1m的博客&#x1f498; &#x1f51e; 《网安小白成长之路(我要变成大佬&#x1f60e;&#xff01;&#xff01;)》真实小白学习历程&#xff0c;手把手带你一起从入门到入狱&#x1f6ad; &…

计算机网络3——数据链路层1

文章目录 一、介绍1、基础2、内容 二、数据链路层的几个共同问题1、数据链路和帧2、三个基本问题1&#xff09;封装成帧2&#xff09;透明传输3&#xff09;差错检测 三、点对点协议 PPP1、PPP协议的特点1&#xff09;PPP 协议应满足的需求2&#xff09;PPP 协议的组成 2、PPP协…

vmware安装ubuntu-18.04系统

一、软件下载 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1fK2kygRdSux1Sr1sOKOtJQ 提取码&#xff1a;twsb 二、安装ubuntu系统 1、把ubuntu-18.04的压缩包下载下来&#xff0c;并且解压 2、打开vmware软件&#xff0c;点击文件-打开 3、选择我们刚刚解…

限制登录Linux服务器的几种方式

一.第一种方法 通过修改TCP Wrappers服务访问控制来实现限制登录Linux 1.这里以sshd服务为例&#xff0c;配置完成后&#xff0c;只允许配置允许的IP才能ssh连接本机服务器&#xff0c;其他IP拒绝判断某一个基于tcp协议的服务是否支持tcp_wrapper&#xff0c;要先判断它是否支…

【C语言】<动态内存管理>我的C语言终末章

&#xff1c;动态内存管理&#xff1e; 1. 为什么要有动态内存分配2. malloc和free2.1 malloc2.2 free 3. calloc和realloc3.1 calloc3.2 realloc 4.常见的动态内存错误4.1 对NULL指针的解引用操作4.2 对动态开辟空间的越界访问4.3 对非动态开辟内存使用free释放4.4 使用free释…

Linux下SPI设备驱动实验:实现SPI发送/接收数据的函数

一. 简介 前面文章介绍了SPI设备数据收发处理流程&#xff0c;后面几篇文章实现了SPI设备驱动框架&#xff0c;加入了字符设备驱动框架代码。文章如下&#xff1a; SPI 设备驱动编写流程&#xff1a;SPI 设备数据收发处理流程中涉及的结构体与函数-CSDN博客 SPI 设备驱动编写…

PgSQL之WITH Queries/Statement

PostgreSQL WITH 子句 在 PostgreSQL 中&#xff0c;WITH 子句提供了一种编写辅助语句的方法&#xff0c;以便在更大的查询中使用。 WITH 子句有助于将复杂的大型查询分解为更简单的表单&#xff0c;便于阅读。这些语句通常称为通用表表达式&#xff08;Common Table Express…

基于有序抖动块截断编码的水印嵌入和提取算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 噪声测试 旋转测试 压缩测试 2.算法运行软件版本 matlab2022a 3.部分核心程序 ............................................................…

Spring(24) Json序列化的三种方式(Jackson、FastJSON、Gson)史上最全!

目录 一、Jackson 方案&#xff08;SpringBoot默认支持&#xff09;1.1 Jackson 库的特点1.2 Jackson 的核心模块1.3 Maven依赖1.4 代码示例1.5 LocalDateTime 格式化1.6 统一配置1.7 常用注解1.8 自定义序列化和反序列化1.9 Jackson 工具类 二、FastJSON 方案2.1 FastJSON 的特…

折叠面板组件(vue)

代码 <template><div class"collapse-info"><div class"collapse-title"><div class"title-left">{{ title }}</div><div click"changeHide"> <Button size"small" v-if"sho…

LeetCode-706. 设计哈希映射【设计 数组 哈希表 链表 哈希函数】

LeetCode-706. 设计哈希映射【设计 数组 哈希表 链表 哈希函数】 题目描述&#xff1a;解题思路一&#xff1a;超大数组解题思路二&#xff1a;拉链法解题思路三&#xff1a; 题目描述&#xff1a; 不使用任何内建的哈希表库设计一个哈希映射&#xff08;HashMap&#xff09;。…

SpringBoot基于RabbitMQ实现消息延迟队列方案

知识小科普 在此之前&#xff0c;简单说明下基于RabbitMQ实现延时队列的相关知识及说明下延时队列的使用场景。 延时队列使用场景 在很多的业务场景中&#xff0c;延时队列可以实现很多功能&#xff0c;此类业务中&#xff0c;一般上是非实时的&#xff0c;需要延迟处理的&a…

【讲解下常见的Web前端框架】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…