伸展树(Splay Tree),或者叫自适应查找树,插入、查找和删除操作的时间都为O(logn)。
伸展树的目的是使被查频率高的那些条目就应当经常处于靠近树根的位置。它的做法是在每次查找后,将被查找的节点splay到根节点。
使用伸展树需要符合90-10法则:在实际情况中,90%的访问发生在10%的数据上。
优点:不需要记录用于平衡树的冗余信息 伸展树节点结构:
struct SplayTree { int value; int lchild; //左儿子在数组中的下标 int rchild; //右儿子在数组中的下标 int parent; //为0表示是根节点 }; SplayTree s[MAX];
有两个基本的操作: 1、伸展操作Splay(T,i)是在保持伸展树有序性的前提下,通过一系列旋转将伸展树T中的元素i调整至树的根部
void Zig(int i){ //节点i右旋转 int j = s[i].parent; s[j].lchild = s[i].rchild; //i的右儿子成为j的左儿子 s[s[i].rchild].parent = j; s[i].rchild = j; s[i].parent = s[j].parent; s[j].parent = i; if(s[i].parent != 0){ if(s[s[i].parent].lchild == j){ s[s[i].parent].lchild = i; }else{ s[s[i].parent].rchild = i; } } } void Zag(int i){//节点i左旋转 int j = s[i].parent; s[j].rchild = s[i].lchild; s[s[i].lchild].parent = j; s[i].lchild = j; s[i].parent = s[j].parent; s[j].parent = i; if(s[i].parent != 0) { if(s[s[i].parent].lchild == j) { s[s[i].parent].lchild = i; } else { s[s[i].parent].rchild = i; } } } void Splay(int T, int i){ while(s[i].parent != T){ if(s[s[i].parent].parent == T){ //父节点是根节点 if(s[s[i].parent].lchild == i) Zig(i); //如果是父节点的左儿子,则右旋 else Zag(i); //如果是父节点的右儿子,则左旋 break; }else{ int j = s[i].parent; int k = s[j].parent; if(s[k].lchild == j && s[j].lchild == i){ Zig(j); Zig(i); }else if(s[k].rchild == j && s[j].rchild == i){ Zag(j); Zag(i); }else if(s[k].lchild == j && s[j].rchild == i){ Zag(i); Zig(i); }else if(s[k].rchild == j && s[j].lchild == i){ Zig(i); Zag(i); } } } }
2、Join(T1,T2):将两个伸展树T1与T2合并成为一个伸展树。其中T1的所有元素都小于T2的所有元素。首先,我们找到伸展树T1中最大的一 个元素i,再通过Splay(T1,i)将i调整到伸展树T1的根。然后再将T2作为i节点的右子树。这样,就得到了新的伸展树i。
int Join(int T1, int T2){ int i = T1; while(s[i].rchild != -1){ i = s[i].rchild; } Splay(0, i); s[i].rchild = T2; s[T2].parent = i; return i; }
其他操作大体与二叉查找树中的操作一样,只需在操作后调用Splay进行元素调整