C语言数据结构-双向链表

news2025/1/15 20:43:38

文章目录

  • 1 双向链表的结构
  • 2 双向链表的实现
    • 2.1 定义双向链表的数据结构
    • 2.2 打印链表
    • 2.3 初始化链表
    • 2.4 销毁链表
    • 2.5 尾插,头插
    • 2.6 尾删,头删
    • 2.7 根据头次出现数据找下标
    • 2.8 定点前插入
    • 2.9 删除pos位置
    • 2.10 定点后插入
  • 3 完整代码
    • 3.1 List.h
    • 3.2 Lish.c
    • 3.3 test.c


1 双向链表的结构

在这里插入图片描述

带头链表的头结点,实际是"哨兵位",哨兵位节点不存储任何有效元素,只是站在这里"放哨的".
哨兵位的意义:遍历循环链表避免死循环.

2 双向链表的实现

笔者在删除,插入数据时,画好图后,也好了代码,但是在调试中多次出现代码位置出错,导致写的代码的含义不符合预期.
所以说思路一定要清晰,多多检查调试

2.1 定义双向链表的数据结构

typedef int ListDataType;

typedef struct ListNode
{
	ListDataType data;		//整型数据
	struct ListNode* next;	//前驱指针
	struct ListNode* pre;	//后驱指针

}ListNode;

2.2 打印链表

链表的第一个数据是phead->next,哨兵位不存储数据
循环链表中,遍历一遍,碰到phead为止

void ListPrint(ListNode* phead)
{
	ListNode* cur = phead->next;	//头结点,哨兵位的下一位
	while (cur != phead)//双向循环链表,循环到哨兵位为止
	{
		printf("%d-> ", cur->data);
		cur = cur->next;
	}
}

在这里插入图片描述

2.3 初始化链表

定义一个双向循环链表后,初始化链表,此时只有一个phead(哨兵位),前驱指针和后驱指针都指向phead自己
哨兵位的数据(data)在应用中不使用,就设置成-1了,与笔者之后使用的正整数形成差异

ListNode* ListInit()
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		perror("malloc error");
		return;
	}
	phead->data = -1;
	phead->next = phead->pre = phead;
	return phead;
}

在这里插入图片描述

调试中发现,phead,phead->next,phead->pre地址相同,data都是笔者设置的-1.

2.4 销毁链表

遍历一遍链表进行销毁,cur碰到phead哨兵位为止
释放cur前,记录下cur->next,释放cur后,把cur->next赋值给cur,以此避免销毁cur后,cur->next不能指向下一个节点的情况
最后再把哨兵位释放,置空.

void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;	//头结点,哨兵位的下一位
	while (cur!=phead)
	{
		//释放cur前,记录下cur->next,释放cur后,把cur->next赋值给cur
		ListNode* NEXT = cur->next;
		free(cur);
		cur = NEXT;
	}
	//释放哨兵位
	free(phead);
	phead = NULL;
}

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

2.5 尾插,头插

先写一个创建内存空间的函数,创建node后,画图示意头插和尾插
一定注意编写代码的顺序,看看笔者注释所说的
在这里插入图片描述
在这里插入图片描述

//插入数据前创建内存空间
ListNode* ListBuyNode(ListDataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	node->data = x;
	node->next = node->pre = NULL;

	return node;
}

void ListPushBack(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* node = ListBuyNode(x);

	//先处理新节点前驱指针和后驱指针
	node->pre = phead->pre;
	node->next = phead;
	//再处理原链表最后一个节点和phead
	phead->pre->next = node;
	phead->pre = node;
}

void ListPushFront(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* node = ListBuyNode(x);
	//先处理新节点前驱指针和后驱指针
	node->pre = phead;
	node->next = phead->next;
	//再处理phead和原链表第一个节点(phead->next)
	phead->next->pre = node;
	phead->next = node;

}

