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

文章目录

  • 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赋值给curListNode* 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;//再处理原链表最后一个节点和pheadphead->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);//先处理nodenode->next = pos;node->pre = pos->pre;//在处理pos->pre和pospos->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 和 nextnode->next = pos->next;node->pre = pos;//pos的next 和 pos->next的prevpos->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赋值给curListNode* 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;//再处理原链表最后一个节点和pheadphead->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);//先处理nodenode->next = pos;node->pre = pos->pre;//在处理pos->pre和pospos->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 和 nextnode->next = pos->next;node->pre = pos;//pos的next 和 pos->next的prevpos->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.mzph.cn/news/211635.shtml

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

相关文章

键盘打字盲打练习系列之矫正坐姿——4

一.欢迎来到我的酒馆 盲打&#xff0c;矫正坐姿&#xff01; 目录 一.欢迎来到我的酒馆二.继续练习二.矫正坐姿1.键鼠快速选购指南2.椅子快速选购指南 三.改善坐姿建议 二.继续练习 前面的章节&#xff0c;我们重点向大家介绍了主键盘区指法和键盘键位。经过一个系列的教程学习…

Mybatis环境搭建

1、开发环境 IDE&#xff1a;IntelliJ IDEA 2022.2.1 (Ultimate Edition) 构建工具&#xff1a;maven 3.6.1 MySQL版本&#xff1a;MySQL 5.7 MyBatis版本&#xff1a;MyBatis 3.5.14 2、工程创建 创建一个Maven工程giser-java-mybatis-demo 基础依赖如下&#xff1a; &…

用友U8 Cloud 多处反序列化RCE漏洞复现

