上两篇博文讲了如何在单链表中插入一个值:

【 C 】在单链表中插入一个新节点的尝试(一)

【 C 】在单链表中插入一个新节点的尝试(二)

这篇博文讲解如何在双链表中插入一个值。


单链表的替代方案就是双链表。在一个双链表中,每个节点都包含两个指针——指向前一个节点的指针和指向后一个节点的指针。这可以使我们以任何方向遍历双链表,甚至可以忽前忽后地在双链表中访问。下图展示了一个双链表:

下面是节点类型的声明:

typedef struct NODE
{struct NODE *fwd;struct NODE *bwd;int    value;
} Node;

现在,存在两个根指针:一个指向链表的第一个节点,另一个指向链表的最后一个节点。这两个指针允许我们从链表的任何一端开始遍历链表。

我们可能想把两个根指针分开声明为两个变量。但这样一来,我们必须把两个指针都传递给插入函数。为根指针声明一个完整的节点更为方便,(下面的例子也是这么做的),只是它的值字段绝不会被使用。在我们的例子中,这个技巧只是浪费了一个整型值的内存空间。对于值字段非常大的链表,分开声明两个指针可能更好一些。另外,我们也可以在根节点的值字段中保存其他一些关于链表的信息,例如链表当前包含的节点数量。

根节点的fwd字段指向链表的第一个节点,根节点的bwd字段指向链表的最后一个节点。如果链表为空,这两个字段都为NULL。链表的第一个节点的bwd字段和最后一个节点的fwd字段都为NULL。在一个有序的链表中,各个节点将根据value字段的值以升序排列。

下面,我们编写一个函数,把一个值插入一个有序的双链表中。dll_insert函数接受两个参数:一个指向根节点的指针和一个需要插入的整型值。

开头提到的两个博文,在单链表中插入函数把重复的值也添加到链表中。在有些应用程序中,不插入重复的值可能更为合适。下面在双链表中插入一个值就是这种情况,dll_insert函数只有当欲插入的值原先不存在于链表中时才将其插入!

让我们以一种更为规范的方法来编写这个函数。当我们把一个节点插入一个链表中时,可能出现4中情况:

  1. 新值可能必须插入到链表的中间位置;
  2. 新值可能必须插入到链表的起始位置;
  3. 新值可能必须插入到链表的结束位置;
  4. 新值可能既插入到链表的起始位置又插入到链表的结束位置。

在情况1和2,新节点的fwd字段必须设置为指向链表的下一个节点的指针,链表的下一个节点的bwd字段必须设置为指向这个新节点。

在情况3和4,新节点的fwd字段必须设置为NULL,根节点的bwd字段必须设置为指向新节点。

在情况1和3,新节点的bwd字段必须设置为指向链表的前一个节点,而链表前一个节点的fwd字段必须设置为指向新节点 。

在情况2和4,新节点的bwd字段必须设置为NULL,根节点的fwd字段必须设置为指向新节点。

为了让认识更为清晰,我画了一个示意图,示意双链表节点之间的情况:

下面的程序就是根据这来设计的,认真体会,或许更为清晰:

程序:

