【从蛋壳到满天飞】JS 数据结构解析和算法实现-二分搜索树(二)

思惟导图

前言

【从蛋壳到满天飞】JS 数据结构解析和算法实现,所有文章大概的内容以下: Arrays(数组)、Stacks(栈)、Queues(队列)、LinkedList(链表)、Recursion(递归思想)、BinarySearchTree(二分搜索树)、Set(集合)、Map(映射)、Heap(堆)、PriorityQueue(优先队列)、SegmentTree(线段树)、Trie(字典树)、UnionFind(并查集)、AVLTree(AVL 平衡树)、RedBlackTree(红黑平衡树)、HashTable(哈希表)html

源代码有三个:ES6(单个单个的 class 类型的 js 文件) | JS + HTML(一个 js 配合一个 html)| JAVA (一个一个的工程)node

所有源代码已上传 github,点击我吧,光看文章可以掌握两成,动手敲代码、动脑思考、画图才能够掌握八成。git

本文章适合 对数据结构想了解而且感兴趣的人群,文章风格一如既往如此,就以为手机上看起来比较方便,这样显得比较有条理,整理这些笔记加源码,时间跨度也算将近半年时间了,但愿对想学习数据结构的人或者正在学习数据结构的人群有帮助。github

二分搜索树的遍历-非递归写法

  1. 前序遍历的递归写法面试

    1. 前序遍历是最天然的一种遍历方式,
    2. 同时也是最经常使用的一种遍历方式,
    3. 若是没有特殊状况的话,
    4. 在大多数状况下都会使用前序遍历。
    5. 先访问这个节点,
    6. 而后访问这个节点的左子树,
    7. 再访问这个节点的右子树,
    8. 整个过程循环往复。
    9. 前序遍历的表示先访问的这个节点。
    function preOrder(node) {
       if (node == null) return;
    
       // ... 要作的事情
       // 访问该节点
    
       // 先一直往左,而后不断返回上一层 再向左、终止,
       // 最后整个操做循环往复,直到所有终止。
       preOrder(node.left);
       preOrder(node.right);
    }
    复制代码
  2. 前序遍历的非递归写法算法

    1. 使用另一个数据结构来模拟递归调用时的系统栈。
    2. 先访问根节点,将根节点压入栈,
    3. 而后把栈顶元素拿出来,对这个节点进行操做,
    4. 这个节点操做完毕以后,再访问这个节点的两个子树,
    5. 也就是把这个节点的左右两个孩子压入栈中,
    6. 压入栈的顺序是先压入右孩子、再压入左孩子,
    7. 这是由于栈是后入先出的,因此要先压入后续要访问的那个节点,
    8. 再让栈顶的元素出栈,对这个节点进行操做,
    9. 这个节点操做完毕以后,再访问这个节点的两个子树,
    10. 可是这个节点是叶子节点,它的两个孩子都为空,
    11. 那么什么都不用压入了, 再去取栈顶的元素,
    12. 对这个节点进行操做,这个节点操做完毕以后,
    13. 再访问这个节点的两个子树,可是这个节点也是叶子节点,
    14. 那么什么都不用压入了,栈中也为空了,整个访问操做结束。
  3. 不管是非递归仍是递归的写法,结果都是一致的数组

    1. 非递归的写法中,栈的应用是帮助你记录你下面要访问的哪些节点,
    2. 这个过程很是像使用栈模拟了一下在系统栈中相应的一个调用,
    3. 至关于在系统栈中记录下一步依次要访问哪些节点。
  4. 将递归算法转换为非递归算法数据结构

    1. 是栈这种数据结构很是重要的一种应用。
  5. 二分搜索树遍历的非递归实现比递归实现复杂不少dom

    1. 由于你使用了一个辅助的数据结构才能完成这个过程,
    2. 使用了栈这种数据结构模拟了系统调用栈,
    3. 在算法语意解读上远远比递归实现的算法语意解读要难不少。
  6. 二分搜索树的中序遍历和后序遍历的非递归实现更复杂ide

    1. 尤为是对于后序遍从来说难度更大,
    2. 可是中序遍历和后序遍历的非递归实现,实际应用并不普遍。
    3. 可是你能够尝试实现中序、后序遍历的非递归实现,
    4. 主要是锻炼你算法实现、思惟逻辑实现思路,
    5. 在解决这个问题的过程当中可能会遇到一些困难,
    6. 能够经过查看网上的资料来解决这个问题,
    7. 这样的问题有可能会在面试题及考试中出现,
    8. 也就是中序和后序遍历相应的非递归实现。
    9. 在经典的教科书中通常都会有这三种遍历的非递归实现,
    10. 经过二分搜索树的前序遍历非递归的实现方式中能够看出,
    11. 彻底可使用模拟系统的栈来完成递归转成非递归这样的操做,
    12. 在慕课上 有一门课《玩转算法面试》中彻底模拟了系统栈的写法,
    13. 也就是将前中后序的遍历都转成了非递归的算法,
    14. 这与经典的教科书上的实现不同,
    15. 可是这种方式对你进一步理解栈这种数据结构仍是二分搜索树的遍历
    16. 甚至是系统调用的过程都是颇有意义的。
  7. 对于前序遍从来说不管是递归写法仍是非递归写法

    1. 对于这棵树来讲都是在遍历的过程当中一直到底,
    2. 这样的一种遍历方式也叫深度优先遍历,
    3. 最终的遍历结果都会先来到整颗树最深的地方,
    4. 直到不能再深了才会开始返回到上一层,
    5. 因此这种遍历就叫作深度优先遍历。
    6. 与深度优先遍历相对应的就是广度优先遍历,
    7. 广度优先遍历遍历出来的结果它的顺序实际上是
    8. 整个二分搜索树的一个层序遍历的顺序。

