数据结构:js实现二叉树

1,172 阅读5分钟

一、树

树的概念

树是计算机中常用到的一种数据结构,是一种非线性的数据结构。它是由n(n>=0)个有限节点组成一个具有层次关系的集合,以分层的方式存储数据。比如文件系统中的文件;树还被用来存储有序列表。

image.png 如图所示:F为整个树的根节点,B,G可以看作两个子树的根节点

树的特点

  • 每个节点有零个或多个子节点
  • 没有父节点的节点称为根节点
  • 每一个非根节点有且只有一个父节点
  • 除了根节点外,每个子节点可以分为多个不相交的子树。

专业名词

  • 节点的度:一个节点含有的子树的个数,叫做该节点的度。
  • 叶节点和终端节点:度为零的节点。
  • 双亲节点或父节点:如图,B为A的父节点。
  • 孩子节点或子节点:如图,A为B的子节点。
  • 兄弟节点:拥有相同父节点的节点称为兄弟节点。A和D
  • 树的度:一棵树中最大的节点的度称为树的度。
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。
  • 树的高度或深度:树中节点的最大层次,如图,高度为4。
  • 祖先:从跟到该节点所经分支上的所有节点。F是所有节点的祖先。

二、二叉树

二叉树是一种特殊的树,表现在它的子节点个数不超过两个。且二叉树的子树有左右之分,其次序不能任意颠倒。(上图所示就是一个二叉树)

二叉树的基本形态

image.png

二叉树具有诸多优点。相对于链表来说,二叉树在进行查找时速度非常快,而相对于数组来说,为二叉树添加或删除元素也非常快。

三、树的遍历

数的遍历分为三种:前序遍历、中序遍历、后续遍历

前序遍历

首先访问根节点,然后遍历左子树,最后遍历右子树

image.png

递归解法

按照访问根节点——左子树——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质 时间复杂度O(n),n为节点个树,空间复杂度O(n),即递归的空间开销(树的高度)

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var preorderTraversal = function(root,res = []) {
    if(root == null) return []
    res.push(root.val)
    preorderTraversal(root.left,res)
    preorderTraversal(root.right,res)
    return res
}

迭代算法

利用栈的先进后出的特性遍历;先创建一个栈,将根节点入栈,再将根节点出栈,加入到结果集当中,再将右子节点入栈,再将左子节点入栈,空节点不入栈;再判断栈顶是否为空,为空则出栈,加入到结果集当中。
时间复杂度O(n),n为节点个树,空间复杂度O(n)

var preorderTraversal = function(root) {
    let res = [] //结果集, stack = [] //栈
    if(root == null) return []
    stack.push(root)
    while(stack.length){
        const node = stack.pop() //出栈
        res.push(node.val) //将根节点加入到结果集
        if(node.right) {
            stack.push(node.right) //右子节点入栈
        }
        if(node.left) {
            stack.push(node.left)//左子节点入栈
        }
    }
    return res
};

中序遍历

首先遍历左子树,然后访问根节点,然后遍历右子树

image.png

递归解法

思路与前序遍历一样,顺序是左——中——右

var inorderTraversal = function(root,res = []) {
    if(root == null) return []
    inorderTraversal(root.left,res)
    res.push(root.val)
    inorderTraversal(root.right,res)
    return res
};

迭代算法

分别定义一个栈和一个数组,先将左子树压入到栈中,直到左子树为null,然后开始弹栈,将值push到数组中,再反过来将右子树压入到栈中, 再开始弹栈,将结果push到数组中。

var inorderTraversal = function(root) {
    let res = [],stack = []
    while(stack.length || root){
        if(root){
            stack.push(root)
            root = root.left
        }else{
            const node = stack.pop()
            res.push(node.val)
            root = node.right
        }
    }
    return res
};

后序遍历

后序遍历是先遍历左子树,然后遍历右子树,最后访问树的根节点。

image.png

递归解法

var postorderTraversal = function(root,res = []) {
    if(root == null) return [] //先判断是否为空,千万不能忘
    postorderTraversal(root.left,res)
    postorderTraversal(root.right,res)
    res.push(root.val)
    return res
};

迭代算法

本质和前序遍历相同,正常前序是中-左-右,后序遍历 = reverse(中-右-左)

var postorderTraversal = function(root) {
    let res = [],stack = []
    if(root == null) return []
    stack.push(root) //根节点入栈
    while(stack.length){
        const node = stack.pop() //弹出根节点
        res.push(node.val)
        if(node.left){ //左节点入栈
            stack.push(node.left)
        }
        if(node.right){//右节点入栈
            stack.push(node.right)
        }
    }
    return res.reverse()
};

当删除树中的节点时,删除过程将按照后序遍历的顺序进行。 也就是说,当删除一个节点时,将首先删除它的左节点和它的右边的节点,然后再删除节点本身。
中序常用来在二叉搜索数中得到递增的有序序列; 后序可用于数学中的后缀表示法,结合栈处理表达式,每遇到一个操作符,就可以从栈中弹出栈顶的两个元素,计算并将结果返回到栈中;

层序遍历

层序遍历介绍

层序遍历就是逐层遍历树结构。

**广度优先搜索(BFS)**是一种广泛运用在树或图这类数据结构中,遍历或搜索的算法。 该算法从一个根节点开始,首先访问节点本身。 然后遍历它的相邻节点,其次遍历它的二级邻节点、三级邻节点,以此类推。

当我们在树中进行广度优先搜索时,我们访问的节点的顺序是按照层序遍历顺序的。

image.png

二叉树的层序遍历

层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。

需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。 而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。

var levelOrder = function(root) {
    let res = []
    if(!root) return []
    let queue = [root] //声明一个队列
    while(queue.length){
        let length = queue.length //记录当前层级节点数,一定要用固定的length,因为queue.length是不断变化的
        let arr = [] //用来接收每一层的节点
        for(let i=0;i<length;i++){ //同一层的所有节点
            const node = queue.shift() //出列
            arr.push(node.val) //放到新数组
            // 存放当前层下一层的节点
            if(node.left) queue.push(node.left)
            if(node.right) queue.push(node.right)
        }
        res.push(arr)
    }
    return res
};