当前位置: 首页 > news >正文

【C语言】实现双向链表


目录

(一)头文件

(二) 功能实现

(1)初始化

 (2)打印链表

(3) 头插与头删

(4)尾插与尾删

(5)指定位置之后插入

(6)删除指定位置的数据

(7)链表的销毁


 

正文开始:

        在实际应用中,常用的双向链表是双向带头循环链表,本文考虑到实际应用,目的也是实现双向带头循环链表。

(一)头文件

        命名"List.h"

        本文不加解释的给出头文件,按照头文件来实现双向链表的基本功能:包括打印链表,链表的初始化,头插与头删,尾插与尾删,在指定位置之后插入数据,删除指定位置的数据,链表的销毁等共九个功能。

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//链表的数据类型
typedef int LTtype;
//双向链表的节点
typedef struct ListNode
{LTtype data;struct ListNode* prev;struct ListNode* next;
}LTNode;//双向链表带有哨兵位,插入数据之前先插入哨兵位
//初始化》传入头节点
void LTInit(LTNode** pphead);//初始化2>返回头节点
LTNode* LtInit0();//销毁链表
void LTDestory(LTNode** pphead);//尾插
void LTtail_push(LTNode* phead,LTtype x);//头插
//是在头节点之后插入,在头节点之前插入等价于尾插
void LThead_push(LTNode* phead,LTtype x);//打印便于调试
void Print(LTNode* phead);//头删
void LTpophead(LTNode* phead);//尾删
void LTpoptail(LTNode* phead);//查找
//为了找到pos位置
LTNode* LTfind(LTNode* phead, LTtype x);//在pos之后插入数据
void LTinsert(LTNode* pos, LTtype x);//删除pos处的数据
void LTerase(LTNode* pos);

(二) 功能实现

        带头相对于不带头有诸多优势:

带头与不带头链表:

        带头链表是指在链表的开头增加一个额外的节点,该节点不存储具体的数据,仅用于辅助操作链表。带头链表的带头节点可以简化链表的操作,提高代码的可读性和可维护性。

        意义:

        带头节点可以避免链表为空的特殊情况处理。在没有带头节点的链表中,如果链表为空,添加、删除等操作都需要额外的处理逻辑。而带头节点的链表中,带头节点一直存在,无论链表是否为空,操作逻辑都是一致的,可以减少代码的复杂性。

        局限:

        造成额外的空间浪费。

(1)初始化

         初始化有两种方法,具体实现如下:

传址初始化

        初始化传递链表的地址(二级指针【通过传址,将形参与实参建立联系】),初始化要插入头指针哨兵位(不保存有效的数据),便于简化代码逻辑。

//双向链表带有哨兵位,插入数据之前先插入哨兵位
void LTInit(LTNode** pphead)
{*pphead = (LTNode*)malloc(sizeof(LTNode));if (*pphead == NULL){perror("malloc");exit(1);}(*pphead)->data = -1;(*pphead)->next = (*pphead)->prev = *pphead;
}

接受返回值初始化

        在函数内申请头节点,最后返回到主函数内:

//初始化2>返回头节点
LTNode* LtInit0()
{LTNode* phead = (LTNode*)malloc(sizeof(LTNode));if (phead == NULL){perror("malloc");exit(1);}phead->data = -1;phead->next = phead->prev = phead;return phead;
}

 (2)打印链表

        以便于调试,理解代码;

        与打印单链表一样,不同的是:while的跳出条件是 pcur != phead;


//打印链表
void Print(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}

(3) 头插与头删

        插入操作需要申请新节点:


//获取新节点
LTNode* LTbuynewnode(LTtype x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc");exit(1);}newnode->data = x;newnode->next = newnode->prev = NULL;return newnode;
}

 

        头插与头删

         头插,是在头节点之后插入,在头节点之前插入等价于尾插;

         先对新节点进行操作,因为新节点与原节点没有联系,不会因为对指针进行操作而丢失节点,造成数据丢失和内存泄漏;

        我们可以直接得到phead,因此先让phead的下一个节点的前驱指针指向newnode,再改变phead的next指针;(如果反过来,先改变phead的next指向newnode,此时newnode后面的节点就找不到了。)


