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

思惟导图

前言

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

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

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

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

树结构

  1. 线性数据结构是把全部的数据排成一排
    1. 树结构是倒立的树,由一个根节点延伸出不少新的分支节点。
  2. 树结构自己是一个种自然的组织结构
    1. 如 电脑中文件夹目录结构就是树结构
    2. 这种结构来源于生活,
    3. 好比 图书馆总体分红几个大馆,
    4. 如 数理馆、文史馆等等,
    5. 到了数理馆还要分红 不少的子类,
    6. 如 数学类的图书、物理类的图书、化学类的图书,计算机类的图书,
    7. 到了计算机类的图书还要再分红各类不一样的子类,
    8. 如 按语言分类 c++、java、c#、php、python、javascript 等等,
    9. 如 按领域分类 网站编程、app 开发、游戏开发、前端、后端等等,
    10. 每个子领域可能又要分红不少领域,
    11. 一直到最后索引到一本一本的书,
    12. 这就是一个典型的树结构。
    13. 还有 一个公司的组织架构也是这样的一种树结构,
    14. 从 CEO 开始下面可能有不一样的部门,
    15. 如财务部门(Marketing Head)、人事部门(HR Head)、
    16. 技术部门(Finance Head)、市场部门(Audit Officer)等等,
    17. 每一个部门下面还有不一样的职能分工,最后才到具体的一个一我的。
    18. 还有家谱,他自己也是一个树结构,
    19. 其实树结构并不抽象,在生活中随处可见。
  3. 树结构很是的高效
    1. 好比文件管理,
    2. 不可能将全部的文件放到一个文件夹中,
    3. 而后用一个线性的结构进行存储,
    4. 那样的话查找文件太麻烦了,
    5. 可是若是给它作成树机构的话,
    6. 那么就能够很容易的检索到目标文件,
    7. 好比说我想检索到个人照片,
    8. 直接找到我的文件夹,而后找到图片文件夹,
    9. 最后找到本身的照片,这样就很快速很高效的找到了目标文件。
    10. 在公司使用这种树形的组织架构也是这个缘由,
    11. CEO 想就技术开发的一些问题进行一些讨论,
    12. 他确定要找相应职能的一些人,
    13. 他不须要去市场部门、营销部门、人事部门、财务部门、行政部门找人,
    14. 他直接去技术部这样的开发部门去找人就行了,
    15. 一会儿就把查询的范围缩小了。
    16. 在数据结构领域设计树结构的本质也是如此。
  4. 在计算机科学领域不少问题的处理
    1. 当你将数据使用树结构进行存储后,出奇的高效。
  5. 二分搜索树(Binary Search Tree)
    1. 二分搜索树有它的局限性
  6. 平衡二叉树:AVL;红黑树,
    1. 平衡二叉树还有不少种
  7. 算法须要使用一些特殊的操做的时候将数据组织成树结构
    1. 会针对某一类特殊的操做产生很是高效的结果,
    2. 使用以及并查集
    3. 都是为了知足对数据某一个类特殊的操做进行高效的处理,
    4. 同时对于某些特殊的数据,不少时候能够另辟蹊径,
    5. 将他们以某种形式存储成树结构,
    6. 结果就是会对这类特殊的数据
    7. 它们所在的那个领域的问题
    8. 相应的解决方案提供极其高效的结果。
  8. 线段树、Trie(字典树、前缀树)
    1. 线段树主要用来处理线段这种特殊的数据,
    2. Trie 主要用于处理字符串这类特殊的数据,
    3. 要想实现快速搜索的算法,
    4. 它的本质依然是须要使用树结构的,
    5. 树结构不见得是显式的展现在你面前,
    6. 它同时也能够用来处理不少抽象的问题,
    7. 这就像栈的应用同样,
    8. 从用户的角度看只看撤销这个操做或者只看括号匹配的操做,
    9. 用户根本想不到这背后使用了一个栈的数据结构,
    10. 可是为了组建出这样的功能是须要使用这种数据结构的,
    11. 同理树也是如此,不少看起来很是高效的运算结果,
    12. 它的背后实际上是由于有树这种数据结构做为支撑的,
    13. 这也是数据结构、包括数据结构在计算机科学领域很是重要的意义,
    14. 数据结构虽然解决的是数据存储的问题,
    15. 可是在使用的层面上不只仅是由于要存储数据,
    16. 更重要的是在你使用某些特殊的数据结构存储数据后,
    17. 能够帮助你辅助你更加高效的解决某些算法问题
    18. 甚至对于某些问题来讲若是没有这些数据结构,
    19. 那么根本无从解决。