初始化成功,我们插入一个数据"1",成功插入
在这里插入图片描述
在这里插入图片描述

2.6 尾删,头删

删除链表至少有除哨兵位的一个数据,换句话说,链表不能只有一个哨兵位
在这里插入图片描述

在这里插入图片描述

void ListPopBack(ListNode* phead)
{
	assert(phead);
	//链表不能只有一个哨兵位
	assert(phead->next != phead);
	ListNode* del = phead->pre;
	//删除节点的前驱指针
	del->pre->next = phead;
	//phead的前驱指针
	phead->pre = del->pre;
}

void ListPopFront(ListNode* phead)
{
	assert(phead && phead->next != phead);
	ListNode* del = phead->next;

	del->next->pre = phead;
	phead->next = del->next;

	free(del);
	del = NULL;

}

在这里插入图片描述

在这里插入图片描述

2.7 根据头次出现数据找下标

这个Find()函数在笔者的多篇博客都有提到缺点,但是我们主要实现功能,笔者在力扣题写过找多个相同元素,删多个相同元素的题

ListNode* ListFind(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

在这里插入图片描述

在这里插入图片描述

2.8 定点前插入

在这里插入图片描述

void ListInsert(ListNode* pos, ListDataType x)
{
	assert(pos);
	ListNode* node= ListBuyNode(x);
	//先处理node
	node->next = pos;
	node->pre = pos->pre;
	//在处理pos->pre和pos
	pos->pre->next= node;
	node->next->pre = node;

}

在这里插入图片描述

2.9 删除pos位置

在这里插入图片描述

void ListErase(ListNode* pos)
{
	assert(pos);
	pos->next->pre = pos->pre;
	pos->pre->next = pos->next;
	free(pos);
	pos = NULL;
}

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

2.10 定点后插入

在这里插入图片描述

void ListInsertAfter(ListNode* pos, ListDataType x)
{
	assert(pos);
	ListNode* node = ListBuyNode(x);
	//node的prev 和 next
	node->next = pos->next;
	node->pre = pos;

	//pos的next 和 pos->next的prev
	pos->next = node;
	node->next->pre = node;
}

在这里插入图片描述

3 完整代码

3.1 List.h

#define _CRT_SECURE_NO_WARNINGS
#include<stdbool.h>
#include<stddef.h>
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//0 定义双向循环链表节点结构
typedef int ListDataType;

typedef struct ListNode
{
	ListDataType data;
	struct ListNode* next;	//前驱指针
	struct ListNode* pre;	//后驱指针

}ListNode;

//0 打印链表
void ListPrint(ListNode* phead);

//1 初始化链表
ListNode* ListInit();

//2 销毁链表
void ListDestory(ListNode* phead);

//3 尾插,头插
void ListPushBack(ListNode* phead, ListDataType x);
void ListPushFront(ListNode* phead, ListDataType x);

//4 尾删,头删
void ListPopBack(ListNode* phead);
void ListPopFront(ListNode* phead);

//5 根据数找到第一次出现的下标
ListNode* ListFind(ListNode* phead, ListDataType x);
  
//6 定点前插入
void ListInsert(ListNode* pos, ListDataType x);

//7 删除pos位置
void ListErase(ListNode* pos);

//8 定点后插入
void ListInsertAfter(ListNode* pos, ListDataType x);



3.2 Lish.c

#include "List.h"

void ListPrint(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur != phead)//双向循环链表,循环到哨兵位为止
	{
		printf("%d-> ", cur->data);
		cur = cur->next;
	}
}

ListNode* ListInit()
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		perror("malloc error");
		return;
	}
	phead->data = -1;
	phead->next = phead->pre = phead;
	return phead;
}

void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;	//头结点,哨兵位的下一位
	while (cur!=phead)
	{
		//释放cur前提前记录下cur->next,释放cur后,把cur->next赋值给cur
		ListNode* NEXT = cur->next;
		free(cur);
		cur = NEXT;
	}
	//释放哨兵位
	free(phead);
	phead = NULL;
}