代码示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    // 自定义二分搜索树节点
    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 实际存储的元素
          this.element = element;
          // 当前节点的左子树
          this.left = left;
          // 当前节点的右子树
          this.right = right;
       }
    }
    
    // 自定义二分搜索树
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索树中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索树中 递归算法 -
       recursiveAdd(node, newElement) {
          // 解决最基本的问题 也就是递归函数调用的终止条件
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeNode(newElement);
          }
    
          // 1. 当前节点的元素比新元素大
          // 那么新元素就会被添加到当前节点的左子树去
          // 2. 当前节点的元素比新元素小
          // 那么新元素就会被添加到当前节点的右子树去
          // 3. 当前节点的元素比新元素相等
          // 什么都不作了,由于目前不添加剧复的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 将复杂问题分解成多个性质相同的小问题,
          // 而后求出小问题的答案,
          // 最终构建出原问题的答案
          return node;
       }
    
       // 判断二分搜索树中是否包含某个元素 +
       contains(element) {
          if (this.root === null) throw new Error("root is null. can't query.");
    
          return this.recursiveContains(this.root, element);
       }
    
       // 判断二分搜索树种是否包含某个元素 递归算法 -
       recursiveContains(node, element) {
          if (node === null) return false;
    
          // 当前节点元素比 要搜索的元素 大
          if (this.compare(node.element, element) > 0)
             return this.recursiveContains(node.left, element);
          else if (this.compare(node.element, element) < 0)
             // 当前元素比 要搜索的元素 小
             return this.recursiveContains(node.right, element);
          // 两个元素相等
          else return true;
       }
    
       // 前序遍历 +
       preOrder(operator) {
          this.recursivePreOrder(this.root, operator);
       }
    
       // 前序遍历 递归算法 -
       recursivePreOrder(node, operator) {
          if (node === null) return;
    
          // 调用一下操做方法
          operator(node.element);
          console.log(node, node.element);
    
          // 继续递归遍历左右子树
          this.recursivePreOrder(node.left, operator);
          this.recursivePreOrder(node.right, operator);
       }
    
       // 前序遍历 非递归算法 +
       nonRecursivePreOrder(operator) {
          let stack = new MyLinkedListStack();
          stack.push(this.root);
    
          let node = null;
          while (!stack.isEmpty()) {
             // 出栈操做
             node = stack.pop();
    
             operator(node.element); // 访问当前的节点
             console.log(node.element);
    
             // 栈是先入后出的,把须要后访问的节点 先放进去,先访问的节点后放进去
             // 前序遍历是访问当前节点,而后再遍历左子树,最后遍历右子树
             if (node.right !== null) stack.push(node.right);
             if (node.left !== null) stack.push(node.left);
          }
       }
    
       // 中序遍历 +
       inOrder(operator) {
          this.recursiveInOrder(this.root, operator);
       }
    
       // 中序遍历 递归算法 -
       recursiveInOrder(node, operator) {
          if (node == null) return;
    
          this.recursiveInOrder(node.left, operator);
    
          operator(node.element);
          console.log(node.element);
    
          this.recursiveInOrder(node.right, operator);
       }
    
       // 后序遍历 +
       postOrder(operator) {
          this.recursivePostOrder(this.root, operator);
       }
    
       // 后序遍历 递归算法 -
       recursivePostOrder(node, operator) {
          if (node == null) return;
    
          this.recursivePostOrder(node.left, operator);
          this.recursivePostOrder(node.right, operator);
    
          operator(node.element);
          console.log(node.element);
       }
    
       // 获取二分搜索树中节点个数 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索树是否为空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一个比较的方法,专门用来比较新增的元素大小 -
       // 第一个元素比第二个元素大 就返回 1
       // 第一个元素比第二个元素小 就返回 -1
       // 第一个元素比第二个元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接写死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 输出二分搜索树中的信息
       // @Override toString 2018-11-03-jwl
       toString() {
          let treeInfo = '';
          treeInfo += this.getBinarySearchTreeString(this.root, 0, treeInfo);
          return treeInfo;
       }
    
       // 写一个辅助函数,用来生成二分搜索树信息的字符串
       getBinarySearchTreeString(node, depth, treeInfo, pageContent = '') {
          //之前序遍历的方式
    
          if (node === null) {
             treeInfo += this.getDepthString(depth) + 'null \r\n';
    
             pageContent = this.getDepthString(depth) + 'null<br /><br />';
             document.body.innerHTML += `${pageContent}`;
    
             return treeInfo;
          }
    
          treeInfo += this.getDepthString(depth) + node.element + '\r\n';
    
          pageContent =
             this.getDepthString(depth) + node.element + '<br /><br />';
          document.body.innerHTML += `${pageContent}`;
    
          treeInfo = this.getBinarySearchTreeString(
             node.left,
             depth + 1,
             treeInfo
          );
          treeInfo = this.getBinarySearchTreeString(
             node.right,
             depth + 1,
             treeInfo
          );
    
          return treeInfo;
       }
    
       // 写一个辅助函数,用来生成递归深度字符串
       getDepthString(depth) {
          let depthString = '';
          for (var i = 0; i < depth; i++) {
             depthString += '-- ';
          }
          return depthString;
       }
    }
    复制代码
  2. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('MyBinarySearchTree Area');
          let myBinarySearchTree = new MyBinarySearchTree();
          let nums = [5, 3, 6, 8, 4, 2];
          for (var i = 0; i < nums.length; i++) {
             myBinarySearchTree.add(nums[i]);
          }
    
          /////////////////
          // 5 //
          // / \ //
          // 3 6 //
          // / \ \ //
          // 2 4 8 //
          /////////////////
    
          this.alterLine('MyBinarySearchTree PreOrder Area');
          myBinarySearchTree.preOrder(this.show);
    
          this.alterLine('MyBinarySearchTree NonRecursivePreOrder Area');
          myBinarySearchTree.nonRecursivePreOrder(this.show);
    
          this.alterLine('MyBinarySearchTree InOrder Area');
          myBinarySearchTree.inOrder(this.show);
    
          this.alterLine('MyBinarySearchTree PostOrder Area');
          myBinarySearchTree.postOrder(this.show);
       }
    
       // 将内容显示在页面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展现分割线
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    
    // 页面加载完毕
    window.onload = function() {
       // 执行主函数
       new Main();
    };
    复制代码

