数组、链表和跳表的基本实现和特征

一、数组(Array)

  • 查询操作:每一个数组都对应着一串连续的内存地址,每一个元素对应这串连续地址中的其中一个地址,这些地址由内存管理器(Memory controller)来管理。所以访问其中的任何一个元素的时间复杂度为O(1),数组的空间复杂度为O(n)
    数组、链表和跳表的基本实现和特征_第1张图片
  • 插入操作:插入操作的步骤有两步,第一步确定插入的下标位置,移动该下标位置之后的所有元素,第二步将元素插入到指定的下标位置,时间复杂度为O(n)。如果插入到最后一个元素,不需要移动其他的元素,时间复杂度为O(1);如果插入到第一个位置,需要移动所有的元素,时间复杂度为O(n)。所以整体的时间复杂度为O(n)。
    数组、链表和跳表的基本实现和特征_第2张图片
  • 删除操作:同插入操作,分两步,先删除指定的元素再前移后边的元素。需要注意的是:前移之后原来最后一个元素的位置设置为空,可以唤起垃圾回收机制,或者把数组的size重新调整。
    数组、链表和跳表的基本实现和特征_第3张图片

二、链表(Linked List)

  • 链表包括Head、Tail和中间节点组成,
  • 每一个节点(包括Head和Tial)都由value和Next指针构成,Next指向下一个节点。如果每一个节点包含Prev/Previous指向前一个节点,则该链表为双向链表,如果不包括Prev,则该链表为单向链表。
  • 如果Tail的Next指向None,则该链表为普通的链表;如果Tail的Next指向了Head,则该链表为循环链表
    数组、链表和跳表的基本实现和特征_第4张图片
  • 随机访问链表中的节点的时间复杂度为O(n)
  • 增加节点的操作:前一个节点的Next指向新的Node,新的Node的的Next指向前一个节点Next原来指向的Node,时间复杂度为O(1)
    数组、链表和跳表的基本实现和特征_第5张图片
  • 删除节点的操作:前一个节点的Next打掉,拿掉要删除的节点,前一个节点的Next指向删除节点的后一个节点,时间复杂度为O(1)
    数组、链表和跳表的基本实现和特征_第6张图片

    区分和数组的增加和删除操作:数组的时间复杂度为O(n)的原因是需要移动元素,链表的复杂度为O(1)的原因是不需要移动元素,只需要改动节点指针的指向节点即可。

三、跳表

  • 跳表对标的是平衡树(AVL Tree)和二分查找,是一种插入、删除和搜索都是O(log n)的数据结构
  • 跳表的使用前提:必须是元素有序的链表情况下才可以使用,换句话说,跳表必须是有序的
    数组、链表和跳表的基本实现和特征_第7张图片
  • 最大的优势:原理简单、容易实现、方便扩展、效率更高。在一些热门的项目中用来代替平衡树,如Redis、LevelDB等。
  • 跳表中相对于原始链表搜索的优化思路:升维和空间换时间。在一维的基础上根据需要添加一维或者多维的搜索索引。例如:查找链表中的数字5,先比较1-7,数字小于7,向下一级索引再比较1-4,大于4,又小于7,所以再向下一级索引找到5。
    数组、链表和跳表的基本实现和特征_第8张图片
    数组、链表和跳表的基本实现和特征_第9张图片
  • 跳表查询的时间复杂度分析:n/2、n/4、n/8、第k级索引节点的个数就是n/(2^k),假设索引有h级,最高的索引有2个节点。n/(2^h) = 2,从而求得h = log2(n) - 1。在跳表中查询任意数据的时间复杂度就是O(logn)
  • 在现实中跳表的索引并不是完全工整的,并且每次的增加和删除节点都会导致跳表索引的更新一遍,维护成本较高,时间复杂度为O(logn)。
    数组、链表和跳表的基本实现和特征_第10张图片
  • 跳表空间复杂度分析:

    • 原始链表大小为n,每2个节点抽一个,每层索引的节点数:n/2, n/4, n/8,......,8, 4, 2
    • 原始链表大小为n,每3个节点抽一个,每层索引的节点数:n/3, n/9, n/27, ......, 9, 3, 1
    • 所以空间复杂度为O(n)
总结:跳表优化了原始链表查询操作的时间复杂度,从O(n)提升至O(logn),但是由于加多级索引的原因,跳表的空间复杂度还是O(n),没有得到优化。

你可能感兴趣的:(python)