六.链表(下):如何轻松写出正确的链表代码

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

在这里插入图片描述

一.理解指针和引用的含义

我们知道,有些语言有指针的概念,比如c语言。有些语言没有指针,取而代之为“引用”,比如java,Python。实际上,它们的意思都是存储所指对象的内存地址。
接下来,我们用指针来讲解,也可以将它理解为引用。其实对于指针的理解,我们只需要记住下面这句话就行了:将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
在编写链表代码时,我们有用到这样的代码:p–>next = q 。含义:p结点中的next指针存储了q结点的内存地址。

二.警惕指针丢失和内存泄漏

不知道有没有这样的感觉,写链表代码时,指针指来指去,最后就不知道指到哪里去了。所以,我们在写的时候,一定注意不要丢了指针。
下面通过一个栗子,我们来看看指针怎么弄丢的?
在这里插入图片描述
如图所示,我们希望在结点a和相邻的结点b之间插入结点x,假如当前结点p指向结点a。如果我们将代码变为下面的样子,就会导致指针丢失和内存泄漏。

p -->next = x; //将p的next指针指向x结点;
x –>next = p -->next; //将x的结点的next指针指向b的结点;
初学者经常在这里犯错,p -->next指针在完成第一次操作之后,已经不再指向结点b了,而是指向结点x。第二行代码相当于将x赋值给x -->next,自己指向自己,因此,整个链表就会断为两半,从结点b往后的所有结点都无法访问了。
对于有些语言来说,比如c语言,内存管理是由程序员负责的,如果没有手动释放结点对应的内存空间,就会导致内存泄漏。所以,我们在插入结点时,一定要注意操作的顺序,要先将结点x的next指针指向b,再把结点a的next指针指向结点x,这样才不会导致丢失指针,导致内存泄漏。所以,我们插入结点时,需要将刚刚的顺序对调一下就行。

三.利用哨兵简化实现难度

首先,我们来回顾一下单链表的插入和删除操作,如果我们在结点p后面插入一个新的结点,只需要下面两行代码就可以搞定

New_node –>next = p next;
P next = new_node;
但是,当我们向一个空链表插入第一个结点,刚刚的逻辑就不能使用了。我们需要进行下面的特殊处理,其中head表示链表的头结点。所以,从这段代码,我们发现,对于单链表的操作,第一个结点和其他结点的插入逻辑是不一样的。

If(head == null){
Head = new_node;
}
我们再来看看单链表结点删除操作,如果删除结点p的后继结点,我们只需要一行代码就可以搞定。

p –>next = p—>next–>next;
但是,如果我们要删除链表中最后一个结点,前面的删除就不ok了。跟插入类似,我们需要对这种情况做特殊处理,代码就变为这样

If(headnext == null){
Head = null;
}
从前面的分析可以看出,针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况做特殊处理。导致代码繁琐,如何解决这个问题?
现在到哨兵登场了,它只参与解决“边界问题”的,不直接参与业务逻辑。如果我们引入哨兵结点,在任何时候,不管链表是不是空,head指针都会一直指向这个哨兵结点。我们也把这种有哨兵结点的链表称为带头链表。相反,没有哨兵结点的链表就称为不带头链表。

观察下面的带头链表,我们可以发现,哨兵结点时不存储数据的。因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以使用相同的代码逻辑。

在这里插入图片描述

实际上,这种利用哨兵简化编程难度的技巧,在很多代码实现中都会用到,比如插入排序,归并排序,动态规划等。

四.重点留意边界条件处理

软件开发中,代码在一些边界或者 异常情况下,最容易产生Bug。链表代码也不例外。要实现没有Bug的链表代码,一定要在编写的过程中以及编写完成之后,检查边界条件是否考虑周全,以及代码在边界条件下是否能正确运行。
我们总结了一些考虑情况,希望大家注意:
1)如果链表为空时,代码能否能正常工作?
2)如果链表只包含一个结点时,代码是否能正常工作?
3)如果链表只包含两个结点时,代码是否正正常工作?
4)代码逻辑在处理头结点和尾节点的时候,是否能正常工作?
当我们写完链表代码时,除了看下在正常情况下能否工作,还要看在下上面列举的边界条件下能否正常工作。如果都没有问题,基本上就没有问题。

五.举例画图,辅助思考

对于稍微复杂的链表操作,我们可以使用举例法和画图法。
比如我们往单链表中插入一个数据操作,我们一般都是把各种情况举一个栗子,画出插入前和插入后的链表变化,如图所示
在这里插入图片描述

六.多写多练,没有捷径

如果你已经理解并掌握了以上的内容,但是还是发现代码会出现问题,归根结底,还是不熟练导致的,将常见的链表操作多写几遍,出问题一点点调试,孰能生巧!
以下5个常见的链表操作,希望大家多多联系
1)单链表反转
2)链表中环的检测
3)两个有序的链表合并
4)删除链表倒数的第n个结点
5)求链表的中间结点

内容小结

本节我们主要学习了写出正确链表代码的六个技巧,希望大家多思考,多练多写。

猜你喜欢

转载自blog.csdn.net/Milogenius/article/details/86727669