• 12.3-1 二叉搜索树insert操作的递归版本
  1. void insert1(Node* pRoot, Node* pAdd)
  2. {
  3. bool bLeft = pAdd->key < pRoot->key;
  4. Node* pNextRoot = bLeft ? pRoot->left : pRoot->right;
  5. if(pNextRoot)
  6. insert1(pNextRoot, pAdd);
  7. else
  8. {
  9. pAdd->parent = pRoot;
  10. if(bLeft)
  11. pRoot->left = pAdd;
  12. else
  13. pRoot->right = pAdd;
  14. }
  15. }
  • 12.3-2 insert过程途经n个节点后遇到了空节点,便将待插入的元素安放上去了,接下来的search过程先路过同样的n个节点,最后与第n+1个节点比较发现相同,于是search完毕。

  • 12.3-3 由12.3-2可得,insert过程和search过程一样,是O(h)-time的,h代表树的高度。

    设一共有n个节点,构建一棵树需要调用n次insert,这个过程耗时O(nh),后续的中序遍历耗时是O(n)。所以排序过程的时间复杂度是由构建过程决定的。

    最坏的情况集合按照正序或者倒序排列,则h = n,总时间是O(n^2)。

    最好情况是集合按照层次遍历顺序排列,最后构建成一棵完全二叉树,h = lg(n) ,总时间为O(n*lg(n))。

  • 12.3-4 从同一棵二叉搜索树上先删除x再删除y,和先删除y再删除x,最终得到的树一定是一样的吗?

    不一定,举个反例:

  1. //现在有一棵树,上面有1,2,3,4四个节点:
  2. 2
  3. / \
  4. 1 4
  5. /
  6. 3
  7. //如果 先删除1 再删除2,结果是:
  8. 2 2 4
  9. / \ \ /
  10. 1 4 --> 4 --> 3
  11. / /
  12. 3 3
  13. //如果 先删除2 再删除1,结果是:
  14. 2 3 3
  15. / \ / \ \
  16. 1 4 --> 1 4 --> 4
  17. /
  18. 3
  19. //得到的结果是不一样的。

下面说说,我是怎样想到这个反例的。

要删除x,根据删除的规则,如果x没有孩子,直接删除;如果x只有一个孩子,就用唯一的孩子顶替x的位置;如果x有两个孩子,就将x的后继节点s顶替x的位置。

从这个规则中可以发现,x有几个孩子会影响到x的接班人人选。如果删除的顺序可以影响到删除x时候x的孩子个数,就会影响到最终树的形状。

那么,y在x的什么位置上,删除y会对x孩子个数造成影响呢?y本身就是x的一个孩子,而且y没有孩子。下面分左孩子和右孩子讨论。因为x只有一个孩子y的情况太简单,也不能成为反例,不做讨论,下面对x有两个孩子的情况进行分析。称以x为根的树为X树。

如果y是x的左孩子,先删y,删除之后,x的孩子个数变成1,此时删除x,X树被其右子树取代,X树的根变为x的右孩子。反过来,先删除x,此时x有两个孩子,X树的根变为x的后继,只要其后继不是它的右孩子本身,那么结果就是不一样的。这也就是上面给出的反例。

如果y是x的右子树,先删y,再删x,X树被x左子树取代,先删x,y顶替x的位置,再删y,X树仍然被x的左子树取代,结果是一样的,不能作为反例。

