笔试算法题(58):二分查找树性能分析(Binary Search Tree Performance Analysis)

议题:二分查找树性能分析(Binary Search Tree Performance Analysis)

分析:

  • 二叉搜索树(Binary Search Tree,BST)是一颗典型的二叉树,同时任何节点的键值大于等于该节点左子树中的所有键值,小于等于该节点右子树中的所有键值,并且每个节点域中保存 一个记录以其为根节点的子树中所有节点个数的属性,这个属性可用于支持贪婪算法的实现;

  • 二叉搜索树的建立是在树的底部添加新的元素,搜索即从根元素开始到达树底部的一条路径,插入和搜索相似(注意对重复键的处理),排序按照节点访问方式不同有前序、中序、后序三种;

  • 二叉搜索树算法的运行时间取决于树的形状,最好情况下根节点与每个外部节点间有㏒N个节点,此时树完全平衡,最坏情况下搜索路径上有N个节点。由于创建二 叉搜索树的时候,第一个插入的元素总是作为根元素,所以元素插入的顺序决定树的形状。在随机情况下,极度平衡和极度不平衡的树都很少出现,所以这种情况下 二叉搜索树算法有着良好的运行情况;

  • 所以平均情况下,N个随机生成的BST树种,一次搜索,插入大约需要1.39㏒N此比较。如果键值不是随机出现,则二叉搜索树退化为N个节点的链表,一次操作为线性O(N)运行时间;

  • 使用BST树存储文件中每一个文本串,基于字符串的排序使得搜索变得容易;

  • BottomUp插入策略:按照前序策略遍历整个树结构,首先查看当前节点是否为NULL,然后与关键值比较查看是否为目标值,不是的话就分别针对左右子 树递归调用搜索算法,然后进入下一个结构,注意在递归调用之间的衔接是由返回一个节点来实现的,所以如果已经到达树底部,则返回一个新节点,这个节点正好 位于上一级的子树连接上,这样正好形成整个树结构;

  • TopDown插入策略:在BottomUp插入策略的基础上,将新插入的节点在递归回溯的时候逐层旋转,知道根节点的位置;使用基于递归插入操作和旋转 操作的策略可以使得最近插入的元素接近BST树的顶部,同时保持树的平衡性。这种插入方式称为从根部插入,实现策略:首先使用普通递归插入将在树底部找到 一个合适的位置插入新的节点,然后使用旋转操作将这个新加入的节点旋转到根节点处,不仅可以保持树的平衡,而且由于最近插入的项被使用的概率大,靠近根节 点则加速搜索效率;

  • 旋转操作:BST树中从根部插入新节点:首要考虑的就是是否能够保持BST树的性质。现在使用基于旋转(Rotation)的转换策略,使得BST树保持原有性质。旋转实质上是交换根节点和一个孩子的角色,同时保持各节点的顺序

     

  • 选择第Kth个值(最小或者最大):利用Node节点中的count标记(此标记说明以当前节点为根节点的子树的所有节点数),可以快速查找给定的序列中 第Kth个最小或者最大值;当然前提是将给定的序列扩建成BST;从根节点开始,首先检查其左子树中节点个数,如果正好为K个则返回根节点本身,如果大于 K个节点,则对左子树递归调用算法,如果小于K个节点,则说明第K个最小键在根节点的右子树中,变成查找右子树中第K-t-1个最小键的项(t为左子树所 有节点,1为根节点自身);

  • BST树的节点删除操作:被删除的节点可以有三种情况,没有子节点,有一个子节点,有两个子节点。第一种情况可直接删除;第二种情况需要临时存储子节点的 索引,并让被删除节点的父节点指向这个这个索引;第三种情况需要维护BST树的性质,所以一般性策略是选择右子树中最小的元素作为新的根节点(右子树中最 小的元素出现在最左边,所以它至多只有一个子节点,可容易删除),然而有时候也会选择左子树中的最大元素作为新的根节点(由于在左右子树中任意选择新的节 点作为新的根节点,所以可能造成BST树的不平衡);

