持续更新中......
基本概念
树是n个结点的有限集,树的结构定义是一个递归定义
在任意一颗非空树中:
- 有且仅有一个特定的称为根的结点
- 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,T3...,其中每一个集合本身又是一棵树,并且称为根的子数
接下来我们来研究一种称为二叉树的抽象数据类型
二叉树是一种树型结构,它的特点是每个结点至多只有两颗子树,(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒
各种操作
二叉树的前中后序遍历
首先,大致了解一下什么是二叉树的前中后序遍历
- 前序:根结点->左子树->右子树
- 中序:左子树->根结点->右子树
- 后序:左子树->右子树->根结点
看个图,更好理解
这里专门说一下后序遍历
后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点。即:
若二叉树为空则结束返回,否则:
- 后序遍历左子树
- 后序遍历右子树
- 访问根结点
已知前序遍历和中序遍历,就能确定后序遍历。
解法:递归三步走
给你二叉树的根节点 root
,返回它节点值的 前序遍历。
//递归 3步走
//前序
var preorderTraversal = function(root) {
let res=[];
//1.确定递归函数的参数和返回值
const dfs=function(root){
//2.确定终止条件
if(root===null)return ;
//3.确定单层递归的逻辑 中 左 右
res.push(root.val);//先序遍历所以从父节点开始
dfs(root.left);//递归左子树
dfs(root.right);//递归右子树
}
//只使用一个参数 使用闭包进行存储结果
dfs(root);
return res;
};
给你二叉树的根节点 root
,返回它节点值的 中序遍历。
//中序
var inorderTraversal = function(root) {
let res=[];
const dfs=function(root){
if(root===null){
return ;
}
dfs(root.left);//左中右
res.push(root.val);
dfs(root.right);
}
dfs(root);
return res;
};
给你二叉树的根节点 root
,返回它节点值的 后序遍历。
//后序
var postorderTraversal = function(root) {
let res=[];
const dfs=function(root){
if(root===null){
return ;
}
dfs(root.left);//左右中
dfs(root.right);
res.push(root.val);
}
dfs(root);
return res;
};
二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例: 二叉树:[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层序遍历结果:
[
[3],
[9,20],
[15,7]
]
解法:层序遍历+队列思想
var levelOrder = function(root) {
const ret = [];
if (!root) {
return ret;
}
const q = [];
q.push(root);
while (q.length !== 0) {
const currentLevelSize = q.length;//常量不能改变 每换一层就出一次for 来一次while q数组长度更新
ret.push([]);//没换一层就添加一个新的数组
for (let i = 1; i <= currentLevelSize; ++i) {
const node = q.shift();//shift删除数组第一个元素,返回删除的元素(改变原数组)
ret[ret.length - 1].push(node.val);//最后一个数组添加
if (node.left) q.push(node.left);//左
if (node.right) q.push(node.right);//右
}
}
return ret;
};
路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
叶子节点 是指没有子节点的节点。
示例:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22 输出:true
解法:递归
//妙啊!!递归
var hasPathSum = function(root, targetSum) {
if(!root){//到最后一个节点之后
return false
}
if(!root.left&&!root.right){
return root.val===targetSum//到叶子节点了
}
return hasPathSum(root.left,targetSum-root.val) || hasPathSum(root.right,targetSum-root.val)//左或右只要有一边能找到就行
};
二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
3
/ \
9 20
/ \
15 7
给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
解法:递归
递归三部曲
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
var maxDepth = function(root) {
let k=0
if(!root) return k
if(maxDepth(root.left) || maxDepth(root.right)){//参数
k++//单层递归的逻辑 根左右的顺序,任何一个不为空就继续 根左右 遍历
}
return k//返回值
}
翻转二叉树
翻转一棵二叉树。
示例:
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
这道题背后有个小故事
但是它真的很难吗,并不是,这只是一道简单题,让我们一探究竟
解法:BFS 队列 广度优先遍历 自顶向下
// 用层序遍历的方式去遍历二叉树。
// 根节点先入列,然后出列,出列就 “做事”,交换它的左右子节点(左右子树)。
// 并让左右子节点入列,往后,这些子节点出列,也被翻转。
// 直到队列为空,就遍历完所有的节点,翻转了所有子树。
var invertTree = function(root) {
let queue=[root]//根节点先入列
while(queue.length>0){
let cur=queue.pop()//然后出列,最后一次把 null放进去了
if(cur===null) continue;//直到队列为空,就遍历完所有的节点,翻转了所有子树 回到while,判断false,直接跳出while
[cur.left,cur.right]=[cur.right,cur.left]//出列就 “做事”,交换它的左右子节点(左右子树)
queue.unshift(cur.left)//并让左右子节点入列,往后,这些子节点出列,也被翻转
queue.unshift(cur.right)
}
return root
};
特别要注意的点,continue的使用,跳出当前循环,最后一次是压入了 null 队列长度=1