二分搜索树(Binary Search Tree)

  1. 二叉树
    1. 和链表同样,也属于动态数据结构,
    2. 不须要建立这个数据结构的时候就定好存储的容量,
    3. 若是要添加元素,直接 new 一个新的空间,
    4. 而后把它添加到这个数据结构中,删除也是同理,
    5. 每个元素也是存到一个节点中,
    6. 这个节点和链表不一样,它除了要存放这个元素 e,
    7. 它还有两个指向其它节点的变量,分别叫作 left、right,
    class Node {
       e; // Element
       left; // Node
       right; // Node
    }
    复制代码
  2. 二叉树也叫多叉树,
    1. 它每个节点最多只能分红两个叉,
    2. 根据这个定义也能定义出多叉树,
    3. 若是每一个节点能够分出十个叉,
    4. 那就能够叫它十叉树,能分多少叉就叫多少叉树,
    5. Trie 字典书自己就是一个多叉树。
  3. 在数据结构领域对应树结构来讲
    1. 二叉树是最经常使用的一种树结构,
    2. 二叉树具备一个惟一的根节点,
    3. 也就是最上面的节点。
    4. 每个节点最多有两个子节点,
    5. 这两个子节点分别叫作这个节点的左孩子和右孩子,
    6. 子节点指向左边的那个节点就是左孩子,
    7. 子节点指向右边的那个节点就是右孩子。
    8. 二叉树每一个节点最多有两个孩子,
    9. 一个孩子都没有的节点一般称之为叶子节点,
    10. 二叉树每一个节点最多有一个父亲,
    11. 根节点是没有父亲节点的。
  4. 二叉树和链表同样具备自然递归的结构
    1. 链表自己是线性的,
    2. 它的操做既可使用循环也可使用递归。
    3. 和树相关的不少操做,
    4. 使用递归的方式去写要比使用非递归的方式简单不少。
    5. 二叉树每个节点的左孩子同时也是一个二叉树的根节点,
    6. 一般叫管这棵二叉树作左子树。
    7. 二叉树每个节点的右孩子同时也是一个二叉树的根节点,
    8. 一般叫管这棵二叉树作右子树。
    9. 也就是说每个二叉树它的左侧和右侧右分别链接了两个二叉树,
    10. 这两个二叉树都是节点个数更小的二叉树,
    11. 这就是二叉树所具备的自然的递归结构。
  5. 二叉树不必定是“满”的
    1. 满二叉树就是除了叶子节点以外,
    2. 每个节点都有两个孩子。
    3. 就算你整个二叉树上只有一个节点,
    4. 它也是一个二叉树,只不过它的左右孩子都是空,
    5. 这棵二叉树只有一个根节点,
    6. 甚至 NULL(空)也是一棵二叉树。
    7. 就像链表中,只有一个节点它也是一个链表,
    8. 也能够把 NULL(空)看做是一个链表。
  6. 二分搜索树是一棵二叉树
    1. 在二叉树定义下全部其它的术语在二分搜索树中也适用,
    2. 如 根节点、叶子节点、左孩子右孩子、左子树、右子树、
    3. 父亲节点等等,这些在二分搜索树中也同样。
  7. 二分搜索树的每个节点的值
    1. 都要大于其左子树的全部节点的值,
    2. 都要小于其右子树的全部节点的值。
    3. 在叶子节点上没有左右孩子,
    4. 那就至关于也知足这个条件。
  8. 二分搜索树的每一棵子树也是二分搜索树
    1. 对于每个节点来讲,
    2. 它的左子树全部的节点都比这个节点小,
    3. 它的右子树全部的节点都比这个节点大,
    4. 那么用二分搜索树来存储数据的话,
    5. 那么再来查找一个数据就会变得很是简单,
    6. 能够很快的知道从左侧找仍是右侧找,
    7. 甚至能够不用看另一侧,
    8. 因此就大大的加快了查询速度。
    9. 在生活中使用树结构,本质也是如此,
    10. 例如我要找一本 JS 编程的书,
    11. 那么进入图书馆我直接进入计算机科学这个区域找这本书,
    12. 其它的类的图书我根本不用去管,
    13. 这也是树这种结构存储数据以后再对数据进行操做时
    14. 才可以很是高效的核心缘由。
  9. 为了可以达到二分搜索树的性质
    1. 必须让存储的元素具备可比较性,
    2. 你要定义好 元素之间如何进行比较,
    3. 由于比较的方式是具备多种的,
    4. 必须保证元素之间能够进行比较。
    5. 在链表和数组中则没有这个要求,
    6. 这个就是二分搜索树存储数据的一个局限性,
    7. 也说明了凡事都是有代价的,
    8. 若是想加快搜索的话就必须对数据有必定的要求。

