Splay Tree 是二叉查找树的一种,它与平衡二叉树、红黑树不同的是,Splay Tree从不强制地保持自身的平衡,每当查找到某个节点n的时候,在返回节点n的同时,Splay Tree会将节点n旋转到树根的位置,这样就使得Splay Tree天生有着一种类似缓存的能力,因为每次被查找到的节点都会被搬到树根的位置,所以当80%的情况下我们需要查找的元素都是某个固定的节点,或者是 一部分特定的节点时,那么在很多时候,查找的效率会是O(1)的效率!当然如果查找的节点是很均匀地分布在不同的地方时,Splay Tree的性能就会变得很差了,但Splay Tree的期望的时间复杂度还是O(nlogn)的。
为了使访问的节点调到树根,必定要有像维护平衡二叉树那样的旋转操作,来维持二叉树节点间的偏序关系;与平衡二叉树类似,Splay有2种旋转操作(左旋zag、右旋zig)和4种旋转情况(LL,LR,RL,RR);旋转操作要保持二叉查找树的性质,通过对访问点x的位置来判断旋转情况,对应的情况使用对应的旋转操作序列,就可以让x的所属层上升,循环对x操作就可以把x调到根节点的位置。
/ \ p x / \ Zig(x) / \ x <> -----------------> <> p / \ / \ <> <> <> <> |
/ \ x p / \ Zag(x) / \ p <> <----------------- <> x / \ / \ <> <> <> <> |
1 struct node 2 { 3 int data; 4 node *left,*right,*father; 5 node(int d=0,node* a=NULL,node *b=NULL,node *c=NULL):data(d),left(a),right(b),father(c){} 6 }*root; 7 void zig(node *k) 8 { 9 node* fa=k->father; 10 fa->left=k->right; 11 if (k->right) k->right->father=fa; 12 k->right=fa; 13 k->father=fa->father; 14 fa->father=k; 15 if (!k->father) return; 16 if (k->father->data>k->data) 17 k->father->left=k; 18 else 19 k->father->right=k; 20 } 21 void zag(node *k) 22 { 23 node* fa=k->father; 24 fa->right=k->left; 25 if (k->left) k->left->father=fa; 26 k->left=fa; 27 k->father=fa->father; 28 fa->father=k; 29 if (!k->father) return; 30 if (k->father->data>k->data) 31 k->father->left=k; 32 else 33 k->father->right=k; 34 } 35 void splay(node *k,node *&root) 36 { 37 while (k->father) 38 { 39 node *fa=k->father; 40 if (fa->father==NULL) 41 { 42 if (k==fa->left) zig(k); 43 else zag(k); 44 45 } else 46 { 47 node *gf=fa->father; 48 if (fa==gf->left && k==fa->left){zig(fa);zig(k);} 49 if (fa==gf->left && k==fa->right){zag(k);zig(k);} 50 if (fa==gf->right && k==fa->left){zig(k);zag(k);} 51 if (fa==gf->right && k==fa->right){zag(fa);zag(k);} 52 } 53 } 54 root=k; 55 }