数据结构:双向链表的实现(C实现)

在这里插入图片描述

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》

文章目录

    • 前言
  • 一、实现思路
    • 1.节点的结构(ListNode)
    • 2.新节点的创建(BuyListNode)
    • 3.头结点的创建(ListCreate)
    • 4.双向链表的销毁(ListDestroy)
    • 5.双向链表的打印(ListPrint)
    • 6.双向链表的尾插(ListPushBack)
    • 7.双向链表的尾删(ListPopBack)
    • 8.双向链表的头插(ListPushFront)
    • 9.双向链表的头删(ListPopFront)
    • 10.双向链表的查找(ListFind)
    • 11.双向链表在pos前插入(ListInsert)
    • 12.双向链表删除pos位置处的节点(ListErase)
  • 二、代码实现
  • 总结


前言

本篇博客,将要实现的是带头双向循环链表,该结构实现尾插,尾删只需要O(1)的时间复杂度。
其结构如下所示:

在这里插入图片描述


一、实现思路

1.节点的结构(ListNode)

既然要实现的链表是双向循环的,那么对于指针域,我们就需要指向节点上一个的指针指向节点下一个的指针。至于双向循环,我们只需要尾节点的next指向头结点,头结点的prev指向尾节点,即可实现双向循环。
如下:
在这里插入图片描述

typedef int LTDataType;//方便以后修改数据类型typedef struct ListNode
{LTDataType data;struct ListNode* next;struct ListNode* prev;
}ListNode;

2.新节点的创建(BuyListNode)

动态开辟一块空间,使该节点的prev,next都指向自己(方便头结构的创建),再为data赋值,返回该空间地址。

在这里插入图片描述

//创建新节点
ListNode* BuyListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc");exit(-1);}newnode->data = x;newnode->next = newnode;newnode->prev = newnode;return newnode;
}

3.头结点的创建(ListCreate)

复用BuyListNode函数,使头结点的data为无效数据(这里即为-1)。

//创建返回链表的头结点
ListNode* ListCreate()
{return BuyListNode(-1);
}

4.双向链表的销毁(ListDestroy)

要销毁链表,我们就要遍历链表,那么如何遍历链表?

  • 以遍历到头节点为结束条件
  • 从头结点的下一个节点开始遍历

如上,我们就可以循环遍历链表。
销毁节点,我们需要两指针变量,一个记录要free的节点(cur),一个记录要free的节点的下一个节点(curNext)。每次free(cur),使cur = curNext。
在这里插入图片描述

//双向链表的销毁
void ListDestroy(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){ListNode* curNext = cur->next;free(cur);cur = curNext;}free(pHead);
}

5.双向链表的打印(ListPrint)

我们只有遍历打印链表即可。

  • 以遍历到头节点为结束条件
  • 从头结点的下一个节点开始遍历
//双向链表的打印
void ListPrint(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;printf("head<=>");while (cur != pHead){printf("%d<=>", cur->data);cur = cur->next;}printf("head\n");
}

6.双向链表的尾插(ListPushBack)

我们只需要让尾节点(tail)的next指向newnode,newnode的prev指向尾节点(tail),newnode的next指向头结点,头结点的prev指向newnode.
我们知道该链表是双向循环的,那么头结点的上一个节点就是尾节点。(与单链表相比该链表实现尾插并不需要遍历找尾,时间复杂度是O(1) )。
在这里插入图片描述

//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = BuyListNode(x);ListNode* tail = pHead->prev;tail->next = newnode;newnode->prev = tail;pHead->prev = newnode;newnode->next = pHead;
}

7.双向链表的尾删(ListPopBack)

我们只需要两个指针tail (指向尾节点),tailPrev (指向尾节点的上一个节点)。
free掉tail,使tailPrev的next指向头结点,头结点的prev指向tailPrev。

  • 注意:head->next == head 时,链表无有效数据,不能尾删数据。

在这里插入图片描述

//双向链表的尾删
void ListPopBack(ListNode* pHead)
{assert(pHead);//pHead->next == pHead时,链表没有元素assert(pHead->next != pHead);ListNode* tail = pHead->prev;ListNode* tailPrev = tail->prev;pHead->prev = tailPrev;tailPrev->next = pHead;free(tail);
}