代码示例

  1. 二分搜索树其实不是支持全部的类型java

    1. 因此应该对元素的类型有所限制,
    2. 这个限制就是 这个类型必须拥有可比较性,
    3. 也就是这个类型 element 必须具备可比较性。
  2. 代码实现node

    class MyBinarySearchTreeNode {
       constructor(element, left, right) {
          // 实际存储的元素
          this.element = element;
          // 当前节点的左子树
          this.left = left;
          // 当前节点的右子树
          this.right = right;
       }
    }
    class MyBinarySearchTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 获取二分搜索树中节点个数
       getSize() {
          return this.size;
       }
    
       // 返回二分搜索树是否为空的bool值
       isEmpty() {
          return this.size === 0;
       }
    }
    复制代码

向二分搜索树中添加元素

  1. 若是二分搜索树的根节点为空的话
    1. 第一个添加的元素就会成为根节点,
    2. 若是再添加一个元素,那么就因该从根节点出发,
    3. 根据二分搜索树的定义,
    4. 每一个节点的值要比它的左子树上全部节点的值大,
    5. 假设第二个添加的元素的值小于第一个添加的元素的值,
    6. 那么很显然第二个添加的元素要被添加到根节点的左子树上去,
    7. 根节点的左子树上只有一个节点,
    8. 那么这个节点就是左子树上的根节点,
    9. 这个左子树上的根节点就是顶层根节点的左孩子。
  2. 按照这样的规则,每来一个新元素从根节点开始,
    1. 若是小于根节点,那么就插入到根节点的左子树上去,
    2. 若是大于根节点,那么就插入到根节点的右子树上去,
    3. 因为无论是左子树仍是右子树,它们又是一棵二分搜索树,
    4. 那么这个过程就是依此类推下去,
    5. 一层一层向下比较新添加的节点的值,
    6. 大的向右,小的向左,不停的向下比较,
    7. 若是这个位置没有被占住,那么就能够在这个位置上添加进去,
    8. 若是这个位置被占了,那就不停的向下比较,
    9. 直到找到一个合适的位置添加进去。
  3. 若是遇到两个元素的值相同,那暂时先不去管,
    1. 也就是不添加进去,由于已经有了,
    2. 自定义二分搜索树不包含重复元素,
    3. 若是想包含重复元素,
    4. 只须要定义左子树小于等于节点、或者右子树大于等于节点,
    5. 只要把“等于”这种关系放进定义里就能够了。
  4. 二分搜索树添加元素的非递归写法,和链表很像
    1. 可是在二分搜索树方面的实现尽可能使用递归来实现,
    2. 就是要锻炼递归算法的书写,
    3. 由于递归算法的不少细节和内容须要不断去体会,
    4. 可是非递归的写法也很实用的,
    5. 由于递归自己是具备更高的开销的,
    6. 虽然在现代计算机上这些开销并不明显,
    7. 可是在一些极端的状况下仍是能够看出很大的区别,
    8. 尤为是对于二分搜索树来讲,
    9. 在最坏的状况下它有可能会退化成一个链表,
    10. 那么在这种状况下使用递归的方式很容易形成系统栈的溢出,
    11. 二分搜索树一些非递归的实现你能够本身练习一下。
  5. 在二分搜索树方面,递归比非递归实现起来更加简单。

