跳跃表简要解析

(看见NOI大纲中有这个没听说过的东西,于是赶紧了解了一下)
链表可以快速插入,但随机访问的复杂度是 O ( n ) O(n) O(n) 的,相比于 O ( log ⁡ n ) O(\log n) O(logn) 的平衡树很慢,就遭受到了各位大佬的嫌弃。于是为了给链表正名,我们尝试优化它。(以下数字为节点编号)
0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 0\rightarrow1\rightarrow2\rightarrow3\rightarrow4\rightarrow5\rightarrow6\rightarrow7\rightarrow8 012345678
结合倍增的思想,我们尝试加几层链表辅助查找
0 → 2 → 4 → 6 → 8 0\rightarrow2\rightarrow4\rightarrow6\rightarrow8 02468
0 → 4 → 8 0\rightarrow4\rightarrow8 048
0 → 8 0\rightarrow8 08
这样显然可以看出查找的复杂度是 O ( log ⁡ n ) O(\log n) O(logn)
但插入删除的时候,如果只插入删除最底层,结构会发生变化,导致复杂度退化。插入上层太多也不行。如何衡量插入几层呢?
考虑随机化。假设我们把一个节点插入第 i i i 层时,以 1 p \frac1p p1 的概率同时把这个节点插入第 i + 1 i+1 i+1 层。
i + 1 i+1 i+1 层的两个数之间期望有 p p p 个第 i i i 层的数,共有 log ⁡ p n \log_pn logpn 层,单次操作复杂度 O ( p log ⁡ p n ) O(p\log_pn) O(plogpn) p p p e e e 的时候最优。当然这些复杂度都是期望的,普通的二叉搜索树还是单次操作期望 O ( log ⁡ n ) O(\log n) O(logn) 的呢,实际上很容易T飞。所以还要证明消耗时间高的概率很小。第 i + 1 i+1 i+1 层两个数之间在第 i i i 层插入一个数且不会进入第 i + 1 i+1 i+1 层的概率是 1 − 1 p 1-\frac1p 1p1 ,所以操作时每一层遍历 k k k 个节点的概率是 ( 1 − 1 p ) k (1-\frac1p)^k (1p1)k,所以单次操作总遍历节点数为 k k k 的概率为 f ( k ) = ( 1 − 1 p ) k ( k + log ⁡ p n − 1 log ⁡ p n − 1 ) f(k)=(1-\frac1p)^k\binom{k+\log_pn-1}{\log_pn-1} f(k)=(1p1)k(logpn1k+logpn1) f ( k + 1 ) = ( 1 − 1 p ) k + log ⁡ p n k + 1 f ( k ) f(k+1)=(1-\frac1p)\frac{k+\log_pn}{k+1}f(k) f(k+1)=(1p1)k+1k+logpnf(k) ,如果 p p p 2 2 2 可以发现当 k k k 超过层数后 f ( k ) f(k) f(k) 在指数级衰减, k k k 较大的概率可以忽视掉 。
这样的数据结构被命名为跳跃表,容易理解,代码也比较短,可以支持 O ( log ⁡ n ) O(\log n) O(logn) 区间查询、区间修改、插入、删除、翻转、合并、分裂,还能可持久化、套其它数据结构,总之和非旋treap的作用极度重合,速度也差不多,唯一优势就是加底层优化后会比较快(所以在OI中并没有什么用)。
以下是洛谷 P3369 【模板】普通平衡树 的代码

#include<bits/stdc++.h>
using namespace std;
const int P=RAND_MAX/exp(1),inf=1e9;
int read(){
	int x=0,f=0;char c;
	while(!isdigit(c=getchar()))f|=c=='-';
	for(;isdigit(c);c=getchar())x=x*10+c-'0';
	return f?-x:x;
}
const int N=13;
struct lis{
	int a,s;
	lis*r,*d;//r指当前层链表的下一个,d指对应节点的下一层
	void up(){s=0;
		for(lis*i=d;i!=r->d;i=i->r)s+=i->s;
	}
	lis*ins(int x){
		if(r->a<x)return r->ins(x);
		if(!d)return r=new lis{x,1,r,0};
		lis*p=d->ins(x);up();
		if(!p||(rand()>P))return 0;
		r=new lis{x,1,r,p};
		r->up();up();
		return r;
	}
	void del(int x){
		if(s<x)r->del(x-s);
		else if(s>x)d->del(x),up();
		else if(!d)r=r->r;
		else{
			d->del(x);
			r=r->r;up();
		}
	}
	int ask(int x){
		return r->a<x?s+r->ask(x):d?d->ask(x):1;
	}
	int kth(int x){
		return x?s>x?d->kth(x):r->kth(x-s):a;
	}
};
int main(){
	srand(time(0));
	lis*L=0,*R=0;
	for(int i=N;i;i--){
		R=new lis{inf,1,0,R};
		L=new lis{-inf,1,R,L};
	}
	for(int n=read(),t,x;n;n--){
		t=read();x=read();
		if(t==1)L->ins(x);
		if(t==2)L->del(L->ask(x));
		if(t==3)cout<<L->ask(x);
		if(t==4)cout<<L->kth(x);
		if(t==5)cout<<L->kth(L->ask(x)-1);
		if(t==6)cout<<L->kth(L->ask(x+1));
		if(t>2)cout<<'\n';
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值