基本概念

二叉搜索树 - 图1

二叉树

二叉树中的节点最多只能有两个子节点:
一个是左侧子节点,另一个是右侧子节点。
这个定义有助于我们写出更高效地在树中插入、查找和删除节点的算法
二叉树在计算机科学中的应用非常广泛。

二叉搜索树

二叉搜索树(BST)是二叉树的一种
但是只允许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大的值。
二叉搜索树 - 图2

二叉搜索树的代码实现

结构

  1. class Node {
  2. constructor(key) {
  3. this.key = key; // 存值
  4. this.left = null; // 左子结点
  5. this.right = null; // 右子结点
  6. }
  7. }
  8. // 设计比较大小的方法,注意可读性
  9. let Compare = {
  10. LES_THAN: -1,
  11. BIG_THAN: 1,
  12. EQUAL: 0,
  13. };
  14. class BinarySearch {
  15. constructor() {
  16. this.root = null; // 二叉树结构的根结点
  17. }
  18. compareFn(a, b) {
  19. if (a === b) {
  20. return Compare.EQUAL;
  21. }
  22. return a < b ? Compare.LES_THAN : Compare.BIG_THAN;
  23. }
  24. }
  • insert(key):向树中插入一个新的键。
  • search(key):在树中查找一个键。如果节点存在,则返回true;如果不存在,则返回false。
  • inOrderTraverse():通过中序遍历方式遍历所有节点。
  • preOrderTraverse():通过先序遍历方式遍历所有节点。
  • postOrderTraverse():通过后序遍历方式遍历所有节点。
  • min():返回树中最小的值/键。
  • max():返回树中最大的值/键。
  • remove(key):从树中移除某个键。

insert

  1. insert(key) {
  2. if (this.root === null) {
  3. this.root = new Node(key);
  4. } else {
  5. this.insertNode(this.root, key);
  6. }
  7. }
  8. insertNode(node, key) {
  9. if (this.compareFn(key, node.key) === Compare.LES_THAN) {
  10. // 如果插入的值比当前结点小,往左子树走
  11. if (node.left === null) {
  12. // 左子树为空,就插入左子树
  13. node.left = new Node(key);
  14. } else {
  15. // 左子树不为空就向下递归判断左右子树
  16. this.insertNode(node.left, key);
  17. }
  18. }
  19. // 右子树
  20. else {
  21. if (node.right === null) {
  22. node.right = new Node(key);
  23. } else {
  24. this.insertNode(node.right, key);
  25. }
  26. }
  27. }

Traverse 遍历

中序遍历(左中右)

  • 要通过中序遍历的方法遍历一棵树,首先要检查以参数形式传入的节点是否为null(这就是停止递归继续执行的判断条件,即递归算法的基线条件)。
  • 然后,递归调用相同的函数来访问左侧子节点。
  • 接着对根节点进行一些操作(callback),然后再访问右侧子节点
  1. inOrderTraverse(cb) {
  2. this.inOrderTraverseNode(this.root, cb);
  3. }
  4. inOrderTraverseNode(node, cb) {
  5. if (node != null) {
  6. this.inOrderTraverseNode(node.left, cb);
  7. cb(node.key);
  8. this.inOrderTraverseNode(node.right, cb);
  9. }
  10. }

先序遍历(中左右)

二叉搜索树 - 图3

  1. preOrderTraverse(cb) {
  2. this.preOrderTraverseNode(this.root, cb);
  3. }
  4. preOrderTraverseNode(node, cb) {
  5. if (node != null) {
  6. cb(node.key);
  7. this.preOrderTraverseNode(node.left, cb);
  8. this.preOrderTraverseNode(node.right, cb);
  9. }
  10. }

后序遍历(左右中)

二叉搜索树 - 图4

  1. postOrderTraverse(cb) {
  2. this.postOrderTraverseNode(this.root, cb);
  3. }
  4. postOrderTraverseNode(node, cb) {
  5. if (node != null) {
  6. this.postOrderTraverseNode(node.left, cb);
  7. this.postOrderTraverseNode(node.right, cb);
  8. cb(node.key);
  9. }
  10. }