8.双向链表的头插(ListPushFront)

我们只需要一个指针 first (头结点的下一个节点),使first的prev指向newnode,newnode的next指向first,head的next指向newnode,newnode的prev指向head。

在这里插入图片描述
head节点是哨兵位,不存储有效数据。

//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* newnode = BuyListNode(x);ListNode* first = pHead->next;newnode->next = first;first->prev = newnode;newnode->prev = pHead;pHead->next = newnode;
}

9.双向链表的头删(ListPopFront)

我们需要两个指针frist (指向头结点的下一个节点),second (指向头结点的下一个的下一个节点)。
free掉first,使second的prev指向头结点,头结点的next指向second。

  • 注意:如果head->next == head,表示链表为空,不能头删。

在这里插入图片描述

//双向链表的头删
void ListPopFront(ListNode* pHead)
{assert(pHead);assert(pHead->next != pHead);ListNode* first = pHead->next;ListNode* second = first->next;second->prev = pHead;pHead->next = second;free(first);
}

10.双向链表的查找(ListFind)

我们需要遍历链表,比较链表元素是否是要查找对象。如果找到了,返回该节点的地址。如果找不到,返回NULL。

//双向链表的查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}

11.双向链表在pos前插入(ListInsert)

我们需要一个指针 posPrev (指向pos前一个节点)。
pos的prev指向newnode,newnode的next指向pos;posPrev的next指向newnode,newnode的prev指向posPrev。

  • 如果pos指向头结点(哨兵位),那么ListInsert相当与完成尾插。
  • 如果pos指向头结点(哨兵位)的下一个节点,那么ListInsert相当于头插。

在这里插入图片描述

//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyListNode(x);ListNode* posPrev = pos->prev;newnode->prev = posPrev;posPrev->next = newnode;newnode->next = pos;pos->prev = newnode;
}

12.双向链表删除pos位置处的节点(ListErase)

我们需要两个指针posPrev(记录pos的上一个节点的地址),posNext(记录pos的下一个节点的地址)。free掉pos,使posNext的prev指向posPrev,posPrev的next指向posNext。

  • 如果pos指向头结点的下一个,那么ListErase相当于头删。
  • 如果pos指向头结点的上一个(也就是尾节点),那么ListErase相当于尾删。

在这里插入图片描述


//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* posNext = pos->next;ListNode* posPrev = pos->prev;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}

二、代码实现

对于头插,尾插函数,我复用了ListInsert函数。
对于头删,尾删函数,我复用了ListErase函数。

//Lish.h 文件#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;
}ListNode;//创建返回链表的头结点
ListNode* ListCreate();//创建新节点
ListNode* BuyListNode(LTDataType x);//双向链表的销毁
void ListDestroy(ListNode* pHead);//双向链表的打印
void ListPrint(ListNode* pHead);//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x);//双向链表的尾删
void ListPopBack(ListNode* pHead);//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x);//双向链表的头删
void ListPopFront(ListNode* pHead);//双向链表的查找
ListNode* ListFind(ListNode* pHead, LTDataType x);//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x);//双向链表删除pos位置的节点
void ListErase(ListNode* pos);

//Lish.c  文件#include "List.h"//创建返回链表的头结点
ListNode* ListCreate()
{return BuyListNode(-1);
}//创建新节点
ListNode* BuyListNode(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc");exit(-1);}newnode->data = x;newnode->next = newnode;newnode->prev = newnode;return newnode;
}//双向链表的打印
void ListPrint(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;printf("head<=>");while (cur != pHead){printf("%d<=>", cur->data);cur = cur->next;}printf("head\n");
}//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{assert(pHead);/*ListNode* newnode = BuyListNode(x);ListNode* tail = pHead->prev;tail->next = newnode;newnode->prev = tail;pHead->prev = newnode;newnode->next = pHead;*/ListInsert(pHead, x);
}//双向链表的尾删
void ListPopBack(ListNode* pHead)
{assert(pHead);//pHead->next == pHead时,链表没有元素assert(pHead->next != pHead);/*ListNode* tail = pHead->prev;ListNode* tailPrev = tail->prev;pHead->prev = tailPrev;tailPrev->next = pHead;free(tail);*/ListErase(pHead->prev);
}//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{assert(pHead);/*ListNode* newnode = BuyListNode(x);ListNode* first = pHead->next;newnode->next = first;first->prev = newnode;newnode->prev = pHead;pHead->next = newnode;*/ListInsert(pHead->next, x);
}//双向链表的头删
void ListPopFront(ListNode* pHead)
{assert(pHead);assert(pHead->next != pHead);/*ListNode* first = pHead->next;ListNode* second = first->next;second->prev = pHead;pHead->next = second;free(first);*/ListErase(pHead->next);
}//双向链表的查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyListNode(x);ListNode* posPrev = pos->prev;newnode->prev = posPrev;posPrev->next = newnode;newnode->next = pos;pos->prev = newnode;
}//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* posNext = pos->next;ListNode* posPrev = pos->prev;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}//双向链表的销毁
void ListDestroy(ListNode* pHead)
{assert(pHead);ListNode* cur = pHead->next;while (cur != pHead){ListNode* curNext = cur->next;free(cur);cur = curNext;}free(pHead);
}

