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

举报
李锐博恩 发表于 2021/07/15 06:08:02 2021/07/15
【摘要】 目录 背景 第一个技巧是语句提炼(statement factoring) 第二个简化技巧 最终简化版本 背景 上篇博文:【 C 】在双链表中插入一个新值的简明程序,讲了一个简明的双链表插入函数,那个函数写的比较中规中矩,就是按照思维逻辑写的,几乎谁都懂,但是程序未免有些冗余,对于优秀的程序员来说,出现这么多的重复代码会感到厌烦。(《c与指针》说的优秀的程序员呀...

目录

背景

第一个技巧是语句提炼(statement factoring)

第二个简化技巧

最终简化版本


背景

上篇博文:【 C 】在双链表中插入一个新值的简明程序,讲了一个简明的双链表插入函数,那个函数写的比较中规中矩,就是按照思维逻辑写的,几乎谁都懂,但是程序未免有些冗余,对于优秀的程序员来说,出现这么多的重复代码会感到厌烦。(《c与指针》说的优秀的程序员呀,什么时候我也能对此感到厌烦呀!)

下面给出一些技巧来消除这些重复的代码:

先把上篇博文中代码贴出来,这篇博文就是以此为基础,来提炼它:


  
  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. }

上面的这段代码中最需要提炼的这部分(把新节点插入到链表中):


  
  1. if( next != NULL ) //这个条件的意思是新节点并非位于链表尾部
  2. {
  3. if(this != rootp ) //新节点并非位于链表起始位置
  4. {
  5. newnode->fwd = next;
  6. this->fwd = newnode;
  7. newnode->bwd = this;
  8. next->bwd = newnode;
  9. }
  10. else //新节点位于链表起始位置
  11. {
  12. newnode->fwd = next;
  13. rootp->fwd = newnode;
  14. newnode->bwd = NULL;
  15. next->bwd = newnode;
  16. }
  17. }
  18. else //新节点位于链表的尾部
  19. {
  20. if( this != rootp ) //新节点并非位于链表的起始位置(链表不为空)
  21. {
  22. newnode->fwd = NULL;
  23. this->fwd = newnode;
  24. newnode->bwd = this;
  25. rootp->bwd = newnode;
  26. }
  27. else
  28. {
  29. //新节点既位于起始位置,又位于尾部,也就是说原链表为空
  30. newnode->fwd = NULL;
  31. rootp->fwd = newnode;
  32. newnode->bwd = NULL;
  33. rootp->bwd = newnode;
  34. }
  35. }

第一个技巧是语句提炼(statement factoring)

如下面的例子所示:


  
  1. if( x == 3 )
  2. {
  3. i = 1;
  4. ...
  5. j = 2;
  6. }
  7. else
  8. {
  9. i = 1;
  10. ...
  11. j = 2;
  12. }

注意不管表达式 x == 3的值是真还是假,语句 i = 1 和 j = 2 都将执行。在 if 之前执行 i = 1 将不会影响 x == 3 的测试结果,所以这两条语句都可以被提炼出来,这样就产生了同样简单但同样完整的语句:


  
  1. i = 1;
  2. if( x == 3 )
  3. {
  4. ...
  5. }
  6. else
  7. {
  8. ...
  9. }
  10. j = 2;

警告:如果if之前的语句会影响到测试的结果,那么千万不能把它提取出来,例如,下面的例子:


  
  1. if( x == 3 )
  2. {
  3. x = 0;
  4. ...
  5. }
  6. else
  7. {
  8. x = 0;
  9. ...
  10. }

语句x = 0,如果提到if之前那就完蛋了。


根据这个语句提炼的技巧,我们来处理插入链表程序的第一个简化:(简化1)


  
  1. if( next != NULL ) //这个条件的意思是新节点并非位于链表尾部
  2. {
  3. newnode->fwd = next; //只要新节点不位于链表尾部,新节点的前向指针就指向下一个节点
  4. if(this != rootp ) //新节点并非位于链表起始位置
  5. {
  6. this->fwd = newnode;
  7. newnode->bwd = this;
  8. }
  9. else //新节点位于链表起始位置
  10. {
  11. rootp->fwd = newnode;
  12. newnode->bwd = NULL;
  13. }
  14. next->bwd = newnode; //只要新节点不位于链表尾部,next指向的节点的后向指针就指向新节点
  15. }
  16. else //新节点位于链表的尾部
  17. {
  18. newnode->fwd = NULL; //新节点位于链表的尾部,则新节点的前向指针为空(NULL)
  19. if( this != rootp ) //新节点并非位于链表的起始位置(链表不为空)
  20. {
  21. this->fwd = newnode;
  22. newnode->bwd = this;
  23. }
  24. else
  25. {
  26. //新节点既位于起始位置,又位于尾部,也就是说原链表为空
  27. rootp->fwd = newnode;
  28. newnode->bwd = NULL;
  29. }
  30. rootp->bwd = newnode; //新节点位于链表尾部,那么根节点的后向指针就指向新节点
  31. }

