您的位置:首页 > 其它

自顶向下的伸展树

2015-07-15 20:58 274 查看
一 伸展树的性质

  伸展树保证从控制开始任意连续M次对树的操作最多花费O(MlogN)时间,一棵伸展树每次操作的摊还(amortized)代价是O(logN)。伸展树的基本思想是,当一个节点被访问后,它就要经过一系列旋转操作被推到根上。另外,伸展树还不要求保留高度或平衡信息。

二 伸展树的伸展操作

  在自底向上的伸展操作的直接实现需要从根沿树往下的一次遍历,以及而后的自底向上的一次遍历。这可以通过保存一些父链来完成,也可以通过将访问路径存储到栈中来完成。但是,这两种方法均需大量的开销,而且两者都必须处理许多特殊的情况(六种,包括镜像的单旋转、一字型旋转、之字形旋转)。

  在自顶向下的操作是在初始访问路径上施行一些旋转,只用到O(1)的附加空间,但却保持了O(logN)的摊还时间界。伸展树SplayTree的节点的域包括:data, left, right。
  在访问的任意时刻,都有一个当前节点X,它是其子树(即树M)的根;每次访问时,需要考虑将节点Y/Z作为下次迭代时树M的根节点X,然后通过伸展操作将树M中小于Y/Z的节点存储在树L中、将大于Y/Z的节点存储在树R中,除了Y/Z的子树的节点外。其中,Y表示沿单向访问路径的下个节点,Z表示沿单向范文路径的下下个节点,单向访问路径是指访问左子树后继续向左子树访问。初始时X为树T的根,而L和R是空树。此外,还可以发现,树L的根节点的右子节点每次都为NULL,树R的根节点的做自己每次都为NULL。

  假设搜索条件为d时,若满足以下条件,那么迭代结束,且树M的根节点仍为X;

    ① X->data == d

    ② X的左子节点为空

    ③ X的右子节点为空

  否则,当满足以下之一条件时,节点Y将被推为下次迭代时树M的根节点X:

    ① Y为X的左子节点,且Y->data <= d

    ② Y为X的右子节点,且Y->data >= d

    ③ 节点Z为空

  否则,节点Z将被推为下次迭代时树M的根节点X。

  


  上图从上至下分别是单旋转、一字型旋转和之字形旋转的实现。然而,在自顶向下的伸展操作中,之字形旋转操作可以简化成两次单旋转操作。

  若下次迭代时节点Y为树M的根,那么执行单旋转操作,节点Y变成树M的新根,X和子树B作为R中最小项的左儿子附接到R上,X的左儿子变成NULL;

  若下次迭代时节点Z为树M的跟,那么执行一字型旋转操作,在X和Y之间施行一次旋转,把Y和X的子树附接到R上,Y的左儿子变成NULL。

  在迭代结束时们需要处理树L、树M、树R以形成一棵树,姑且可以称之为重聚操作。如下图所示,由于树L的根节点没有右子节点,可以将节点X的左子树挂成树L的根节点的右子树;类似地,将节点X的右子树挂成树R的根节点的左子树。最后将树L和树R分别挂成节点X的左子树和右子树,此时,所有节点重聚成一棵树,根节点为X。

  


伸展的代码实现:

  由于伸展树节点不需要parent域,所以实现左旋、右旋操作的函数需要使用指针引用作为参数。

void rotateWithLeftChild(BinaryTreeNode *&n)
{
BinaryTreeNode *k1 = n->left;
BinaryTreeNode *k2 = n;
k2->left = k1->right;
k1->right = k2;
n = k1;
}

void rotateWithRightChild(BinaryTreeNode *&n)
{
BinaryTreeNode *k1 = n;
BinaryTreeNode *k2 = n->right;
k1->right = k2->left;
k2->left = k1;
n = k2;
}


  在伸展操作的函数中,需要额外的O(1)的空间(即header)用于保存树L和树R的根节点信息,其中header.right保存树L的根节点、header.left保存树R的根节点。

  树L还需要一个指示指针leftTreeMax指向树L中的最大项,每当树M中的节点移到树L中均需挂成leftTreeMax节点的右子节点,然后leftTreeMax重指向原leftTreeMax的右子节点,移动后还需要改变树M的根节点,初始时leftTreeMax指向header。此外,当有节点从树M移到树L中时,需要将它的right设置为NULL,以断开树L和树M的关联。

  树R同样需要一个指示指针rightTreeMin做相同的工作。

  迭代结束后,需要重聚操作生成一棵树。

