splay做排序
2018将至,这也就当娱乐吧
洛谷上有一篇splay版的,但那份题解中的代码太长(4.3k),这里只有1.4k
其实其它题解都挺简单易懂的
但是本蒟蒻发现对于排序,二叉排序树中就带着“排序”俩字,为啥不用它呢
(实践证明虽然打不过STL,但在其它的算法中还挺快的,总共152ms,加上随机化会更快)
使用这种二叉树就有一点二分的感觉
——用一棵二叉树去找一个元素在一个已经排好序的序列中的位置
并且找到位置后只用加上一个儿子节点即可,不用一个序列从前往后整体移动(花销很大)
↑这里将时间复杂度从 O(n) O ( n ) 降到了 O(log2n) O ( l o g 2 n )
但直接在一棵二叉排序树中插入节点有可能退化为一条链的情况(数据刚好从大到小,时间复杂度化为
O(n2)
O
(
n
2
)
)
所以加上维护排序树平衡的操作(就是splay中的左旋右旋)
左旋右旋就是在保证整棵树的中序遍历不变的前提下改变这棵树的构造,具体可以百度(写在洛谷里面可能有点占空间)
左右旋转之后基本能保证插入时间复杂度平均在 O(nlog2n) O ( n l o g 2 n ) 左右
下面贴上自以为很短的代码(比4.3k版本小了许多):
#include<bits/stdc++.h>
using namespace std;
#define rg register
#define blood(x) (ch[f[x]][1]==x)
template <typename _Tp> inline void read(_Tp&x){
char c11=getchar();x=0;bool booo=0;
while(c11!='-'&&!isdigit(c11))c11=getchar();if(c11=='-'){c11=getchar();booo=1;}
while(isdigit(c11)){x=x*10+c11-'0';c11=getchar();}if(booo)x=-x;return ;
}
const int N=105000;
int data[N],f[N],ch[N][2];
int root,sz,n;
void init();
inline void rotate(int x){
//左旋与右旋 这里其实将左右旋合并了,使代码简短了很多
int fa=f[x],grand=f[f[x]],le=blood(x);
f[x]=grand;
if(grand)ch[grand][blood(fa)]=x;
ch[fa][le]=ch[x][le^1];
if(ch[fa][le])f[ch[fa][le]]=fa;
f[fa]=x;ch[x][le^1]=fa;
return ;
}
void splay(int x,int target){
for(rg int fa;(fa=f[x])!=target;rotate(x))
if(f[f[x]]!=target)
rotate(blood(x)==blood(f[x])?f[x]:x);
if(!target)root=x;
return ;
}
void ins(int x){
if(!root){root=++sz;ch[sz][0]=ch[sz][1]=f[sz]=0,data[sz]=x;return ;}
int now=root;
while("Happy New Year!!!")
if(ch[now][data[now]<x])
now=ch[now][data[now]<x];
else break;
ch[now][data[now]<x]=++sz;
f[sz]=now,data[sz]=x;
ch[sz][0]=ch[sz][1]=0;
splay(sz,0);
return ;
}
void print(int now){//输出
if(ch[now][0])print(ch[now][0]);
printf("%d ",data[now]);
if(ch[now][1])print(ch[now][1]);
return ;
}
int main(){
init();
print(root);
return 0;
}
void init(){read(n);int A;for(rg int i=1;i<=n;++i){read(A);ins(A);}return ;}//读入