代码示例

  1. 代码python

    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.");
    
          if (this.root === null) {
             this.root = new MyBinarySearchTreeNode(element);
             this.size++;
          } else this.root = this.recursiveAdd(this.root, element);
       }
    
       // 添加元素到二分搜索树中 递归算法 -
       recursiveAdd(node, newElement) {
          // 解决最基本的问题 也就是递归函数调用的终止条件
          if (node === null) {
             node = new MyBinarySearchTreeNode(newElement);
             this.size++;
             return node;
          }
    
          // 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;
       }
    
       // 获取二分搜索树中节点个数 +
       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;
       }
    }
    复制代码
  2. 对于二分搜索的插入操做c++

    1. 上面的代码是相对比较复杂的,
    2. 能够进行改进一下,
    3. 让代码总体简洁一些,
    4. 由于递归算法是有不少不一样的写法的,
    5. 并且递归的终止条件也是有不一样的考量。

深刻理解递归终止条件

  1. 改进添加操做
    1. 递归算法有不少不一样的写法,
    2. 递归的终止条件也有不一样的考量。
  2. 以前的算法
    1. 向以 node 为根的二分搜索树中插入元素 e,
    2. 其实将新的元素插入至 node 的左孩子或者右孩子,
    3. 若是 node 的左或右孩子为空,那能够进行相应的赋值操做,
    4. 若是是 node 的左右孩子都不为空的话,
    5. 那就只能递归的插入到相应 node 的左或右孩子中,
    6. 由于这一层节点已经满了,只能考虑下一层了,
    7. 下一层符合要求而且节点没有满,就能够进行相应的赋值操做了。
    8. 可是有对根节点作出了特殊的处理,要防止根节点为空的状况发生,
    9. 若是根节点为空,那么就将第一个元素赋值为根节点,
    10. 可是除了根节点之外,其它节点不须要作这种特殊处理,
    11. 因此致使逻辑上并不统一,而且递归的终止条件很是的臃肿,

代码示例

  1. 代码git

    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;
       }
    
       // 获取二分搜索树中节点个数 +
       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;
       }
    }
    复制代码
  2. 虽然代码量更少了,可是也更难理解的了一些github

    1. 首先从宏观的语意的角度去理解定义这个函数的语意后
    2. 整个递归函数处理的逻辑如何成立的,
    3. 其次从微观的角度上能够写一些辅助代码来帮助你一点一点的查看,
    4. 从一个空的二分搜索树开始,往里添加三五个元素,
    5. 看看每一个元素是如何逐步的添加进去。
    6. 能够尝试一些链表这个程序插入操做的递归算法,
    7. 其实这两者之间是拥有很是高的类似度的,
    8. 只不过在二分搜索树中须要判断一下是须要插入到左子树仍是右子树而已,
    9. 对于链表来讲直接插入到 next 就行了,
    10. 经过两者的比较就能够更加深刻的理解这个程序。

二分搜索树的查询操做

  1. 查询操做很是的容易
    1. 只须要不停的看每个 node 里面存的元素,
    2. 不会牵扯到整个二分搜索树的添加操做
  2. 和添加元素同样须要使用递归的进行实现
    1. 在递归的过程当中就须要从二分搜索树的根开始,
    2. 逐渐的转移在二分搜索树的子树中缩小问题的规模,
    3. 缩小查询的树的规模,直到找到这个元素 e 或者发现找不到这个元素 e。
  3. 在数组和链表中有索引这个概念,
    1. 可是在二分搜索树中没有索引这个概念。

代码示例

  1. 代码

    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;
       }
    
       // 获取二分搜索树中节点个数 +
       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;
       }
    }
    复制代码

