自顶向下的伸展树
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域,所以实现左旋、右旋操作的函数需要使用指针引用作为参数。
在伸展操作的函数中,需要额外的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做相同的工作。
迭代结束后,需要重聚操作生成一棵树。
三 插入节点
插入节点时需要:
① 检测根节点root是否存在,不存在则直接插为根节点root。
② 若节点存在,则需进行伸展操作,把离被插入数据最近的节点移到根节点处。
③ 若根节点和被插入数据相等,则不插入(数据各异的情况下)。
④ 若被插入数据小于根节点数据,则将root的左子树作为新插入节点的左子树,将root及其右子树作为新插入节点的右子树,root重新指向新插入节点。
⑤ 若被插入数据大于根节点数据,则将root的右子树作为新插入节点的右子树,将root及其左子树作为新插入节点的左子树,root重新指向新插入节点。
四 删除节点
删除节点时需要:
① 检测根节点是否存在,若不存在则不进行删除操作
② 若根节点存在,则进行伸展操作,把离被删除数据最近的节点移到根节点处
③ 若根节点和被删除数据相等,且根节点的左子树不存在,则将root重新指向原root的右子节点,并将原root删除。
④ 若根节点和被删除数据相等,且根节点的左子树存在,则将root的左子树进行伸展操作,伸展操作后,根据二叉搜索树的性质可知,左子树根节点的右子节点必然为NULL,此时将左子树根节点的右节点指向root的右子节点;并将root重新指向原root左子树根节点,并将原root删除。
五 完整代码
5.1 SplayTree.hpp
5.2 main.cpp
5.3 运行结果截图
伸展树保证从控制开始任意连续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 运行结果截图
相关文章推荐
- iOS中的c第一天基本数据类型
- Beanutils基本用法
- 吐槽 CSDN markdown编辑器!!
- zoj 3640 Help Me Escape
- hadoop配置相关前置修改
- AC自动机学习笔记
- hdu2955 — Robberies (01背包)
- Linux的ioctl和fcntl
- Linux性能之磁盘I/O
- VS2012 使用BUG
- 4.2 多数据系列的面积图
- IOS Quartz2D 相关
- 排序(只换位一次)
- Java troubleshooting guide
- Android ListView滑动过程中图片显示重复错乱闪烁问题解决
- zoj 3639 Guess a Function
- ZOJ 2771 Get Out of the Glass 很普通的计数dp
- LeetCode从零单排之零分段——Delete Node in a Linked List(删除链表中的元素)
- Android 开发 单选按钮的实现
- 冒泡方排序