样例:

  1 struct Node {
  2         int value;
  3         int count;
  4         Node *left;
  5         Node *right;
  6         Node(int v, int c=1, Node* l=NULL, Node* r=NULL):
  7                                 count(c), value(v), left(l), right(r) {
  8 
  9         }
 10 };
 11 /**
 12  * 对root节点进行右旋转操作,也就是:
 13  * 1. 让root原来的左孩子变成newRoot;
 14  * 2. 让root变成newRoot的右子节点;
 15  * 3. 让root原来的左孩子的的右子节点变成root的左子节点
 16  * */
 17 Node* rightRotate(Node *root) {
 18         Node *newRoot=root->left;
 19         root->left=root->left->right;
 20         newRoot->right=root;
 21         return newRoot;
 22 }
 23 /**
 24  * 对root节点进行左旋转操作,也就是:
 25  * 1. 让root原来的右孩子变成newRoot;
 26  * 2. 让root变成newRoot的左子节点;
 27  * 3. 让root原来的右孩子的左子节点变成root的右子节点
 28  * */
 29 Node* leftRotate(Node *root) {
 30         Node *newRoot=root->right;
 31         root->right=root->right->left;
 32         newRoot->left=root;
 33         return newRoot;
 34 }
 35 
 36 Node* binaryTreeSearch(Node *root, int target) {
 37 
 38         if(root==NULL)
 39                 return NULL;
 40 
 41         if(target>root->value)
 42                 return binaryTreeSearch(root->right, target);
 43         else if(target<root->value)
 44                 return binaryTreeSearch(root->left, target);
 45         else
 46                 return root;
 47 }
 48 
 49 Node* binaryTreeInsert(Node *root, int target) {
 50 
 51         if(root==NULL) {
 52                 return new Node(target);
 53         }
 54 
 55         if(target>root->value)
 56                 root->right=binaryTreeInsert(root->right, target);
 57         else if(target<root->value)
 58                 root->left=binaryTreeInsert(root->left, target);
 59 
 60         return root;
 61 }
 62 /**
 63  * 这样可以将新插入的元素旋转到为root;
 64  * 不仅可以保持BST的平衡性,而且可以保证
 65  * 新插入的元素的最大访问延迟;
 66  * */
 67 Node* binaryTreeInsertTopDown(Node *root, int target) {
 68 
 69         if(root==NULL) {
 70                 return new Node(target);
 71         }
 72 
 73         if(target>root->value) {
 74                 root->right=binaryTreeInsert(root->right, target);
 75                 root=leftRotate(root);
 76         }
 77         else if(target<root->value) {
 78                 root->left=binaryTreeInsert(root->left, target);
 79                 root=rightRotate(root);
 80         }
 81 
 82         return root;
 83 }
 84 
 85 Node* binaryTreeInsertWithCount(Node *root, int target) {
 86 
 87         if(root==NULL) {
 88                 return new Node(target);
 89         }
 90 
 91         if(target>root->value)
 92                 root->right=binaryTreeInsert(root->right, target);
 93         else if(target<root->value)
 94                 root->left=binaryTreeInsert(root->left, target);
 95         root->count++;
 96         return root;
 97 }
 98 /**
 99  * 从一个序列中选定第K大的数字,
100  * */
101 int binaryTreeSelect(Node *root, int k) {
102         /**
103          * 如果当前root为NULL,则选择失败
104          * */
105         if(root==NULL) {
106                 printf("\nfind nothing-_-\n");
107                 return -1;
108         }
109         /**
110          * 如果root的左子节点为NULL
111          * */
112         if(root->left==NULL) {
113                 if(k==1)
114                         return root->value;
115                 return binaryTreeSelect(root->right, k-1);
116         }
117         /**
118          * 如果root的左子节点不为NULL;
119          * 1. 如果K<=leftCount,则Kth个节点在左子树中
120          * 2. 如果K==leftCount+1,则kth个节点就是root自身
121          * 3. 如果k>leftCount+1,则Kth个节点就是右子树中的k-1-leftCount个节点
122          * */
123         int leftCount=root->left->count;
124         if(leftCount>=k)
125                 return binaryTreeSelect(root->left, k);
126         else if(leftCount+1==k)
127                 return root->value;
128         else
129                 return binaryTreeSelect(root->right, k-1-leftCount);
130 }
131 
132 /**
133  * 将指定的元素target旋转到根节点
134  * */
135 Node* binaryTreeRotate(Node *root, int target) {
136 
137         if(root==NULL)
138                 return NULL;
139 
140         if(target>root->value) {
141                 root->right=binaryTreeRotate(root->right,target);
142                 leftRotate(root);
143         } else if(target<root->value) {
144                 root->left=binaryTreeRotate(root->left,target);
145                 rightRotate(root);
146         }
147 
148         return root;
149 }
150 /**
151  * 此方法寻找root的左子树中具有最大value的子节点,也就是最‘左边’的子节点;
152  * */
153 Node* subtreeRightMaximum(Node *root) {
154         Node *cur=root;
155         Node *pre;
156         while(cur!=NULL) {
157                 pre=cur;
158                 cur=cur->left;
159         }
160         return pre;
161 }
162 /**
163  * 此方法寻找root的右子树中具有最大value的子节点,也就是最‘左边’的子节点;
164  * */
165 Node* subTreeLeftMaximum(Node* root) {
166         Node *cur=root;
167         Node *pre;
168         while(cur!=NULL) {
169                 pre=cur;
170                 cur=cur->right;
171         }
172         return pre;
173 }
174 
175 Node* binaryTreeDelete(Node *root, int target) {
176 
177         if(root==NULL)
178                 return NULL;
179         Node *temp;
180         Node *newRoot;
181         /**
182          * 如果target比root->value大,则说明其位于root的
183          * 右子树,则继续递归
184          * 如果target比root->value小,则说明其位于root的
185          * 左子树,则继续递归
186          * 如果target等于root->value,则说明当前节点root
187          * 就是需要删除的节点,然后分三种情况讨论:
188          * 1. 如果root没有左右子节点
189          * 2. 如果root只有左节点或者只有右节点
190          * 3. 如果root德尔左右子节点都存在;
191          * */
192         if(target>root->value)
193                 root->right=binaryTreeDelete(root->right, target);
194         else if(target<root->value)
195                 root->left=binaryTreeDelete(root->left, target);
196         else {
197                 if(root->left==NULL && root->right) {
198                         delete root;
199                         return NULL;
200                 } else if(root->left==NULL) {
201                         temp=root->right;
202                         delete root;
203                         return temp;
204                 } else if(root->right==NULL) {
205                         temp=root->left;
206                         delete root;
207                         return temp;
208                 }
209                 /**
210                  * 左右子节点都存在的情况,需要从左右子树中寻找下一个根节点;
211                  * 这里是从右子树中选取最小的一个节点作为新的根节点;
212                  * */
213                 newRoot=subtreeRightMaximum(root->right);
214                 /**
215                  * 由于右子树中最小的节点必然至多只有一个右节点,所以其删除操作
216                  * 较为简单;然后将其的左右子树替换成当前的左右子树;
217                  * */
218                 newRoot=binaryTreeDelete(root->right, newRoot->value);
219                 newRoot->right=root->right;
220                 newRoot->left=root->left;
221                 delete root;
222         }
223 
224 }

 

