数据结构--双链表

目录

一、引言

二 、链表的分类

1.单向或双向

2.带头或不带头 

3.循环或不循环 

 三、双链表的概念与基本结构

1.概念

2.基本结构 

三、双链表的常见操作

1.创建节点

2.初始化 

3.头插

4.尾插

5.头删

6.尾删

7.打印

8.查找

9.插入节点

10.删除节点

11.销毁链表

五、完整代码

1.List.h

2.List.c

3.test.c

六、总结


一、引言

双链表是一种在节点之间通过两个指针进行连接的数据结构,每个节点都有两个指针:一个指向前一个节点,另一个指向下一个节点。带头节点的双链表在实际应用中非常常见,本文将详细介绍其实现方法,并提供完整的 C 语言代码示例。

二 、链表的分类

链表的结构⾮常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:

1.单向或双向

  • 单向链表:

    • 每个节点包含一个指向下一个节点的指针。
    • 只能从头到尾单向遍历。
    • 插入和删除操作较简单,但需要从头开始查找节点。
  • 双向链表:

    • 每个节点包含两个指针,一个指向下一个节点,一个指向前一个节点。
    • 可以从头到尾或尾到头双向遍历。
    • 插入和删除操作更灵活,但每个节点占用更多内存。

2.带头或不带头 

  • 带头节点:

    • 链表有一个额外的头节点,它不存储实际数据,只作为链表的起始点。
    • 操作如插入和删除更简单,因为头节点简化了边界条件处理。
  • 不带头节点:

    • 链表从第一个实际数据节点开始,没有额外的头节点。
    • 需要特别处理空链表和边界情况。

3.循环或不循环 

  • 循环链表:

    • 链表的尾节点指向头节点,形成一个循环结构。
    • 遍历时可以从任何节点开始,不会遇到“末尾”问题。
  • 非循环链表:

    • 链表的尾节点指向 NULL,表示链表的结束。
    • 遍历时需要检查是否到达链表末尾。

虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构:单链表和双向带头循环链表
1. ⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结 构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多。
2. 带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带 来很多优势,实现反⽽简单了,后⾯我们代码实现了就知道了。

本节我们所讲的双链表即为双向带头循环链表。 

 三、双链表的概念与基本结构

1.概念

双链表简介
双链表是一种链表变体,每个节点都包含三个部分:
存储的数据。
指向前一个节点的指针(prev)。
指向下一个节点的指针(next)。
带头节点的双链表有一个特殊的节点称为头节点,它不存储有效数据,只是作为链表的一个起始辅助节点存在。头节点的 prev 指针指向自己,next 指针指向链表的第一个有效节点。

2.基本结构 

双链表的基本结构如下:

typedef struct ListNode
{DataType data;//数据域struct ListNode* prev;//指针域,指向前一个节点struct ListNode* next;//指针域,指向后一个节点
}LN;

三、双链表的常见操作

1.创建节点

//申请节点
LN* ListBuyNode(DataType x)
{LN* node = (LN*)malloc(sizeof(LN));if (node == NULL){perror("malloc fail!");exit(1);}node->data = x;node->prev = node->next = node;//前后指针都指向自己,使得链表循环起来
}

2.初始化 

//初始化
void ListInit(LN** pphead)
{*pphead = ListBuyNode(-1);//头节点,随便给一个值(用不到)
}

3.头插

//头插
void ListPushFront(LN* phead, DataType x)
{assert(phead);LN* newnode = ListBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;//上面两行代码位置不能交换
}

4.尾插

//尾插
void ListPushBack(LN* phead, DataType x)
{assert(phead);LN* newnode = ListBuyNode(x);newnode->prev = phead->prev;//新节点prev指向原来的尾节点newnode->next = phead;//新节点next指向头节点phead->prev->next = newnode;//原来的尾节点next指向newnodephead->prev = newnode;//头节点的prev指向newnode//上面两行代码位置不能交换
}

5.头删

//头删
void ListPopFront(LN* phead)
{assert(phead && phead->next != phead);//链表必须有效且链表不为空LN* del = phead->next;del->next->prev = phead;phead->next = del->next;//删除del节点free(del);del = NULL;
}

 

