双向链表

定义

我们一开始学习的链表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表。

虽然使用单向链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,如果算法中需要大量地找某指定节点的前驱节点,使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,而 "从后往前" 找并不是它的强项。

为了能够高效率解决类似的问题,就发明了双向链表。从名字上理解双向链表,即链表是 "双向" 的,如下图所示:

 

从上图中可以看到,双向链表中各节点包含以下 3 部分信息(如下图所示):

●  指针域 prior:用于指向当前节点的直接前驱节点;

●  数据域 data:用于存储数据元素。

●  指针域 next:用于指向当前节点的直接后继节点;

 

因此,双链表的节点结构用 C 语言实现为:

  1. typedef struct Node {
  2.  
  3. struct Node *prior;//指向直接前驱节点
  4.  
  5. ElemType data;//数据域
  6.  
  7. struct Node *next;//指向直接后继节点
  8.  
  9. } Node;

注意:因为带头节点会更好操作,所以我的代码都有头节点。

1、双向链表的创建

同单链表相比,双链表仅是各节点多了一个用于指向直接前驱的指针域。因此,我们可以在单链表的基础轻松实现对双链表的创建。

  1. //1、初始化双向链表(带头节点)
  2.  
  3. Status initLinkList(LinkList *list){
  4.  
  5. //创建头节点
  6.  
  7. *list = malloc(sizeof(Node));
  8.  
  9. if (*list == NULL) {
  10.  
  11. return ERROR;
  12.  
  13. }
  14.  
  15. (*list)->prior = NULL;
  16.  
  17. (*list)->data = -1;
  18.  
  19. (*list)->next = NULL;
  20.  
  21. printf("已初始化链表~\n");
  22.  
  23. return OK;
  24.  
  25. }

2、遍历双向链表

和单向链表遍历方式一模模一样样,这里就不多讲。我多加了一层使用prior指针逆序输出,相信有点基础的同学应该能一眼看明白。

  1. //2、遍历双向链表
  2.  
  3. void printfLinkLisk(LinkList list){
  4.  
  5. printf("遍历链表:\n");
  6.  
  7. if (list == NULL || list->next == NULL) {
  8.  
  9. printf("这是一个空链表\n");
  10.  
  11. return;
  12.  
  13. }
  14.  
  15. LinkList p = list;
  16.  
  17. //判断next是否全部正确
  18.  
  19. printf("根据next从前往后遍历:");
  20.  
  21. while (p->next) {
  22.  
  23. printf("%d ",p->next->data);
  24.  
  25. p = p->next;
  26.  
  27. }
  28.  
  29. printf("\n");
  30.  
  31. //判断prior是否全部正确
  32.  
  33. printf("根据prior从后往前遍历:");
  34.  
  35. while (p != list) {
  36.  
  37. printf("%d ",p->data);
  38.  
  39. p = p->prior;
  40.  
  41. }
  42.  
  43. printf("\n");
  44.  
  45. }

3、根据索引位置添加节点

因为我的双向链表有头节点,所以只有两种添加情况:

●  添加至表的中间位置

同单链表添加数据类似,双向链表中间位置添加数据需要经过以下 4 个步骤(步骤中的顺序中 3 必须放到 1 和 2 后面,其它顺序可变),如下图所示:

● 将priorNode->next节点的prior指向新节点;

● 将新节点->next指向原来的priorNode->next;

● 将priorNode->next指向新节点;

● 新节点的prior指向priorNode。

 

● 添加至表尾