BinaryTreeNode* splay(Comparable d, BinaryTreeNode *midTreeRoot)
{
static BinaryTreeNode header(0);
header.left = header.right = NULL;

BinaryTreeNode *leftTreeMax, *rightTreeMin;
leftTreeMax = rightTreeMin = &header;

while (midTreeRoot->data != d)
{
if (d < midTreeRoot->data)
{
if (midTreeRoot->left == NULL)
break;
if (d < midTreeRoot->left->data && midTreeRoot->left->left)  // zig-zig
rotateWithLeftChild(midTreeRoot);
// 右连接
rightTreeMin->left = midTreeRoot;
rightTreeMin = midTreeRoot;
midTreeRoot = midTreeRoot->left;
rightTreeMin->left = NULL;
}
else if (d > midTreeRoot->data)
{
if (midTreeRoot->right == NULL)
break;
if (d > midTreeRoot->right->data && midTreeRoot->right->right)  // zig-zig
rotateWithRightChild(midTreeRoot);
// 左连接
leftTreeMax->right = midTreeRoot;
leftTreeMax = midTreeRoot;
midTreeRoot = midTreeRoot->right;
leftTreeMax->right = NULL;
}
else
break;
}

leftTreeMax->right = midTreeRoot->left;
rightTreeMin->left = midTreeRoot->right;
midTreeRoot->left = header.right;
midTreeRoot->right = header.left;

return midTreeRoot;
}


三 插入节点

  插入节点时需要:

  ① 检测根节点root是否存在,不存在则直接插为根节点root。

  ② 若节点存在,则需进行伸展操作,把离被插入数据最近的节点移到根节点处。

  ③ 若根节点和被插入数据相等,则不插入(数据各异的情况下)。

  ④ 若被插入数据小于根节点数据,则将root的左子树作为新插入节点的左子树,将root及其右子树作为新插入节点的右子树,root重新指向新插入节点。

  ⑤ 若被插入数据大于根节点数据,则将root的右子树作为新插入节点的右子树,将root及其左子树作为新插入节点的左子树,root重新指向新插入节点。

void insert(Comparable d)
{
BinaryTreeNode *newNode = new BinaryTreeNode(d);

if (root == NULL)
root = newNode;
else
{
root = splay(d, root);
if (d < root->data)
{
newNode->left = root->left;
newNode->right = root;
root->left = NULL;
root = newNode;
}
else if (d > root->data)
{
newNode->right = root->right;
newNode->left = root;
root->right = NULL;
root = newNode;
}
else
return;
}
}


四 删除节点

  删除节点时需要:

  ① 检测根节点是否存在,若不存在则不进行删除操作

  ② 若根节点存在,则进行伸展操作,把离被删除数据最近的节点移到根节点处

  ③ 若根节点和被删除数据相等,且根节点的左子树不存在,则将root重新指向原root的右子节点,并将原root删除。

  ④ 若根节点和被删除数据相等,且根节点的左子树存在,则将root的左子树进行伸展操作,伸展操作后,根据二叉搜索树的性质可知,左子树根节点的右子节点必然为NULL,此时将左子树根节点的右节点指向root的右子节点;并将root重新指向原root左子树根节点,并将原root删除。

void remove(Comparable d)
{
if (root)
{
root = splay(d, root);

if (d == root->data)
{
BinaryTreeNode *newRoot = NULL;

if (!root->left)
{
newRoot = root->right;
}
else
{
newRoot = root->left;
newRoot = splay(d, newRoot);
newRoot->right = root->right;
}
delete root;
root = newRoot;
}
}
}


五 完整代码

5.1 SplayTree.hpp

#include <iostream>

using namespace std;