二分搜索树的遍历-前序遍历

  1. 遍历操做就是把这个数据结构中全部的元素都访问一遍

    1. 在二分搜索树中就是把全部节点都访问一遍,
  2. 访问数据结构中存储的全部元素是由于与业务相关,

    1. 例如 给全部的同窗加两分,给全部的员工发补贴等等,
    2. 因为你的数据结构是用来存储数据的,
    3. 不只能够查询某些特定的数据,
    4. 还应该有相关的方式将全部的数据都进行访问。
  3. 在线性结构下,遍历是极其容易的

    1. 不管是数组仍是链表只要使用一下循环就行了,
    2. 可是这件事在树结构下没有那么简单,
    3. 可是也没有那么难:)。
  4. 在树结构下遍历操做并无那么难

    1. 若是你对树结构不熟悉,那么可能就有点难,
    2. 可是若是你熟悉了树结构,那么并不是是那么难的操做,
    3. 尤为是你在掌握递归操做以后,遍历树就更加不难了。
  5. 对于遍历操做,两个子树都要顾及

    1. 即要访问左子树中全部的节点又要访问右子树中全部的节点,
    2. 下面的代码中的遍历方式也称为二叉树的前序遍历,
    3. 先访问这个节点,再访问左右子树,
    4. 访问这个节点放在了访问左右子树的前面因此就叫前序遍历。
    5. 要从宏观与微观的角度去理解这个代码,
    6. 从宏观的角度来看,
    7. 定义好了遍历的这个语意后整个逻辑是怎么组建的,
    8. 从微观的角度来看,真正的有一个棵二叉树的时候,
    9. 这个代码是怎样怎样一行一行去执行的。
    10. 当你熟练的掌握递归的时候,
    11. 有的时候你能够不用遵照 那种先写递归终止的条件,
    12. 再写递归组成的的逻辑 这样的一个过程,如写法二,
    13. 虽然什么都不干,可是也是 return 了,
    14. 和写法一中写的逻辑实际上是等价的,
    15. 也就是在递归终止条件这部分能够灵活处理。
    16. 写法一看起来逻辑比较清晰,递归终止在前,递归组成的逻辑在后。
    // 遍历以node为根的二分搜索树 递归算法
    function traverse(node) {
       if (node === null) {
          return;
       }
    
       // ... 要作的事情
    
       // 访问该节点 两边都要顾及
       // 访问该节点的时候就去作该作的事情,
       // 如 给全部学生加两分
       traverse(node.left);
       traverse(node.right);
    }
    
    // 写法二 这种逻辑也是能够的
    function traverse(node) {
       if (node !== null) {
          // ... 要作的事情
    
          // 访问该节点 两边都要顾及
          // 访问该节点的时候就去作该作的事情,
          // 如 给全部学生加两分
          traverse(node.left);
          traverse(node.right);
       }
    }
    复制代码

代码示例(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);
       }
    
       // 获取二分搜索树中节点个数 +
       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;
       }
    }
    复制代码
  2. 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 //
          /////////////////
          myBinarySearchTree.preOrder(this.show);
    
          this.show(myBinarySearchTree.contains(1));
          console.log(myBinarySearchTree.contains(1));
       }
    
       // 将内容显示在页面上
       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. 辅助函数叫作 getBinarySearchTreeString,
    3. 这个函数的做用是,生成以 node 为根节点,
    4. 深度为 depth 的描述二叉树的字符串,
    5. 这样一来要新增一个辅助函数,
    6. 这个函数的做用是,根据递归深度生成字符串,
    7. 这个辅助函数叫作 getDepthString。

