链表定义
链表是一种线性表
链表的内存结构是不连续的
链表的每一块内存被称为节点(node),除了存储数据还要存储指针
链表的特点
- 插入\删除效率高
时间复杂度O(1),需要已知要插入\删除位置之前的节点指针,插入\删除时,只需要操作相关指针
如果是删除节点中值等于某个给定值的节点,时间复杂度为O(n),需要遍历链表
- 查找效率低
查找时间复杂度(O(n)),需要从头遍历链表
- 内存消耗高
和数组相比,内存消耗高,需要额外存储指针
链表数组对比
- 常用操作时间复杂度对比
插入 | 删除 | 查找 | |
---|---|---|---|
数组 | O(n) | O(n) | O(1) |
链表 | O(1) | O(1) | O(n) |
- 优缺点对比
数组
优点:简单易用,使用连续的内存,可以借助缓存机制,提高效率
缺点:数组占用连续内存,当需要的内存空间比较大时,容易出现内存不足问题;大小固定,当数组存储空间不够时,需要进行扩容,此时需要拷贝原有数据到新内存地址,操作比较耗时
链表
优点:链表没有大小限制,天然支持动态扩容
缺点:占用内存高,需要额外存储指针;插入删除操作频繁,会导致频繁的内存申请和释放,容易产生内存碎片,导致频繁的gc(java 自动垃圾回收)
常用链表
- 单链表
每个节点只包含一个指针
尾节点的后继指针为null
- 循环链表
除尾节点指向首节点外,其它同单链表
适合存储有循环特点的数据,如约瑟夫环问题
- 双向链表
节点除存储数据外,还存储两个指针,一个指向前驱节点,一个指向后继节点
尾节点的后继指针、首节点的前驱指针为null
双向链表使用更方便,所以实际使用更广泛
- 双向循环链表
尾节点的后继指针指向首节点,首节点的前驱指针指向尾节点
轻松写出链表代码技巧总结
- 理解指针\引用的含义
将某个变量(对象)赋值给指针(引用),实际上就是就是将这个变量(对象)的地址赋值给指针(引用)
- 警惕指针丢失和内存泄漏
- 利用“哨兵”简化实现难度
链表中的“哨兵”节点是解决边界问题的,不参与业务逻辑。
“哨兵”节点不存储数据,无论链表是否为空,head指针都会指向它,作为链表的头结点始终存在。这样,插入(删除)第一个节点和插入(删除)其他节点都可以统一为相同的代码实现逻辑。
- 重点留意边界条件处理
如果链表为空,代码能否正常工作
如果链表只含有一个(或者两个)节点,代码能否正常工作
代码能否正确处理头结点\尾节点
- 举例画图,辅助思考
- 多写多练