6.尾删

//尾删
void ListPopBack(LN* phead)
{assert(phead&&phead->next!=phead);//链表必须有效且链表不为空LN* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;//删除del节点free(del);del = NULL;
}

7.打印

//打印
void ListPrint(LN* phead)
{LN* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

8.查找

//查找
LN* ListFind(LN* phead, DataType x)
{LN* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;//找到了}pcur = pcur->next;}return NULL;//没有找到 
}

9.插入节点

//插入,pos节点后插入
void ListInsert(LN* pos, DataType x)
{assert(pos);LN* newnode = ListBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}

10.删除节点

//删除
void ListErase(LN* pos)//这里不传二级指针,是为了保持接口一致性
{assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}

11.销毁链表

//销毁链表
void ListDestory(LN* phead)
{assert(phead);LN* pcur = phead->next;while (pcur != phead){LN* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}

注:

LTErase和LTDestroy参数理论上要传一级,因为我们需要让形参的改变影响到实参,但是为了保持接口一致性才传的一级。传一级存在的问题是,当形参phead置为NULL后,实参plist不会被修改为NULL,因此解决办法是:调用完方法后手动将实参置为NULL~ 

五、完整代码

1.List.h

该部分放顺序表结构定义、函数的声明

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
typedef struct ListNode
{DataType data;//数据域struct ListNode* prev;//指针域,指向前一个节点struct ListNode* next;//指针域,指向后一个节点
}LN;
//初始化
void ListInit(LN** pphead);
//尾插
void ListPushBack(LN*phead,DataType x);
//头插
void ListPushFront(LN*phead,DataType x);
//打印
void ListPrint(LN* phead);
//尾删
void ListPopBack(LN* phead);
//头删
void ListPopFront(LN* phead);
//查找
LN* ListFind(LN* phead, DataType x);
//插入,pos节点后插入
void ListInsert(LN* pos, DataType x);
//删除
void ListErase(LN* pos);
//销毁链表
void ListDestory(LN* phead);

2.List.c

该部分是函数功能的实现

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
//申请节点
LN* ListBuyNode(DataType x)
{LN* node = (LN*)malloc(sizeof(LN));if (node == NULL){perror("malloc fail!");exit(1);}node->data = x;node->prev = node->next = node;//前后指针都指向自己,使得链表循环起来
}
//初始化
void ListInit(LN** pphead)
{*pphead = ListBuyNode(-1);//头节点,随便给一个值(用不到)
}
//尾插
void ListPushBack(LN* phead, DataType x)
{assert(phead);LN* newnode = ListBuyNode(x);newnode->prev = phead->prev;//新节点prev指向原来的尾节点newnode->next = phead;//新节点next指向头节点phead->prev->next = newnode;//原来的尾节点next指向newnodephead->prev = newnode;//头节点的prev指向newnode//上面两行代码位置不能交换
}
//头插
void ListPushFront(LN* phead, DataType x)
{assert(phead);LN* newnode = ListBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;//上面两行代码位置不能交换
}
//尾删
void ListPopBack(LN* phead)
{assert(phead&&phead->next!=phead);//链表必须有效且链表不为空LN* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;//删除del节点free(del);del = NULL;
}
//头删
void ListPopFront(LN* phead)
{assert(phead && phead->next != phead);//链表必须有效且链表不为空LN* del = phead->next;del->next->prev = phead;phead->next = del->next;//删除del节点free(del);del = NULL;
}
//打印
void ListPrint(LN* phead)
{LN* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}
//查找
LN* ListFind(LN* phead, DataType x)
{LN* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;//找到了}pcur = pcur->next;}return NULL;//没有找到 
}
//插入,pos节点后插入
void ListInsert(LN* pos, DataType x)
{assert(pos);LN* newnode = ListBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
//删除
void ListErase(LN* pos)//这里不传二级指针,是为了保持接口一致性
{assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}
//销毁链表
void ListDestory(LN* phead)
{assert(phead);LN* pcur = phead->next;while (pcur != phead){LN* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}

3.test.c

测试,函数的调用

#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
void test01()
{LN* plist = NULL;ListInit(&plist);/*ListPushBack(plist, 3);ListPushBack(plist, 2);ListPushBack(plist, 1);ListPushBack(plist, 0);*/ListPushFront(plist, 1);ListPushFront(plist, 2);ListPushFront(plist, 3);ListPushFront(plist, 4);// ListPopBack(plist);//ListPopFront(plist);LN* find = ListFind(plist, 3);//if (find == NULL)//	printf("没找到\n");//else//	printf("找到了\n");ListInsert(find, 99);ListErase(find);find = NULL;ListPrint(plist);ListDestory(plist);plist = NULL;}
int main()
{test01();return 0;
}

六、总结

带头节点的双链表在进行节点的插入和删除操作时具有较好的灵活性。头节点的存在简化了链表操作的边界条件,减少了对空链表和链表边界的特殊处理。通过本文的实现和示例代码,你应该能掌握双链表的基本操作,并能够将其应用于实际的编程任务中。
希望这个博客对你有帮助!如果你有任何问题或者需要进一步的解释,欢迎在评论区留言。

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

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

相关文章

OpenAi assistant run always fails when called from PHP

题意&#xff1a;从 PHP 调用时&#xff0c;OpenAI 助理运行总是失败。 问题背景&#xff1a; The runs I create with the openai-php library fail direct in 100% of cases. What am I doing wrong? I do not have much experience with php but this is the test script.…

Codeforces Round 973 (Div. 2) - D题

传送门&#xff1a;Problem - D - Codeforces 题目大意&#xff1a; 思路&#xff1a; 尽量要 最大值变小&#xff0c;最小值变大 即求 最大值的最小 和 最小值的最大 -> 二分答案 AC代码&#xff1a; 代码有注释 #include<bits/stdc.h> using namespace std; #…

neo4j(spring) 使用示例

文章目录 前言一、neo4j是什么二、开始编码1. yml 配置2. crud 测试3. node relation 与java中对象的关系4. 编码测试 总结 前言 图数据库先驱者 neo4j&#xff1a;neo4j官网地址 可以选择桌面版安装等多种方式,我这里采用的是docker安装 直接执行docker安装命令: docker run…

Git之如何删除Untracked文件(六十八)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

基于Springboot的助学金管理系统设计与实现

文未可获取一份本项目的java源码和数据库参考。 一、研究背景 利用计算机来实现助学金管理系统&#xff0c;已经成为一种趋势&#xff0c;相比传统的手工管理方式&#xff0c;利用软件进行助学金管理系统&#xff0c;有着执行快&#xff0c;可行性高、容量存储大&#xff0c;…

【C#】内存的使用和释放

在 C# 中&#xff0c;内存管理主要是由 .NET 的垃圾回收器&#xff08;Garbage Collector, GC&#xff09;自动处理的。然而&#xff0c;了解如何正确地使用和释放内存对于编写高效且可靠的代码非常重要。以下是一些关键点和最佳实践&#xff1a; 1. 内存分配 托管资源&#x…

CSS——弹性盒子布局(display: flex)

CSS——弹性盒子布局&#xff08;display: flex&#xff09; 我们经常听说一种布局&#xff1a;Flexbox或者是弹性布局&#xff0c;它的全称叫做弹性盒子布局&#xff08;Flexible Box Layout&#xff09;&#xff0c;那么它到底该如何实现呢&#xff1f;从我们熟悉的 display…

大模型训练实战经验总结

在当今AI技术飞速发展的背景下&#xff0c;定制化大模型的自主训练已成为满足特定行业需求、保障数据安全、提升模型应用效能的关键途径。本文将深度剖析这一过程的核心价值与实践智慧&#xff0c;从数据隐私保护、模型透明度增强&#xff0c;到数据预处理的精细操作&#xff0…

Packet Tracer - IPv4 ACL 的实施挑战(完美解析)

目标 在路由器上配置命名的标准ACL。 在路由器上配置命名的扩展ACL。 在路由器上配置扩展ACL来满足特定的 通信需求。 配置ACL来控制对网络设备终端线路的 访问。 在适当的路由器接口上&#xff0c;在适当的方向上 配置ACL。…

Python编码系列—Python外观模式:简化复杂系统的快捷方式

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

ZYNQ FPGA自学笔记~操作PLL

一 时钟缓冲器、管理和路由 垂直时钟中心&#xff08;clock backbone&#xff09;将设备分为相邻的左侧和右侧区域&#xff0c;水平中心线将设备分为顶部和底部两侧。clock backbone中的资源镜像到水平相邻区域的两侧&#xff0c;从而将某些时钟资源扩展到水平相邻区域。BUFG不…

windows下编译MicroRTS-Py

1.microRTS&#xff08;java&#xff09; microRTS是java写的跨平台的小型即时战略模拟器。 Farama-Foundation/MicroRTS: A simple and highly efficient RTS-game-inspired environment for reinforcement learning (github.com)https://github.com/Farama-Foundation/Micr…

Kubeadm快速安装 Kubernetes集群

1. Kubernetes简介 Kubernetes&#xff08;k8s&#xff09;是谷歌开源的容器编排平台&#xff0c;用于自动化部署、扩展和管理容器化应用程序。它具有以下特点&#xff1a; 开源容器化自动部署扩展高可用 2. Kubernetes架构 Kubernetes遵循主从式架构设计&#xff0c;主要分…

Python用TOPSIS熵权法重构粮食系统及期刊指标权重多属性决策MCDM研究|附数据代码...

原文链接&#xff1a;https://tecdat.cn/?p37724 在当今世界&#xff0c;粮食系统的稳定性至关重要。尽管现有的全球粮食系统在生产和分配方面表现出较高的效率&#xff0c;但仍存在大量人口遭受饥饿以及诸多粮食安全隐患。与此同时&#xff0c;在学术领域&#xff0c;准确评估…

OpenAI GPT o1技术报告阅读(3)-英文阅读及理解

✨继续阅读报告&#xff1a;使用大模型来学习推理(Reason) 原文链接&#xff1a;https://openai.com/index/learning-to-reason-with-llms/ 这次我们继续看一个英文阅读理解的案例。 原问题&#xff1a; The following passage is the draft of an excerpt from a contempora…

基于OpenCV的YOLOv5图片检测

利用OpenCV的DNN模块加载onnx模型文件进行图片检测。 1、使用的yolov5工程代码&#xff0c;调用export.py导出onnx模型。 2、下载opencv版本&#xff0c;https://opencv.org/releases/ 使用opencv版本4.5.3或以上&#xff0c;本文使用的opencv4.6.0 3、使用vc20…

css设置overflow:hiden行内元素会发生偏移的现象

父级元素包含几个行内元素 <div id"box"><p><span>按钮</span><span>测试文字文字文字测试文字文字文字</span><span>看这里</span></p></div>#box p{width: 800px;font-size: 30px;}#box p span{disp…

VMware启动时报错: “另一个程序已锁定文件的一部分,进程无法访问” 分析记录

项目场景&#xff1a; VMware启动时报错: “另一个程序已锁定文件的一部分,进程无法访问” 问题描述 VMware启动时报错: “另一个程序已锁定文件的一部分,进程无法访问” 原因分析&#xff1a; 虚拟机开启后会对部分文件继续加密&#xff0c;关闭时虚拟机会自动对其解密&…

css设置动态数组渲染及中间线平均分开显示

效果图&#xff1a; <template><div class"container"><div v-for"(item, index) in items" :key"index" class"item-container"><span class"item">{{ item }}</span><span v-if"in…

二级C语言2023-9易错题

1 二叉树结点数计算&#xff1a; 一棵二叉树有10个度为1的结点&#xff0c;7个度为2的结点&#xff0c;则该二叉树共有____个结点。 解&#xff1a; 2 指针&#xff1a; 有以下程序 #inctude<stdio.h> #include<stdlib.h> main() { int *a&#xff0c;*b&…