//Comparable必须重载 ① '>' ② '<' ③ '==' ④ '<<'
template <typename Comparable>
class SplayTree
{
public:
SplayTree()
{
root = NULL;
}

~SplayTree()
{
makeEmpty();
}

bool isEmpty()
{
return root ? false : true;
}

void makeEmpty()
{
makeEmpty(root);
}

bool search(Comparable d)
{
if (!root)
return false;
root = splay(d, root);
if (d == root->data)
return true;
return false;
}

void insert(Comparable d)
{
BinaryTreeNode *newNode = new BinaryTreeNode(d);

if (root == NULL)
root = newNode;
else
{
root = splay(d, root);
if (d < root->data)
{
newNode->left = root->left;
newNode->right = root;
root->left = NULL;
root = newNode;
}
else if (d > root->data)
{
newNode->right = root->right;
newNode->left = root;
root->right = NULL;
root = newNode;
}
else
return;
}
}

void remove(Comparable d)
{
if (root)
{
root = splay(d, root);

if (d == root->data)
{
BinaryTreeNode *newRoot = NULL;

if (!root->left)
{
newRoot = root->right;
}
else
{
newRoot = root->left;
newRoot = splay(d, newRoot);
newRoot->right = root->right;
}
delete root;
root = newRoot;
}
}
}

void printTree()
{
if (root)
{
printTree(root);
cout << endl;
}
else
cout << "树中没有数据" << endl;
}

private:
struct BinaryTreeNode
{
Comparable data;
BinaryTreeNode *left;
BinaryTreeNode *right;

BinaryTreeNode(Comparable d, BinaryTreeNode *l=NULL, BinaryTreeNode *r=NULL): data(d), left(l), right(r) {  }
};

BinaryTreeNode *root;

void makeEmpty(BinaryTreeNode *&n) // 需要改变指针参数
{
if (n)
{
makeEmpty(n->left);
makeEmpty(n->right);
delete n;
n = NULL;
}
return;
}

void printTree(BinaryTreeNode *r)
{
if (!r)
return;
printTree(r->left);
cout << r->data << " ";
printTree(r->right);
}

void rotateWithLeftChild(BinaryTreeNode *&n)
{
BinaryTreeNode *k1 = n->left;
BinaryTreeNode *k2 = n;
k2->left = k1->right;
k1->right = k2;
n = k1;
}

void rotateWithRightChild(BinaryTreeNode *&n)
{
BinaryTreeNode *k1 = n;
BinaryTreeNode *k2 = n->right;
k1->right = k2->left;
k2->left = k1;
n = k2;
}

BinaryTreeNode* splay(Comparable d, BinaryTreeNode *midTreeRoot)
{
static BinaryTreeNode header(0);
header.left = header.right = NULL;

BinaryTreeNode *leftTreeMax, *rightTreeMin;
leftTreeMax = rightTreeMin = &header;

cout << "start splaying:" << endl;

while (midTreeRoot->data != d)
{
if (d < midTreeRoot->data)
{
if (midTreeRoot->left == NULL)
break;
if (d < midTreeRoot->left->data && midTreeRoot->left->left)  // zig-zig
rotateWithLeftChild(midTreeRoot);
// 右连接
rightTreeMin->left = midTreeRoot;
rightTreeMin = midTreeRoot;
midTreeRoot = midTreeRoot->left;
rightTreeMin->left = NULL;
}
else if (d > midTreeRoot->data)
{
if (midTreeRoot->right == NULL)
break;
if (d > midTreeRoot->right->data && midTreeRoot->right->right)  // zig-zig
rotateWithRightChild(midTreeRoot);
// 左连接
leftTreeMax->right = midTreeRoot;
leftTreeMax = midTreeRoot;
midTreeRoot = midTreeRoot->right;
leftTreeMax->right = NULL;
}
else
break;
}

cout << "before reassembling:" << endl;
cout << "L-Tree: ";
printTree(header.right);
cout << endl;
cout << "M-Tree: ";
printTree(midTreeRoot);
cout << endl;
cout << "R-Tree: ";
printTree(header.left);
cout << endl;

leftTreeMax->right = midTreeRoot->left;
rightTreeMin->left = midTreeRoot->right;
midTreeRoot->left = header.right;
midTreeRoot->right = header.left;

cout << "after reassembling:" << endl;
cout << "M-Tree: ";
printTree(midTreeRoot);
cout << endl;

return midTreeRoot;
}
};


5.2 main.cpp

#include <ctime>
#include <iostream>
#include <cstdlib>

#include "SplayTree.hpp"

using namespace std;

const int LENGTH = 9;

void generateTree(SplayTree<int> *tree)
{
srand((unsigned int)time(NULL));
int i = LENGTH;
while(i--)
{
tree->insert(rand() % 100);
tree->printTree();
}
}

void checkSearch(SplayTree<int> *tree)
{
int elem;
cout << "请输入用来测试搜索的数据: ";
cin >> elem;
if (tree->search(elem))
cout << "存在" << endl;
else
cout << "不存在" << endl;
}

void checkRemove(SplayTree<int> *tree)
{
if (tree->isEmpty())
{
generateTree(tree);
tree->printTree();
}
while (!tree->isEmpty())
{
int elem;
cout << "请输入要移除的数据: ";
cin >> elem;
tree->remove(elem);
tree->printTree();

}
}

int main()
{
SplayTree<int> *tree = new SplayTree<int>();
generateTree(tree);
tree->printTree();

checkSearch(tree);
checkSearch(tree);

checkRemove(tree);

system("pause");
}


5.3 运行结果截图

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: