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

举报
李锐博恩 发表于 2021/07/15 05:46:22 2021/07/15
【摘要】 上两篇博文讲了如何在单链表中插入一个值: 【 C 】在单链表中插入一个新节点的尝试(一) 【 C 】在单链表中插入一个新节点的尝试(二) 这篇博文讲解如何在双链表中插入一个值。 单链表的替代方案就是双链表。在一个双链表中,每个节点都包含两个指针——指向前一个节点的指针和指向后一个节点的指针。这可以使我们以任何方向遍历双链表,甚至可以忽前忽后地在双链表中访问。下图展示...

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

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

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

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


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

 

下面是节点类型的声明:


  
  1. typedef struct NODE
  2. {
  3. struct NODE *fwd;
  4. struct NODE *bwd;
  5. int value;
  6. } 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字段必须设置为指向新节点。

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

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

程序:


  
  1. //简明的双链表插入函数
  2. //把一个值插入到一个双链表中,rootp是一个指向根节点的指针,value是要插入的新值
  3. //返回值:如果欲插值原先已经存在与链表中,函数返回0
  4. //如果内存不足导致无法插入,返回返回-1;如果插入成功,函数返回1
  5. #include < stdlib.h>
  6. #include < stdio.h >
  7. #include < dll_node.h > //这是自己建立的一个头文件,用于保存双链表的节点声明
  8. int dll_insert( Node *rootp, int new_value )
  9. {
  10. Node *this; //指向新节点之前的那个节点
  11. Node *next; //指向新节点之后的那个节点
  12. Node *newnode; //指向新节点本身
  13. //查看value是否已经存在于链表中,如果是就返回。
  14. //否则,为新值创建一个新的节点(newnode将指向它)
  15. for( this = rootp; ( next = this->fwd ) != NULL; this = next )
  16. {
  17. if( next->value == new_value ) //如果插入的新值在链表中已经存在,则返回0,不需要插入
  18. return 0;
  19. if( next->value > new_value ) //如果插入的新值小于当前节点的值,则直接跳出循环,也就是需要在该节点之前插入新值
  20. break;
  21. //如果上面两种情况都不是,则插入的新值大于当前节点的值,继续循环
  22. }
  23. //为新节点开辟内存空间
  24. newnode = ( Node*)malloc( sizeof( Node ) );
  25. if( newnode == NULL )
  26. return -1; //如果内存分配不成功,则返回 -1;
  27. newnode->value = new_value; //为新节点赋新值
  28. if( next != NULL ) //这个条件的意思是新节点并非位于链表尾部
  29. {
  30. if(this != rootp ) //新节点并非位于链表起始位置
  31. {
  32. newnode->fwd = next;
  33. this->fwd = newnode;
  34. newnode->bwd = this;
  35. next->bwd = newnode;
  36. }
  37. else //新节点位于链表起始位置
  38. {
  39. newnode->fwd = next;
  40. rootp->fwd = newnode;
  41. newnode->bwd = NULL;
  42. next->bwd = newnode;
  43. }
  44. }
  45. else //新节点位于链表的尾部
  46. {
  47. if( this != rootp ) //新节点并非位于链表的起始位置(链表不为空)
  48. {
  49. newnode->fwd = NULL;
  50. this->fwd = newnode;
  51. newnode->bwd = this;
  52. rootp->bwd = newnode;
  53. }
  54. else
  55. {
  56. //新节点既位于起始位置,又位于尾部,也就是说原链表为空
  57. newnode->fwd = NULL;
  58. rootp->fwd = newnode;
  59. newnode->bwd = NULL;
  60. rootp->bwd = newnode;
  61. }
  62. }
  63. return 1;
  64. }

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

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


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

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

( next = this->fwd ) == NULL
 

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


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

循环终止。

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

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

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


  
  1. newnode->fwd = next;
  2. this->fwd = newnode;

链表的样子如下:

然后,执行下列语句:


  
  1. newnode->bwd = this;
  2. next->bwd = newnode;

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

 

 

文章来源: reborn.blog.csdn.net,作者:李锐博恩,版权归原作者所有,如需转载,请联系作者。

原文链接:reborn.blog.csdn.net/article/details/82665322

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。