根据上面的技巧简化了下,道理也能说得通,程序也没有那么冗余了。确实不错!


第二个简化技巧

用下面的例子说明:


  
  1. if( pointer != NULL )
  2. field = pointer;
  3. else
  4. field = NULL;

这段代码的意图就是设置一个和 pointer 相等的变量,如果pointer 未指向任何东西,这个变量就设置为 NULL。但是,请看下面这条语句:

field = pointer;
 

和上面的那条语句功能完全一致!但是简洁很多。

根据这个情况,我们可以把(简化1)程序中的这条语句改进下:


  
  1. if( next != NULL ) //这个条件的意思是新节点并非位于链表尾部
  2. {
  3. newnode->fwd = next; //只要新节点不位于链表尾部,新节点的前向指针就指向下一个节点
  4. ...
  5. }
  6. else //新节点位于链表的尾部
  7. {
  8. newnode->fwd = NULL; //新节点位于链表的尾部,则新节点的前向指针为空(NULL)
  9. ...
  10. }

改为(简化2)


  
  1. if( next != NULL ) //这个条件的意思是新节点并非位于链表尾部
  2. {
  3. newnode->fwd = next; //只要新节点不位于链表尾部,新节点的前向指针就指向下一个节点
  4. ...
  5. }
  6. else //新节点位于链表的尾部
  7. {
  8. newnode->fwd = next; //新节点位于链表的尾部,则新节点的前向指针为空(NULL)
  9. ...
  10. }

上面的改动引起了连锁效应,因为技巧1中可以再次对(简化2)进行提炼,如下:


  
  1. newnode->fwd = next;
  2. if( next != NULL ) //这个条件的意思是新节点并非位于链表尾部
  3. {
  4. ...
  5. }
  6. else //新节点位于链表的尾部
  7. {
  8. ...
  9. }

上面比较完整的程序版本是这样的:


  
  1. newnode->fwd = next;
  2. if( next != NULL ) //这个条件的意思是新节点并非位于链表尾部
  3. {
  4. if(this != rootp ) //新节点并非位于链表起始位置
  5. {
  6. this->fwd = newnode;
  7. newnode->bwd = this;
  8. }
  9. else //新节点位于链表起始位置
  10. {
  11. rootp->fwd = newnode;
  12. newnode->bwd = NULL;
  13. }
  14. next->bwd = newnode; //只要新节点不位于链表尾部,next指向的节点的后向指针就指向新节点
  15. }
  16. else //新节点位于链表的尾部
  17. {
  18. if( this != rootp ) //新节点并非位于链表的起始位置(链表不为空)
  19. {
  20. this->fwd = newnode;
  21. newnode->bwd = this;
  22. }
  23. else
  24. {
  25. //新节点既位于起始位置,又位于尾部,也就是说原链表为空
  26. rootp->fwd = newnode;
  27. newnode->bwd = NULL;
  28. }
  29. rootp->bwd = newnode; //新节点位于链表尾部,那么根节点的后向指针就指向新节点
  30. }

亮瞎纯钛合金眼镜的时刻到了,嵌套的两个if语句竟然完全一致,这样就出大事了,根据技巧1,可以把这两个嵌套内的if语句提出来,如下:(简化3)


  
  1. newnode->fwd = next;
  2. if(this != rootp )
  3. {
  4. this->fwd = newnode;
  5. newnode->bwd = this;
  6. }
  7. else
  8. {
  9. rootp->fwd = newnode;
  10. newnode->bwd = NULL;
  11. }
  12. if( next != NULL )
  13. {
  14. next->bwd = newnode;
  15. }
  16. else
  17. {
  18. rootp->bwd = newnode;
  19. }

最后一次需要简化的是第一个if语句,用第2个技巧简化:


  
  1. newnode->fwd = next;
  2. this->fwd = newnode;
  3. if(this != rootp )
  4. {
  5. newnode->bwd = this;
  6. }
  7. else
  8. {
  9. newnode->bwd = NULL;
  10. }
  11. if( next != NULL )
  12. {
  13. next->bwd = newnode;
  14. }
  15. else
  16. {
  17. rootp->bwd = newnode;
  18. }

最终简化版本

那最后给出最终完整版本:


  
  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. newnode->fwd = next;
  29. this->fwd = newnode;
  30. if(this != rootp )
  31. {
  32. newnode->bwd = this;
  33. }
  34. else
  35. {
  36. newnode->bwd = NULL;
  37. }
  38. if( next != NULL )
  39. {
  40. next->bwd = newnode;
  41. }
  42. else
  43. {
  44. rootp->bwd = newnode;
  45. }
  46. return 1;
  47. }

过程很有意思,但是我却不愿意读了。。。

程序真的很简化了,简化到了我恐怕都不认识了。也许是我菜吧!供大家参考!

 

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

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

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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