自顶向下的伸展树

一 伸展树的性质

  伸展树保证从控制开始任意连续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 运行结果截图

时间: 2024-10-14 11:41:22

自顶向下的伸展树的相关文章

伸展树基础(2)-12.1

在X插入时,展开使得X成为新的根. 查找X时,也要对X或者是因为没找到而路径上最后一个节点进行展开. 初步想法是沿着根往下进行一次遍历,以及而后从底向上的一次遍历.这样太麻烦了啊. 所以本文介绍自顶向下的伸展树:在初始访问路径上就进行一次次的旋转. 我们设X为中间树的根,L存放树T中小于X中的节点,但不存放X的子树的节点,R同理.初始化时X为T的根,L和R为空树 我们设展开函数为Splay(int Item,Position X),也就是说如果Item在X中,那么Item要变成新的根:不在的话,

PHP算法 《树形结构》 之 伸展树(1) - 基本概念

伸展树的介绍 1.出处:http://www.cnblogs.com/skywang12345/p/3604238.html 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造.(01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x].如果y是x的左子树中的一个结点,则key[y] <= key

高级数据结构实现——自顶向下伸展树

[0]README 1) 本文部分内容转自 数据结构与算法分析,旨在理解 高级数据结构实现——自顶向下伸展树 的基础知识: 2) 源代码部分思想借鉴了数据结构与算法分析,有一点干货原创代码,for original source code, please visithttps://github.com/pacosonTang/dataStructure-algorithmAnalysis/tree/master/chapter12/p345_topdown_splay_tree 3) you c

伸展树

伸展树(Splay Tree)树平衡二叉查找树的一种,具有二叉查找树的所有性质.在性能上又比普通的二叉查找树有所改进:普通的二叉查找树在最坏情况下的查找操作的时间复杂度为O(n)(当二叉树退化成一条链的时候),而伸展树在任何情况下的平摊时间复杂度均为 O(log2n). 特性 和普通的二叉查找树相比,具有任何情况下.任何操作的平摊O(log2n)的复杂度,时间性能上更好 和一般的平衡二叉树比如 红黑树.AVL树相比,维护更少的节点额外信息,空间性能更优,同时编程复杂度更低 在很多情况下,对于查找

查找——清晰图解伸展树SplayTree

伸展树 伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它由Daniel Sleator和Robert Tarjan创造,后者对其进行了改进. 假设想要对一个二叉查找树执行一系列的查找操作.为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置.于是想到设计一个简单方法,在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方.splaytree应运而生.splaytree是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转

poj_3468 伸展树

题目大意 一个数列,每次操作可以是将某区间数字都加上一个相同的整数,也可以是询问一个区间中所有数字的和.(这里区间指的是数列中连续的若干个数)对每次询问给出结果. 思路 1. 伸展树的一般规律 对于区间的查找更新操作,可以考虑使用伸展树.线段树等数据结构.这里使用伸展树来解决.     伸展树对数组进行维护的核心思想是,将需要维护的一组数单独提取出来,形成一棵子树(一般为整棵树的根节点的右子节点的左孩子节点 为根),然后再这个子树上进行操作.此时进行某些操作(如 ADD, SUM 等),只需要在

HYSBZ 1503 郁闷的出纳员 伸展树

题目链接: https://vjudge.net/problem/26193/origin 题目描述: 中文题面....... 解题思路: 伸展树, 需要伸展树的模板, 突然发现自己昨天看到的模板不是太好, 现在又新找了一个,  很简练, 自己将模板的实现从头到尾看了一遍, 觉得数组实现的实在是非常的巧妙, 然后自己照着敲了一遍, 边敲边看, 崩掉了.....肯定是哪里手残了, 没有必要浪费时间去改了, 有那时间不如看点别的 代码: #include <iostream> #include &

伸展树整理

伸展树 1.在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置.因此,在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方.伸展树应运而生.伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去. 2.操作 (1)伸展操作 伸展操作Splay(x,S)是在保持伸展树有序性的前提下,通过一系列旋转将伸展树S中的元素x调整至树的

Splay伸展树

伸展树,感觉对我这种菜鸟还是有些吃力,主要也是旋转的时候吧,把要查询的节点旋转到根节点,看网上是有两种方法,一是从上到下,一是从下到上.从上到下,是把树拆分成中树,左树和右树,始终保证目标节点在中树,不断向下把中树的节点添到右树或者左树,直到目标节点为中树的根,再将三树合并起来.另外一种从下到上,旋转操作类似AVL树中的旋转,但是判断好像不是很好写,我写的是从上到下的,另外删除也很巧妙,先把目标节点旋转到根,若此时左子树为空直接删除,否则找到左子树最右的节点当头,利用伸展树的特殊旋转就可以一步删