总结

以上就是双向链表的实现!!!
在这里插入图片描述

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

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

相关文章

6.5.tensorRT高级(1)-alphapose模型导出、编译到推理(无封装)

目录 前言1. alphapose导出2. alphapose推理3. 讨论总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-alphap…

ROS添加节点

1 下载项目源码 &#xff08;1&#xff09;这里我使用是哔哩哔哩的博主源码机器人工匠王杰 https://github.com/6-robot/wpr_simulation.git &#xff08;2&#xff09;建立工作空间 在主目录下载建立如下文件夹 catkin_ws----       ----src &#xff08;3&#xff09;…

MySQL — InnoDB介绍

文章目录 InnoDB 主要特点InnoDB 架构In-Memory StructuresBuffer PoolChange BufferAdaptive Hash IndexLog Buffer On-Disk StructuresSystem TablespaceFile-Per-Table TablespacesGeneral TablespacesUndo TablespacesTemporary TablespacesDoublewrite BufferRedo LogUndo…

【Datawhale AI 夏令营第二期】AI 量化模型预测挑战赛

文章目录 赛题分析赛题背景赛事任务赛题数据集评价指标 Baseline实践导入模块EDA特征工程模型训练与验证结果输出 改进 赛题分析 赛题背景 量化金融在国外已经有数十年的历程&#xff0c;而在国内兴起还不到十年。这是一个极具挑战的领域。量化金融结合了数理统计、金融理论、…

【雕爷学编程】MicroPython动手做(29)——物联网之SIoT 2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

[PyTorch][chapter 46][LSTM -1]

前言&#xff1a; 长短期记忆网络&#xff08;LSTM&#xff0c;Long Short-Term Memory&#xff09;是一种时间循环神经网络&#xff0c;是为了解决一般的RNN&#xff08;循环神经网络&#xff09;存在的长期依赖问题而专门设计出来的。 目录&#xff1a; 背景简介 LSTM C…

VR全景在建筑工程行业能起到哪些作用?

在建筑工程领域&#xff0c;数字化技术为行业的发展起到巨大的推动作用&#xff0c;虽然建筑施工行业主要是依赖于工人劳动力和施工设备&#xff0c;但是VR全景在该行业中方方面面都能应用&#xff0c;从设计建模到项目交付&#xff0c;帮助建筑师以及项目方更好的理解每个环节…

数字电路的重要概念——静态功耗和动态功耗

静态功耗和动态功耗&#xff1a; CMOS电路功耗是由静态功耗和动态功耗组成的&#xff0c;动态功耗远大于静态功耗 1&#xff1a;静态功耗&#xff1a; 我们从一个简单的反相器角度来理解和说明静态功耗的概念&#xff0c;众所周知&#xff0c;反相器是由PMOS和NMOS互补组成的…

【ES】笔记-let 声明及其特性

let 声明及其特性 声明变量 变量赋值、也可以批量赋值 let a;let b,c,d;let e100;let f521,giloveyou,h[];变量不能重复声明 let star罗志祥;let star小猪;块级作用域&#xff0c;let声明的变量只在块级作用域内有效 {let girl周杨青;}console.log(girl)注意&#xff1a;在 i…

Redis可视化工具

