二叉树总结 + 各种遍历方式的实现 +有关题目

二叉树

一、二叉树的种类

满二叉树、完全二叉树、二叉搜索树、平衡二叉搜索树(AVL树)

1、二叉搜索树:

二叉搜索树是有数值的了,二叉搜索树是一个有序树。

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

2、平衡二叉搜索树:

它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

二、存储方式

1、链式存储 – 常用的方式

在这里插入图片描述

2、数组存储

根据下标来遍历数组。如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

三、遍历方式

1、节点定义

function TreeNode(val,left,right){
	// 数值
	this.val = (val === undefined ? 0 : val);
	// 指针
	this.left = (left === undefined ? null : left);
	this.right = (right === undefined ? null : right);
}

2、深度优先遍历

在这里插入图片描述
不同遍历的先后顺序:
在这里插入图片描述
深度优先遍历使用递归和迭代两种形式。递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来

解题模板 --递归实现

在这里插入图片描述

(1)前序遍历

144. 二叉树的前序遍历

a. 递归
/**
 * 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) {
    // 定义一个数组存储二叉树中的节点
    let res = [];
     
    // 定义递归函数
    function dfs(root){
        // 节点为空
        if(root === null) return;
        
        // 前序遍历
        res.push(root.val);
        // 递归左子树
        dfs(root.left);
        // 递归右子树
        dfs(root.right);    

    }
    
    // 调用递归函数打印出结果
    dfs(root);
    return res;

};
b.迭代 – 非模板

在这里插入图片描述

var preorderTraversal = function(root) {
    // 判空
    if(root == null) return[];
    
    // 定义一个栈,用来进行迭代。定义一个数组,用来存储遍历得到的数据
    const stack = [], res=[];
    
    
    // 初始化栈,根节点入栈
    stack.push(root);
    
    // 开始迭代,栈非空就弹出栈顶元素
    while(stack.length){
        // 弹出栈顶元素
        const cur = stack.pop();
        
        // 并且将节点对应的元素存入到res中
        res.push(cur.val)
        
        // 向栈中存入左右子树,先存右子树,然后存左子树。这样出栈的时候,就是先左,后右。
        if(cur.right != null) stack.push(cur.right);
        if(cur.left != null) stack.push(cur.left);    
    }
    
    return res;
};
c.迭代 – 模板

在这里插入图片描述

var preorderTraversal = function(root) {
    // 判空
    if(root == null) return[];
    
    // 定义一个栈,用来进行迭代。定义一个数组,用来存储遍历得到的数据
    const stack = [], res=[];
    
    // 外层循环时为了对右子树进行同样的操作,由于右子树可能为空,所以得加条件
    while(root || stack.length){
        // 一直将节点的左子树压入栈中
        while(root){
            // 前序遍历,所以入栈的时候,就可以放到res
            res.push(root.val);
            stack.push(root);
            root = root.left;
        }

        // 然后弹出栈顶元素,并且堆这个元素的右子树进行同样的操作
        root = stack.pop();
        // 检查这个节点得右子树
        root = root.right;
    }
    
    return res;
};
(2)中序遍历

94.中序遍历

a. 递归
var inorderTraversal = function(root) {
    let res=[];
    
    // 递归函数
    function dfs(root){
        if(!root) return [];
        dfs(root.left);
        res.push(root.val);
        dfs(root.right);
    }
    
    dfs(root)
    return res;

};
b.迭代

在这里插入图片描述

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

    return res;
};
(3)后序遍历

145.后序遍历

a. 递归
var postorderTraversal = function(root) {
    let res=[];
    
    function dfs(root){
        if(root == null) return [];
        
        dfs(root.left);
        dfs(root.right);
        res.push(root.val);
    }
    dfs(root);
    return res;
};
b.迭代
var postorderTraversal = function(root) {
    if(!root) return[];
    
    let stack=[],res=[];
    
    while(root || stack.length){
        while(root){
            stack.push(root);
            
            // 每次在res的头部插入
            res.unshift(root.val);
            root = root.right;
        }
        root = stack.pop();
        root = root.left; 
    }
    return res;
};

3、广度优先遍历

102.层次遍历

var levelOrder = function(root) {
    // 定义队列,存储结果
    let queue = [],res = [];
    
    // 判空
    if(!root) return res;
    
    // 初始化队列
    queue.push(root);
    
    // 终止条件是队列为空
    while(queue.length){
        const arr = [];
        
        // 记录当前值的节点数量,后续操作出入队数量会变化
        const n = queue.length;
        
        // 这里的n用来控制每一层需要从队头弹出元素的次数,有多少元素就进行多少次。并且每次都会检查弹出元素的左右子树,并且添加到队尾。
        for(let i=0;i<n;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;
};

四、有关题目

226. 翻转二叉树

题目链接
思想:在遍历的过程中,交换每个节点的左右子树即可。下面使用的是前序遍历,还可以使用中序遍历等方式。

// 前序遍历
var invertTree = function(root) {
    if (!root) return null;

    // 左右子树交换
    [root.left, root.right] = [root.right, root.left];  // 根

    // 左右子树内部交换
    if(root.left) invertTree(root.left);  // 左
    if(root.right) invertTree(root.right);  // 右
    
    return root;
};

199.二叉树的右视图

使用层次遍历,每次存储每层路径的路径的时候,从头部存入path。然后最后把每个path的头部元素存入res中即可。

var rightSideView = function(root) {
    let queue = [] ,res = []
    if(!root) return res
    // 初始化队列
    queue.push(root)

    while(queue.length){
        let len = queue.length
        let path = []
        for(let i=0; i<len; i++){
            // 从头部弹出节点
            let node = queue.shift()
            // 从path头部插入
            path.unshift(node.val)

            if(node.left) queue.push(node.left)
            if(node.right) queue.push(node.right)
        }
        // 把path的头部元素弹出放入res
        res.push(path.shift())
    }
    return res
};

103、二叉树的锯齿形层次遍历

在二叉树层次遍历的基础上的改进,添加一个控制层数的变量。队列中弹出元素还是从头部弹出,但是最后压入每层的数组path的时候,奇数层从头部添加,偶数层从尾部添加。从而实现锯齿形层序遍历。

var zigzagLevelOrder = function(root) {
    // 定义一个队列用于存储每个节点的左右子树, 定义一个存储最后结果的数组
    var queue=[], result = [];
    
    // 判空操作,j为空树
    if(!root) return result;
    
    // 初始化队列,将根节点存储
    queue.push(root);
    // 用来控制每一层遍历的方向
    var j = 0;
    
    // 每次将每一层的元素弹出队列的时候,需要将这一层的所有元素全部弹出。
    // 每次弹出元素的时候,要检查这个元素的左右子树是否存在
    while(queue.length){
        // 定义一个存储每一层元素的数组
        const arr = [];
        
        // 用n来控制每一层弹出的元素。当队列当中有几个元素,就要弹出多少元素。因为这个队列当中的元素是上一层节点的左右子节点。
        const n = queue.length;        
        for(let i=0;i<n;i++){
            // 从队头弹出元素
            var node = queue.shift();
            
            // 添加条件判断,奇数层从头部添加,偶数层从尾部添加
            if(j%2 == 0){
                arr.push(node.val);
            }else{                           
                arr.unshift(node.val); 
            }

            // 检查这个元素的左右子树是否存在.存在就放进队列当中
            if(node.left) queue.push(node.left);
            if(node.right) queue.push(node.right); 
        } 
        // 每一层的元素遍历结束之后,存储到最后的结果中。
        result.push(arr);
        j = j + 1;
    }
    return result;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值