c语言实现双向链表

news2025/2/27 4:19:48

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、链表的分类
    • 1、 单向或者双向链表
    • 2、带头结点或者不带头结点的链表
    • 3、循环或者非循环链表
  • 二、带头双向循环链表
    • 1、链表结点的结构体定义
    • 2、链表的初始化
    • 3、链表的头插和尾插
    • 3、链表的头删和尾删
    • 4、链表指定位置结点的插入和删除
    • 5、链表的头插尾插头删尾删使用ListInsert和ListErase实现
  • 总结


前言

我们知道当要删除一个单链表中目标结点的上一个结点时,需要先遍历单链表,然后找到该目标结点,并且标记目标结点的上一个结点,这样才能实现删除目标结点的上一个结点的操作。而还有一种双向链表,该链表的结点不仅有next指针域存储下一个结点的地址,还有一个prev指针域用来存储上一个结点的地址,这样双向链表删除目标结点的上一个结点就不要再遍历链表了,这只是双向链表的一个特点,双向链表还有很多实用的地方。

一、链表的分类

1、 单向或者双向链表

在这里插入图片描述

2、带头结点或者不带头结点的链表

在这里插入图片描述

3、循环或者非循环链表

在这里插入图片描述
上述这些链表经过组合共有8中链表结构
在这里插入图片描述
虽然有很多种链表结构,但是我们最常用的还是两种结构:
不带头单向非循环链表 即为前面我们介绍的单链表。
不带头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多,在刷题中关于链表的题,都是使用单链表来考核。
在这里插入图片描述
带头双向循环链表即为我们这次介绍的链表结构
带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现增删操作时反而简单了。
在这里插入图片描述

二、带头双向循环链表

1、链表结点的结构体定义

下面代码就为带头双向循环链表的结点的结构体定义,可以看到相比于单链表,双向链表不仅定义了next指针域用来存储下一个结点的地址,还定义了prev指针域用来存储上一个结点的地址。

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

2、链表的初始化

带头双向循环链表是带头结点的,所以链表在初始化时就要创建一个头结点。单链表我们使用了二级指针,因为当单链表为空时,此时指向结构体的指针指向NULL,要改变指向结构体的指针,我们就需要得到指向结构体的指针的地址,所以使用了二级指针。但是双向链表的操作我们只改变结构体里面的next指针域和prev指针域,并不会改变指向结构体的指针,所以只需用一级指针即可。
带头双向循环链表的初始化就是创建一个头结点。即代码如下。

LTNode* CreateNode(LTDataType x)
{
	//创建一个新结点
	LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
	if (newNode == NULL)
	{
		perror("CreateNode_malloc_fail");
		exit(-1);
	}
	//将结点的数据域改为指定值x,
	newNode->data = x;
	newNode->next = NULL;
	newNode->prev = NULL;
	//将该结点返回
	return newNode;  
}

LTNode* ListInit()
{
	//带头双向循环链表的初始化就是创建一个头结点
	//该结点的数据域不需要存有用数据
	LTNode* phead = CreateNode(-1);
	//当链表只有一个头结点时,此时phead的next还是phead
	//phead的prev是phead,这样设置为了使后面的插入删除的代码也适用于只有一个头结点的链表。
	phead->next = phead;
	phead->prev = phead;

	//将头结点返回
	return phead;
}

3、链表的头插和尾插

链表的头插和尾插。


void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	
	//创建一个结点
	LTNode* newNode = CreateNode(x);
	//记录原来链表的尾结点
	LTNode* tailPrev = phead->prev;
	//将新结点插入到原来的尾结点之后。
	tailPrev->next = newNode;
	newNode->prev = tailPrev;
	phead->prev = newNode;
	newNode->next = phead;
	

	/*
	//还可以不记录原来的尾结点,不过这种操作需要注意前后顺序,不然会发生地址丢失的现象
	//创建一个结点
	LTNode* newNode = CreateNode(x);
	//先将尾结点的指针改变
	phead->prev->next = newNode;
	newNode->prev = phead->prev;

	//上面两语句和下面两语句的顺序不能变
	//再将newNode的指针改变
	phead->prev = newNode;
	newNode->next = phead;
	*/
}


void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	//创建一个新结点
	LTNode* newNode = CreateNode(x);
	//记录头结点后面的结点
	LTNode* headNext = phead->next;
	//将新结点插入在头结点之后。
	newNode->next = headNext;
	headNext->prev = newNode;
	phead->next = newNode;
	newNode->prev = phead;

	/*
	//还可以使用这种不创建结点的方法,不过要注意顺序
	//创建一个新结点
	LTNode* newNode = CreateNode(x);
	phead->next->prev = newNode;
	newNode->next = phead->next;

	//语句执行顺序不能变
	phead->next = newNode;
	newNode->prev = phead;
	*/
}

当链表中插入结点后,我们可以打印结点的值。

void ListPrint(LTNode* phead)
{
	assert(phead);
	LTNode* curr = phead->next;
	while (curr != phead)
	{
		printf("%d ", curr->data);
		curr = curr->next;
	}
	printf("\n");
}

3、链表的头删和尾删