与添加到表中间的步骤只需要少掉步骤 1。因为priorNode->next是Null,不能用它执行操作,否则会崩溃。

  1. //3、根据索引位置插入数据至链表中
  2.  
  3. Status insertLinkList(LinkList *list, int index, ElemType data){
  4.  
  5. if (list == NULL || index < 0) {
  6.  
  7. return ERROR;
  8.  
  9. }
  10.  
  11. int i = 0;
  12.  
  13. LinkList priorNode = *list;
  14.  
  15. //判断插入的位置,这里开始位置是0,index超过链表长度则插入末尾
  16.  
  17. while (i < index && priorNode->next != NULL) {
  18.  
  19. priorNode = priorNode->next;
  20.  
  21. i++;
  22.  
  23. }
  24.  
  25. LinkList newNode = malloc(sizeof(Node));
  26.  
  27. if (newNode == NULL) {
  28.  
  29. return ERROR;
  30.  
  31. }
  32.  
  33. newNode->data = data;
  34.  
  35. //插入操作共四步,看好了,别眨眼
  36.  
  37. //1.将priorNode->next节点的前驱指向新节点
  38.  
  39. if (priorNode->next) {
  40.  
  41. priorNode->next->prior = newNode;
  42.  
  43. }
  44.  
  45. //2.将新节点->next指向原来的priorNode->next
  46.  
  47. newNode->next = priorNode->next;
  48.  
  49. //3.将priorNode->next指向新节点
  50.  
  51. priorNode->next = newNode;
  52.  
  53. //4.新节点的前驱指向priorNode
  54.  
  55. newNode->prior = priorNode;
  56.  
  57. return OK;
  58.  
  59. }

4、根据索引位置删除节点

根据索引删除节点时,只需遍历链表找到要删除的结点,更改前驱节点的next和后继节点的prior即可。

 
  1. //4、根据索引位置删除节点
  2.  
  3. Status deleteLinkListByIndex(LinkList *list, int index, ElemType *data){
  4.  
  5. if (*list == NULL || index < 0) {
  6.  
  7. return ERROR;
  8.  
  9. }
  10.  
  11. LinkList locaNode = *list;
  12.  
  13. int i = 0;
  14.  
  15. while (i <= index) {
  16.  
  17. locaNode = locaNode->next;
  18.  
  19. if (locaNode == NULL) {
  20.  
  21. printf("没有这个你想要删除的节点\n");
  22.  
  23. return ERROR;
  24.  
  25. }
  26.  
  27. i++;
  28.  
  29. }
  30.  
  31. //开始删除,只需要做两步
  32.  
  33. //1、更改前驱节点的next
  34.  
  35. locaNode->prior->next = locaNode->next;
  36.  
  37. //2、更改后继节点的prior。
  38.  
  39. if (locaNode->next) {
  40.  
  41. locaNode->next->prior = locaNode->prior;
  42.  
  43. }
  44.  
  45. *data = locaNode->data;
  46.  
  47. free(locaNode);
  48.  
  49. return OK;
  50.  
  51. }

5、根据存储的值删除节点

根据值删除节点时,只需遍历链表找到要删除的结点,更改前驱节点的next和后继节点的prior即可。

  1. //5、根据存储的值删除节点
  2.  
  3. Status deleteLinkListByData(LinkList *list, ElemType data){
  4.  
  5. if (*list == NULL) {
  6.  
  7. return ERROR;
  8.  
  9. }
  10.  
  11. LinkList locaNode = (*list)->next;
  12.  
  13. while (locaNode) {
  14.  
  15. if (locaNode->data == data) {
  16.  
  17. break;
  18.  
  19. }
  20.  
  21. locaNode = locaNode->next;
  22.  
  23. }
  24.  
  25. if (locaNode == NULL) {
  26.  
  27. printf("没有这个你想要删除的节点\n");
  28.  
  29. return ERROR;
  30.  
  31. }
  32.  
  33. //开始删除,只需要做两步
  34.  
  35. locaNode->prior->next = locaNode->next;
  36.  
  37. if (locaNode->next) {
  38.  
  39. locaNode->next->prior = locaNode->prior;
  40.  
  41. }
  42.  
  43. free(locaNode);
  44.  
  45. return OK;
  46.  
  47. }

6、根据值查找节点

方法同单向链表

  1. //6、查找元素
  2.  
  3. Status selectNode(LinkList list, ElemType data, LinkList *locaNode){
  4.  
  5. if (list == NULL) {
  6.  
  7. return ERROR;
  8.  
  9. }
  10.  
  11. LinkList p = list->next;
  12.  
  13. while (p) {
  14.  
  15. if (p->data == data) {
  16.  
  17. *locaNode = p;
  18.  
  19. break;
  20.  
  21. }
  22.  
  23. p = p->next;
  24.  
  25. }
  26.  
  27. if (*locaNode == NULL) {
  28.  
  29. printf("没有这个你想要的节点\n");
  30.  
  31. return ERROR;
  32.  
  33. }
  34.  
  35. else {
  36.  
  37. return OK;
  38.  
  39. }
  40.  
  41. }