最大最小值

  1. min() {
  2. let curr = this.minNode(this.root);
  3. }
  4. minNode(node) {
  5. let curr = node;
  6. while (curr && curr.left) {
  7. curr = curr.left;
  8. }
  9. return curr;
  10. }
  11. max() {
  12. let curr = this.maxNode(this.root);
  13. }
  14. maxNode(node) {
  15. let curr = node;
  16. if (curr && curr.right) {
  17. curr = curr.right;
  18. }
  19. return curr;
  20. }

搜索值是否存在

  1. search(key) {
  2. return this.searchNode(this.root, key);
  3. }
  4. searchNode(node, key) {
  5. if (node === null) {
  6. return false;
  7. }
  8. if (this.compareFn(key, node.key) === Compare.LES_THAN) {
  9. return this.searchNode(node.left, key);
  10. }
  11. // 右子树
  12. else if (this.compareFn(key, node.key) === Compare.BIG_THAN) {
  13. return this.searchNode(node.right, key);
  14. }
  15. else {
  16. return true;
  17. }
  18. }

删除树结点

找到需要删除的结点后,要分三种情况

  1. 是叶结点
    二叉搜索树 - 图5
  2. 有一个子结点
    二叉搜索树 - 图6
  3. 有两个子结点:需要把下面最小的结点补充到这个被删除结点的位置
    二叉搜索树 - 图7
    思路:需要记录删除结点后的数
  1. remove(key) {
  2. return this.removeNode(this.root, key);
  3. }
  4. removeNode(node, key) {
  5. // 没有了
  6. if (node === null) {
  7. return null;
  8. }
  9. // 找左子树
  10. if (this.compareFn(key, node.key) === Compare.LES_THAN) {
  11. node.left = this.removeNode(node.left, key);
  12. return node;
  13. }
  14. // 找右子树
  15. else if (this.compareFn(key, node.key) === Compare.BIG_THAN) {
  16. node.right = this.removeNode(node.right, key);
  17. return node;
  18. }
  19. else {
  20. // 1. 删除的是个叶结点
  21. if (!node.left && !node.right) {
  22. node = null;
  23. return node;
  24. }
  25. // 2. 没有左(右)子结点,但是有右(左)子结点(只有一个孩子结点)
  26. else if (!node.left && node.right) {
  27. node = node.right;
  28. return node;
  29. }
  30. else if (node.left && !node.right) {
  31. node = node.left;
  32. return node;
  33. }
  34. // 3. 被删除的结点有两个孩子结点
  35. else {
  36. const minNode = this.minNode(node.right); // 找到孩子结点中的最小的一个
  37. // 用这个最小的结点替换掉删除结点
  38. node.key = minNode.key;
  39. // 删除找到的最小结点,并将这个结点的左子树重构(返回删除后的结点)
  40. node.right = this.removeNode(node.right, minNode.key);
  41. return node;
  42. }
  43. }
  44. }