Redis可视化工具 1、RedisInsight 下载地址&#xff1a;https://redis.com/redis-enterprise/redis-insight/ 双击软件进行安装&#xff0c;安装完后弹出如下界面&#xff1a; 安装完成后在主界面选择添加Redis数据库&#xff1b; 选择手动添加数据库&#xff0c;输入Redis…

【统计学精要】:使用 Python 实现的统计检验— 1/10

一、介绍 欢迎来到“掌握 Python 统计测试&#xff1a;综合指南”&#xff0c;它将介绍本手册中您需要熟悉使用 Python 的所有基本统计测试和分析方法。本文将为您提供统计测试及其应用的全面介绍&#xff0c;无论您是新手还是经验丰富的数据科学家。 使用来自现实世界的实际示…

HarmonyOS 开发基础(五)对用户名做点啥

一、实现用户名检验 条件渲染 、生命周期 1.规定用户名长度 2.限定使用的数字及字母&#xff08;涉及正则表达&#xff09; // 导出方式直接从文件夹 import MyInput from "../common/commons/myInput" Entry Component /* 组件可以基于struct实现&#xff0c;组件…

驱动开发(中断)

头文件&#xff1a; #ifndef __LED_H__ #define __LED_H__#define PHY_LED1_MODER 0X50006000 #define PHY_LED1_ODR 0X50006014 #define PHY_LED1_RCC 0X50000A28#define PHY_LED2_MODER 0X50007000 #define PHY_LED2_ODR 0X50007014 #define PHY_LED2_RCC 0X50000A28#def…

在word的文本框内使用Endnote引用文献,如何保证引文编号按照上下文排序

问题 如下图所示&#xff0c;我在word中插入了一个文本框&#xff08;为了插图&#xff09;&#xff0c;然后文本框内有引用&#xff0c;结果endnote自动将文本框内的引用优先排序&#xff0c;变成文献[1]了&#xff0c;而事实上应该是[31]。请问如何能让文本框内的排序也自动…

maven install命令:将包安装在本地仓库,供本地的其它工程或者模块依赖

说明 有时候&#xff0c;自己本地的maven工程依赖于本地的其它工程&#xff0c;或者manven工程中的一个模块依赖于另外的模块&#xff0c;可以执行maven的install命令&#xff0c;将被依赖的包安装在maven本地仓库。 示例 一个工程包含几个模块&#xff0c;模块之间存在依赖…

第一个maven项目(IDEA生成)

第一个maven项目&#xff08;IDEA生成&#xff09; 步骤1 配置Project SDK 步骤2 配置maven File->Settings搜索maven

风辞远的科技茶屋:来自未来的信号枪

很久之前&#xff0c;有位朋友问我&#xff0c;现在科技资讯这么发达了&#xff0c;你们还写啊写做什么呢&#xff1f; 我是这么看的。最终能够凝结为资讯的那个新闻点&#xff0c;其实是一系列事情最终得出的结果&#xff0c;而这个结果又会带来更多新的结果。其中这些“得出”…

kagNet:对常识推理的知识感知图网络 2023 AAAI 8.4+8.5

这里写目录标题 摘要介绍概述问题陈述推理流程 模式图基础概念识别模式图构造概念网通过寻找路径来匹配子图基于KG嵌入的路径修剪 知识感知图网络图卷积网络&#xff08;GCN&#xff09;关系路径编码分层注意机制 实验数据集和实验步骤比较方法KAGNET是实施细节性能比较和分析与…

python GUI nicegui初识一(登录界面创建)

最近尝试了python的nicegui库&#xff0c;虽然可能也有一些不足&#xff0c;但个人感觉对于想要开发不过对ui设计感到很麻烦的人来说是很友好的了&#xff0c;毕竟nicegui可以利用TailwindCSS和Quasar进行ui开发&#xff0c;并且也支持定制自己的css样式。 这里记录一下自己利…

【Spring框架】Spring事务

目录 Spring中事务的实现编程式事务声明式事务Transactional 作⽤范围Transactional 参数说明注意事项Transactional ⼯作原理 MySQL 事务隔离级别Spring 事务隔离级别事务传播机制 Spring中事务的实现 Spring中事务操作分为两类&#xff1a; 1.编程式事务 2.声明式事务 编程…