数据结构与算法之链表篇(下)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_41923658/article/details/84151997

Q:如何轻松写出正确的链表代码?

总结起来,就是投入时间+技巧;

一、投入时间:

         只要愿意投入时间,大多数人都是可以学会的,比如说,如果你真能花上一个周末或者一整天时间,就去写链表反转这一个代码,多谢几遍,一直练到能毫不费力地写出Bug free的代码。

二、技巧:

1、理解技巧或引用的含义

        链表的结构并不是很难,但是一旦它和指针混在一起,就容易让人摸不着头脑,所以要写对链表代码,首先要理解好指针。

        有些语言有“指针”概念,比如C语言;有些语言没有指针,取而代之的是“引用”,比如:Java,Python,实际上都是储存所指对象的内存地址。

        指针的理解,可以总结为下面一句话:

        将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来,指针中储存了这个变量的地址,指向了这个变量,通过指针就可一找到这个变量。举个例子:

         在编写链表代码的时候,我们经常会这样写代码:p->next=q;这行代码就是说,p结点中的next指针存储了q结点的内存地址;

       复杂一点:p->next=p->next->next,这行代码就是说,p结点的next指针存储了p结点的下下一个结点的内存地址。

    2、警惕指针丢失和内存泄漏

        以单链表的插入操作为例,来进行分析:

         在结点a和相邻的结点b之间插入结点x,假设当前指针p指向结点a。如果我们将代码实现成下面这个样子,就会发生指针丢失和内存泄漏。

 p->next指针在完成第一步操作之后,已经不再指向结点b了,而是指向结点x。第2行代码相当于将x赋值给x-next,自己指向自己。因此,整个链表也就断成了两半,从结点b往后的结点都无法访问了。

         对于有些语言来说,比如C语言,内存管理是由程序员负责的,如果没有手动释放结点对应的内存空间,就会产生内存泄漏。所以我们插入结点时,一定要注意操作的顺序,要先将结点x 的next指针指向结点b,再把结点a的next指针指向结点x ,对于刚刚插入代码,我们只需要将第一行和第二行的代码顺序颠倒以下就可以。

        同理,删除链表结点时,也一定要记得释放内存空间,否则,也会出现内存泄漏的问题。当然对于像JAVA,就不必考虑这么多。

3、利用哨兵简化实现难度

但我们要向一个空链表中插入第一个结点,需要进行下面的特殊处理,其中head表示链表的头结点。所以,对于单链表的插入操作,第一个结点和其他结点的插入逻辑是不一样的。

对于单链表结点删除操作,如果要删除结点p的后继结点,只需一行代码就可以搞定。

但是,如果我们要删除链表中的最后一个结点,前面的删除代码就失效了。那么跟删除类似,我们也需要对于种情况特殊处理。代码如下:

我们可以看出,对于链表的插入和删除操作,需要对插入的第一个结点和删除最后一个结点的情况进行特殊处理。代码实现起来比较繁琐,那么如何让解决呢?

那么我们就需要引入“哨兵”,由上面可知,head=null表示链表中没有结点,其中head表示头指针结点,指向链表的第一个结点。

如果引入“哨兵”结点,在任何时候,不管链表是不是空,head一直会指向这个哨兵结点,我们也把这种带有“哨兵”结点的链表叫做带头链表,相反,没有“哨兵”结点的链表叫做不带头链表。

下图是一个带头链表,你可以发现,“哨兵”结点是不存储数据的,因为“哨兵”结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为相同的代码实现逻辑了。

这种用“哨兵"简化变成难度技巧,在很多代码实现中都有用到,比如:插入排序,归并排序、动态规划等等。举个例子感受一下:

         代码一:

代码二:

 对于两段代码,在字符串很长的时候,代码二运行更快,因为两段代码中执行次数最多的部分就是while循环那一部分。第二段代码中,我们通过一个“哨兵”a[n-1]=key,成功省掉了一个比较语句i<n,不要小看这一条语句,当累积执行几万次的时候,累积的时间就很明显了。

4、重点留意边界条件处理

 检查链表代码是否正确的边界条件

(1)、如果链表为空,代码是否能正常工作?

(2)、如果链表只包含一个结点时,代码是否能正常工作?

(3)、如果链表只包含两个结点时,代码是否能正常工作?

(4)、代码逻辑在处理头结点和尾结点的时候,能否正常工作?

当然,边界条件不止我列举的那些,针对不同的场景,可能还有特定的边界条件,需要具体思考,不过套路一样。

5、举例画图,辅助思考

举个例子:往单链表中插入一个数据,我们可以把各种情况都列举出来,画出插入前与插入后的变化(如下图)

6、多写多练,没有捷径

对于不熟练的操作代码,多写几遍,出了问题一点一点调试,熟能生巧。

Ps:五种常见的链表操作


欢迎大家扫码关注微信公众号,其中含有有大量免费的人工智能、图像处理、IT资料:

                                                                             Change,There is no better way !

猜你喜欢

转载自blog.csdn.net/weixin_41923658/article/details/84151997