二分搜索树的层序遍历

  1. 二分搜索树的 前序、中序、后序遍历
    1. 它们本质上都是深度优先遍历。
  2. 对于二分搜索树来讲
    1. 每个节点都有一个相应的深度的值,
    2. 根节点做为深度为 0 相应的节点,
    3. 有一些教科书 会把根节点做为深度为 1 相应的节点,
    4. 若是以计算机世界里索引的定义为准那就是使用 0,
    5. 根节点就是第 0 层。
  3. 先遍历第 0 层、再遍历第 1 层、再遍历下一层,
    1. 这样的一层一层的遍历就称为广度优先遍历,
    2. 逐层向下遍历的节点在广度上进行拓展,
    3. 这样的一个遍历顺序就叫作层序遍历、广度优先遍历,
    4. 而不像以前那样 先顺着一个枝杈向着最深的地方走。
  4. 对于层序遍历的实现或者广度优先遍历的实现
    1. 一般不是使用递归的方式进行实现的,
    2. 而是使用非递归的方式进行实现的,
    3. 而且在其中须要使用另外的一个数据结构队列,
    4. 从根节点开始排着队的进入这个队列,
    5. 队列中存储的就是待遍历的元素,
    6. 每一次遍历的它的元素以后再将它的左右孩子也排进队列中,
    7. 整个过程依此类推。
  5. 先入队根节点,而后看队首是否有元素,
    1. 有的话就对队首的元素进行操做,
    2. 操做完毕后就将操做完毕的元素的左右孩子也入队,
    3. 而后再对队列中的元素进行操做,
    4. 队列中的元素又操做完毕了,
    5. 再让操做完毕的这些元素的左右孩子入队,
    6. 最后在对队列中的元素进行操做,
    7. 这些元素都是叶子节点没有左右孩子了,,
    8. 不用入队了,队列中没有元素,整个过程处理完毕,
    9. 这个处理过程就是一层一层的进行处理的一个顺序,
    10. 这就是二分搜索树的广度优先遍历,也叫层序遍历。
  6. 相对于深度优先遍从来说,广度优先遍历的优势
    1. 它能更快的找到你想要查询的那个元素,
    2. 这样的区别主要用于搜索策略上,
    3. 而不是用在遍历这个操做上,
    4. 虽然遍历要将整个二叉树上全部的元素都访问一遍,
    5. 这种状况下深度优先遍历和广度优先遍历是没有区别的。
    6. 可是若是想在一棵树中找到某一个问题的解,
    7. 那对于深度优先遍从来说
    8. 它会从根节点一股脑的跑到这棵树很是深的地方,
    9. 可是颇有可能这个问题的解并不在那么深的地方而是很浅的地方,
    10. 这样一来深度优先遍历要花很长时间才能访问到这个很浅的地方,
    11. 例如前序遍历,若是这个问题的解在右子树上很浅的位置,
    12. 你从一开始就从根节点遍历到左子树的最深处,那就不必了,
    13. 可是这个经常使用于算法设计中,如无权图的最短路径,
    14. 树这种结构在算法设计里也有很是重要的应用,
    15. 尤为是不少时候设计出一个算法,可能真正不须要把这个树发现出来,
    16. 可是这个算法的整个过程就是在一棵虚拟的树中完成的。
  7. 在图中也是有深度优先遍历和广度优先遍历的
    1. 在树中和图中进行深度优先遍历其实它们的实质是同样的,
    2. 不一样的点,对于图来讲须要记录一下对于某一个节点以前是否曾经遍历过,
    3. 由于对于图来讲每个节点的前驱或者放在树这个模型中
    4. 相应的术语就是每一节点它的父亲可能有多个,
    5. 从而产生重复访问这样的问题,而这样的问题在树结构中是不存在的,
    6. 因此在图结构中须要作一个相应的记录。

代码示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    // 自定义二分搜索树节点
    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 实际存储的元素
          this.element = element;
          // 当前节点的左子树
          this.left = left;
          // 当前节点的右子树
          this.right = right;
       }
    }
    
    // 自定义二分搜索树
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索树中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索树中 递归算法 -
       recursiveAdd(node, newElement) {
          // 解决最基本的问题 也就是递归函数调用的终止条件
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeNode(newElement);
          }
    
          // 1. 当前节点的元素比新元素大
          // 那么新元素就会被添加到当前节点的左子树去
          // 2. 当前节点的元素比新元素小
          // 那么新元素就会被添加到当前节点的右子树去
          // 3. 当前节点的元素比新元素相等
          // 什么都不作了,由于目前不添加剧复的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 将复杂问题分解成多个性质相同的小问题,
          // 而后求出小问题的答案,
          // 最终构建出原问题的答案
          return node;
       }
    
       // 判断二分搜索树中是否包含某个元素 +
       contains(element) {
          if (this.root === null) throw new Error("root is null. can't query.");
    
          return this.recursiveContains(this.root, element);
       }
    
       // 判断二分搜索树种是否包含某个元素 递归算法 -
       recursiveContains(node, element) {
          if (node === null) return false;
    
          // 当前节点元素比 要搜索的元素 大
          if (this.compare(node.element, element) > 0)
             return this.recursiveContains(node.left, element);
          else if (this.compare(node.element, element) < 0)
             // 当前元素比 要搜索的元素 小
             return this.recursiveContains(node.right, element);
          // 两个元素相等
          else return true;
       }
    
       // 前序遍历 +
       preOrder(operator) {
          this.recursivePreOrder(this.root, operator);
       }
    
       // 前序遍历 递归算法 -
       recursivePreOrder(node, operator) {
          if (node === null) return;
    
          // 调用一下操做方法
          operator(node.element);
          console.log(node, node.element);
    
          // 继续递归遍历左右子树
          this.recursivePreOrder(node.left, operator);
          this.recursivePreOrder(node.right, operator);
       }
    
       // 前序遍历 非递归算法 +
       nonRecursivePreOrder(operator) {
          let stack = new MyLinkedListStack();
          stack.push(this.root);
    
          let node = null;
          while (!stack.isEmpty()) {
             // 出栈操做
             node = stack.pop();
    
             operator(node.element); // 访问当前的节点
             console.log(node.element);
    
             // 栈是先入后出的,把须要后访问的节点 先放进去,先访问的节点后放进去
             // 前序遍历是访问当前节点,而后再遍历左子树,最后遍历右子树
             if (node.right !== null) stack.push(node.right);
             if (node.left !== null) stack.push(node.left);
          }
       }
    
       // 中序遍历 +
       inOrder(operator) {
          this.recursiveInOrder(this.root, operator);
       }
    
       // 中序遍历 递归算法 -
       recursiveInOrder(node, operator) {
          if (node == null) return;
    
          this.recursiveInOrder(node.left, operator);
    
          operator(node.element);
          console.log(node.element);
    
          this.recursiveInOrder(node.right, operator);
       }
    
       // 后序遍历 +
       postOrder(operator) {
          this.recursivePostOrder(this.root, operator);
       }
    
       // 后序遍历 递归算法 -
       recursivePostOrder(node, operator) {
          if (node == null) return;
    
          this.recursivePostOrder(node.left, operator);
          this.recursivePostOrder(node.right, operator);
    
          operator(node.element);
          console.log(node.element);
       }
    
       // 层序遍历
       levelOrder(operator) {
          let queue = new MyLinkedListQueue();
          queue.enqueue(this.root);
    
          let node = null;
          while (!queue.isEmpty()) {
             node = queue.dequeue();
    
             operator(node.element);
             console.log(node.element);
    
             // 队列 是先进先出的,因此从左往右入队
             // 栈 是后进先出的, 因此从右往左入栈
             if (node.left !== null) queue.enqueue(node.left);
    
             if (node.right !== null) queue.enqueue(node.right);
          }
       }
    
       // 获取二分搜索树中节点个数 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索树是否为空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一个比较的方法,专门用来比较新增的元素大小 -
       // 第一个元素比第二个元素大 就返回 1
       // 第一个元素比第二个元素小 就返回 -1
       // 第一个元素比第二个元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接写死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 输出二分搜索树中的信息
       // @Override toString 2018-11-03-jwl
       toString() {
          let treeInfo = '';
          treeInfo += this.getBinarySearchTreeString(this.root, 0, treeInfo);
          return treeInfo;
       }
    
       // 写一个辅助函数,用来生成二分搜索树信息的字符串
       getBinarySearchTreeString(node, depth, treeInfo, pageContent = '') {
          //之前序遍历的方式
    
          if (node === null) {
             treeInfo += this.getDepthString(depth) + 'null \r\n';
    
             pageContent = this.getDepthString(depth) + 'null<br /><br />';
             document.body.innerHTML += `${pageContent}`;
    
             return treeInfo;
          }
    
          treeInfo += this.getDepthString(depth) + node.element + '\r\n';
    
          pageContent =
             this.getDepthString(depth) + node.element + '<br /><br />';
          document.body.innerHTML += `${pageContent}`;
    
          treeInfo = this.getBinarySearchTreeString(
             node.left,
             depth + 1,
             treeInfo
          );
          treeInfo = this.getBinarySearchTreeString(
             node.right,
             depth + 1,
             treeInfo
          );
    
          return treeInfo;
       }
    
       // 写一个辅助函数,用来生成递归深度字符串
       getDepthString(depth) {
          let depthString = '';
          for (var i = 0; i < depth; i++) {
             depthString += '-- ';
          }
          return depthString;
       }
    }
    复制代码
  2. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('MyBinarySearchTree Area');
          let myBinarySearchTree = new MyBinarySearchTree();
          let nums = [5, 3, 6, 8, 4, 2];
          for (var i = 0; i < nums.length; i++) {
             myBinarySearchTree.add(nums[i]);
          }
    
          /////////////////
          // 5 //
          // / \ //
          // 3 6 //
          // / \ \ //
          // 2 4 8 //
          /////////////////
    
          this.alterLine('MyBinarySearchTree PreOrder Area');
          myBinarySearchTree.preOrder(this.show);
    
          this.alterLine('MyBinarySearchTree NonRecursivePreOrder Area');
          myBinarySearchTree.nonRecursivePreOrder(this.show);
    
          this.alterLine('MyBinarySearchTree InOrder Area');
          myBinarySearchTree.inOrder(this.show);
    
          this.alterLine('MyBinarySearchTree PostOrder Area');
          myBinarySearchTree.postOrder(this.show);
    
          this.alterLine('MyBinarySearchTree LevelOrder Area');
          myBinarySearchTree.levelOrder(this.show);
       }
    
       // 将内容显示在页面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展现分割线
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    
    // 页面加载完毕
    window.onload = function() {
       // 执行主函数
       new Main();
    };
    复制代码