在删除链表中的结点时,要先判定链表是否为空,如果链表为空,则不可以删除。
判断链表是否为空。

bool ListEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

头删和尾删。

void ListPopBack(LTNode* phead)
{
	assert(phead);
	//如果链表为空,则会断言
	//assert(!ListEmpty(phead));
	
	//也可以这样判断
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
}


void ListPopFront(LTNode* phead)
{
	assert(phead);
	//如果链表为空,则会断言
	//assert(!ListEmpty(phead));

	//也可以这样判断
	assert(phead->next != phead);

	LTNode* headNext = phead->next;
	LTNode* headNextNext = headNext->next;

	phead->next = headNextNext;
	headNextNext->prev = phead;
	free(headNext);
}

4、链表指定位置结点的插入和删除

void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newNode = CreateNode(x);
	LTNode* posPrev = pos->prev;
	posPrev->next = newNode;
	newNode->prev = posPrev;
	newNode->next = pos;
	pos->prev = newNode;
}

void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

5、链表的头插尾插头删尾删使用ListInsert和ListErase实现

当链表的ListInsert和ListErase函数实现后,则上述实现的头插尾插头删尾删就可以使用这两个函数来实现。
尾插

void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	//在phead结点之前插入新结点就相当于尾插。
	ListInsert(phead, x);
}

头插


void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	//在phead的next结点之前插入一个结点,就相当于头插法
	ListInsert(phead->next, x);
}

尾删

void ListPopBack(LTNode* phead)
{
	assert(phead);
	//如果链表为空,则会断言
	//assert(!ListEmpty(phead));
	
	//也可以这样判断
	assert(phead->next != phead);
	
	//删除phead的上一个结点,就相当于删除尾结点
	ListErase(phead->prev);
}

头删

void ListPopFront(LTNode* phead)
{
	assert(phead);
	//如果链表为空,则会断言
	//assert(!ListEmpty(phead));

	//也可以这样判断
	assert(phead->next != phead);
	
	//删除phead的下一个结点就相当于头删
	ListErase(phead->next);
}

总结

当我们写出来指定位置插入结点和删除结点的ListInsert和ListErase方法后,头插尾插头删尾删都可以用这两个方法实现,所以当我们需要快速写出来一个双向链表的操作时,可以直接写出来这两个函数,这两个函数实现了,其他的操作都可以调用这两个函数来实现。

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

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

相关文章

交换机的基本原理与配置(三)

目录 3. 交换机基本配置 3.1 交换机配置前的连接 3.2 Cisco交换机的命令行模式 3.3 交换机的常见命令 3. 交换机基本配置 交换机的基本配置介绍如下。 3.1 交换机配置前的连接 配置一台Cisco交换机的方法有多种&#xff0c;本节介绍通过Console(控制台)接口讲行配置的方式这…

PCIE超高速实时运动控制卡在六面外观视觉检测上的应用

市场应用背景 XPCIE1028超高速实时运动控制卡在六面外观检测高速视觉筛选中的应用&#xff0c;结合正运动技术提供的专用筛选机调试软件&#xff0c;可实现15000pcs/分钟的IO触发检测速度&#xff0c;只需简单参数设置&#xff0c;搭配图像采集硬件和视觉处理软件&#xff0c;…

交换机的基本原理与配置(一)

目录 简介 1. 数据链路层 1.1 数据链路层的功能 1.2 以太网的由来 1.3 以太网帧格式 简介 在讲解OSI参考模型的章节中&#xff0c;我们已经对以太网数据单元有了初步的认识&#xff0c;本章将在此基础 上进一步学习数据链路层的主要内容&#xff0c;并首次接触网络中的一个…

电商平台按关键字搜索商品淘宝京东拼多多api接口PHP示例

关键词搜索商品接口的作用是通过调用接口来实现在电商平台中进行商品搜索。具体而言&#xff0c;该接口可以提供以下功能和作用&#xff1a; 商品搜索&#xff1a;用户可以通过输入关键词&#xff0c;在电商平台上进行商品搜索。接口可以根据关键词对商品的名称、描述、标签等…

Mybatis动态之灵活使用

目录 ​编辑 1.MyBatis中的动态SQL是什么&#xff1f; 2.MyBatis中的动态SQL作用 3.代码演示 4. #和 $使用 2.1 #使用 ( 1 ) #占位符语法 ( 2 ) #优点#占位符语法在使用动态SQL时具有以下优点&#xff1a; 2.2 $使用 ( 1 ) $占位符语法 ( 2 ) $优点$占位符语法在使用动…

RabbitMQ 消费者

RabbitMQ的消费模式分两种&#xff1a;推模式和拉模式&#xff0c;推模式采用Basic.Consume进行消费&#xff0c;拉模式则是调用Basic.Get进行消费。   消费者通过订阅队列从RabbitMQ中获取消息进行消费&#xff0c;为避免消息丢失可采用消费确认机制 消费者 拉模式拉模式的实…

ChatGPT应用于高职教育的四大潜在风险

