前面讲解了平衡二叉查找树,注意到一个结点被访问后,接下来很有可能被再次访问,所以可以采取把最近访问的结点上移(比如说移动到根结点),方便以下访问。这次要讲解的伸展树就是来解决该问题的。
伸展树又称自适应查找树,它的各种操作平均复杂度为O(lgn),其复杂度边界是均摊的。虽然某次操作可能需要代价O(n),但是连续M次操作的代价为O(M*lgn)。
伸展树的最核心的操作就是伸展,插入、删除、查找都要用到伸展操作。一般来说,伸展操作可以从搜索路径上最后一个结点开始,自底向上进行左旋和右旋操作,但是该方式需要知道父节点信息,而计算父节点或存储父节点需要较大的开销。于是一般采用自顶向下的实现方式。从根结点开始搜索某一结点x,不断进行左旋和右旋,同时把大于x的结点接到树R的左子树中,把小于x的结点接到树L的右子树上,直到查找路径的最后一个结点。然后把树进行重组,就得到了伸展后的树。
详细原理请看参考资料,参考资料[1][2]都对原理进行了详尽的阐述,并提供了实现。下面直接给出代码,代码中有详细注释。
#include <cstdlib>
#include <iostream>
using namespace std;
typedef struct BinNode
{
int data;
struct BinNode* left;
struct BinNode* right;
}BinNode, *BiTree;
BinNode* splay(int i, BinNode* t)
{
BinNode N; //哨位结点
BinNode* L;
BinNode* R;
BinNode* y;
// if(t == NULL) //空树
// return t;
N.left = N.right = NULL;
L = R = &N; //指针L和R初始指向哨位结点N
while(true)
{
if(i < t->data)
{
if(t->left != NULL && i < t->left->data)//右旋转
{
y = t->left;
t->left = y->right;
y->right = t;
t = y;
}
if(t->left == NULL)
{
break;
}
R->left = t; //右链接,把t挂接为R的左子树
R = t; //t为新的R
t = t->left; //在t的左子树中继续查找
}
else if(i > t->data)
{
if(t->right != NULL && i > t->right->data)//左旋转
{
y = t->right;
t->right = y->left;
y->left = t;
t = y;
}
if(t->right == NULL)
{
break;
}
L->right = t; //左链接,把t挂接为L的右子树
L = t; //t为新的L
t = t->right; //在t右子树中继续查找
}
else //与当前根结点元素相同,重复
{
break;
}
}
L->right = t->left; //重新组合
R->left = t->right;
t->left = N.right; //N.right为实际左子树的根
t->right = N.left; //N.left为实际右子树的根
return t;
}
BinNode* insert(int i, BinNode* t)
{
BinNode* newNode = new BinNode;//开辟一个新结点
newNode->data = i;
newNode->left = newNode->right = NULL;
if(t == NULL) //t为空树
{
return newNode;
}
t = splay(i,t); //返回伸展后的新根结点
if(i < t->data) //欲插入元素在树中不存在,且小于根结点元素值
{
newNode->left = t->left;
newNode->right = t;
t->left = NULL;
return newNode;
}
else if(i > t->data) //欲插入元素在树中不存在,且小于根结点元素值
{
newNode->right = t->right;
newNode->left = t;
t->right = NULL;
}
else //欲插入元素在树中存在,即等于根结点元素值
{
delete newNode;
return t;
}
}
BinNode* remove(int i, BinNode* t)
{
BinNode* newRoot = NULL;
if(t == NULL) //空树
{
return t;
}
t = splay(i,t);
if(i == t->data) //找到该元素结点
{
if(t->left == NULL) //根结点左子树为空
{
newRoot = t->right;
}
else
{
newRoot = splay(i, t->left);//newRoot必然指向t的左结点中最大的结点,
//也就是伸展前t的前驱结点
newRoot->right= t->right;
}
delete t;
return newRoot;
}
return t; //树中不存在该元素
}
BinNode* search(int i, BinNode* t, bool& exist)
{
exist = false;
if(t == NULL)
return NULL;
t = splay(i, t);
if(i == t->data)
{
exist = true;
}
return t;
}
void inOrder(BinNode* t)
{
if(t)
{
inOrder(t->left);
cout<<t->data<<" ";
inOrder(t->right);
}
}
void preOrder(BinNode* t)
{
if(t)
{
cout<<t->data<<" ";
preOrder(t->left);
preOrder(t->right);
}
}
void printTree(BinNode* t)
{
cout<<"preOrder: "<<endl;
preOrder(t);
cout<<endl;
cout<<"inOrder: "<<endl;
inOrder(t);
cout<<endl<<endl;
}
int main(int argc, char *argv[])
{
BinNode* root = NULL;
bool res = false;
//insert
for(int i=20; i>0; i--)
root = insert(i,root);
cout<<"after insert: "<<endl;
printTree(root);
//remove
for(int i= 1; i<20; i += 2)
root = remove(i,root);
cout<<"after remove :"<<endl;
printTree(root);
//search
cout<<"search elements: "<<endl;
for(int i=1; i<20; i++)
{
root = search(i,root,res);
if(res)
{
cout<<"("<<i<<","<<"yes"<<")"<<endl;;
}
else
{
cout<<"("<<i<<","<<"no"<<")"<<endl;
}
}
cout<<"after search: "<<endl;
printTree(root);
system("PAUSE");
return EXIT_SUCCESS;
}
参考资料:
[1]Data Structures and Algorithm Analysis in C++(third editon) (数据结构与算法分析C++描述,第3版 )
[2一篇博客文章:http://www.cnblogs.com/kernel_hcy/archive/2010/03/17/1688360.html
[3]详细C和java实现:http://www.link.cs.cmu.edu/link/ftp-site/splaying/