跳表数据结构原理和应用如何深入解析?

摘要:跳表SKIPLIST 本文是新学习的内容 跳表是对有序链表的一种改进 有序链表随机访问很慢,不能像数组一样二分 (有序链表是默认从小到大排序,跳表这里也一样) 跳表支持的操作:插入一个((key,val))元素 删除(key)的元素
跳表SKIPLIST 本文是新学习的内容 跳表是对有序链表的一种改进 有序链表随机访问很慢,不能像数组一样二分 (有序链表是默认从小到大排序,跳表这里也一样) 跳表支持的操作:插入一个\((key,val)\)元素 删除\(key\)的元素,查找元素\(key\)的\(val\),查找\(key\)第\(k\)小的元素的\((key,val)\) 这里面是\(key\)有序 对于有序链表只有一层 跳表是间隔一些节点就向上增加一层然后上一层同理 这样子查找时候就可以先根据上层确定一个大概的范围再往下跳,次数大大减少了 具体来说,最底下一层每个元素都有,相当于原始有序链表,标记为第\(0\)层 每往上一层层数编号就\(+1\) 然后每个节点是否在上一层出现,是由一个概率\(p\)决定的,也就是说p的概率会出现在上一层,那么上一层到再上一层还是一个道理,\((1-p)\)的概率到这一层为止 然后每一层的节点还是按照顺序连接起来组成有序链表 每个节点只需要记录,他的\(key\),\(val\),以及后继\(nexts\) 我们不妨给最底下一层的节点开一个\(nexts[i]\)数组,因为每一层除了\(nexts[i]\)不一样,剩下都是一样的,这样就相当于共用一个节点,不同的\(nexts[i]\)代表不同的层 然后需要一个\(heads\)作为每一层链表的头部,头部的\(key\)取一个极小值比如-INT_MAX 这样的话插入任何元素都插入在\(heads\)后面 然后需要记录一个全局变量\(lv\),是当前的最上面一层的编号,那么第\(lv\)层就是最上面一层,这样子插入时候增加层数只需要往后放而不是往前插入 层数: 然后这个层数有多少层呢,我们希望是把有\(1/p\)个节点的这一层作为最上面一层 这样期望的层数\(L\)有多少呢? 最底下一层有\(n\)个,第\(1\)层有\(np\)个,第\(2\)层有\(np^2\)个…第\(L-1\)层有\(np^{(L-1)}\)个 \(np^{(L-1)}=p^{(-1)}\) \(np^L=1\) \(L=log(p,1/n)\) \(=log(1/p,n)\) 空间复杂度: 虽然说层数到了\(logn\)级别,但是每个点不是都每个层都有,有些节点层数高,有些节点层数低,所以均摊起来应该算每个节点的期望层数,所以就是问每个点的期望层数: 那么设期望层数是\(x\),\(p\)的概率有上一层,那么此时层数是\(x+1\),\((1-p)\)的概率就只有这一层 所以\(x=p(x+1)+1-p\) \((1-p)x=1\) \(x=1/(1-p)\) 所以每个点的期望层数是常数 所以说期望空间复杂度\(O(n)\) 当然最坏每个点都到最上层\(O(nlogn)\) 但是现实是我们不同层的共用一个节点只是\(nexts[i]\)不同,所以说要提前开好最大层数来算的空间所以就\(O(nlogn)\)了 查询操作: 假如要查询\(num\)这个\(key\)值的\(val\) 从第\(lv\)层开始,首先确定\(num\)处在哪两个节点之间,然后才能往下找,这里面就有一个边界问题,就是区间端点问题,所以我们规定在点\(x\)和点\(y\)(相邻)之间的元素是\((x->key,y->key]\),左开右闭,所以说查找时候我们一直往右找 直到这个点右边\((nexts[i])\)的\(key >=num\),那么\(num\)就落在这个点和这个点的\(nexts[i]\)的区间里,那么就要从这个点往下一层找了 然后就去下一层同一个点开始找,还是同样的道理 一直到第\(0\)层,最后循环结束假如在\(p\)点,那么\(num\)在\(p\)和\(p->nexts[0]\)之间,也就是说如果\(num\)存在,那么\(num\)和\(p->nexts[0]->key\)相等,直接返回对应的\(val\)即可 然后有一个边界问题,如果说给出的\(num\)比这一层最大的\(key\)都大,那么到这一层最后一个元素时候\(p->nexts[i]\)是空的,所以相当于没有上界,直接跳到下一层。如果到最后一层了,然后走到头\(p->nexts[0]\)是空的,那么实际上这个\(num\)就不存在。
阅读全文