//头插,是在头节点之后插入,在头节点之前插入等价于尾插
void LThead_push(LTNode* phead, LTtype x)
{assert(phead);LTNode* newnode = LTbuynewnode(x);//先对新节点进行操作newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;}

头删

        断言传过来的指针不为空,链表不为空,通过assert实现;

        通过创建del指针(要被删除的节点),保存旧头节点;

        通过创建next指针,保存旧头节点的next节点;

        这样命名后进行头删,逻辑清晰,不易出错;


//头删
void LTpophead(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* del = phead->next;//被删除的节点LTNode* next = del->next;//del的后置节点phead->next = next;next->prev = phead;free(del);del = NULL;
}

 

(4)尾插与尾删

尾插

        与头插的逻辑完全相同


//尾插
void LTtail_push(LTNode* phead, LTtype x)
{assert(phead);LTNode* newnode = LTbuynewnode(x);newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}

尾删

        与头删的逻辑完全相同


//尾删
void LTpoptail(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* del = phead->prev;//被删除的节点LTNode* prev = del->prev;//del的前置节点prev->next = phead;phead->prev = prev;free(del);del = NULL;
}

 

(5)指定位置之后插入

查找

         查找数据值为x的节点,找到返回此节点,找不到返回NULL;


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

 

 在指定位置之后插入数据

        根据find找到的pos位置,申请新节点,插入到pos之后


//在pos位置之后插入数据
void LTinsert(LTNode* pos,LTtype x)
{assert(pos);LTNode* newnode = LTbuynewnode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}

 

(6)删除指定位置的数据

         删除指定位置的数据,需要的指针有pos的前驱节点,pos节点。


//删除pos处的数据
void LTerase(LTNode* pos)
{assert(pos);LTNode* prev = pos->prev;LTNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);pos = NULL;
}

 

(7)链表的销毁

        链表的销毁共有两种方法:

传址销毁