//插入数据前创建内存空间
ListNode* ListBuyNode(ListDataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	node->data = x;
	node->next = node->pre = NULL;

	return node;
}

void ListPushBack(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* node = ListBuyNode(x);

	//先处理新节点前驱指针和后驱指针
	node->pre = phead->pre;
	node->next = phead;
	//再处理原链表最后一个节点和phead
	phead->pre->next = node;
	phead->pre = node;
}

void ListPushFront(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* node = ListBuyNode(x);
	//先处理新节点前驱指针和后驱指针
	node->pre = phead;
	node->next = phead->next;
	//再处理phead和原链表第一个节点(phead->next)
	phead->next->pre = node;
	phead->next = node;

}

void ListPopBack(ListNode* phead)
{
	assert(phead);
	//链表不能只有一个哨兵位
	assert(phead->next != phead);
	ListNode* del = phead->pre;
	//删除节点的前驱指针
	del->pre->next = phead;
	//phead的前驱指针
	phead->pre = del->pre;
}

void ListPopFront(ListNode* phead)
{
	assert(phead && phead->next != phead);
	ListNode* del = phead->next;

	del->next->pre = phead;
	phead->next = del->next;

	free(del);
	del = NULL;

}

ListNode* ListFind(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

void ListInsert(ListNode* pos, ListDataType x)
{
	assert(pos);
	ListNode* node= ListBuyNode(x);
	//先处理node
	node->next = pos;
	node->pre = pos->pre;
	//在处理pos->pre和pos
	pos->pre->next= node;
	node->next->pre = node;

}

void ListErase(ListNode* pos)
{
	assert(pos);
	pos->next->pre = pos->pre;
	pos->pre->next = pos->next;
	free(pos);
	pos = NULL;
}

void ListInsertAfter(ListNode* pos, ListDataType x)
{
	assert(pos);
	ListNode* node = ListBuyNode(x);
	//node的prev 和 next
	node->next = pos->next;
	node->pre = pos;

	//pos的next 和 pos->next的prev
	pos->next = node;
	node->next->pre = node;
}


3.3 test.c

#include"List.h"

void test1()
{
	ListNode* plist = ListInit();
}

void test2()
{
	ListNode* plist = ListInit();;
	ListPushBack(plist, 1);
	ListPushBack(plist, 4);
	ListPushBack(plist, 7);

	ListPrint(plist);  //1->4->7->
	ListDestory(plist);
}

void test3()
{
	ListNode* plist = ListInit();;
	ListPushFront(plist, 1);
	ListPushFront(plist, 4);
	ListPushFront(plist, 7);

	ListPrint(plist);//7->4->1->
	ListDestory(plist);
}

void test4()
{
	ListNode* plist = ListInit();;
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	ListPushBack(plist, 6);
	ListPushBack(plist, 7);
	ListPushBack(plist, 8);
	ListPushBack(plist, 9);
	ListPrint(plist);
	printf("\n");

	ListPopBack(plist);
	ListPopBack(plist);
	ListPopFront(plist);
	ListPopFront(plist);

	ListPrint(plist);
	ListDestory(plist);
}

void test5()
{
	ListNode* plist = ListInit();;
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	ListPrint(plist);
	printf("\n");

	//测试指定位置
	ListNode* Find1 = ListFind(plist, 2);
	ListNode* Find2 = ListFind(plist, 4);
	ListInsert(Find1, 10);
	ListInsertAfter(Find2, 20);
	ListPrint(plist);
	printf("\n");

	ListErase(Find1);
	ListPrint(plist);
	ListDestory(plist);
}

int main()
{
	//test1();
	//test2();
	//test3();
	//test4();
	test5();


	return 0;
}

笔者在删除,插入数据时,画好图后,也好了代码,但是在调试中多次出现代码位置出错,导致写的代码的含义不符合预期.
所以说思路一定要清晰,多多检查调试

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

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

相关文章

博士论文查重【保姆教程】

大家好&#xff0c;今天来聊聊博士论文查重&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 博士论文查重&#xff1a;确保学术诚信的重要环节 引言 博士论文是博士研究生学术研究成果的重要体现&#x…

每日一练2023.12.9—— 矩阵A乘以B【PTA】

题目链接&#xff1a;L1-048 矩阵A乘以B 题目要求&#xff1a; 给定两个矩阵A和B&#xff0c;要求你计算它们的乘积矩阵AB。需要注意的是&#xff0c;只有规模匹配的矩阵才可以相乘。即若A有Ra​行、Ca​列&#xff0c;B有Rb​行、Cb​列&#xff0c;则只有Ca​与Rb​相等时&a…

RTMP流设置超时时间失败

使用FFmpeg(版本是5.0.3&#xff09;将rtmp流作为输入&#xff0c;设置超时时间&#xff08;使用-timeout参数&#xff09;&#xff0c;结果报错&#xff1a;Cannot open Connection tcp://XXX:1935?listen&listen_timeout 通过./ffmpeg -help full 命令查看FFmpeg帮助&am…

js判断是否对象自身为空

文章目录 一、前言二、JSON.stringify三、for in 配合 hasOwnProperty四、Object.keys五、Object.getOwnPropertyNames六、Object.getOwnPropertyNames 结合 Object.getOwnPropertySymbols七、Reflect.ownKeys八、最后 一、前言 如何判断一个对象为空&#xff1f; 先上结论&a…

golang开发之个微机器人的二次开发

简要描述&#xff1a; 下载消息中的文件 请求URL&#xff1a; http://域名地址/getMsgFile 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型…

一文读懂:如何利用JMeter轻松提升系统性能

jemter是一款开源的性能测试工具、纯属记录&#xff0c;方便回忆使用 &#xff08;一&#xff09;、创建线程组 右键添加一个http请求 如果你想学习自动化测试&#xff0c;我这边给你推荐一套视频&#xff0c;这个视频可以说是B站播放全网第一的自动化测试教程&#xff0c;同…

gitbash下载安装

参考教程 零、下载 官网地址 2.43.0win64 链接&#xff1a;https://pan.baidu.com/s/16urs_nmky7j20-qNzUTTkg 提取码&#xff1a;7jaq 一、安装 图标组件&#xff08;Additional icons&#xff09;&#xff1a;选择是否创建桌面快捷方式&#xff1b;桌面浏览&#xff08;Win…

spring cloud 修改bootstrap文件的文件名

前言 spring boot 2.1.2.RELEASE spring cloud 默认的启动文件 spring cloud 默认的启动文件为 bootstrap.yml 修改bootstrap文件的文件名 添加参数 --spring.config.locationclasspath:bootstrap.yml或者 --spring.cloud.bootstrap.locationclasspath:bootstrap.yml还可…

JUC包(面试常问)

1. Callable接口 类似于Runnable接口&#xff0c;Runnable描述的任务&#xff0c;不带返回值&#xff1b;Callable描述的任务带返回值。 public class Test {//创建线程&#xff0c;计算12...1000public static void main(String[] args) throws ExecutionException, Interru…

西南科技大学数字电子技术实验三(MSI逻辑器件设计组合逻辑电路及FPGA的实现)FPGA部分

一、实验目的 进一步掌握MIS(中规模集成电路)设计方法。通过用MIS译码器、数据选择器实现电路功能,熟悉它们的应用。进一步学习如何记录实验中遇到的问题及解决方法。二、实验原理 1、4位奇偶校验器 Y=S7i=0DiMi D0=D3=D5=D6=D D1=D2=D4=D7= `D 2、组合逻辑电路 F=A`B C …

Python爬虫-实现批量抓取王者荣耀皮肤图片并保存到本地

前言 本文是该专栏的第12篇,后面会持续分享python爬虫案例干货,记得关注。 本文以王者荣耀的英雄皮肤为例,用python实现批量抓取“全部英雄”的皮肤图片,并将图片“批量保存”到本地。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。注意,这里抓取的图片…

97基于matlab的改进的带记忆的模拟退火算法求解TSP问题

基于matlab的改进的带记忆的模拟退火算法求解TSP问题&#xff0c;采用多普勒型降温曲线描述迭代过程&#xff0c;在传统算法的基础上增加记忆功能&#xff0c;可测试中国31/64/144以及att48城市的数据&#xff0c;也可自行输入数据进行测试&#xff0c;测试结果基本达到当前最优…

IBM Qiskit量子机器学习速成(六)

量子卷积神经网络 卷积和池化&#xff1a;卷积神经网络的必备成分 卷积神经网络被广泛应用于图像和音频的识别当中&#xff0c;关键在于“卷积”操作赋予神经网络统筹学习数据的能力。 执行卷积操作需要输入数据与卷积核&#xff0c;卷积核首先与输入数据左上角对齐&#xf…

法律服务网站建设效果如何

律师事务所及法律知识咨询机构等往往是众多人群需求的服务&#xff0c;服务多样化及内容多元化&#xff0c;市场中也有大量品牌&#xff0c;在实际消费服务中大多以本地事务所为主&#xff0c;而线上咨询服务则一般没有区域限制&#xff0c;同行增多及人们知识获取渠道增加&…

uniapp微信小程序分包,小程序分包

前言&#xff0c;都知道我是一个后端开发、所以今天来写一下uniapp。 起因是美工给我的切图太大&#xff0c;微信小程序不让了&#xff0c;在网上找了一大堆分包的文章&#xff0c;我心思我照着写的啊&#xff0c;怎么就一直报错呢&#xff1f; 错误原因 tabBar的页面被我放在分…

Windows11安装使用Oracle21C详细步骤<图文保姆级>新版本

Windows11安装使用Oracle21C详细步骤<图文保姆级>新版本 Database Software Downloads | Oracle 中国 下载完成后解压缩 双击setup.exe 打开安装页面 同意下一步 更改自己的路径点击下一步 输入密码 下一步安装等待即可 等待加载配置时间有点久 完成即可 使用 搜索…

Java TCP(一对一)聊天简易版

客户端 import java.io.*; import java.net.Socket; import java.util.Date; import javax.swing.*;public class MyClient {private JFrame jf;private JButton jBsend;private JTextArea jTAcontent;private JTextField jText;private JLabel JLcontent;private Date data;p…

【概率论】MCMC 之 Gibbs 采样

上一篇文章讲到&#xff0c;MCMC 中的 HM 算法&#xff0c;它可以解决拒绝采样效率低的问题&#xff0c;但是实际上&#xff0c;当维度高的时候 HM 算法还是在同时处理多个维度&#xff0c;以两个变量 x [ x , y ] \mathbf{x} [x,y] x[x,y] 来说&#xff0c;也就是同时从联合…

【11】Qt Designer

目录 VSCode添加外部工具 QtDesigner PyUIC PyRCC 加载UI文件模板代码 QMainWindow QWidget 常用知识点 1. 修改标题图标 2. 图片资源管理 3. 图片按钮 4. 加载对话框 5. 动态加载Widget 6. 修改主题 其他注意事项 事件被多次触发 PyQt5提供了一个可视化图形工…

为什么微服务需要 API 网关?

本文我们主要讲解为什么微服务需要 API 网关。 对网关我们并不陌生&#xff0c;网关的概念来源于计算机网络&#xff0c;表示不同网络之间的关口。在系统设计中&#xff0c;网关也是一个重要的角色&#xff0c;其中最典型的是各大公司的开放平台&#xff0c;开放平台类网关是企…