《算法与数据结构》学习笔记5---链表(下)

前言

    本篇主要介绍写链接代码的一些方法。

正文

  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 = x;  // 将 p 的 next 指针指向 x 结点;
x->next = p->next;  // 将 x 的结点的 next 指针指向 b 结点;

    p->next 指针在完成第一步操作之后,已经不再指向结点 b 了,而是指向结点 x。第 2 行代码相当于将 x 赋值给 x->next,自己指向自己。因此,整个链表也就断成了两半,从结点 b 往后的所有结点都无法访问到了。
    对于有些语言来说,比如 C 语言,内存管理是由程序员负责的,如果没有手动释放结点对应的内存空间,就会产生内存泄漏。所以,在插入结点时,一定要注意操作的顺序,要先将结点 x 的 next 指针指向结点 b,再把结点 a 的 next 指针指向结点 x,这样才不会丢失指针,导致内存泄漏。所以,对于刚刚的插入代码,我们只需要把第 1 行和第 2 行代码的顺序颠倒一下就可以了。正确代码如下:

x->next = p->next;
p->next = x;

    删除链表结点时,也一定要记得手动释放内存空间,否则,也会出现内存泄漏的问题。当然,对于像 Java 这种虚拟机自动管理内存的编程语言来说,就不需要考虑这么多了。

  1. 利用哨兵简化实现难度
        前面提到了在结点p后面插入一个结点,只需要两步,但是如果要向一个空链表中插入第一个结点,上面提到的方法 就不可用了。那么在空链表中插入第一个结点的操作为:
if (head == null) {
 head = new_node;
}

    对于单链表,如果要删除结点p的后继结点:

p->next = p->next->next;

    但是如果删除链表中的最后一个结点:

if (head->next == null) {
   head = null;
}

    **针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。**这样代码实现起来就会很繁琐,不简洁,而且也容易因为考虑不全而出错。
    head=null 表示链表中没有结点了。其中 head 表示头结点指针,指向链表中的第一个结点。如果引入哨兵结点,在任何时候,不管链表是不是空,head 指针都会一直指向这个哨兵结点。也把这种有哨兵结点的链表叫带头链表。相反,没有哨兵结点的链表就叫作不带头链表。
在这里插入图片描述
    哨兵结点是不存储数据的。因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为相同的代码实现逻辑了。实际上,这种利用哨兵简化编程难度的技巧,在很多代码实现中都有用到,比如插入排序、归并排序、动态规划等。

  1. 留意边界条件处理
    软件开发中 ,代码在一些边界或者异常情况下,最容易产生 Bug。链表代码也不例外。要实现没有 Bug 的链表代码,一定要在编写的过程中以及编写完成之后,检查边界条件是否考虑全面,以及代码在边界条件下是否能正确运行。
    可以用来检查链表代码正确的边界条件:
         - 如果链表为空时,代码是否能正常工作?
         - 如果链表只包含一个结点时,代码是否能正常工作?
         - 如果链表只包含两个结点时,代码是否能正常工作?
        - 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?

   5.举例画图,辅助思考
    如果想不明白,那就画个图。
在这里插入图片描述

  1. 勤加练习
    熟能生巧。
    列举5个常见的链表操作:
        - 单链表反转
        - 链表中环的检测
        - 两个有序链表合并
         - 删除链表倒数第N个结点
         - 求链表的中间结点

PS:内容为本人学习笔记 ,转自极客时间上的数据结构与算法课程。

猜你喜欢

转载自blog.csdn.net/xue605826153/article/details/86920162