数组和链表对比
数组是连续的内存区域、链表不是连续的内存区域。
因此,数组在通过下标查询时的时间复杂度低,而链表在查询时需要遍历链表,所以时间复杂度较高。
对于插入和删除,因为数组是连续的内存区域,所以每次插入和删除都需要做数据的迁移,因此时间复杂度较高。而链表因为在内存区域本身就不是连续的,所以时间复杂度比数组低。
链表分类
常见的有:单向链表、循环链表、双向链表、双向循环链表。
链表时间复杂度
查找:O(n)
插入、删除:O(1)
注:双向链表在很多情况下的查找、插入、删除时间复杂度都要低于单向链表,是一种用空间换时间的方式。
写链表代码技巧
技巧一:理解指针或引用的含义
实际上,如果画个图看懂链表的结构并不是很难。难点在于链表往往伴随着指针。和指针混在一起就容易难以理解。所以,要想写对链表代码,一定要理解清楚指针的用法。
技巧二:警惕指针丢失和内存泄漏
(图来自王争《数据结构与算法之美》课程)
如图,当我们希望在a结点与b结点之间插入结点x,那么,如果我们这么写:
p->next = x; // 将 p 的 next 指针指向 x 结点;
x->next = p->next; // 将 x 的结点的 next 指针指向 b 结点;
则会出现指针丢失的情况,因为b结点的地址存储于p->next中,而第一步我们就将p->next中存储的地址换为了x结点的地址,那么b结点的地址就丢失了。此时我们需要x->next指针存储b的地址却已经找不到b结点的地址了。
解决的办法就是把上面两个代码调换下位置。
当我们删除链表结点时,一定要记得手动释放内存空间,否则会出现内存泄漏的情况。当然,对于像Java这种有虚拟机自动管理内存的语言,就不需要考虑这些了。
技巧三:利用哨兵简化实现难度
简单的说,就是给单链表加一个哨兵结点,如图:
(图来自王争《数据结构与算法之美》课程)
这样我们在插入结点时,碰到插入头节点也无需进行额外判断。在删除结点时,碰到删除最后一个结点也无需进行额外判断。
技巧四:重点留意边界条件处理
在写代码时,当正常情况下程序运行时,最应当注意的是边界情况与异常情况,边界情况与异常情况可以处理的很好,这个程序代码才可以说是健壮的。
在写链表代码时,有如下几个边界条件需要重点留意:
- 如果链表为空时,代码能否正常工作?
- 如果链表只包含一个结点时,代码是否能正常工作?
- 如果链表只包含两个结点时,代码是否能正常工作?
- 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
技巧五:举例画图,辅助思考
在写链表代码时,如果脑袋里装了太多的处理过程总是会比较吃力的,此时可以借助画图的方式来帮助自己理清思路,辅助思考。
技巧六:多写多练
凡是不肯花相应的时间和精力是一定做不好的。所以要多花时间和精力做练习。
常见链表操作
- 单链表反转
- 链表中环的检测
- 两个有序的链表合并
- 删除链表倒数第n个结点
- 求链表的中间结点
总结
写链表代码不是件简单的事,必须付出相应的时间和精力才会有所成长。然后同时也要注意使用以上技巧,会有所帮助。