目前&#xff0c;ChatGPT还是一种仍未成熟的技术&#xff0c;当其介入高职教育生态后&#xff0c;高职院校师生在享受ChatGPT带来的便利的同时&#xff0c;也应该明白ChatGPT引发的风险也会随之进入高职教育领域&#xff0c;如存在知识信息、伦理意识与学生主体方面的风险与挑战…

轻松正确使用代理IP

Hey&#xff0c;亲爱的程序员小伙伴们&#xff01;在进行爬虫时&#xff0c;你是否曾使用过别人的代理IP&#xff1f;是否因此慌乱&#xff0c;担心涉及违法问题&#xff1f;不要惊慌&#xff01;今天我将和你一起揭开法律迷雾&#xff0c;为你的爬虫之路保驾护航。快跟上我的节…

C++核心编程——类和对象(二)、友元、多态、文件操作

C对象模型和this指针 4.3.1 成员变量和成员函数分开存储 在C中&#xff0c;类内的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上 空类&#xff08;类里面是空的&#xff09;&#xff0c;空对象占用内存空间为&#xff1a;1字节。 静态成员变量&#xff0…

简单屏幕共享 通过web screego windows 生成证书

生成证书用 linux 生成&#xff0c;在 windows 下使用 windows 生成证书 https://juejin.cn/post/6925006735933440014 下载地址 https://github.com/screego/server/releases 修改完配置后&#xff0c;运行 screego serve 需要修改的几个地方 # 局域网 ip 或公网 ip&…

PHP请求API接口对接电商平台亚马逊国际站按关键字搜索商品案例

关键词搜索商品API接口的用途主要包括以下几个方面&#xff1a; 实现商品搜索&#xff1a;通过关键词搜索商品API接口&#xff0c;电商平台可以为消费者提供一个简单、快捷的商品搜索功能。用户只需输入关键词&#xff0c;就可以得到与该关键词相关的商品列表。 提供便捷的商…

vue 转盘抽奖功能,可控制抽奖概率

实现逻辑&#xff1a; 思路&#xff1a;首先需要一个转盘&#xff0c;然后需要一个抽奖按钮定位在中间&#xff0c;图片提前设计或者用背景颜色代替&#xff08;这里用的是图片&#xff0c;然后计算概率&#xff09;&#xff0c;使用css完成转动效果&#xff0c;每次转动完成之…

谈谈收音机的发展

目录 1.什么是收音机 2.收音机的工作原理 3.收音机的发展历史 4.收音机的历史作用 1.什么是收音机 收音机是一种电子设备&#xff0c;用于接收和播放广播电台的无线电信号。它是人们获取各种音乐、新闻、娱乐和其他广播节目的常用设备。 收音机通常由以下几个部分组成&…

无涯教程-PHP - 简介

PHP 7是最期待的&#xff0c;它是PHP编程语言的主要功能版本。 PHP 7于2015年12月3日发布。本教程将以简单直观的方式教您PHP 7的新功能及其用法。 无涯教程假设您已经了解旧版本的PHP&#xff0c;现在就可以开始学习PHP 7的新功能。 使用下面的示例- <html><head&…

【学会动态规划】摆动序列(27)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

优化学习体验是在线培训系统的关键功能

在线培训系统是当今教育领域的一个重要工具&#xff0c;帮助学生和教师提高学习效果和教学质量。一个功能完善的在线培训系统可以提供丰富多样的学习资源和交互方式&#xff0c;以满足不同学生的需求。 个性化学习路径 每个学生的学习需求和进度都不同。通过个性化学习路径功…

【Python机器学习】实验16 卷积、下采样、经典卷积网络

文章目录 卷积、下采样、经典卷积网络1. 对图像进行卷积处理2. 池化3. VGGNET4. 采用预训练的Resnet实现猫狗识别 TensorFlow2.2基本应用5. 使用深度学习进行手写数字识别 卷积、下采样、经典卷积网络 1. 对图像进行卷积处理 import cv2 path data\instance\p67.jpg input_…

AMBA总线协议(7)——AHB(五):传输仲裁

一、前言 在之前的文章中我们讨论了AHB的很多传输细节&#xff0c;主要有控制信号&#xff0c;地址信号的译码&#xff0c;从机的响应等&#xff0c;其中重点介绍了双周期响应&#xff0c;最后介绍了数据总线及端结构&#xff0c;在本文中我们将继续介绍AHB传输的仲裁机制。 仲…

利用大模型反馈故障的解决方案

背景 观测云有两个错误巡检脚本&#xff0c;RUM 错误巡检和 APM 错误巡检&#xff0c;代码均开源。 错误巡检的主要目的是发现新出现的错误消息(error stack)&#xff0c;原有的巡检在上报了相应的事件报告后&#xff0c;只是定位了问题&#xff0c;并没有给出合适的解决方案。…

数据分析实战│价格预测挑战【文末赠书】

文本分析是指对文本信息的表示及特征项的选取&#xff0c;商品文本的描述能够反映特定立场、观点、价值和利益。考虑到网上海量的商品数量&#xff0c;对产品的定价难度很大&#xff0c;因此可以使用商品描述帮助商户定价。比如&#xff0c;服装具有较强的季节性价格趋势&#…