0x01 产品简介 用友U8 Cloud是用友推出的新一代云ERP,主要聚焦成长型、创新型企业,提供企业级云ERP整体解决方案。 0x02 漏洞概述 用友U8 Cloud存在多处(TableInputOperServlet、LoginServlet 、FileTransportServlet、CacheInvokeServlet、ActionHandlerServlet、Servle…

【rabbitMQ】springboot整合rabbitMQ模拟简单收发消息

目录 1.创建项目和模块 2.添加rabbitMQ依赖 3.启动rabbitMQ服务 4.引入rabbitMQ服务端信息 5.通过单元测试模拟业务发送消息 6. 接收消息 1.创建项目和模块 2.添加rabbitMQ依赖 <!-- rabbitmq依赖--> <dependency> <groupId>org.sp…

JavaEE 09 锁策略

1.锁策略 1.1 乐观锁与悲观锁 其实前三个锁是同一种锁,只是站在不同的角度上去进行描述,此处的乐观与悲观其实是指在预测的角度上看会发生锁竞争的概率大小,概率大的则是悲观锁,概率小的则是乐观锁 乐观锁在加锁的时候就会做较少的事情,加锁的速度较快,但是消耗的cpu资源等也会…

排序算法-选择/堆排序(C语言)

1基本思想&#xff1a; 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的 数据元素排完 。 2 直接选择排序: 在元素集合 array[i]--array[n-1] 中选择关键码最大 ( 小 ) 的数据元素…

PHP基础 - 数组遍历与排序

介绍 在PHP中,数组遍历和排序是常见的操作,用于对数组中的元素进行访问和排序 数组遍历 1)数值数组的遍历 使用 foreach 循环遍历数组:foreach 循环是最常用的遍历数组的方法,它可以遍历索引数组和关联数组。例如:$fruits = array("apple", "banana&q…

AG1KLPQ48 User Manual

1.&#xff09;软件安装&#xff1a; 解压缩或执行安装文件&#xff0c;安装 Supra 软件。执行文件为 bin 目录中的 Supra.exe。 运行 Supra&#xff0c;选择菜单 File -> Import license&#xff0c;选择 license 文件并导入 License。 2.&#xff09;新建项目&#xff1a;…

Elaticsearch 学习笔记

文章目录 Elaticsearch 学习笔记一、什么是 Elaticsearch &#xff1f;二、Elaticsearch 安装1 es 安装2 问题解决3 数据格式 三、索引操作1 PUT 请求&#xff1a;在postman中&#xff0c;向 ES 服务器发 PUT 请求&#xff08;PUT请求相当于创建的意思&#xff09;2 GET 请求&a…

Base64编码解码

一、Base64编码技术简介 Base64编码是一种广泛应用于网络传输和数据存储的编码方式。它将原始数据转换为可打印的字符形式&#xff0c;以便于传输和存储。Base64编码后的数据长度是原始数据长度的约3/4&#xff0c;具有一定的压缩效果。 Base64编码解码 -- 一个覆盖广泛主题工…

【trino权威指南】使用trino详解:trino client安装、查询sql、DBeaver连接trino、java通过JDBC连接trino

文章目录 一. Trino CLI1. 安装client2. 使用client执行sql 二. JDBC driver 连接Trino1. 通过DBeaver用户界面连接2. JDBC Driver in java2.1. 环境配置2.2. 注册和配置driver2.3. 连接参数2.4. 查询例子 一. Trino CLI 1. 安装client Trino CLI提供了一个基于终端的交互式s…

上海交通大学生存手册PDF

强烈推荐所有大学生去阅读《上海交通大学生存手册》。虽然它可能有些冗长&#xff0c;但非常重要&#xff0c;因为它道出了大学教育的本质。 如果几年前我能够看到这本书&#xff0c;也许我的大学生活会有所不同。现在我将向正在上大学或者将要上大学的你推荐这本书。 无论你…

通过虚拟机安装Open5GS 和UERANSIM记录

目录 wsl虚拟环境尝试失败 step1 安装wsl: step2下载Ubuntu 20.04.6 LTS: step3升级wsl&#xff1a; step4生成用户: step5 linux下安装软件需要的镜像&#xff1a; step6 安装图形界面xfce和浏览器&#xff1a; step6 安装chrome virtual box安装ubuntu step7&#xf…

AWS攻略——Peering连接VPC

文章目录 创建IP/CIDR不覆盖的VPC创建VPC创建子网创建密钥对创建EC2 创建Peering接受Peering邀请修改各个VPC的路由表修改美东us-east-1 pulic subnet的路由修改悉尼ap-southeast-2路由 测试知识点 我们回顾下《AWS攻略——VPC初识》中的知识&#xff1a; 一个VPC只能设置在一…

Android引用SDK包实现高德地图展示

一、准备工作 注册高德地图开放平台 注册过程我就不多说了&#xff0c;挺简单的&#xff0c;需要登录&#xff0c;然后注册成为开发者&#xff0c;还需要支付宝认证、手机号码验证、邮箱验证挺多的&#xff0c;但是速度很快。基本上随时验证随时注册成功。新建应用新建…

重点车辆安全监测预警技术方案

目录 1.系统架构 2.详细设计 2.1驾驶员信息监控 2.1.1驾驶员基本信息管理 2.1.2人车匹配信息 2.1.3驾驶员在线状态管理 2.2车辆状态信息管理 2.2.1车辆信息管理 2.1.2车辆在路状态管理 2.3重点车辆安全监测预警系统云平台 2.3.1云平台需求分析 2.3.2 设计思想 2.4.…

urllib 异常、cookie、handler及代理(四)

目录 一、urllib异常 二、urllib cookie登录 三、urllib handler 处理器的基本使用 四、urllib 代理和代理池 参考 一、urllib异常 URLError/HTTPError 简介&#xff1a; 1.HTTPError类是URLError类的子类 2.导入的包urllib.error.HTTPError urllib.error.URLError 3.h…

20道计算机网络面试题

网络分层 1、说说OSI 七层、TCP/IP 四层的关系和区别&#xff1f; OSI 七层从下往上依次是&#xff1a;物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。一张图给你整明白&#xff1a; TCP/IP 四层从下往上依次是&#xff1a;网络接口层、网络层、传输层、应用…

MATLAB - 评估拟合优度、评价拟合效果

系列文章目录 文章目录 系列文章目录前言一、如何评估拟合优度二、拟合优度统计2.1 SSE - 误差引起的平方和2.2 R 平方2.3 自由度调整 R 平方2.4 均方根误差 三、MATLAB - 评估曲线拟合度3.1 加载数据并拟合多项式曲线3.2 绘制拟合方程、数据、残差和预测范围图3.3 评估指定点3…

java--Object

1.Object类的作用 Object类是java中所有类的祖宗类&#xff0c;因此&#xff0c;java中所有类的对象都可以直接使用Object类中提供一些方法 2.Object类的常见方法 ①toString存在的意义&#xff1a;toString()方法存在的意义就是为了被子类重写&#xff0c;以便返回对象具体的…