补充:

  • BST中搜索和插入的策略都是一样的,从传入的树节点开始,首先判断其是否为NULL,如果是的话对于搜索来讲表示失败,对于插入来讲表示需要插入新的节 点;如果不是NULL的话,对于搜索来讲比对是否为目标值,然后针对左右子树递归调用,对于插入来讲比对是否相同,表示树中已经有同样的节点算法说明;

  • BST树的构建和搜索也使用同样的遍历策略,所以插入与搜索一样容易实现;旋转可用于防止树变得不平衡,实现删除,合并和其他操作的辅助操作,BST树的 插入操作可以通过在树的底部插入新元素,然后使用左旋和右旋将新元素带到根节点处,防止树的不平衡状态。每次BST搜索命中的项也可以通过旋转带到根节点 处;

  • 使用BST树进行选择算法最大的缺点就是计数域的出现导致额外的内存占用,树结构改变时需要额外的维护操作,同时我们可以对查找到的节点元素使用旋转操作,将其放到根节点的位置,下次使用的时候就能很快定位;

 


BST树的性能特征总结:

  • 二叉搜索树算法的运行时间取决于树的形状,最好情况下树可能完全平衡,这样一次搜索过程就是一条路径的长度㏒N,最差情况下树退化为链表,这样一次搜索过程路径长度可能为N;

  • 使用插入操作构建BST树的过程中,越是前面的节点对树最终形状的影响越是大,第一个元素就是树根,对于随机序列来讲,最坏情况出现的概率很小,所以平均情况能保持较好的运行时间,㏒N;

  • 使用索引项来表示搜索节点,避免动态分配内存。当序列以随机序列插入时,生成完全平衡树的概率很小,但二叉树路径的长度和树的高度与BST的搜索开销联系 紧密。平均情况下一棵根据N个随机键生成的BST树中,搜索命中(插入和搜索失败)大约需要1.39㏒N次比较。最坏情况下,可能需要N此比较(也就是顺 序搜索);

转载于:https://www.cnblogs.com/leo-chen-2014/p/3762132.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值