学习方法

  1. 不少时候学习知识
    1. 并非简单的一起一起把它们学过了就能够了,
    2. 不少时候要想可以达到灵活运用可以达到理解的深入,都须要进行比对,
    3. 刻意的去找到从不一样方法之间它们的区别和联系,
    4. 以及本身去总结不一样的方法适用于什么样的场合,
    5. 只有这样,这些知识才可以在你的脑海中才不是一个一个的碎片,
    6. 而是有机的联系起来的,面对不一样的问题才能很是的快的
    7. 而且准确的说出来用怎样的方法去解决更加的好。

二分搜索树的删除节点-删除最大最小值

  1. 对于二分搜索树来讲删除一个节点相对来讲是比较复杂的
    1. 能够先对这个操做进行拆解,从最简单的开始。
  2. 删除二分搜索树的最小值和最大值
    1. 删除二分搜索树中任意元素会复用到
    2. 删除二分搜索树最大值和最小值相应的逻辑。
    3. 要想删除二分搜索树中最大值和最小值,
    4. 那么就要先找到二分搜索树中的最大值和最小值。
  3. 找到二分搜索树中的最大值和最小值是很是容易的
    1. 每个节点的左子树上全部的节点的值都小于当前这个节点,
    2. 每个节点的右子树上全部的节点的值都大于当前这个节点,
    3. 那么从根节点开始一直向左,直到不能再左了,就能找到最小值,
    4. 反之从根节点开始一直向右,知道不能再右了,就能找到最大值。
    5. 这个操做就像操做链表同样,就像是在找一条链上的尾节点。
  4. 删除最大元素节点
    1. 要删除最大元素的这个节点可能有左孩子节点可是没有右孩子节点,
    2. 因此可能会致使没法继续向右因而递归就终止了,
    3. 那么这个时候删除这个节点能够采用当前节点的左孩子替代当前这个节点,
    4. 覆盖操做也算是删除了当前这个节点了。
    5. 若是你像返回被删除的这个最大元素节点,你能够先查询出这个最大的元素节点,
    6. 而后存到一个变量中,最后再调用删除这个最大元素节点的方法,最终返回存的这个变量。
  5. 删除最小元素节点
    1. 要删除的最小元素的节点可能有右孩子节点可是没有左孩子节点,
    2. 会致使没法继续向左而递归终止,你不能删除这个节点的同时连右孩子一块儿删除,
    3. 因此这个时候删除这个节点能够采用当前节点的右孩子替代当前这个节点,
    4. 覆盖操做也算是删除了当前这个节点了,
    5. 其它的和删除最大元素同样,先查询出来,而后存起来,删除这个最大元素后,
    6. 再返回以前存起来的最大元素的变量。

