前端学习数据结构1 二分排序树(BST)

对于我这样一个毕业了一年半左右的前端来说,很摸着良心的讲,平时工作中,没有遇到过写什么树。但是有大块时间的时候,觉得这些还是得好好复习一下,或者说预习。今天的主角是二分搜索树。

二分搜索树

二分搜索树也是二叉树,和二叉树长的一样,就是有个特点,每个节点的值比他的左子树的值大,比他的右子树的值小。如下图所示:

那么接下来就来实现一下这个树:

// 声明节点构造函数 当前节点的值,左节点,右节点
class Node {
  constructor(value) {
    this.value = value
    this.left = null
    this.right = null
  }
}
// 二分搜索树构造函数
class BST {
  constructor() {
    this.root = null
    this.size = 0
  }
  getSize() {
    return this.size
  }
  isEmpty() {
    return this.size === 0
  }
  addNode(v) {
    // 每次添加子节点后,更新树
    this.root = this._addChild(this.root, v)
  }
  _addChild(node, v) {
    if (!node) {
      this.size++
      return new Node(v)
    }
    if (node.value > v) {
      console.log(`在${node.value}节点,添加左节点${v}`)
      node.left = this._addChild(node.left, v)
    } else if (node.value < v) {
      console.log(`在${node.value}节点,添加右节点${v}`)
      node.right = this._addChild(node.right, v)
    }
    return node
  }    
}

let bst = new BST()
// 第一个节点
bst.addNode(10)
// 后续节点
bst.addNode(8)
bst.addNode(6)
bst.addNode(3)
bst.addNode(7)
bst.addNode(9)
bst.addNode(12)
bst.addNode(11)
bst.addNode(15)
bst.addNode(14)

console.log(bst)
复制代码

运行结果如下:

左节点值是8 右节点值12 中间节点值是10。

再展开左节点看一下

右边就不再展开赘述了

二分搜索树--遍历

二叉树遍历 分为深度遍历(先序遍历、中序遍历、后序遍历,三种遍历的区别在于何时访问节点), 广度遍历(一层层地遍历)

先序遍历

绕不开的递归又出现了,想起有一天右边同事妹子很开心的和我说她知道了递归的终极奥义:“递归的终极奥义就是:不要想递归是怎么具体一步步实现的”

那我先来实现一下先序遍历

class BST { 
    ...
    // 添加先序遍历实现,其实就是很简单的几行代码
    preTraversal() {
      this._pre(this.root)
    }
    _pre(node) {
      if (node) {
        // 访问节点的值
        console.log(node.value)
        // 递归左右子树
        this._pre(node.left)
        this._pre(node.right)
      }
    }
}
// 用上面生成的bst实例执行一下,结果如下图
bst.preTraversal()
复制代码

那么这个结果是如何生成的呢?

  1. 先是打印10,这个毫无争议 然后 this._pre(node.left),this._pre(node.right)这两个方法看似两行,其实左子树没有遍历完结的话是不会去遍历右子树的
  2. 此时this._pre(node.left)中参数是如下图部分,同样,会对这部分执行那3行代码,首先会打印8 , 然后以8那个节点作为根节点,去遍历左右子树

3. 打印8之后,遍历左子树的参数如下图部分,同样,会对这部分执行那3行代码,首先会打印6 , 然后以6那个节点作为根节点,去遍历左右子树

4. 这个时候遍历左子树时候 this_pre(node.left)的参数只是3节点,3节点没有子树,那么在执行上面那3行代码只是打印3 ,this._pre(node.left)和this._pre(node.right)执行不下去了。

  1. 上面第三步骤时候,打印6之后,先遍历左子树,后遍历右子树。而此时的遍历左子树只是打印3。于是要去遍历6的右子树,也就是打印7。
  2. 打印7之后,本例中,作为节点8的左节点已经遍历完毕。遍历8的右节点,也就是打印9,之后8的右节点也遍历完毕。
  3. 再往回退,打印9之后,也就是10节点的左节点已经全部遍历完毕。 所以打印的结果是 10 8 6 3 7 9
  4. 同样的逻辑此时该去遍历10节点的右节点了。依次打印12 11 15 14 ,所以最终结果就是 10 8 6 3 7 9 12 11 15 14

一步步的推导递归的具体实现后,还真的觉的上面所说递归的奥义那句话总结的是很有意思的。

中序遍历
class BST { 
    ...
    // 添加中序遍历实现,其实就是很简单的几行代码
    midTraversal() {
      this._mid(this.root)
    }
    _mid(node) {
      if (node) {
        // 1语句 后面讲解时候说'1语句'就指代下面这句
        this._mid(node.left)
        // 2语句
        console.log(node.value)
        // 3语句
        this._mid(node.right)
      }
    }
}
// 用上面生成的bst实例执行一下,结果如下图
bst.midTraversal()
复制代码

  1. 上面的逐条分析之后,中序遍历就很容易理解了。上面的循环体主要三条语句,在第一次执行 2语句之前,我们要想一下,此时的参数node,是什么?换个问法,在参数node是树中的哪个节点的时候,才会第一次的执行2语句? 我来截图吧

只有当node是这个3节点的时候,才会第一次触发这个console.log(node.value),而 此时此刻3语句也相当于是一条废语句。

  1. 现在回想一下,当node参数是这个3节点的时候,回退一下,他的上一步执行函数中node是什么?我来截图

node是图中这个6节点的时候, 1语句执行完毕,才会执行 此时此刻的console.log(node.value),也就是打印出了6,然后执行 3语句 ,以7节点作为node参数,再去执行123语句,此时此刻13语句都是废语句,也就是打印出7 。也就是打印 3 6 7

  1. 那么再回退一下,打印出7之后,也就是node.left 是6这个节点的递归执行完毕了,此时此刻的node是8,然后执行console.log(node.value),再去执行3语句,此时此刻的3语句的参数是node.left,也就是9节点,对这个9节点,依次执行123语句,9节点没有子节点,所以只是打印出9 ,至此,整个8节点遍历完毕。

  2. 打印出了9之后, 8节点完全遍历完毕了,8节点作为node.left,那么此时的node是10节点,执行2语句,打印出10 ,那么后续的,相信不用我再说了吧

  3. 这就是为什么二分排序树的中序遍历的结果是排序好的。

后序遍历
class BST { 
    ...
    // 添加后序遍历实现,其实就是很简单的几行代码
    backTraversal() {
      this._back(this.root)
    }
    _back(node) {
      if (node) {
        // 1语句 后面讲解时候说'1语句'就指代下面这句
        this._back(node.left)
        // 2语句
        this._back(node.right)
        // 3语句
        console.log(node.value)
      }
    }
}
// 用上面生成的bst实例执行一下,结果如下图
bst.backTraversal()
复制代码

  1. 递归的奥义:就是不要想递归是怎么一步步具体实现的。
  2. 后续遍历与之前遍历方式的区别是的3语句是打印,那么能否根据上面的前序推导和中序推导加上奥义,直接看着树的图,写出后续遍历的结果呢?
广度遍历

未完待续

(以上参考掘金小册,融入自己的实操和思考,因为是收费小册,参考的地址原文没办法贴出来,yck老师很赞,建议大家都去看看他的小册)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值