文章目录
列表
说到列表,我们一般会想到它的2种实现方式:数组和链表。下面对比一下2种方式的优缺点:
底层实现 | 特点 | 优点 | 缺点 |
---|---|---|---|
数组 | 内存地址空间连续 | 1. 支持O(1)复杂度通过偏移量随机访问元素 | 1.插入删除操作的平均时间复杂度为O(n); 2.存在未使用的数组空间,一定程度上浪费了内存空间; 3.数组需要动态扩容缩容和新旧数组复制,影响性能 |
链表 | 内存地址不连续,每个链表节点都会记录下一个节点的内存地址 | 1. 插入删除操作平均时间复杂度为O(1); 2. 链表多长就占多少个空间,没有浪费多余内存 |
元素查询时间复杂度为O(n),n为链表长度; |
接下来我以Java的List举例说明2种实现方式。
数组实现(ArrayList)
List实现类ArrayList的底层实现是数组,现假设有整型列表List<Integer> arrList = new ArrayList<>();
,其底层数组如图:
ArrayList默认的容量大小是10,当我们往列表添加7个元素后,效果如图所示,如果要获取第4个元素22,我们只需进行arrList.get(3)
就行,时间复杂度O(1)。但当我们要删除第4个元素时,要把它后面所有值一次往前挪一位,平均时间复杂度为O(n),同样的插入时后面所有元素一次往后挪一位。
链表实现(LinkedList)
List实现类LinkedList的底层实现是链表,由一个或多个链表节点Node连接而成,是一个双端链表,底层实现结构如下图:
同样的,当我们要访问第4个元素时,它需要从头开始遍历,路径为41->2->16->22,才能得到指定位置的元素,平均时间复杂度为O(n)。如果要删除第4个元素,在删除的那瞬间,只需要把被删除元素前驱节点的next和后继结点的prev改变即可,没有元素的移动,只需要O(1)复杂度,但要注意的是:仅仅是删除的瞬间为O(1),删除前我们要先找到这个元素,查找时间复杂度仍为O(n),插入操作同理。
关于有序列表的思考
有序列表指的是列表里面的元素值是有序的,要么升序要么降序,按某种指定的规则来排序。对于有序的数组,我们可以用二分法花费O(logn)复杂度查找到指定元素所在的下标,但从数组的结构来看,如果要在数组中删除指定元素或在指定元素的前/后插入新元素,时间复杂度O(n);对于有序的链表,查询元素位置复杂度依旧是O(n),插入和删除也是O(1)没变。
一句话概括就是:有序数组查询快、增删慢,有序链表查询慢、增删快。
假设我们现在想要实现一个即能快速查询、又能做到快速增删的数据结构,我们该如何取舍呢?答案就是:“小孩子才做选择题,我全都要!~”,我们今天的合体版主角应运而生——跳跃表。(忽略前摇过长- -!)
什么是跳跃表?
跳跃表