代码示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    // 自定义二分搜索树节点
    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 实际存储的元素
          this.element = element;
          // 当前节点的左子树
          this.left = left;
          // 当前节点的右子树
          this.right = right;
       }
    }
    
    // 自定义二分搜索树
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索树中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索树中 递归算法 -
       recursiveAdd(node, newElement) {
          // 解决最基本的问题 也就是递归函数调用的终止条件
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeNode(newElement);
          }
    
          // 1. 当前节点的元素比新元素大
          // 那么新元素就会被添加到当前节点的左子树去
          // 2. 当前节点的元素比新元素小
          // 那么新元素就会被添加到当前节点的右子树去
          // 3. 当前节点的元素比新元素相等
          // 什么都不作了,由于目前不添加剧复的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 将复杂问题分解成多个性质相同的小问题,
          // 而后求出小问题的答案,
          // 最终构建出原问题的答案
          return node;
       }
    
       // 判断二分搜索树中是否包含某个元素 +
       contains(element) {
          if (this.root === null) throw new Error("root is null. can't query.");
    
          return this.recursiveContains(this.root, element);
       }
    
       // 判断二分搜索树种是否包含某个元素 递归算法 -
       recursiveContains(node, element) {
          if (node === null) return false;
    
          // 当前节点元素比 要搜索的元素 大
          if (this.compare(node.element, element) > 0)
             return this.recursiveContains(node.left, element);
          else if (this.compare(node.element, element) < 0)
             // 当前元素比 要搜索的元素 小
             return this.recursiveContains(node.right, element);
          // 两个元素相等
          else return true;
       }
    
       // 找到二分搜索树中的最大值的元素 +
       maximum() {
          if (this.size === 0) throw new Error('binary search tree is empty.');
    
          return this.recursiveMaximum(this.root).element;
       }
    
       // 找到二分搜索树中的最大值的元素的节点 递归算法 -
       recursiveMaximum(node) {
          // 解决最基本的问题 向右走再也走不动了,说明当前节点就是最大值节点。
          if (node.right === null) return node;
    
          return this.recursiveMaximum(node.right);
       }
    
       // 删除二分搜索树中最大值的元素的节点,并返回这个节点的元素 +
       removeMax() {
          let maxElement = this.maximum();
          this.root = this.recursiveRemoveMax(this.root);
          return maxElement;
       }
    
       // 删除二分搜索树中最大值的元素的节点,并返回这个节点 递归算法 -
       recursiveRemoveMax(node) {
          if (node.right === null) {
             // 先存 当前这个节点的左子树,
             // 由于可能当前这个节点仅仅没有右子树,只有左子树,
             // 那么左子树能够替代当前这个节点。
             let leftNode = node.left;
             node.left = null;
             this.size--;
    
             return leftNode;
          }
    
          node.right = this.recursiveRemoveMax(node.right);
          return node;
       }
    
       // 找到二分搜索树中的最小值 +
       minimum() {
          if (this.size === 0) throw new Error('binary search tree is empty.');
    
          return this.recursiveMinimum(this.root).element;
       }
    
       // 找到二分搜索树中的最小值的元素的节点 递归算法 -
       recursiveMinimum(node) {
          if (node.left === null) return node;
    
          return this.recursiveMinimum(node.left);
       }
    
       // 删除二分搜索树中最小值的元素的节点,并返回这个节点的元素 +
       removeMin() {
          let leftNode = this.minimum();
          this.root = this.recursiveRemoveMin(this.root);
          return leftNode;
       }
    
       // 删除二分搜索树中最小值的元素的节点,并返回这个节点 递归算法 -
       recursiveRemoveMin(node) {
          // 解决最简单的问题
          if (node.left == null) {
             let rightNode = node.right;
             node.right = null;
             this.size--;
             return rightNode;
          }
    
          // 将复杂的问题拆分为性质相同的小问题,
          // 而后求出这些小问题的解后构建出原问题的答案
          node.left = this.recursiveRemoveMin(node.left);
          return node;
       }
    
       // 前序遍历 +
       preOrder(operator) {
          this.recursivePreOrder(this.root, operator);
       }
    
       // 前序遍历 递归算法 -
       recursivePreOrder(node, operator) {
          if (node === null) return;
    
          // 调用一下操做方法
          operator(node.element);
          console.log(node, node.element);
    
          // 继续递归遍历左右子树
          this.recursivePreOrder(node.left, operator);
          this.recursivePreOrder(node.right, operator);
       }
    
       // 前序遍历 非递归算法 +
       nonRecursivePreOrder(operator) {
          let stack = new MyLinkedListStack();
          stack.push(this.root);
    
          let node = null;
          while (!stack.isEmpty()) {
             // 出栈操做
             node = stack.pop();
    
             operator(node.element); // 访问当前的节点
             console.log(node.element);
    
             // 栈是先入后出的,把须要后访问的节点 先放进去,先访问的节点后放进去
             // 前序遍历是访问当前节点,而后再遍历左子树,最后遍历右子树
             if (node.right !== null) stack.push(node.right);
             if (node.left !== null) stack.push(node.left);
          }
       }
    
       // 中序遍历 +
       inOrder(operator) {
          this.recursiveInOrder(this.root, operator);
       }
    
       // 中序遍历 递归算法 -
       recursiveInOrder(node, operator) {
          if (node == null) return;
    
          this.recursiveInOrder(node.left, operator);
    
          operator(node.element);
          console.log(node.element);
    
          this.recursiveInOrder(node.right, operator);
       }
    
       // 后序遍历 +
       postOrder(operator) {
          this.recursivePostOrder(this.root, operator);
       }
    
       // 后序遍历 递归算法 -
       recursivePostOrder(node, operator) {
          if (node == null) return;
    
          this.recursivePostOrder(node.left, operator);
          this.recursivePostOrder(node.right, operator);
    
          operator(node.element);
          console.log(node.element);
       }
    
       // 层序遍历
       levelOrder(operator) {
          let queue = new MyLinkedListQueue();
          queue.enqueue(this.root);
    
          let node = null;
          while (!queue.isEmpty()) {
             node = queue.dequeue();
    
             operator(node.element);
             console.log(node.element);
    
             // 队列 是先进先出的,因此从左往右入队
             // 栈 是后进先出的, 因此从右往左入栈
             if (node.left !== null) queue.enqueue(node.left);
    
             if (node.right !== null) queue.enqueue(node.right);
          }
       }
    
       // 获取二分搜索树中节点个数 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索树是否为空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一个比较的方法,专门用来比较新增的元素大小 -
       // 第一个元素比第二个元素大 就返回 1
       // 第一个元素比第二个元素小 就返回 -1
       // 第一个元素比第二个元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接写死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 输出二分搜索树中的信息
       // @Override toString 2018-11-03-jwl
       toString() {
          let treeInfo = '';
          treeInfo += this.getBinarySearchTreeString(this.root, 0, treeInfo);
          return treeInfo;
       }
    
       // 写一个辅助函数,用来生成二分搜索树信息的字符串
       getBinarySearchTreeString(node, depth, treeInfo, pageContent = '') {
          //之前序遍历的方式
    
          if (node === null) {
             treeInfo += this.getDepthString(depth) + 'null \r\n';
    
             pageContent = this.getDepthString(depth) + 'null<br /><br />';
             document.body.innerHTML += `${pageContent}`;
    
             return treeInfo;
          }
    
          treeInfo += this.getDepthString(depth) + node.element + '\r\n';
    
          pageContent =
             this.getDepthString(depth) + node.element + '<br /><br />';
          document.body.innerHTML += `${pageContent}`;
    
          treeInfo = this.getBinarySearchTreeString(
             node.left,
             depth + 1,
             treeInfo
          );
          treeInfo = this.getBinarySearchTreeString(
             node.right,
             depth + 1,
             treeInfo
          );
    
          return treeInfo;
       }
    
       // 写一个辅助函数,用来生成递归深度字符串
       getDepthString(depth) {
          let depthString = '';
          for (var i = 0; i < depth; i++) {
             depthString += '-- ';
          }
          return depthString;
       }
    }
    复制代码
  2. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('MyBinarySearchTree remove Min Node Area');
          {
             let tree = new MyBinarySearchTree();
    
             let n = 100;
             let random = Math.random;
    
             for (var i = 0; i < n; i++) {
                tree.add(n * n * n * random());
             }
    
             let array = new MyArray(n);
    
             while (!tree.isEmpty()) {
                array.add(tree.removeMin());
             }
    
             // 数组中的元素从小到大排序的
             console.log(array.toString());
    
             for (var i = 1; i < n; i++) {
                //若是数组后面的元素小于数组前面的元素
                if (array.get(i) < array.get(i - 1))
                   throw new Error(
                      'error. array element is not (small - big) sort.'
                   );
             }
    
             console.log('removeMin test completed.');
             this.show('removeMin test completed.');
          }
    
          this.alterLine('MyBinarySearchTree remove Max Node Area');
          {
             let tree = new MyBinarySearchTree();
    
             let n = 100;
             let random = Math.random;
    
             for (var i = 0; i < n; i++) {
                tree.add(n * n * n * random());
             }
    
             let array = new MyArray(n);
    
             while (!tree.isEmpty()) {
                array.add(tree.removeMax());
             }
    
             // 数组中的元素从大到小排序的
             console.log(array.toString());
    
             for (var i = 1; i < n; i++) {
                //若是数组后面的元素大于数组前面的元素
                if (array.get(i) > array.get(i - 1))
                   throw new Error(
                      'error. array element is not (big - small) sort.'
                   );
             }
    
             console.log('removeMax test completed.');
             this.show('removeMax test completed.');
          }
       }
    
       // 将内容显示在页面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展现分割线
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    
    // 页面加载完毕
    window.onload = function() {
       // 执行主函数
       new Main();
    };
    复制代码