//销毁链表
void LTDestory(LTNode** pphead)
{assert(pphead);//哨兵位不为空assert(*pphead);LTNode* pcur = (*pphead)->next;while (pcur != *pphead){LTNode* next = pcur->next;free(pcur);pcur = next;}//释放哨兵位free(pphead);//此时可以改变哨兵位的pphead = NULL;}

 传值销毁

        一级指针phead传递的是值,不是phead的地址,所以在函数中改变phead并不会改变phead的实际值
        函数返回后,仍需要手动phead = NULL;


//推荐使用一级,为了保持接口的一致性
void LTDestory0(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;//无效的置空,当做是习惯
}
//一级指针phead传递的是值,不是phead的地址,所以在函数中改变phead并不会改变phead的实际值
//函数返回后,仍需要手动phead = NULL;

 

完~

未经作者同意禁止转载 

相关文章:

【C语言】实现双向链表

目录 &#xff08;一&#xff09;头文件 &#xff08;二&#xff09; 功能实现 &#xff08;1&#xff09;初始化 &#xff08;2&#xff09;打印链表 &#xff08;3&#xff09; 头插与头删 &#xff08;4&#xff09;尾插与尾删 &#xff08;5&#xff09;指定位置之后…...

Python操作MySQL基础

除了使用图形化工具以外&#xff0c;我们也可以使用编程语言来执行SQL从而操作数据库。在Python中&#xff0c;使用第三方库: pymysql来完成对MySQL数据库的操作。 安装第三方库pymysql 使用命令行,进入cmd&#xff0c;输入命令pip install pymysql. 创建到MySQL的数据库连接…...

【数学建模】【2024年】【第40届】【MCM/ICM】【E题 财产保险的可持续性】【解题思路】

一、题目 &#xff08;一&#xff09; 赛题原文 2024 ICM Problem E: Sustainability of Property Insurance Extreme-weather events are becoming a crisis for property owners and insurers. The world has endured “more than $1 trillion in damages from more than …...

SpringCloud--Eureka注册中心服务搭建注册以及服务发现

注意springboot以及springcloud版本&#xff0c;可能有莫名其妙的错误&#xff0c;这里使用的是springboot-2.6.13&#xff0c;springcloud-2021.0.5 一&#xff0c;Eureka-Server搭建&#xff1a; 1.创建项目&#xff1a;引入依赖 <dependency><groupId>org.sp…...

ansible shell模块 可以用来使用shell 命令 支持管道符 shell 模块和 command 模块的区别

这里写目录标题 说明shell模块用法shell 模块和 command 模块的区别 说明 shell模块可以在远程主机上调用shell解释器运行命令&#xff0c;支持shell的各种功能&#xff0c;例如管道等 shell模块用法 ansible slave -m shell -a cat /etc/passwd | grep root # 可以使用管道…...

qss的使用

参考&#xff1a;qss样式表笔记大全(二)&#xff1a;可设置样式的窗口部件列表&#xff08;上&#xff09;&#xff08;持续更新示例&#xff09;_51CTO博客_qss样式...

archlinux 使用 electron-ssr 代理 socks5

提前下载好 pacman 包 https://github.com/shadowsocksrr/electron-ssr/releases/download/v0.2.7/electron-ssr-0.2.7.pacman 首先要有 yay 和 aur 源&#xff0c;这个可以参考我之前的博客 虚拟机内使用 archinstall 安装 arch linux 2024.01.01 安装依赖 yay 安装的&#…...

macos安装local模式spark

文章目录 配置说明安装hadoop安装Spark测试安装成功 配置说明 Scala - 3.18 Spark - 3.5.0 Hadoop - 3.3.6 安装hadoop 从这里下载相应版本的hadoop下载后解压&#xff0c;配置系统环境变量 > sudo vim /etc/profile添加以下两行 export HADOOP_HOME/Users/collinsliu/…...

机器学习算法之支持向量机(SVM)

SVM恐怕大家即使不熟悉&#xff0c;也听说过这个大名吧&#xff0c;这一节我们就介绍这相爱相杀一段内容。 前言&#xff1a;在介绍一个新内容之SVM前&#xff0c;我们不觉映入眼帘的问题是为什么要引入SVM&#xff1f;吃的香&#xff0c;睡的着的情况下&#xff0c;肯定不会是…...

线性判别分析(LDA)

一、说明 LDA 是一种监督降维和分类技术。其主要目的是查找最能分隔数据集中两个或多个类的特征的线性组合。LDA 的主要目标是找到一个较低维度的子空间&#xff0c;该子空间可以最大限度地区分不同类别&#xff0c;同时保留与歧视相关的信息。 LDA 是受监督的&#xff0c;这意…...

Vue 前置导航

Vue 前置导航&#xff08;Vue Front Navigation&#xff09;是一种在 Vue.js 框架中实现导航功能的常见方式。它通常用于构建单页应用程序&#xff08;Single Page Application&#xff09;&#xff0c;通过在页面顶部或侧边栏显示导航菜单&#xff0c;使用户能够轻松切换到不同…...

串行通信,并行通信,波特率,全双工,半双工,单工等通信概念

串行通信&#xff1a; 只使用一根线来进行数据发送或者是接收&#xff0c;串行通信传输数据是一位一位进行传输 并行通信&#xff1a; 使用多跟线进行数据的发送和接收&#xff0c;并行通信可以一次传输多个数据位 波特率&#xff1a; 每秒传输数据的位数&#xff0c;决定…...

鸿蒙系统进一步学习(一):学习资料总结,少走弯路

随着鸿蒙Next的计划越来越近&#xff0c;笔者之前的鸿蒙系统扫盲系列中&#xff0c;有很多朋友给我留言&#xff0c;不同的角度的问了一些问题&#xff0c;我明显感觉到一点&#xff0c;那就是许多人参与鸿蒙开发&#xff0c;但是又不知道从哪里下手&#xff0c;因为资料太多&a…...

异步复位同步释放原则

复位信号有一个非常重要的原则&#xff0c;叫作异步复位同步释放原则。异步复位指一个寄存器的复位信号随时可以复位&#xff0c;不必考虑该寄存器的时钟信号正处在哪个相位上。同步释放是指一个寄存器的复位信号从复位态回到释放态的时机&#xff0c;必须与该寄存器的时钟信号…...

M1 Mac使用SquareLine-Studio进行LVGL开发

背景 使用Gui-Guider开发遇到一些问题&#xff0c;比如组件不全。使用LVGL官方的设计软件开发 延续上一篇使用的基本环境。 LVGL项目 新建项目 选择Arduino的项目&#xff0c;设定好分辨率及颜色。 设计UI 导出代码 Export -> Create Template Project 导出文件如图…...

web3知识体系汇总

web3.0知识体系 1.行业发展 2. web3的特点&#xff1a; 1、统一身份认证系统 2、数据确权与授权 3、隐私保护与抗审查 4、去中心化运行 Web3.0思维技术思维✖金融思维✖社群思维✖产业思维”&#xff0c;才能从容理解未来Web3.0时代的大趋势。 3.技术栈 Web3.jsSolidit…...

服务器与电脑的区别?

目录 一、什么是服务器 二、什么是电脑 三、服务器和电脑的区别 一、什么是服务器 服务器是指一种专门提供计算和存储资源、运行特定软件服务的物理或虚拟计算机。服务器主要用于接受和处理来自客户端&#xff08;如个人电脑、手机等&#xff09;的请求&#xff0c;并向客户…...

结束 代码随想录 链表章节(下一张

环形链表II 首先&#xff0c;先判断有没有环&#xff0c;像物理相对速度一样 只要 相对速度为1 那么快指针绝对会在环里追上慢指针&#xff0c;最后x 和z 的距离其实最后两个index总会相遇&#xff0c;相遇的点就是入口 class Solution { public:ListNode *detectCycle(List…...

re:从0开始的CSS学习之路 6. 字体相关属性

1. 字体相关属性 font-size 字体大小 font-family 字体的系列&#xff08;字体簇&#xff09; 可以设置多个字体&#xff0c;每个字体之间以逗号隔开 设置多个字体的目的是为了用户尽可能的支持字体 网页字体的五大类&#xff1a; serif 衬线字体 sans-serif 非衬线字体 monos…...

FPGA(基于xilinx)中PCIe介绍以及IP核XDMA的使用

Xilinx中PCIe简介以及IP核XDMA的使用 例如&#xff1a;第一章 PCIe简介以及IP核的使用 文章目录 Xilinx中PCIe简介以及IP核XDMA的使用一、PCIe总线概述1.PCIe 总线架构2.PCIe 不同版本的性能指标及带宽计算3.PCIe 接口信号 二、XDMA1.XDMA 与其它 PCIe IP 的区别2.XDMA简介 三…...

【笔记】旧AI,新人类

AI擅长"旧"&#xff0c;人类擅长"新" 关于人机分工的一点思考 不久前&#xff0c;一场颇具戏剧性的"人机对决"在餐饮界引起了不小的波澜。"美膳狮"智能炒菜机器人与湘菜厨师杨孙同台竞技&#xff0c;共同炒制三道菜&#xff1a;XO酱笋…...

别再只调库了!手写KNN算法识别MNIST数字,从距离计算到加权投票的完整实现与性能对比

从零构建KNN算法&#xff1a;MNIST手写数字识别的底层实现与深度优化 在机器学习入门阶段&#xff0c;K最近邻&#xff08;KNN&#xff09;算法往往是第一个接触的经典分类方法。大多数教程止步于调用sklearn的几行代码&#xff0c;却忽略了算法底层的精妙设计。本文将带您从数…...

3种高级策略突破AI编辑器限制:Cursor Pro逆向工程技术解析

3种高级策略突破AI编辑器限制&#xff1a;Cursor Pro逆向工程技术解析 【免费下载链接】cursor-free-vip [Support 0.45]&#xff08;Multi Language 多语言&#xff09;自动注册 Cursor Ai &#xff0c;自动重置机器ID &#xff0c; 免费升级使用Pro 功能: Youve reached your…...

Perplexity实时新闻查询失效真相:Webhook劫持、缓存穿透与CDN时钟漂移三重陷阱

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;Perplexity实时新闻查询失效真相&#xff1a;Webhook劫持、缓存穿透与CDN时钟漂移三重陷阱 Perplexity 的实时新闻查询功能近期频繁返回陈旧或空结果&#xff0c;表面看是 API 延迟&#xff0c;实则深陷 Webh…...

UVM验证中的迭代模式:从寄存器遍历到配置组合的实战应用

1. 项目概述&#xff1a;为什么要在UVM中谈迭代模式&#xff1f;如果你做过芯片验证&#xff0c;尤其是用SystemVerilog和UVM搭过测试平台&#xff0c;那你肯定对“遍历”这个概念不陌生。比如&#xff0c;你需要检查一个存储阵列里每一个地址的读写是否正确&#xff0c;或者需…...

2026年便利店成交金额究竟要达到多少,才能摆脱亏损困境?

在便利店行业竞争日益激烈的当下&#xff0c;众多便利店品牌都在为实现盈利而努力。美喜福作为便利店行业的一员&#xff0c;在这一背景下有着独特的发展路径和潜力。那么&#xff0c;2026年便利店成交金额究竟要达到多少才能摆脱亏损困境呢&#xff1f;让我们结合美喜福的实际…...

别再手动改端口了!用这个OrCAD小补丁,3分钟搞定原理图端口标准化

告别混乱设计&#xff1a;OrCAD端口标准化高效解决方案 在复杂的电子设计项目中&#xff0c;原理图的整洁与规范程度直接影响着团队协作效率和后期维护成本。当多位工程师共同参与同一项目时&#xff0c;端口类型和朝向的不统一往往成为困扰PCB设计团队的常见问题。这种看似微小…...

从CTF靶场到实战:手把手教你复现ctfshow web3的PHP伪协议利用(附BurpSuite抓包技巧)

从CTF靶场到实战&#xff1a;深入解析PHP伪协议利用与BurpSuite实战技巧 在网络安全领域&#xff0c;CTF比赛不仅是检验技能的竞技场&#xff0c;更是学习实战渗透技术的绝佳资源。ctfshow web3这道题目巧妙地将PHP伪协议利用与文件包含漏洞结合在一起&#xff0c;为我们提供了…...

Synopsys ICC 2016环境变量配置详解:从.bashrc编辑到license启动的保姆级步骤

Synopsys ICC 2016环境变量配置全流程实战指南 当你第一次打开Synopsys ICC 2016却遭遇"Command not found"时&#xff0c;90%的问题都源于环境变量配置不当。作为芯片设计领域的工业级工具链&#xff0c;正确的环境配置不仅是运行的先决条件&#xff0c;更是后续所有…...

别再傻傻分不清了!给硬件工程师的SI、PI、EMI关系速查手册(附高频PCB设计实例)

硬件工程师实战指南&#xff1a;SI、PI、EMI的三角关系与高频PCB设计避坑 当你第一次面对DDR4布线导致的EMI测试失败时&#xff0c;可能会陷入这样的困惑&#xff1a;明明是信号完整性问题&#xff0c;为什么整改方案却是调整电源层的去耦电容&#xff1f;这种看似跨领域的因果…...