如何轻松写出的正确的链表代码
技巧一:掌握指针或引用的含义
事实上,看懂链表的结构并不是很难,但是一旦把它和指针混在一起,就和容易让人摸不着头脑。所以,要写好代码,首先就要理解好指针。
有些语言有“指针”的概念 ,比如C语言;有些语言没有指针,取而代之的是“引用”。实际上,它们的意思都是一样的,都是存储所指对象的内存地址。
对于指针的理解:将某个变量赋值给指针,实际上就是将这变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就可以找到这个变量。
在编写链表代码的时候,我们经常会这样写:p->next=q,这行代码是说,p结点的next指针存储的是q结点的内存地址。
还有一个比较复杂的:p->next=p->next->next,这行代码表示,p结点的next指针存储的是p结点的下下个结点的内存地址。
技巧二:警惕指针丢失和内存泄漏
写链表代码的时候,指针指来指去,一会就不知道指哪里了。所以,我们在写的时候,一定注意不要丢了指针。
指针是怎么弄丢的呢?举例说明一下:假如一个链表有头结点a和尾结点b两个结点,我们希望在a和b之间插入结点x,假设当前指针p指向结点a,即p=a如果向下述一样实现代码,就会发生指针丢失和内存泄漏。
p->next=x;//将p的next指针指向x结点;
x-next=p->next;//将x的next结点指向b结点
但是这里有个问题:p->next指针在完成第一步操作之后,已经不再指向结点p了,而是指向结点x。第二行代码相当于将x赋给x->next,自己指向自己。因此,整个链表就断成了两半,从b以后的结点都无法访问了。
技巧三:利用哨兵简化实现难度
首先,回顾一下单链表的插入和删除操作。如果我么在p结点的后边插入一个新的结点,下面两行代码就能实现:
new_node->next=p-next;
p->next=new_node;
但是,对于一个空链表插入一个新节点,上述代码逻辑就不能用了。我们需要进行如下的特殊处理:
if(head=null){head=new_node}
单链表删除结点,要删除p结点的后继结点,只需要一行代码就能实现:
p->next=p->next->next
但是,如果我们要删除链表的尾结点,上述代码逻辑就不work了。跟插入类似,我们也需要做如下的特殊处理:
if(p->next==null){p=null;}
从上述的分析得出,针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况做特殊处理。这样代码就会看起来很繁琐,不简洁,而且也容易因为考虑不全而出错。如何来解决这个问题呢?
哨兵就可以派上用场了。
若我们引入哨兵结点,在任何时候,不管链表是不是空,head指针都会一直指向这哨兵结点。我们把这种有哨兵结点的链表称作带头链表。相反,没有哨兵的链表叫做不带头链表。
哨兵结点是不存储数据的。因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为相同的代码了。
技巧四:重点留意边界条件处理
软件开发中,代码在以下边界或者异常情况下,最容易产生Bug。
我们经常用来检查链表代码是否正确的边界条件有这几个:
1)如果链表为空时,代码是否正常工作?
2)如果链表只包含一个结点时,代码是否正常工作?
3)如果链表只包含两个结点时,代码是否正常工作?
4)代码处理头结点和尾结点时,代码是否能正常工作?
技巧五:举例画图,辅助思考
技巧六:多写多练,没有捷径
如果已理解不能够掌握之前的技巧,但是手写代码还是会出现各种各样的错误,也不要着急,就是把常见的链表操作都自己多写几遍,出问题就一点一点调试,孰能生巧。
下述选了链表的五个常用操作。将这几个操作都能写熟练,不熟就多写几遍,链表代码就不用害怕了。
1)单链表反转
2)链表中环的检测
3)两个有序的链表合并
4)删除链表倒数第n个结点
5)求链表的中间结点
下一篇:数据结构与算法:栈:如何实现浏览器的前进和后退功能?