二分搜索树的删除节点-删除任意元素

  1. 在二分搜索树种删除最大值最小值的逻辑
    1. 从根节点开始,向左或者向右遍历,
    2. 遍历到最左或者最右时,
    3. 记录这个节点的右子树或者左子树,
    4. 而后返回,而后让这条分支上每一个节点的左或者右子树进行层层覆盖,
    5. 而后层层返回新的节点,直到最后返回给根节点、覆盖掉根节点,
    6. 从而达到了删除最小或最大节点的目的。
    7. 删除最小值的节点就不停的向左遍历,最后记录右子树,
    8. 由于被删除的节点要被这个节点的右子树替代掉,
    9. 只有这样才可以达到删除最小值的节点的效果。
    10. 删除最大值的节点就不停的向右遍历,最后记录左子树,
    11. 由于被删除的节点要被这个节点的左子树替代掉,
    12. 只有这样才可以达到删除最大值的节点的效果。
  2. 删除二分搜索树上任意节点会发生的状况
    1. 删除的这个节点只有左孩子,这个逻辑和上面的相似,
    2. 就让这个节点的左孩子取代这个节点的位置。
    3. 删除的这个节点只有右孩子,这个逻辑也是同样,
    4. 就让这个节点的右孩子取代这个节点的位置。
    5. 删除的这个节点是叶子节点,这个逻辑也同样,
    6. 由于 null 也是一个二分搜索树、也是一个节点、也是一个孩子,
    7. 直接让 null 取代这个节点的位置便可。
    8. 真正难的地方是去删除左右都有孩子这样的节点,
    9. 在 1962 年,Hibbard(计算机科学家)提出-Hibbard Deletion,
    10. 找到离这个节点的值最近而且大的那个节点来取代这个节点,
    11. 也就是找到 这个节点的右孩子的左孩子(右子树的左子树上最小的节点),
    12. 例如待删除的节点为 d,那么就是 s = min(d->right),
    13. 找到比当前节点大最小且最近的节点,这个 s 就是 d 的后继,
    14. 执行 s->right = delMin(d->right)这样的操做,
    15. 以后让 s->left = d->left,
    16. 删除的 d 后,s 是新的子树的根,返回这个 s 节点就能够了。
    17. 除了找待删除节点 d 的后继 s 以外,还能够找待删除节点的前驱 p,
    18. 也就是找到 这个节点的左孩子的右孩子(左子树的右子树上最大的节点)。
    19. 不管使用前驱仍是后继来取代待删除的这个节点
    20. 都可以继续保持二分搜索树的性质。
  3. 对于二分搜索树来讲
    1. 相对于数组、栈、队列、链表这些数据结构要复杂一些,
    2. 二分搜索树自己也是学习其它的树,如 平衡二叉树的基础。

