存储特点:1、零散内存空间和相同类型的数据 2、线性表(数据之间只有前后关系)
下标随机访问效率低()
链表特点:
构成 | 特点 | 适用场景 | |
单链表 | 结点(数据+后继指针) | 头结点:第一个结点(记录链表基地址) 尾结点:最后一个结点(指针指向NULL) | |
循环链表 | 同上 | 尾结点指针指向链表的头结点 | 约瑟夫问题(丢手绢) |
双向链表 | 结点(前驱指针+数据+后继指针) |
复杂度分析:
插入 | 删除 | 查询 | 问题 | |
单链表 | O(n) | O(n) 根据值查询,O(n)+删除O(1) O(n) 根据结点删除,找前驱结点O(n) | 随机访问O(n)
数组的效率高是因为内存地址连续 | 为什么有序单链表不能用二叉树? |
循环链表 | ||||
双向链表 | O(1) | O(n) 根据值查询,O(n)+删除O(1) O(1) 根据结点删除,找前驱结点O(1) | 内存不连续只能一一比较 | |
双向循环列表 |
数组和链表对比
插入 | 删除 | 查询 | 内存空间 | |
数组 | O(n) | O(n) | 随机访问O(1) | 连续(CPU缓存机制,可以预读) |
链表 | O(1) | O(n) | O(n) | 零散(CPU缓存不友好,无法预读) |
链表代码书写技巧
1、理解指针或引用的含义:将变量赋值给指针,就是把变量的地址赋值给指针,通过指针就能找到变量
2、防止指针丢失和内存泄漏(先将后面的后继结点赋值好,在赋值好前面的后继结点)
3、利用哨兵,可以节约效率(就是一个特定值)
4、边界条件处理
如果链表为空时,代码是否能正常工作?
如果链表只包含一个结点时,代码是否能正常工作?
如果链表只包含两个结点时,代码是否能正常工作?
代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
5、举例画图,辅助思考
单链表反转
链表中环的检测
两个有序的链表合并
删除链表倒数第 n 个结点
求链表的中间结点