议题:二分查找树性能分析(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=, 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 -;
  108. }
  109. /**
  110. * 如果root的左子节点为NULL
  111. * */
  112. if(root->left==NULL) {
  113. if(k==)
  114. return root->value;
  115. return binaryTreeSelect(root->right, k-);
  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+==k)
  127. return root->value;
  128. else
  129. return binaryTreeSelect(root->right, k--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此比较(也就是顺 序搜索);

笔试算法题(58):二分查找树性能分析(Binary Search Tree Performance Analysis)的更多相关文章

  1. LeetCode之“树”:Validate Binary Search Tree

    题目链接 题目要求: Given a binary tree, determine if it is a valid binary search tree (BST). Assume a BST is ...

  2. [Swift]LeetCode270. 最近的二分搜索树的值 $ Closest Binary Search Tree Value

    Given a non-empty binary search tree and a target value, find the value in the BST that is closest t ...

  3. [刷题] 235 Lowest Common Ancestor of a Binary Search Tree

    要求 给定一棵二分搜索树和两个节点,寻找这两个节点的最近公共祖先 示例 2和8的最近公共祖先是6 2和4的最近公共祖先是2 思路 p q<node node<p q p<=node& ...

  4. 【树】Validate Binary Search Tree

    需要注意的是,左子树的所有节点都要比根节点小,而非只是其左孩子比其小,右子树同样.这是很容易出错的一点是,很多人往往只考虑了每个根节点比其左孩子大比其右孩子小.如下面非二分查找树,如果只比较节点和其左 ...

  5. 【leetcode刷题笔记】Convert Sorted List to Binary Search Tree

    Given a singly linked list where elements are sorted in ascending order, convert it to a height bala ...

  6. 【leetcode刷题笔记】Convert Sorted Array to Binary Search Tree

    Given an array where elements are sorted in ascending order, convert it to a height balanced BST. 题解 ...

  7. 第33题:LeetCode255 Verify Preorder Sequence in Binary Search Tree 验证先序遍历是否符合二叉搜索树

    题目 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 考点 1.BST 二叉搜索树 2.递归 思路 1.后序 ...

  8. 算法与数据结构基础 - 二叉查找树(Binary Search Tree)

    二叉查找树基础 二叉查找树(BST)满足这样的性质,或是一颗空树:或左子树节点值小于根节点值.右子树节点值大于根节点值,左右子树也分别满足这个性质. 利用这个性质,可以迭代(iterative)或递归 ...

  9. PAT题库-1064. Complete Binary Search Tree (30)

    1064. Complete Binary Search Tree (30) 时间限制 100 ms 内存限制 32000 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHE ...

随机推荐

  1. bzoj 3830: [Poi2014]Freight【dp】

    参考:https://blog.csdn.net/zqh_wz/article/details/52953516 妙啊 看成分段问题,因为火车只能一批一批的走(易证= =)设f[i]为到i为止的车都走 ...

  2. Ubuntu 18.04 关闭蓝牙开机启动

    sudo gedit /etc/rc.local 然后,加入下面一行 rfkill block bluetooth

  3. thunderbird 登录网易邮箱

    登录密码不是自己的密码,而是在网易邮箱中设置的客户端授权ma,自己先进入邮箱进行设置即可

  4. ROS学习笔记九:ROS工具

    ROS有各种工具可以帮助用户使用ROS.应该指出,这些GUI工具是对输入型命令工具的补充.如果包括ROS用户个人发布的工具,那么ROS工具的数量很庞大.其中,本文讨论的工具是对于ROS编程非常有用的辅 ...

  5. 题解报告:hdu 2069 Coin Change(暴力orDP)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2069 Problem Description Suppose there are 5 types of ...

  6. Join方法,yield方法,线程的优先级

  7. AngularJS ng-repeat下使用ng-model

    1 2 3 blue:<input type="radio" value="1" ng-model="selectValue"/> ...

  8. WCF入门大致思路

    WCF服务: 1.IServer.cs(类似接口,WCF接口) 2.Server.svc(实现了WCF接口)右键浏览器运行可以看到WCF服务链接,类似(http://localhost:4609/Us ...

  9. c#.net 正则匹配以特定字符串开头,以特定字符串结尾

    string[] unit = Getunit(result40, "(?<=(开始字符串))[.\\s\\S]*?(?=(结束字符串))"); private string ...

  10. 【Java】包装类型

    Java中的基本类型功能简单,不具备对象的特性,为了使基本类型具备对象的特性,所以出现了包装类,就可以像操作对象一样操作基本类型数据. 一.基本类型对应的包装类 基本类型                ...