代码示例(class: MyBinarySearchTree, class: Main)

  1. MyBinarySearchTree

    // 自定义二分搜索树节点
    class MyBinarySearchTreeNode {
       constructor(element, left = null, right = null) {
          // 实际存储的元素
          this.element = element;
          // 当前节点的左子树
          this.left = left;
          // 当前节点的右子树
          this.right = right;
       }
    }
    
    // 自定义二分搜索树
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 添加元素到二分搜索树中 +
       add(element) {
          if (element === null) throw new Error("element is null. can't store.");
    
          this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索树中 递归算法 -
       recursiveAdd(node, newElement) {
          // 解决最基本的问题 也就是递归函数调用的终止条件
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeNode(newElement);
          }
    
          // 1. 当前节点的元素比新元素大
          // 那么新元素就会被添加到当前节点的左子树去
          // 2. 当前节点的元素比新元素小
          // 那么新元素就会被添加到当前节点的右子树去
          // 3. 当前节点的元素比新元素相等
          // 什么都不作了,由于目前不添加剧复的元素
          if (this.compare(node.element, newElement) > 0)
             node.left = this.recursiveAdd(node.left, newElement);
          else if (this.compare(node.element, newElement) < 0)
             node.right = this.recursiveAdd(node.right, newElement);
          else {
          }
    
          // 将复杂问题分解成多个性质相同的小问题,
          // 而后求出小问题的答案,
          // 最终构建出原问题的答案
          return node;
       }
    
       // 判断二分搜索树中是否包含某个元素 +
       contains(element) {
          if (this.root === null) throw new Error("root is null. can't query.");
    
          return this.recursiveContains(this.root, element);
       }
    
       // 判断二分搜索树种是否包含某个元素 递归算法 -
       recursiveContains(node, element) {
          if (node === null) return false;
    
          // 当前节点元素比 要搜索的元素 大
          if (this.compare(node.element, element) > 0)
             return this.recursiveContains(node.left, element);
          else if (this.compare(node.element, element) < 0)
             // 当前元素比 要搜索的元素 小
             return this.recursiveContains(node.right, element);
          // 两个元素相等
          else return true;
       }
    
       // 找到二分搜索树中的最大值的元素 +
       maximum() {
          if (this.size === 0) throw new Error('binary search tree is empty.');
    
          return this.recursiveMaximum(this.root).element;
       }
    
       // 找到二分搜索树中的最大值的元素的节点 递归算法 -
       recursiveMaximum(node) {
          // 解决最基本的问题 向右走再也走不动了,说明当前节点就是最大值节点。
          if (node.right === null) return node;
    
          return this.recursiveMaximum(node.right);
       }
    
       // 删除二分搜索树中最大值的元素的节点,并返回这个节点的元素 +
       removeMax() {
          let maxElement = this.maximum();
          this.root = this.recursiveRemoveMax(this.root);
          return maxElement;
       }
    
       // 删除二分搜索树中最大值的元素的节点,并返回这个节点 递归算法 -
       recursiveRemoveMax(node) {
          if (node.right === null) {
             // 先存 当前这个节点的左子树,
             // 由于可能当前这个节点仅仅没有右子树,只有左子树,
             // 那么左子树能够替代当前这个节点。
             let leftNode = node.left;
             node.left = null;
             this.size--;
    
             return leftNode;
          }
    
          node.right = this.recursiveRemoveMax(node.right);
          return node;
       }
    
       // 找到二分搜索树中的最小值 +
       minimum() {
          if (this.size === 0) throw new Error('binary search tree is empty.');
    
          return this.recursiveMinimum(this.root).element;
       }
    
       // 找到二分搜索树中的最小值的元素的节点 递归算法 -
       recursiveMinimum(node) {
          if (node.left === null) return node;
    
          return this.recursiveMinimum(node.left);
       }
    
       // 删除二分搜索树中最小值的元素的节点,并返回这个节点的元素 +
       removeMin() {
          let leftNode = this.minimum();
          this.root = this.recursiveRemoveMin(this.root);
          return leftNode;
       }
    
       // 删除二分搜索树中最小值的元素的节点,并返回这个节点 递归算法 -
       recursiveRemoveMin(node) {
          // 解决最简单的问题
          if (node.left == null) {
             let rightNode = node.right;
             node.right = null;
             this.size--;
             return rightNode;
          }
    
          // 将复杂的问题拆分为性质相同的小问题,
          // 而后求出这些小问题的解后构建出原问题的答案
          node.left = this.recursiveRemoveMin(node.left);
          return node;
       }
    
       // 删除二分搜索树上的任意节点
       remove(element) {
          this.root = this.recursiveRemove(this.root, element);
       }
    
       // 删除二分搜索树上的任意节点 递归算法
       // 返回删除对应元素节点后新的二分搜索树的根
       recursiveRemove(node, element) {
          if (node === null) return null;
    
          // 当前节点的元素值比待删除的元素小 那么就向当前节点的右子树中去找
          if (this.compare(node.element, element) < 0) {
             node.right = this.recursiveRemove(node.right, element);
             return node;
          } else if (this.compare(node.element, element) > 0) {
             // 向当前节点的左子树中去找
             node.left = this.recursiveRemove(node.left, element);
             return node;
          } else {
             // 若是找到了相同值的节点了,开始进行相应的处理
    
             // 若是这个节点左子树为空,那么就让这个节点的右子树覆盖当前节点
             if (node.left === null) {
                let rightNode = node.right;
                node.right = null;
                this.size--;
                return rightNode;
             }
    
             // 若是当前节点的右子树为空,那么就让这个节点的左子树覆盖当前节点
             if (node.right === null) {
                let leftNode = node.left;
                node.left = null;
                this.size--;
                return leftNode;
             }
    
             // 若是当前节点的左右子树都不为空,那么就开始特殊操做
             // 1. 先找到当前节点右子树上最小的那个节点,保存起来
             // 2. 而后删除掉当前节点右子树上最小的那个节点,
             // 3. 让保存起来的那个节点覆盖掉当前节点
             // 1. 也就是保存起来的那个节点的right = 删除掉当前节点右子树上最小的节点后返回的那个节点
             // 2. 再让保存起来的那个节点的left = 当前节点的left
             // 4. 解除当前节点及其left和right,全都赋值为null,这样就至关于把当前节点从二分搜索树中剔除了
             // 5. 返回保存的这个节点
    
             let successtor = this.recursiveMinimum(node.right);
             successtor.right = this.recursiveRemoveMin(node.right);
    
             // 恢复removeMin 操做的this.size -- 带来的影响
             this.size++;
    
             successtor.left = node.left;
    
             // 开始正式的删除当前节点的操做
             node = node.left = node.right = null;
             this.size--;
    
             // 返回当前保存的节点
             return successtor;
          }
       }
    
       // 前序遍历 +
       preOrder(operator) {
          this.recursivePreOrder(this.root, operator);
       }
    
       // 前序遍历 递归算法 -
       recursivePreOrder(node, operator) {
          if (node === null) return;
    
          // 调用一下操做方法
          operator(node.element);
          console.log(node, node.element);
    
          // 继续递归遍历左右子树
          this.recursivePreOrder(node.left, operator);
          this.recursivePreOrder(node.right, operator);
       }
    
       // 前序遍历 非递归算法 +
       nonRecursivePreOrder(operator) {
          let stack = new MyLinkedListStack();
          stack.push(this.root);
    
          let node = null;
          while (!stack.isEmpty()) {
             // 出栈操做
             node = stack.pop();
    
             operator(node.element); // 访问当前的节点
             console.log(node.element);
    
             // 栈是先入后出的,把须要后访问的节点 先放进去,先访问的节点后放进去
             // 前序遍历是访问当前节点,而后再遍历左子树,最后遍历右子树
             if (node.right !== null) stack.push(node.right);
             if (node.left !== null) stack.push(node.left);
          }
       }
    
       // 中序遍历 +
       inOrder(operator) {
          this.recursiveInOrder(this.root, operator);
       }
    
       // 中序遍历 递归算法 -
       recursiveInOrder(node, operator) {
          if (node == null) return;
    
          this.recursiveInOrder(node.left, operator);
    
          operator(node.element);
          console.log(node.element);
    
          this.recursiveInOrder(node.right, operator);
       }
    
       // 后序遍历 +
       postOrder(operator) {
          this.recursivePostOrder(this.root, operator);
       }
    
       // 后序遍历 递归算法 -
       recursivePostOrder(node, operator) {
          if (node == null) return;
    
          this.recursivePostOrder(node.left, operator);
          this.recursivePostOrder(node.right, operator);
    
          operator(node.element);
          console.log(node.element);
       }
    
       // 层序遍历
       levelOrder(operator) {
          let queue = new MyLinkedListQueue();
          queue.enqueue(this.root);
    
          let node = null;
          while (!queue.isEmpty()) {
             node = queue.dequeue();
    
             operator(node.element);
             console.log(node.element);
    
             // 队列 是先进先出的,因此从左往右入队
             // 栈 是后进先出的, 因此从右往左入栈
             if (node.left !== null) queue.enqueue(node.left);
    
             if (node.right !== null) queue.enqueue(node.right);
          }
       }
    
       // 获取二分搜索树中节点个数 +
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索树是否为空的bool值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 新增一个比较的方法,专门用来比较新增的元素大小 -
       // 第一个元素比第二个元素大 就返回 1
       // 第一个元素比第二个元素小 就返回 -1
       // 第一个元素比第二个元素相等 就返回 0
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is null. can't compare.");
    
          // 先直接写死
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 输出二分搜索树中的信息
       // @Override toString 2018-11-03-jwl
       toString() {
          let treeInfo = '';
          treeInfo += this.getBinarySearchTreeString(this.root, 0, treeInfo);
          return treeInfo;
       }
    
       // 写一个辅助函数,用来生成二分搜索树信息的字符串
       getBinarySearchTreeString(node, depth, treeInfo, pageContent = '') {
          //之前序遍历的方式
    
          if (node === null) {
             treeInfo += this.getDepthString(depth) + 'null \r\n';
    
             pageContent = this.getDepthString(depth) + 'null<br /><br />';
             document.body.innerHTML += `${pageContent}`;
    
             return treeInfo;
          }
    
          treeInfo += this.getDepthString(depth) + node.element + '\r\n';
    
          pageContent =
             this.getDepthString(depth) + node.element + '<br /><br />';
          document.body.innerHTML += `${pageContent}`;
    
          treeInfo = this.getBinarySearchTreeString(
             node.left,
             depth + 1,
             treeInfo
          );
          treeInfo = this.getBinarySearchTreeString(
             node.right,
             depth + 1,
             treeInfo
          );
    
          return treeInfo;
       }
    
       // 写一个辅助函数,用来生成递归深度字符串
       getDepthString(depth) {
          let depthString = '';
          for (var i = 0; i < depth; i++) {
             depthString += '-- ';
          }
          return depthString;
       }
    }
    复制代码
  2. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('MyBinarySearchTree Remove Node Area');
          {
             let n = 100;
    
             let tree = new MyBinarySearchTree();
             let array = new MyArray(n);
    
             let random = Math.random;
    
             for (var i = 0; i < n; i++) {
                tree.add(n * n * n * random());
                array.add(tree.removeMin());
             }
    
             // 数组中的元素从小到大排序的
             console.log(array.toString());
    
             for (var i = 0; i < n; i++) {
                tree.remove(array.get(i));
             }
    
             console.log(
                'removeMin test ' +
                   (tree.isEmpty() ? 'completed.' : 'no completed.')
             );
             this.show(
                'removeMin test ' +
                   (tree.isEmpty() ? 'completed.' : 'no completed.')
             );
          }
       }
    
       // 将内容显示在页面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展现分割线
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    
    // 页面加载完毕
    window.onload = function() {
       // 执行主函数
       new Main();
    };
    复制代码