其它辅助代码

  1. #include "stdlib.h"
  2.  
  3. #define OK 1
  4.  
  5. #define ERROR 0
  6.  
  7. //元素类型
  8.  
  9. typedef int ElemType;
  10.  
  11. //状态类型
  12.  
  13. typedef int Status;
  14.  
  15. //定义节点结构体
  16.  
  17. typedef struct Node {
  18.  
  19. struct Node *prior;
  20.  
  21. ElemType data;
  22.  
  23. struct Node *next;
  24.  
  25. } Node;
  26.  
  27. typedef Node *LinkList;
  28.  
  29. int main(int argc, const char * argv[]) {
  30.  
  31. LinkList list;
  32.  
  33. initLinkList(&list);
  34.  
  35. for (int i = 0; i < 10; i ++) {
  36.  
  37. insertLinkList(&list, i, i);
  38.  
  39. }
  40.  
  41. printfLinkLisk(list);
  42.  
  43. int index, data;
  44.  
  45. printf("输入你想插入的位置(从0开始)和存储的值:");
  46.  
  47. scanf("%d %d",&index,&data);
  48.  
  49. insertLinkList(&list, index, data);
  50.  
  51. printfLinkLisk(list);
  52.  
  53. printf("输入你想删除的位置(从0开始):");
  54.  
  55. scanf("%d",&index);
  56.  
  57. deleteLinkListByIndex(&list, index, &data);
  58.  
  59. printfLinkLisk(list);
  60.  
  61. printf("输入你想删除的节点的值(只删最前的那个):");
  62.  
  63. scanf("%d",&data);
  64.  
  65. deleteLinkListByData(&list, data);
  66.  
  67. printfLinkLisk(list);
  68.  
  69. printf("\n");
  70.  
  71. return 0;
  72.  
  73. }

输出结果:

 

看到这里是不是又学到了很多新知识呢~

如果你很想学编程,小编推荐我的C语言/C++编程学习基地【点击进入】!

都是学编程小伙伴们,带你入个门还是简简单单啦,一起学习,一起加油~

还有许多学习资料和视频,相信你会喜欢的!

涉及:游戏开发、常用软件开发、编程基础知识、课程设计、黑客等等......

 

 

【C语言教程】双向链表学习总结和C语言代码实现!值得学习~的更多相关文章

  1. 电脑小白学习软件开发-C#语言基础之循环重点讲解,习题

    写代码也要读书,爱全栈,更爱生活.每日更新原创IT编程技术及日常实用视频. 我们的目标是:玩得转服务器Web开发,搞得懂移动端,电脑客户端更是不在话下. 本教程是基础教程,适合任何有志于学习软件开发的 ...

  2. Swift语言教程中文文档

    Swift语言教程中文文档 Swift语言教程(一)基础数据类型 Swift语言教程(二)基础数据类型 Swift语言教程(三)集合类型 Swift语言教程(四) 集合类型 Swift语言教程(五)控 ...

  3. Swift3.0语言教程使用URL字符串

    Swift3.0语言教程使用URL字符串 Swift3.0语言教程使用URL字符串,和路径一样,URL其实也是字符串,我们可以将这些字符串称为URL字符串.本小节将讲解URL字符串的使用. 1.编码 ...

  4. Swift3.0语言教程使用路径字符串

    Swift3.0语言教程使用路径字符串 Swift3.0语言教程使用路径字符串,路径其实是字符串的一种,我们称为路径字符串.本小节将讲解如何使用路径字符串. 1.组合路径 开发者可以将数组快速的组合成 ...

  5. Swift3.0语言教程字符串大小写转化

    Swift3.0语言教程字符串大小写转化 Swift3.0语言教程字符串大小写转化,在字符串中,字符串的格式是很重要的,例如首字母大写,全部大写以及全部小写等.当字符串中字符很多时,通过人为一个一个的 ...

  6. Swift3.0语言教程替换子字符串

    Swift3.0语言教程替换子字符串 Swift3.0语言教程替换子字符串,替换子字符串其实就是将字符串中的子字符串删除,然后再进行添加.为了让这一繁琐的过程变的简单,NSString提供了替换子字符 ...

  7. Swift3.0语言教程获取C字符串

    Swift3.0语言教程获取C字符串 Swift3.0语言教程获取C字符串,为了让Swift和C语言可以实现很好的交互,开发者可以使用NSString的cString(using:)方法在指定编码格式 ...

  8. Swift3.0语言教程获取字符串长度

    Swift3.0语言教程获取字符串长度 Swift3.0语言教程获取字符串长度,当在一个字符串中存在很多的字符时,如果想要计算字符串的长度时相当麻烦的一件事情,在NSString中可以使用length ...

  9. Swift2.0语言教程之类的方法

    Swift2.0语言教程之类的方法 Swift2.0语言的方法 方法其实就是函数,只不过它被定义在了类中.在Swift中,根据被使用的方式不同,方法分为了实例方法和类型方法两种.这两种方法的定义也和O ...

  10. C++语言实现双向链表

    这篇文章是关于利用C++模板的方式实现的双向链表以及双向链表的基本操作,在之前的博文C语言实现双向链表中,已经给大家分析了双向链表的结构,并以图示的方式给大家解释了双向链表的基本操作.本篇文章利用C+ ...

