目录
技巧一:理解指针或者引用的含义
技巧二:警惕指针丢失和内存泄漏
技巧三:利用哨兵简化实现难度
技巧四:重点留意边界条件处理
技巧五:举例画图,辅助思考
技巧六:多写多练,没有捷径
链表的概念回顾:
头结点和头指针的区别
- 链表中第一个结点的存储位置叫做头指针,那么整个链表的存取就必须是从头指针开始进行了。之后的每一个结点,其实就是上一个的后继指针指向的位置。链式存储时只要不是循环链表,就一定存在头指针。
- 头指针就是链表的名字。头指针仅仅是个指针而已。头结点是为了操作的统一与方便而设立的,放在第一个元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等)。有了头结点后,对在第一个元素结点前插入结点和删除第一个结点,其操作与对其它结点的操作统一了
- 头结点不是链表所必需的。
- 头指针具有标识作用,故常用头指针冠以链表的名字。无论链表是否为空,头指针均不为空。头指针是链表的必要元素
写链表代码是最考验逻辑思维能力的。因为,链表代码到处都是指针的操作、边界条件的处理,稍有不慎就容易产生 Bug。链表代码写得好坏,可以看出一个人写代码是否够细心,考虑问题是否全面,思维是否缜密。所以,这也是很多面试官喜欢让人手写链表代码的原因
自己有决心并且付出精力是成功的先决条件,除此之外,我们还需要一些方法和技巧。分享几个写链表代码技巧。
技巧一:理解指针或者引用的含义
- 不管是“指针”还是“引用”,实际上,它们的意思都是一样的,都是存储所指对象的内存地址。
- 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量
技巧二:警惕指针丢失和内存泄漏
写链表代码的时候,指针指来指去,一会儿就不知道指到哪里了。所以,我们在写的时候,一定注意不要弄丢了指针。
单链表的插入操作为例:
- 插入结点时,一定要注意操作的顺序
- 要先将结点 x 的 next 指针指向结点 b,再把结点 a 的 next 指针指向结点 x,这样才不会丢失指针,导致内存泄漏
- 删除结点时,一定要手动释放内存
正确的插入操作:
x->next = p->next; // 将x的结点的next指针指向b结点;
p->next = x; // 将p的next指针指向x结点;
技巧三:利用哨兵简化实现难度
单链表的插入和删除操作分析:单链表的插入操作,第一个结点和其他结点的插入逻辑是不一样的;
- 向一个空链表中插入第一个结点
if (head == null) {head = new_node;
}
-
结点 p 后面插入一个新的结点
new_node->next = p->next;
p->next = new_node;
- 删除结点 p 的后继结点
p->next = p->next->next;
- 删除链表中的最后一个结点
if (head->next == null) {head = null;
}
针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。这样代码实现起来就会很繁琐,不简洁,而且也容易因为考虑不全而出错。
如何表示一个空链表吗?head=null 表示链表中没有结点了。其中 head 表示头结点指针,指向链表中的第一个结点。如果我们引入哨兵结点,在任何时候,不管链表是不是空,head 指针都会一直指向这个哨兵结点。我们也把这种有哨兵结点的链表叫带头链表。相反,没有哨兵结点的链表就叫作不带头链表。
带头链表的示意图:
利用哨兵简化编程难度的技巧,在很多代码实现中都有用到,比如插入排序、归并排序、动态规划等。
技巧四:重点留意边界条件处理
软件开发中,代码在一些边界或者异常情况下,最容易产生 Bug。链表代码也不例外。要实现没有 Bug 的链表代码,一定要在编写的过程中以及编写完成之后,检查边界条件是否考虑全面,以及代码在边界条件下是否能正确运行:
检查链表代码是否正确的边界条件有以下几个:
- 如果链表为空时,代码是否能正常工作?
- 如果链表只包含一个结点时,代码是否能正常工作?
- 如果链表只包含两个结点时,代码是否能正常工作?
- 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
实际上,不光光是写链表代码,你在写任何代码时,也千万不要只是实现业务正常情况下的功能就好了,一定要多想想,你的代码在运行的时候,可能会遇到哪些边界情况或者异常情况。遇到了应该如何应对,这样写出来的代码才够健壮!
技巧五:举例画图,辅助思考
微复杂的链表操作,比如前面我们提到的单链表反转,指针一会儿指这,一会儿指那,一会儿就被绕晕了。总感觉脑容量不够,想不清楚。所以这个时候就要使用大招了,举例法和画图法。
技巧六:多写多练,没有捷径
精选了 5 个常见的链表操作。你只要把这几个操作都能写熟练,不熟就多写几遍,我保证你之后再也不会害怕写链表代码。
- 单链表反转
- 链表中环的检测
- 两个有序的链表合并
- 删除链表倒数第 n 个结点
- 求链表的中间结点
以上几个操作的代码后面更新补充