代码示例(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);
       }
    
       // 获取二分搜索树中节点个数 +
       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

    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 //
          /////////////////
    
          console.log(myBinarySearchTree.toString());
       }
    
       // 将内容显示在页面上
       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. 前序遍历的表示先访问的这个节点。
    function preOrder(node) {
       if (node == null) return;
    
       // ... 要作的事情
       // 访问该节点
    
       // 先一直往左,而后不断返回上一层 再向左、终止,
       // 最后整个操做循环往复,直到所有终止。
       preOrder(node.left);
       preOrder(node.right);
    }
    复制代码
  2. 中序遍历

    1. 先访问左子树,再访问这个节点,
    2. 最后访问右子树,整个过程循环往复。
    3. 中序遍历的表示先访问左子树,
    4. 而后再访问这个节点,最后访问右子树,
    5. 访问这个节点的操做放到了访问左子树和右子树的中间。
    function inOrder(node) {
       if (node == null) return;
    
       inOrder(node.left);
    
       // ... 要作的事情
       // 访问该节点
    
       inOrder(node.right);
    }
    复制代码
  3. 中序遍历后输出的结果是排序后的结果。

    1. 中序遍历的结果是二分搜索树中
    2. 存储的全部的元素从小到大进行排序后的结果,
    3. 这是二分搜索树一个很重要的一个性质。
    4. 二分搜索树任何一个节点的左子树上全部的节点值都比当前节点的小,
    5. 二分搜索树任何一个节点的右子树上全部的节点值都比当前节点的大,
    6. 每个节点的遍历都是从左往本身再往右,
    7. 先遍历这个节点的左子树,先把比本身节点小的全部元素都遍历了,
    8. 再遍历这个节点,而后再遍历比这个节点大的全部元素,这个过程是递归完成的,
    9. 以 小于、等于、大于的顺序遍历获得的结果天然就是一个从小到大的排序的,
    10. 你也能够 使用大于 等于 小于的顺序遍历,那样结果就是从大到小排序了。
    11. 也正是由于这个缘由,二分搜索树有的时候也叫作排序树,
    12. 这是二分搜索树额外的效能,
    13. 当你使用数组、链表时若是想让你的元素是顺序的话,
    14. 必须作额外的工做,不然没有办法保证一次遍历获得的元素都是顺序排列的,
    15. 可是对于二分搜索树来讲,你只要听从他的定义,
    16. 而后使用中序遍历的方式遍历整棵二分搜索树就可以获得顺序排列的结果。
  4. 后序遍历

    1. 先访问左子树,再访问右子树,
    2. 最后访问这个节点,整个过程循环往复。
    3. 后序遍历的表示先访问左子树,
    4. 而后再访问右子树,最后访问这个节点,
    5. 访问这个节点的操做放到了访问左子树和右子树的后边。
    function inOrder(node) {
       if (node == null) return;
    
       inOrder(node.left);
       inOrder(node.right);
       // ... 要作的事情
       // 访问该节点
    }
    复制代码
  5. 二分搜索树的前序遍历和后序遍历并不像中序遍历那样进行了排序

    1. 后续遍历的应用场景是那些必须先处理完左子树的全部节点,
    2. 而后再处理完右子树的全部节点,最后再处理当前的节点,
    3. 也就是处理完这个节点的孩子节点以后再去处理当前这个节点。
    4. 一个典型的应用是在内存释放方面,若是须要你手动的释放内存,
    5. 那么就须要先把这个节点的孩子节点全都释放完而后再来释放这个节点自己,
    6. 这种状况使用二叉树的后序遍历的方式,
    7. 先处理左子树、再处理右子树、最后处理本身。
    8. 可是例如javac#JS这样的语言都有垃圾回收机制,
    9. 因此不须要你对内存管理进行手动的控制,
    10. c++ 语言中须要手动的控制内存,
    11. 那么在二分搜索树内存释放这方面就须要使用后序遍历。
    12. 对于一些树结构的问题,
    13. 不少时候也是须要先针对一个节点的孩子节点求解出答案,
    14. 最终再由这些答案组合成针对这个节点的答案,
    15. 树形问题有分治算法、回溯算法、动态规划算法等等。
  6. 二分搜索树的前中后序遍历

    1. 主要从程序的角度进行分析,
    2. 不少时候对一些问题的分析,若是直接给你一个树结构,
    3. 而后你可以直接看出来对于这棵树来讲它的前中后序遍历的结果是怎样的,
    4. 那就能够大大加快解决问题的速度,
    5. 同时这样的一个问题也是和计算机相关的考试的题目,
    6. 对于这样的一个问题的更加深刻的理解
    7. 也能够帮助你理解二分搜索树这种数据结构。

代码示例(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);
       }
    
       // 中序遍历 +
       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 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. 在遍历左子树以前会去访问一下这个节点而后才能遍历它的左子树,
    3. 在遍历完左子树以后才可以回到这个节点,以后才会去遍历它的右子树,
    4. 在遍历右子树以后又回到了这个节点。
    5. 这就是每个节点使用这种递归遍历的方式其实会访问它三次,
  2. 对二分搜索树前中后这三种顺序的遍历

    1. 其实就对应于这三个访问机会是在哪里进行真正的那个访问操做,
    2. 在哪里输出访问的这个节点的值,
    3. 是先访问这个节点后再遍历它的左右子树,
    4. 仍是先遍历左子树而后访问这个节点最后遍历右子树,
    5. 再或者是 先遍历左右子树再访问这个节点。
    function traverse(node) {
       if (node === null) return;
    
       // 1. 第一个访问的机会 前
    
       traverse(node.left);
    
       // 2. 第二个访问的机会 中
    
       traverse(node.right);
    
       // 3. 第三个访问的机会 后
    }
    复制代码
  3. 二叉树前中后序遍历访问节点的不一样

    1. 前序遍历访问节点都是在第一个访问机会的位置才去访问节点,
    2. 中序遍历访问节点都是在第二个访问机会的位置才去访问节点,
    3. 后序遍历访问节点都是在第三个访问机会的位置才去访问节点,
相关文章
相关标签/搜索