更多与二分搜索树相关

已经实现的二分搜索树功能

  1. 添加元素 add
  2. 删除元素 remove
  3. 查询元素 contains
  4. 遍历元素 order

其它实现的二分搜索树功能

  1. 能够很是方便的拿到二分搜索树中最大值和最小值, 2. 这是由于二分搜索树自己有一个很是重要的特性, 3. 也就是二分搜索树具备顺序性, 4. 这个顺序性就是指 二分搜索树中全部的元素都是有序的, 5. 例如使用中序遍历遍历的元素就是将元素从小到大排列起来, 6. 也正是有顺序性才可以很方便的得到 7. 二分搜索树中最大值(maximum)最小值(minimum), 8. 包括给定一个值能够拿到它的前驱(predecessor)和后继(successor)。
  2. 也由于这个顺序性也能够对它进行 floor 和 ceil 的操做,
    1. 也就是找比某一个元素值大的元素或者值小的元素,
    2. 前驱、后继中指定的元素必定要在这棵二分搜索树中,
    3. 而 floor 和 ceil 中指定的这个元素能够不在这棵二分搜索树中。
  3. 相应的二分搜索树还能够实现 rank 和 select 方法,
    1. rank 也就是指定一个元素找到它的排名,
    2. select 是一个反向的操做,也就是找到排名为多少名的那个元素。
    3. 对于二分搜索树来讲均可以很是容易的实现这两个操做。
    4. 实现 rank 和 select 最好的方式是对于二分搜索树每个节点
    5. 同时还维护一个 size,
    6. 这个 size 就是指以这个节点为根的二分搜索树有多少个元素,
    7. 也就是每个节点为根的二分搜索树中有多少的元素,
    8. 那么这个 size 就为多少,
    9. 也就是每个节点包括本身以及下面的子节点的个数,
    10. 每个 node 在维护了一个 size 以后,
    11. 那么实现 rank 和 select 这两个操做就会容易不少,
    12. 也就是给 node 这个成员变量添加一个 size,
    13. 那么对于二分搜索树其它操做如添加和删除操做时,
    14. 也要去维护一下这个节点的 size,
    15. 只有这样实现这个 rank 和 select 就会很是简单,
    16. 这样作以后,对于整棵二分搜索树而言,
    17. 就再也不须要二分搜索树的 size 变量了,
    18. 若是要看整棵二分搜索树有多少个节点,
    19. 直接看root.size就行了,很是的方便。
  4. 维护 depth 的二分搜索树
    1. 对于二分搜索树的每个节点还能够维护一个深度值,
    2. 也就是这个节点的高度值,也就是这个节点处在第几层的位置,
    3. 维护这个值在一些状况下是很是有帮助的。
  5. 支持重复元素的二分搜索树
    1. 只须要定义每个根节点的左子树全部的节点都是
    2. 小于等于这个根节点值的,
    3. 而每个根节点的右子树全部的节点都是大于这个根节点值的,
    4. 这样的定义就很好的支持了重复元素的二叉树的实现。
  6. 还能够经过维护每个节点的 count 变量来实现重复元素的二分搜索树,
    1. 也就是记录一下这个节点所表明的元素在这个二分搜索树中存储的个数,
    2. 当你添加进重复的节点后,直接让相应节点的count++便可,
    3. 若是你删除这个重复的节点时,直接让相应节点的count--便可,
    4. 若是 count 减减以后为 0,那么就从二分搜索树中真正删除掉。

其它

  1. 在二分搜索树中相应的变种其实大可能是在 node 中维护一些数据
    1. 就能够方便你进行一些其它特殊状况的处理,
  2. 相关的习题能够去 leetcode 中找到,
    1. 树标签:https://leetcode-cn.com/tag/tree/
    2. 如第一题,二叉树的最大深度,这个题和链表是很是像的,
    3. 它有一个答题的模板,你提交的时候要按照这个模板来进行提交。
  3. 其它
    1. 二分搜索树的复杂度分析,
    2. 二分搜索树有两个重要的应用集合和映射,
    3. 其实用数组和链表也可以实现集合和映射,
    4. 二分搜索树也有它的局限性。
相关文章
相关标签/搜索