还有一种可能,删除x,x的接班人是自己的后继s,原以s为根的树S会发生改变,从S树上节点能否找出一个y作为反例,我暂时没有想清楚。

  • 12.3-5 二叉搜索树的每个节点保存“后继”,“左孩子”,“右孩子”三个属性,在O(h)时间内实现insert delete search。(这道题在《算法导论》第三版的中文版翻译有误)

    为什么要把“父亲”属性替换成“后继”属性呢?相比保存“父亲”,保存“后继”属性的优势在于查找后继节点时间O(1),排序虽然都是O(n)但是常数项较小。执行这两种操作时,性能相当于单向链表。而执行插入、删除、查找操作时,性能相当于二叉树。

    我们先来总结一下这些操作需要读写哪些属性。

    insert操作需要修改的有:父亲节点的孩子属性,前驱节点和新插入节点的后继属性

    delete操作需要修改的有:父亲节点的孩子属性,前驱节点的后继属性

    search操作需要读取的有:节点的孩子属性

    根据上面的总结可以发现,这道题的关键点是在O(h)时间内找到父亲节点和前驱节点。

    查找父亲节点的做法是从Root向下逐级查找;如果当前节点没有左孩子,那么查找前驱节点的做法也是从Root向下查找,可以和父亲节点的查找工作合并起来。如果当前节点有左孩子,那么前驱节点是左孩子的最大节点。

  1. #include <iostream>
  2. #include <cassert>
  3. using namespace std;
  4. struct Node
  5. {
  6. int key;
  7. Node* succ;
  8. Node* left;
  9. Node* right;
  10. Node(int k):key(k),succ(nullptr),left(nullptr),right(nullptr){}
  11. };
  12. Node* minimum(Node* pRoot);
  13. Node* parent_pred(Node* pRoot, int key, Node*& pPred);//为新插入节点,找父亲的同时从父亲中找前驱
  14. void insert(Node* pRoot, int key)
  15. {
  16. Node* pNew = new Node(key);
  17. Node* pPred;
  18. Node* pParent = parent_pred(pRoot, key, pPred);
  19. Node* pHead = minimum(pRoot);
  20. //upate parent\'s child
  21. if(key < pParent->key)
  22. pParent->left = pNew;
  23. else
  24. pParent->right = pNew;
  25. //update pPred\'s succ and pNew\'s succ
  26. if(pPred)
  27. {
  28. pNew->succ = pPred->succ;
  29. pPred->succ = pNew;
  30. }
  31. else
  32. {
  33. pNew->succ = pHead;//注意:这个头结点一定要在插入之前获取
  34. }
  35. }
  36. Node* pred(Node* pNode); //从左子树上找前驱
  37. Node* parent(Node* pRoot, Node* pNode); //找父亲
  38. Node* parent_pred(Node* pRoot, Node* pNode, Node*& pPred); //为已有节点,找父亲的同时从父亲中找前驱
  39. void Delete(Node*& pRoot, Node* pDelete)
  40. {
  41. Node* pDeleteReplace = nullptr;
  42. if(!pDelete->left)
  43. {
  44. pDeleteReplace = pDelete->right;//no child //only right
  45. }
  46. else
  47. {
  48. pDeleteReplace = pDelete->left; //only left
  49. if(pDelete->right) //both
  50. {
  51. Node* pSucc = pDelete->succ;
  52. if(pSucc != pDelete->right)
  53. {
  54. Node* pSuccParent = parent(pRoot, pSucc);
  55. if(pSucc->key < pSuccParent->key)
  56. pSuccParent->left = pSucc->right;
  57. else
  58. pSuccParent->right = pSucc->right;
  59. pSucc->right = pDelete->right;
  60. }
  61. pSucc->left = pDelete->left;
  62. pDeleteReplace = pSucc;
  63. }
  64. }
  65. //update parent\'s child, pred\'s succ
  66. Node* pPred;
  67. Node* pDeleteParent = parent_pred(pRoot, pDelete, pPred);
  68. bool bLeft;
  69. if(pDeleteParent)
  70. bLeft = pDeleteParent->left == pDelete;
  71. if(pDeleteParent)
  72. {
  73. if(bLeft)
  74. pDeleteParent->left = pDeleteReplace;
  75. else
  76. pDeleteParent->right = pDeleteReplace;
  77. }
  78. else
  79. {
  80. pRoot = pDeleteReplace;
  81. }
  82. if(pPred)
  83. {
  84. pPred->succ = pDelete->succ;
  85. }
  86. }
  87. Node* search(Node* pRoot, int key)
  88. {
  89. Node* pCurrent = pRoot;
  90. int keyCurrent;
  91. while(pCurrent)
  92. {
  93. keyCurrent = pCurrent->key;
  94. if(key == keyCurrent)
  95. break;
  96. if(key < keyCurrent)
  97. pCurrent = pCurrent->left;
  98. else
  99. pCurrent = pCurrent->right;
  100. }
  101. return pCurrent;
  102. }
  103. Node* minimum(Node* pRoot)
  104. {
  105. Node* pMin = pRoot;
  106. while(pMin->left)
  107. {
  108. pMin = pMin->left;
  109. }
  110. return pMin;
  111. }
  112. Node* parent_pred(Node* pRoot, int key, Node*& pPred)
  113. {
  114. Node* pCurrent = pRoot;
  115. Node* pParent = nullptr;
  116. pPred = nullptr;
  117. while(pCurrent)
  118. {
  119. pParent = pCurrent;
  120. if(key < pCurrent->key)
  121. {
  122. pCurrent = pCurrent->left;
  123. }
  124. else
  125. {
  126. pCurrent = pCurrent->right;
  127. pPred = pParent;
  128. }
  129. }
  130. return pParent;
  131. }
  132. Node* parent(Node* pRoot, Node* pNode)
  133. {
  134. Node* pCurrent = pRoot;
  135. Node* pParent = nullptr;
  136. while(pNode != pCurrent)
  137. {
  138. assert(pCurrent);
  139. pParent = pCurrent;
  140. if(pNode->key < pCurrent->key)
  141. pCurrent = pCurrent->left;
  142. else
  143. pCurrent = pCurrent->right;
  144. }
  145. return pParent;
  146. }
  147. Node* parent_pred(Node* pRoot, Node* pNode, Node*& pPred)
  148. {
  149. Node* pCurrent = pRoot;
  150. Node* pParent = nullptr;
  151. pPred = nullptr;
  152. while(pNode != pCurrent)
  153. {
  154. assert(pCurrent);
  155. pParent = pCurrent;
  156. if(pNode->key < pCurrent->key)
  157. pCurrent = pCurrent->left;
  158. else
  159. {
  160. pCurrent = pCurrent->right;
  161. pPred = pParent;
  162. }
  163. }
  164. return pParent;
  165. }
  166. Node* pred(Node* pNode)
  167. {
  168. Node* pPred = pNode->left;
  169. while(pPred->right)
  170. pPred = pPred->right;
  171. return pPred;
  172. }
  173. void walk(Node* pRoot)
  174. {
  175. Node* pCurrent = minimum(pRoot);
  176. while(pCurrent)
  177. {
  178. cout << pCurrent->key << "\t";
  179. pCurrent = pCurrent->succ;
  180. }
  181. cout << endl;
  182. }
  183. void test()
  184. {
  185. //build
  186. Node* pRoot = new Node(4);
  187. insert(pRoot, 2);
  188. insert(pRoot, 5);
  189. insert(pRoot, 1);
  190. insert(pRoot, 3);
  191. insert(pRoot, 7);
  192. insert(pRoot, 6);
  193. insert(pRoot, 8);
  194. walk(pRoot);
  195. //search
  196. cout << search(pRoot, 3)->key << endl;
  197. cout << search(pRoot, 6)->key << endl;
  198. cout << search(pRoot, 4)->key << endl;
  199. //delete
  200. Delete(pRoot, pRoot->right);//delete 5
  201. Delete(pRoot, pRoot->left); //delete 2
  202. Delete(pRoot, pRoot->left); //delete 3
  203. Delete(pRoot, pRoot);// delete 4
  204. Delete(pRoot, pRoot->left);//delete 1
  205. walk(pRoot);
  206. //destroy
  207. Node* pCurrent = minimum(pRoot);
  208. while(pCurrent)
  209. {
  210. delete pCurrent;
  211. pCurrent = pCurrent->succ;
  212. }
  213. pCurrent = nullptr;
  214. }
  215. /*output
  216. 1 2 3 4 5 6 7 8
  217. 3
  218. 6
  219. 4
  220. 6 7 8
  221. */
  • 12.3-6 当x有两个孩子的时候,x的接班人y取x的前驱和后继都是可以的,具体实现方法是:用一个bool量控制下一个y取两者中的哪一个,交替着选取。在y取后继节点时,需要将y现有的右孩子托付给自己的父亲(已经在《算法导论》12.3节习题 实现);若y取前驱节点,需要做的修改是把自己的左孩子托付给自己的父亲。

版权声明:本文为meixiaogua原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/meixiaogua/p/9895377.html