完整代码

  1. class Node {
  2. constructor(key) {
  3. this.key = key; // 存值
  4. this.left = null; // 左子结点
  5. this.right = null; // 右子结点
  6. }
  7. }
  8. // 设计比较大小的方法,注意可读性
  9. let Compare = {
  10. LES_THAN: -1,
  11. BIG_THAN: 1,
  12. EQUAL: 0,
  13. };
  14. class BinarySearch {
  15. constructor() {
  16. this.root = null; // 二叉树结构的根结点
  17. }
  18. compareFn(a, b) {
  19. if (a === b) {
  20. return Compare.EQUAL;
  21. }
  22. return a < b ? Compare.LES_THAN : Compare.BIG_THAN;
  23. }
  24. insert(key) {
  25. if (this.root === null) {
  26. this.root = new Node(key);
  27. } else {
  28. this.insertNode(this.root, key);
  29. }
  30. }
  31. insertNode(node, key) {
  32. if (this.compareFn(key, node.key) === Compare.LES_THAN) {
  33. // 如果插入的值比当前结点小,往左子树走
  34. if (node.left === null) {
  35. // 左子树为空,就插入左子树
  36. node.left = new Node(key);
  37. } else {
  38. // 左子树不为空就向下递归判断左右子树
  39. this.insertNode(node.left, key);
  40. }
  41. }
  42. // 右子树
  43. else {
  44. if (node.right === null) {
  45. node.right = new Node(key);
  46. } else {
  47. this.insertNode(node.right, key);
  48. }
  49. }
  50. }
  51. inOrderTraverse(cb) {
  52. this.inOrderTraverseNode(this.root, cb);
  53. }
  54. inOrderTraverseNode(node, cb) {
  55. if (node != null) {
  56. this.inOrderTraverseNode(node.left, cb);
  57. cb(node.key);
  58. this.inOrderTraverseNode(node.right, cb);
  59. }
  60. }
  61. // 前序
  62. preOrderTraverse(cb) {
  63. this.preOrderTraverseNode(this.root, cb);
  64. }
  65. preOrderTraverseNode(node, cb) {
  66. if (node != null) {
  67. cb(node.key);
  68. this.preOrderTraverseNode(node.left, cb);
  69. this.preOrderTraverseNode(node.right, cb);
  70. }
  71. }
  72. // 后序遍历
  73. postOrderTraverse(cb) {
  74. this.postOrderTraverseNode(this.root, cb);
  75. }
  76. postOrderTraverseNode(node, cb) {
  77. if (node != null) {
  78. this.postOrderTraverseNode(node.left, cb);
  79. this.postOrderTraverseNode(node.right, cb);
  80. cb(node.key);
  81. }
  82. }
  83. min() {
  84. let curr = this.minNode(this.root);
  85. }
  86. minNode(node) {
  87. let curr = node;
  88. while (curr && curr.left) {
  89. curr = curr.left;
  90. }
  91. return curr;
  92. }
  93. max() {
  94. let curr = this.maxNode(this.root);
  95. }
  96. maxNode(node) {
  97. let curr = node;
  98. if (curr && curr.right) {
  99. curr = curr.right;
  100. }
  101. return curr;
  102. }
  103. search(key) {
  104. return this.searchNode(this.root, key);
  105. }
  106. searchNode(node, key) {
  107. if (node === null) {
  108. return false;
  109. }
  110. if (this.compareFn(key, node.key) === Compare.LES_THAN) {
  111. return this.searchNode(node.left, key);
  112. }
  113. // 右子树
  114. else if (this.compareFn(key, node.key) === Compare.BIG_THAN) {
  115. return this.searchNode(node.right, key);
  116. }
  117. else {
  118. return true;
  119. }
  120. }
  121. remove(key) {
  122. return this.removeNode(this.root, key);
  123. }
  124. removeNode(node, key) {
  125. // 没有了
  126. if (node === null) {
  127. return null;
  128. }
  129. // 左子树
  130. if (this.compareFn(key, node.key) === Compare.LES_THAN) {
  131. node.left = this.removeNode(node.left, key);
  132. return node;
  133. }
  134. // 右子树
  135. else if (this.compareFn(key, node.key) === Compare.BIG_THAN) {
  136. node.right = this.removeNode(node.right, key);
  137. return node;
  138. }
  139. else {
  140. // 1. 删除的是个叶结点
  141. if (!node.left && !node.right) {
  142. node = null;
  143. return node;
  144. }
  145. // 2. 没有左(右)子结点,但是有右(左)子结点(只有一个孩子结点)
  146. else if (!node.left && node.right) {
  147. node = node.right;
  148. return node;
  149. }
  150. else if (node.left && !node.right) {
  151. node = node.left;
  152. return node;
  153. }
  154. // 3. 被删除的结点有两个孩子结点
  155. else {
  156. const minNode = this.minNode(node.right); // 找到孩子结点中的最小的一个
  157. // 用这个最小的结点替换掉删除结点
  158. node.key = minNode.key;
  159. // 删除找到的最小结点,并将这个结点的左子树重构(返回删除后的结点)
  160. node.right = this.removeNode(node.right, minNode.key);
  161. return node;
  162. }
  163. }
  164. }
  165. }
  166. let tree = new BinarySearch();
  167. tree.insert(4);
  168. tree.insert(3);
  169. tree.insert(2);
  170. tree.insert(1);
  171. tree.insert(5);
  172. tree.insert(6);
  173. console.log(tree);
  174. // tree.inOrderTraverse(function (key) {
  175. // console.log(key);
  176. // });
  177. // tree.preOrderTraverse(function (key) {
  178. // console.log(key);
  179. // });
  180. tree.postOrderTraverse(function (key) {
  181. console.log(key);
  182. });
  183. console.log(tree.search(10));
  184. console.log(tree.remove(5));