随机推荐

  1. python应用 曲线拟合04

    python应用 曲线拟合04 → 多项式拟合 主要是使用 numpy 库中的 polyfit() 函数,见第 66 行, z = np.polyfit(x_proton, y, 3) ,其中待拟合曲 ...

  2. go http请求流程分析

    前言 golang作为常驻进程, 请求第三方服务或者资源(http, mysql, redis等)完毕后, 需要手动关闭连接, 否则连接会一直存在; 连接池是用来管理连接的, 请求之前从连接池里获取连 ...

  3. 微信小程序-组件-基础内容

    1.text 1.作用:类似html的行内元素 2.常用属性: -space值:ensp:中文字符空格一半大小 emsp:中文字符空格大小 nbsp:根据设置字体的大小决定空格大小 -decode:d ...

  4. C enum(枚举)

    C enum(枚举) 枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读. 枚举语法定义格式为: enum 枚举名 {枚举元素1,枚举元素2,……}; 接下来我们举个例子,比如:一星期有 ...

  5. WIN10自动修复失败无限循环

    网上解决方案大部分都是要重置电脑或者进pe修复系统,手头没有启动盘又不想重置电脑的可以照下边的操作试一试 1.先暂时禁用自动修复功能cmd管理员(winre可以进cmd)执行bcdedit bcded ...

  6. [05] 通过P/Invoke加速C#程序

    通过P/Invoke加速C#程序 任何语言都会提供FFI机制(Foreign Function Interface, 叫法不太一样), 大多数的FFI机制是和C API. C#提供了P/Invoke来 ...

  7. [剑指Offer]57-和为s的数字

    题目一 输入一个递增的数组和一个数字,在数组中查找2个数字,是他们的和正好为S,如果有多对的和为S,则输出任意一对即可. 题解 关键信息是数组有序.初始化i,j指向第一个和第二个数,与S比较,若小了, ...

  8. openstack (共享服务) 消息队列rabbitmq服务

    云计算openstack共享组件——消息队列rabbitmq(3)   一.MQ 全称为 Message Queue, 消息队列( MQ ) 是一种应用程序对应用程序的通信方法.应用程序通过读写出入队 ...

  9. Burger King使用RayOnSpark进行基于实时情景特征的快餐食品推荐

    作者:Luyang Wang, Kai Huang, Jiao Wang, Shengsheng Huang, Jason Dai 基于深度学习的推荐模型已广泛应用于各种电商平台中,为用户提供推荐.目 ...

  10. 有向图的基本算法-Java实现

    有向图 有向图同无向图的区别为每条边带有方向,表明从一个顶点至另一个顶点可达.有向图的算法多依赖深度搜索算法. 本文主要介绍有向图的基本算法,涉及图的表示.可达性.检测环.图的遍历.拓扑排序以及强连通 ...