【js】-【树基础】-学习笔记
声明:本笔记是根据掘金小册总结的,如果要学习更多细节,请移步https://juejin.cn/book/6844733800300150797
1 二叉树递归
在 JS 中,二叉树使用对象来定义。它的结构分为三块:
数据域
左侧子结点(左子树根结点)的引用
右侧子结点(右子树根结点)的引用
- 定义二叉树构造函数
// 二叉树结点的构造函数
function TreeNode(val) {
this.val = val;
this.left = this.right = null;
}
- 新建一个二叉树结点
const node = new TreeNode(1)
1.1 先序遍历
按格式输出:
输入:
{1,#,2,3}
返回值:
[1,2,3]
// 所有遍历函数的入参都是树的根结点对象
function preorderTraversal( root ) {
// write code here
let res=[];
function pro(root){
if(!root ) return;
res.push(root.val) ;
pro(root.left);
pro(root.right);
}
pro(root);
return res;
}
1.2 中序遍历
# 所有遍历函数的入参都是树的根结点对象
function inorder(root) {
// 递归边界,root 为空
if(!root) {
return
}
// 递归遍历左子树
inorder(root.left)
// 输出当前遍历的结点值
console.log('当前遍历的结点值是:', root.val)
// 递归遍历右子树
inorder(root.right)
}
1.3 后序遍历
function postorder(root) {
// 递归边界,root 为空
if(!root) {
return
}
// 递归遍历左子树
postorder(root.left)
// 递归遍历右子树
postorder(root.right)
// 输出当前遍历的结点值
console.log('当前遍历的结点值是:', root.val)
}
2 二叉树迭代
2.1 先序遍历
- 将根结点入栈
- 取出栈顶结点,将结点值 push 进结果数组
- 若栈顶结点有右孩子,则将右孩子入栈
- 若栈顶结点有左孩子,则将左孩子入栈
const preorderTraversal = function(root) {
// 定义结果数组
const res = []
// 处理边界条件
if(!root) {
return res
}
// 初始化栈结构
const stack = []
#1 首先将根结点入栈
stack.push(root)
// 若栈不为空,则重复出栈、入栈操作
while(stack.length) {
# 2 将栈顶结点记为当前结点
const cur = stack.pop()
# 3 当前结点就是当前子树的根结点,把这个结点放在结果数组的尾部
res.push(cur.val)
# 4 若当前子树根结点有右孩子,则将右孩子入栈,若当前子树根结点有左孩子,则将左孩子入栈
if(cur.right) {
stack.push(cur.right)
}
if(cur.left) {
stack.push(cur.left)
}
}
// 返回结果数组
return res
};
2.2 后序遍历
左 -> 右 -> 根
const postorderTraversal = function(root) {
// 定义结果数组
const res = []
// 处理边界条件
if(!root) {
return res
}
// 初始化栈结构
const stack = []
// 首先将根结点入栈
stack.push(root)
// 若栈不为空,则重复出栈、入栈操作
while(stack.length) {
// 将栈顶结点记为当前结点
const cur = stack.pop()
# 当前结点就是当前子树的根结点,把这个结点放在结果数组的头部
res.unshift(cur.val)
// 若当前子树根结点有左孩子,则将左孩子入栈
if(cur.left) {
stack.push(cur.left)
}
// 若当前子树根结点有右孩子,则将右孩子入栈
if(cur.right) {
stack.push(cur.right)
}
}
// 返回结果数组
return res
};
2.3 中序遍历
const inorderTraversal = function(root) {
// 定义结果数组
const res = []
// 初始化栈结构
const stack = []
// 用一个 cur 结点充当游标
let cur = root
# 当 cur 不为空、或者 stack 不为空时,重复以下逻辑
while(cur || stack.length) {
// 这个 while 的作用是把寻找最左叶子结点的过程中,途径的所有结点都记录下来
while(cur) {
# 将途径的结点入栈
stack.push(cur)
# 继续搜索当前结点的左孩子
cur = cur.left
}
# 取出栈顶元素
cur = stack.pop()
// 将栈顶元素记录
res.push(cur.val)
// 尝试读取 cur 结点的右孩子
cur = cur.right
}
// 返回结果数组
return res
};
3 二叉搜索树
3.1 查找数据域为某一特定值的结点
function search(root, n) {
// 若 root 为空,查找失败,直接返回
if(!root) {
return
}
// 找到目标结点,输出结点对象
if(root.val === n) {
console.log('目标结点是:', root)
} else if(root.val > n) {
// 当前结点数据域大于n,向左查找
search(root.left, n)
} else {
// 当前结点数据域小于n,向右查找
search(root.right, n)
}
}
3.2 插入新结点
function insertIntoBST(root, n) {
// 若 root 为空,说明当前是一个可以插入的空位
if(!root) {
// 用一个值为n的结点占据这个空位
root = new TreeNode(n)
return root
}
if(root.val > n) {
// 当前结点数据域大于n,向左查找
root.left = insertIntoBST(root.left, n)
} else {
// 当前结点数据域小于n,向右查找
root.right = insertIntoBST(root.right, n)
}
// 返回插入后二叉搜索树的根结点
return root
}
3.3 删除指定结点
var deleteNode = function(root, key) {
if(!root) return null;
if(root.val >key){
root.left = deleteNode(root.left, key)
}else if(root.val < key){
root.right = deleteNode(root.right, key)
}else{
# 1. 找到结点了
if(!root.left) return root.right;
# 2.没有左结点,直接返回右边的
if(!root.right) return root.left;
# 3.左右结点都有,
var node = root.right;
while(node.left) node=node.left;
# 4.寻找欲删除节点右子树的最左节点
node.left =root.left;
# 5.将欲删除节点的左子树成为其右子树的最左节点的左子树
return root.right; //欲删除节点的右子顶替其位置,节点被删除
}
return root;
};
3.4 二叉搜索树的验证
检验每棵子树中是否都满足 左 < 根 < 右
这样的关系
const isValidBST = function(root) {
// 定义递归函数
function dfs(root, minValue, maxValue) {
// 若是空树,则合法
if(!root) {
return true
}
// 若右孩子不大于根结点值,或者左孩子不小于根结点值,则不合法
if(root.val <= minValue || root.val >= maxValue) return false
// 左右子树必须都符合二叉搜索树的数据域大小关系
return dfs(root.left, minValue,root.val) && dfs(root.right, root.val, maxValue)
}
// 初始化最小值和最大值为极小或极大
return dfs(root, -Infinity, Infinity)
};
3.5 将排序数组转化为二叉搜索树
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
var sortedArrayToBST = function(nums) {
if(!nums.length) return null;
const middle= Math.floor(nums.length/2);
let root = new TreeNode(nums[middle]);
root.left= sortedArrayToBST(nums.slice(0,middle));
root.right = sortedArrayToBST(nums.slice(middle+1));
return root;
};
3.6 其他搜索树的模板
void BST(TreeNode root, int target) {
if (root.val == target)
// 找到目标,做点什么
if (root.val < target)
BST(root.right, target);
if (root.val > target)
BST(root.left, target);
}
4 平衡二叉树
4.1 判断它是否是高度平衡的二叉树
一棵高度平衡二叉树定义为: 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
var isBalanced = function(root) {
if(!root) return true;
return Math.abs(height(root.left)-height(root.right))<=1&&isBalanced(root.left)&&isBalanced(root.right);
};
var height = function(root)
{
if (!root)
return 0;
else
return Math.max(height(root.left), height(root.right)) + 1;
}
5. 对称性递归
5.1 相同的树
var isSameTree = function(p, q) {
if(!p&&!q) return true;
else if(!p||!q) return false;
return p&&q &&(p.val===q.val)&& isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
};
5.2 二叉树的最大深度
var maxDepth = function(root) {
if(!root) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
};
5.3 翻转二叉树
const invertTree = function(root) {
// 定义递归边界
if(!root) {
return root;
}
// 递归交换右孩子的子结点
let right = invertTree(root.right);
// 递归交换左孩子的子结点
let left = invertTree(root.left);
// 交换当前遍历到的两个左右孩子结点
root.left = right;
root.right = left;
return root;
};
5.4 合并二叉树
var mergeTrees = function(root1, root2) {
if(!root1) return root2;
if(!root2) return root1;
if(root1&&root2) root1.val+=root2.val
root1.left = mergeTrees(root1.left,root2.left);
root1.right = mergeTrees(root1.right,root2.right);
return root1;
};
5.5 对称二叉树
构造一个辅助函数判断两棵树是否是镜像对称的,然后题目只要判断两棵这个树是否镜像对称
而比较两棵树是否镜像对称,即一棵树的左子树和另一棵树的右子树,以及一棵树的右子树和另一棵树的左子树是否镜像对称
var isSymmetric = function(root) {
return isMirror(root, root);
};
var isMirror =function(p,q){
if(!q&&!p) return true;
if(!q || !p) return false;
return (p.val==q.val) && isMirror(p.left,q.right)&&isMirror(p.right,q.left);
}
5.6 判断一棵树是否为另一颗树的子结构
var isSubStructure = function(A, B) {
if(!A || !B) return false;
# 判断A树和B树是否完全相等 或 左边的树包含B树 或 右边的树包含B树
return isSameTree(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B)
};
var isSameTree = function(A, B) {
// B为空,说明B树被遍历到底部了。证明B树完全存在于A树中。 返回 true
if(!B) return true;
// A为空,且不满足上面的判断(即B不为空),说明A树遍历完了还没符合上面的情况,证明B树不存在于这条分支。 返回 false
if(!A) return false;
// 当前节点的值不相等,不 ok
if(A.val !== B.val) return false;
# 递归考察左子树、右子树
return isSameTree(A.left, B.left) && isSameTree(A.right, B.right);
};
5.7 单值二叉树
所有节点值均相等
var isUnivalTree = function(root) {
if(!root) return true;
if(root.left&&root.left.val!=root.val || root.right && root.right.val != root.val)
return false;
return isUnivalTree(root.left) && isUnivalTree(root.right)
};
5.8 重建二叉树
给定节点数为 n 的二叉树的前序遍历
和中序遍历
结果,请重建出该二叉树并返回它的头结点。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。
提示:
1.vin.length == pre.length
2.pre 和 vin 均无
重复元素
3.vin出现的元素均出现在 pre里
4.只需要返回根结点,系统会自动输出整颗树做答案对比
数据范围:n \le 2000n≤2000,节点的值 -10000 \le val \le 10000−10000≤val≤10000
要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)
输入:
[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]
复制
返回值:
{1,2,3,4,#,5,6,#,7,#,#,8}
复制
说明:
返回根节点,系统会输出整颗二叉树对比结果,重建结果如题面图示
function reConstructBinaryTree(pre, vin)
{
// write code here
if(!pre.length||!vin.length) return null;
//通过pre找到根节点
const root= new TreeNode(pre.shift());
//左子树的长度
const index=vin.indexOf(root.val);
root.left=reConstructBinaryTree(pre,vin.slice(0,index));
root.right=reConstructBinaryTree(pre,vin.slice(index+1));
return root;
}
6 层次遍历
6.1判断是不是完全二叉树
function isCompleteTree( root ) {
let rootArr=[];
rootArr.push(root);
let end=false;
while(rootArr.length){
// 取出第一个
const node=rootArr.shift();
if(!node) end=true;
else{
if(rootArr.length && end) return false;
rootArr.push(node.left);
rootArr.push(node.right);
}
}
return true;
}