一 伸展树的性质
伸展树保证从控制开始任意连续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域,所以实现左旋、右旋操作的函数需要使用指针引用作为参数。
1 void rotateWithLeftChild(BinaryTreeNode *&n) 2 { 3 BinaryTreeNode *k1 = n->left; 4 BinaryTreeNode *k2 = n; 5 k2->left = k1->right; 6 k1->right = k2; 7 n = k1; 8 } 9 10 void rotateWithRightChild(BinaryTreeNode *&n) 11 { 12 BinaryTreeNode *k1 = n; 13 BinaryTreeNode *k2 = n->right; 14 k1->right = k2->left; 15 k2->left = k1; 16 n = k2; 17 }
在伸展操作的函数中,需要额外的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做相同的工作。
迭代结束后,需要重聚操作生成一棵树。
1 BinaryTreeNode* splay(Comparable d, BinaryTreeNode *midTreeRoot) 2 { 3 static BinaryTreeNode header(0); 4 header.left = header.right = NULL; 5 6 BinaryTreeNode *leftTreeMax, *rightTreeMin; 7 leftTreeMax = rightTreeMin = &header; 8 9 while (midTreeRoot->data != d) 10 { 11 if (d < midTreeRoot->data) 12 { 13 if (midTreeRoot->left == NULL) 14 break; 15 if (d < midTreeRoot->left->data && midTreeRoot->left->left) // zig-zig 16 rotateWithLeftChild(midTreeRoot); 17 // 右连接 18 rightTreeMin->left = midTreeRoot; 19 rightTreeMin = midTreeRoot; 20 midTreeRoot = midTreeRoot->left; 21 rightTreeMin->left = NULL; 22 } 23 else if (d > midTreeRoot->data) 24 { 25 if (midTreeRoot->right == NULL) 26 break; 27 if (d > midTreeRoot->right->data && midTreeRoot->right->right) // zig-zig 28 rotateWithRightChild(midTreeRoot); 29 // 左连接 30 leftTreeMax->right = midTreeRoot; 31 leftTreeMax = midTreeRoot; 32 midTreeRoot = midTreeRoot->right; 33 leftTreeMax->right = NULL; 34 } 35 else 36 break; 37 } 38 39 leftTreeMax->right = midTreeRoot->left; 40 rightTreeMin->left = midTreeRoot->right; 41 midTreeRoot->left = header.right; 42 midTreeRoot->right = header.left; 43 44 return midTreeRoot; 45 }
三 插入节点
插入节点时需要:
① 检测根节点root是否存在,不存在则直接插为根节点root。
② 若节点存在,则需进行伸展操作,把离被插入数据最近的节点移到根节点处。
③ 若根节点和被插入数据相等,则不插入(数据各异的情况下)。
④ 若被插入数据小于根节点数据,则将root的左子树作为新插入节点的左子树,将root及其右子树作为新插入节点的右子树,root重新指向新插入节点。
⑤ 若被插入数据大于根节点数据,则将root的右子树作为新插入节点的右子树,将root及其左子树作为新插入节点的左子树,root重新指向新插入节点。
1 void insert(Comparable d) 2 { 3 BinaryTreeNode *newNode = new BinaryTreeNode(d); 4 5 if (root == NULL) 6 root = newNode; 7 else 8 { 9 root = splay(d, root); 10 if (d < root->data) 11 { 12 newNode->left = root->left; 13 newNode->right = root; 14 root->left = NULL; 15 root = newNode; 16 } 17 else if (d > root->data) 18 { 19 newNode->right = root->right; 20 newNode->left = root; 21 root->right = NULL; 22 root = newNode; 23 } 24 else 25 return; 26 } 27 }
四 删除节点
删除节点时需要:
① 检测根节点是否存在,若不存在则不进行删除操作
② 若根节点存在,则进行伸展操作,把离被删除数据最近的节点移到根节点处
③ 若根节点和被删除数据相等,且根节点的左子树不存在,则将root重新指向原root的右子节点,并将原root删除。
④ 若根节点和被删除数据相等,且根节点的左子树存在,则将root的左子树进行伸展操作,伸展操作后,根据二叉搜索树的性质可知,左子树根节点的右子节点必然为NULL,此时将左子树根节点的右节点指向root的右子节点;并将root重新指向原root左子树根节点,并将原root删除。
1 void remove(Comparable d) 2 { 3 if (root) 4 { 5 root = splay(d, root); 6 7 if (d == root->data) 8 { 9 BinaryTreeNode *newRoot = NULL; 10 11 if (!root->left) 12 { 13 newRoot = root->right; 14 } 15 else 16 { 17 newRoot = root->left; 18 newRoot = splay(d, newRoot); 19 newRoot->right = root->right; 20 } 21 delete root; 22 root = newRoot; 23 } 24 } 25 }
五 完整代码
5.1 SplayTree.hpp
1 #include <iostream> 2 3 using namespace std; 4 5 //Comparable必须重载 ① ‘>‘ ② ‘<‘ ③ ‘==‘ ④ ‘<<‘ 6 template <typename Comparable> 7 class SplayTree 8 { 9 public: 10 SplayTree() 11 { 12 root = NULL; 13 } 14 15 ~SplayTree() 16 { 17 makeEmpty(); 18 } 19 20 bool isEmpty() 21 { 22 return root ? false : true; 23 } 24 25 void makeEmpty() 26 { 27 makeEmpty(root); 28 } 29 30 bool search(Comparable d) 31 { 32 if (!root) 33 return false; 34 root = splay(d, root); 35 if (d == root->data) 36 return true; 37 return false; 38 } 39 40 void insert(Comparable d) 41 { 42 BinaryTreeNode *newNode = new BinaryTreeNode(d); 43 44 if (root == NULL) 45 root = newNode; 46 else 47 { 48 root = splay(d, root); 49 if (d < root->data) 50 { 51 newNode->left = root->left; 52 newNode->right = root; 53 root->left = NULL; 54 root = newNode; 55 } 56 else if (d > root->data) 57 { 58 newNode->right = root->right; 59 newNode->left = root; 60 root->right = NULL; 61 root = newNode; 62 } 63 else 64 return; 65 } 66 } 67 68 void remove(Comparable d) 69 { 70 if (root) 71 { 72 root = splay(d, root); 73 74 if (d == root->data) 75 { 76 BinaryTreeNode *newRoot = NULL; 77 78 if (!root->left) 79 { 80 newRoot = root->right; 81 } 82 else 83 { 84 newRoot = root->left; 85 newRoot = splay(d, newRoot); 86 newRoot->right = root->right; 87 } 88 delete root; 89 root = newRoot; 90 } 91 } 92 } 93 94 void printTree() 95 { 96 if (root) 97 { 98 printTree(root); 99 cout << endl; 100 } 101 else 102 cout << "树中没有数据" << endl; 103 } 104 105 private: 106 struct BinaryTreeNode 107 { 108 Comparable data; 109 BinaryTreeNode *left; 110 BinaryTreeNode *right; 111 112 BinaryTreeNode(Comparable d, BinaryTreeNode *l=NULL, BinaryTreeNode *r=NULL): data(d), left(l), right(r) { } 113 }; 114 115 BinaryTreeNode *root; 116 117 void makeEmpty(BinaryTreeNode *&n) // 需要改变指针参数 118 { 119 if (n) 120 { 121 makeEmpty(n->left); 122 makeEmpty(n->right); 123 delete n; 124 n = NULL; 125 } 126 return; 127 } 128 129 void printTree(BinaryTreeNode *r) 130 { 131 if (!r) 132 return; 133 printTree(r->left); 134 cout << r->data << " "; 135 printTree(r->right); 136 } 137 138 void rotateWithLeftChild(BinaryTreeNode *&n) 139 { 140 BinaryTreeNode *k1 = n->left; 141 BinaryTreeNode *k2 = n; 142 k2->left = k1->right; 143 k1->right = k2; 144 n = k1; 145 } 146 147 void rotateWithRightChild(BinaryTreeNode *&n) 148 { 149 BinaryTreeNode *k1 = n; 150 BinaryTreeNode *k2 = n->right; 151 k1->right = k2->left; 152 k2->left = k1; 153 n = k2; 154 } 155 156 BinaryTreeNode* splay(Comparable d, BinaryTreeNode *midTreeRoot) 157 { 158 static BinaryTreeNode header(0); 159 header.left = header.right = NULL; 160 161 BinaryTreeNode *leftTreeMax, *rightTreeMin; 162 leftTreeMax = rightTreeMin = &header; 163 164 cout << "start splaying:" << endl; 165 166 while (midTreeRoot->data != d) 167 { 168 if (d < midTreeRoot->data) 169 { 170 if (midTreeRoot->left == NULL) 171 break; 172 if (d < midTreeRoot->left->data && midTreeRoot->left->left) // zig-zig 173 rotateWithLeftChild(midTreeRoot); 174 // 右连接 175 rightTreeMin->left = midTreeRoot; 176 rightTreeMin = midTreeRoot; 177 midTreeRoot = midTreeRoot->left; 178 rightTreeMin->left = NULL; 179 } 180 else if (d > midTreeRoot->data) 181 { 182 if (midTreeRoot->right == NULL) 183 break; 184 if (d > midTreeRoot->right->data && midTreeRoot->right->right) // zig-zig 185 rotateWithRightChild(midTreeRoot); 186 // 左连接 187 leftTreeMax->right = midTreeRoot; 188 leftTreeMax = midTreeRoot; 189 midTreeRoot = midTreeRoot->right; 190 leftTreeMax->right = NULL; 191 } 192 else 193 break; 194 } 195 196 cout << "before reassembling:" << endl; 197 cout << "L-Tree: "; 198 printTree(header.right); 199 cout << endl; 200 cout << "M-Tree: "; 201 printTree(midTreeRoot); 202 cout << endl; 203 cout << "R-Tree: "; 204 printTree(header.left); 205 cout << endl; 206 207 leftTreeMax->right = midTreeRoot->left; 208 rightTreeMin->left = midTreeRoot->right; 209 midTreeRoot->left = header.right; 210 midTreeRoot->right = header.left; 211 212 cout << "after reassembling:" << endl; 213 cout << "M-Tree: "; 214 printTree(midTreeRoot); 215 cout << endl; 216 217 return midTreeRoot; 218 } 219 };
5.2 main.cpp
1 #include <ctime> 2 #include <iostream> 3 #include <cstdlib> 4 5 #include "SplayTree.hpp" 6 7 using namespace std; 8 9 const int LENGTH = 9; 10 11 void generateTree(SplayTree<int> *tree) 12 { 13 srand((unsigned int)time(NULL)); 14 int i = LENGTH; 15 while(i--) 16 { 17 tree->insert(rand() % 100); 18 tree->printTree(); 19 } 20 } 21 22 void checkSearch(SplayTree<int> *tree) 23 { 24 int elem; 25 cout << "请输入用来测试搜索的数据: "; 26 cin >> elem; 27 if (tree->search(elem)) 28 cout << "存在" << endl; 29 else 30 cout << "不存在" << endl; 31 } 32 33 void checkRemove(SplayTree<int> *tree) 34 { 35 if (tree->isEmpty()) 36 { 37 generateTree(tree); 38 tree->printTree(); 39 } 40 while (!tree->isEmpty()) 41 { 42 int elem; 43 cout << "请输入要移除的数据: "; 44 cin >> elem; 45 tree->remove(elem); 46 tree->printTree(); 47 48 } 49 } 50 51 int main() 52 { 53 SplayTree<int> *tree = new SplayTree<int>(); 54 generateTree(tree); 55 tree->printTree(); 56 57 checkSearch(tree); 58 checkSearch(tree); 59 60 checkRemove(tree); 61 62 system("pause"); 63 }
5.3 运行结果截图