//简明的双链表插入函数
//把一个值插入到一个双链表中,rootp是一个指向根节点的指针,value是要插入的新值
//返回值:如果欲插值原先已经存在与链表中,函数返回0
//如果内存不足导致无法插入,返回返回-1;如果插入成功,函数返回1#include < stdlib.h>
#include < stdio.h >
#include < dll_node.h > //这是自己建立的一个头文件,用于保存双链表的节点声明int dll_insert( Node *rootp, int new_value )
{Node *this; //指向新节点之前的那个节点Node *next; //指向新节点之后的那个节点Node *newnode; //指向新节点本身//查看value是否已经存在于链表中,如果是就返回。//否则,为新值创建一个新的节点(newnode将指向它)for( this = rootp; ( next = this->fwd ) != NULL; this = next ){if( next->value == new_value )  //如果插入的新值在链表中已经存在,则返回0,不需要插入return 0;if( next->value > new_value )  //如果插入的新值小于当前节点的值,则直接跳出循环,也就是需要在该节点之前插入新值break;//如果上面两种情况都不是,则插入的新值大于当前节点的值,继续循环}//为新节点开辟内存空间newnode = ( Node*)malloc( sizeof( Node ) );if( newnode == NULL )return -1; //如果内存分配不成功,则返回 -1;newnode->value = new_value; //为新节点赋新值if( next != NULL ) //这个条件的意思是新节点并非位于链表尾部{if(this != rootp )  //新节点并非位于链表起始位置{newnode->fwd = next;this->fwd = newnode;newnode->bwd = this;next->bwd = newnode;}else  //新节点位于链表起始位置{newnode->fwd = next;rootp->fwd = newnode;newnode->bwd = NULL;next->bwd = newnode;}}else //新节点位于链表的尾部{if( this != rootp ) //新节点并非位于链表的起始位置(链表不为空){newnode->fwd = NULL;this->fwd = newnode;newnode->bwd = this;rootp->bwd = newnode;}else{//新节点既位于起始位置,又位于尾部,也就是说原链表为空newnode->fwd = NULL;rootp->fwd = newnode;newnode->bwd = NULL;rootp->bwd = newnode;}}return 1;}

一开始,函数使 this 指向根节点。next 指针始终指向 this 之后的那个节点。它的思路是这两个指针同步前进,直到新节点应该插入这两者之间。for循环检查next所指向的节点的值,判断是否到达需要插入的位置。

如果在链表中找到新值,也就是说链表中本身就存在需要插入的新值:

if( next->value == new_value )  //如果插入的新值在链表中已经存在,则返回0,不需要插入return 0;

那么就简单的返回。否则,当到达链表尾部(

( next = this->fwd ) == NULL

)或者找到适当的插入位置:

 if( next->value > new_value )  //如果插入的新值小于当前节点的值,则直接跳出循环,也就是需要在该节点之前插入新值break;

循环终止。

在任何一种情况下,新节点都应该插入到 this 指向的节点的后面。注意,在我们决定新值是否应该实际插入到链表之前,并不为它分配内存。如果事先分配内存,如果发现新值原先已经存在于链表中,就有可能发生内存泄露。

当我们把12插入到链表中时,观察下情况:下面这张图显示了for循环终止之后几个变量的状态。

然后,函数为新节点分配内存,下面几条语句执行之后,

newnode->fwd = next;
this->fwd = newnode;

链表的样子如下:

然后,执行下列语句:

 newnode->bwd = this;
next->bwd = newnode;

这就完成了把新值插入到链表中的过程:

【 C 】在双链表中插入一个新值的简明程序相关推荐

  1. 【 C 】简化双链表插入函数(对在双链表中插入一个新值的简明程序的简化)

    目录 背景 第一个技巧是语句提炼(statement factoring) 第二个简化技巧 最终简化版本 背景 上篇博文:[ C ]在双链表中插入一个新值的简明程序,讲了一个简明的双链表插入函数,那个 ...

  2. 【 C 】在单链表中插入一个新节点的尝试(二)

    在上篇博文中:[ C ]在单链表中插入一个新节点的尝试(一),我们最后提到了如果向单链表的开头(起始位置)插入一个节点,上篇博文中给出的程序显然完成不了这任务. 这篇博文中,我们将解决这个问题,给出一 ...

  3. 【 C 】在单链表中插入一个新节点的尝试(一)

    根据<C和指针>中讲解链表的知识,记录最终写一个在单链表中插入一个新节点的函数的过程,这个分析过程十分的有趣,准备了两篇博文,用于记录这个过程. 链表是以结构体和指针为基础的,所以结构体和 ...

  4. 数据结构例16.试设计一个算法, 使得在一个有序的单链表中插入一个元素后仍然有序。

    /* 16.试设计一个算法, 使得在一个有序的单链表中插入一个元素后仍然有序. */ # include <iostream> # include <stdlib.h> # i ...

  5. 双链表中插入节点(C语言实现)

    文章目录 1. 相关背景介绍 1.1 双链表概念 1.2 双链表的优势与劣势 1.3 双链表插入节点的位置 2. 不同位置插入数据 2.1 在DLL的前端添加节点 2.2 在给定节点之前添加节点 2. ...

  6. 链表相关操作:创建链表、遍历链表、求链表长度、链表中删除一个节点、链表中插入一个节点、反转单链表...

    1 #include<iostream> 2 #include<stdlib.h> 3 4 typedef struct node 5 { 6 int data; 7 stru ...

  7. C++ 单链表基本操作分析与实现 链表   链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结

    C++ 单链表基本操作分析与实现 链表 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以 ...

  8. 链表问题2——在双链表中删除倒数第K个节点

    题目 实现一个函数,可以删除双链表中倒数第K个节点. 要求 如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1). 思路 双链表的思路与前一篇文章单链表的思路基本一致,注意last指针 ...

  9. 如何在Java中创建一个新的List

    本文翻译自:How to make a new List in Java We create a Set as: 我们创建一个Set为: Set myset = new HashSet() How d ...

最新文章

  1. TensorFlow AI 新品更易用!联手NVIDIA,支持Swift和JavaScript
  2. 熬夜变傻有科学依据,人类睡觉时会被“洗脑”,科学家首次拍下全程
  3. Delphi XE5 常见问题解答
  4. 路由器中的管理间距和量度参数
  5. 再谈fedora下的音乐和视频播放器的安装
  6. 更新KB915597补丁后导致“您的windows副本不是正版”的解决方案
  7. 使用Gradle引导旧式Ant构建
  8. 关于 springcloud gateway 设置 context-path 的问题
  9. Oracle中“不等于”的使用
  10. Keil 中的预处理命令const
  11. windowswps怎么以文件形式发送_wps怎么以文件形式分享
  12. E9启动后无法打印日志
  13. 詹姆斯——永远的皇帝
  14. JetBrains下载历史版本(IDEA、PyCharm、WebStorm、PhpStorm等)
  15. 假设检验中两类错误及最小样本量计算
  16. ZYNQ之AXI简介
  17. 解读运营指标:DAU/MAU
  18. uniapp设置百度小程序索引页dynamicLib、usingComponents、swan-sitemap-list
  19. 挖个冰块就能修自己!用「冰」做的科考机器车
  20. 西游记中会七十二变的三个人

热门文章

  1. python flask 教程_Flask 教程 第一章:Hello, World!
  2. 华为java安全编码规范_Java安全编码之SQL注入
  3. proto文件支持继承吗_搞懂 Javascript中this 指向及继承原理
  4. linux7开放3306端口,CentOS 7 开放3306端口访问
  5. SQL之 Stuff和For xml path
  6. clob存base64文件存不进去_Kafka 和 RocketMQ 底层存储之那些你不知道的事
  7. python的迭代器for_python特性(二):迭代器与for语句
  8. linux系统core dump设置,linux coredump设置
  9. 从0 开始 DIY你的Arduino UNO
  10. 交叉熵损失函数公式_交叉熵损失函数对其参数求导