二叉树层次遍历
//思路
特殊情况 ,根节点为null,深度为0,return 0
借助队列,队列储存每一层的所有节点,
先保存数组长度,用于控制依次遍历循环——遍历每一个节点保存值,该节点的所有子结点从队尾入队,访问过的节点从头出队
注意——需要先把width存下来,用于for循环的结束标志,因为for循环里面直接操作了queue,不能去都不敢动态获取
队列初始值为root,对每一层都要进行上述遍历——while循环控制,队列空代表叶节点这一层遍历完成,此时遍历结束,退出循环
每次循环开始前初始化一个curNodes储存该层所有节点,每次循环结束,将curNodes压入result
var levelOrder = function(root) {
if (root === null) return []; //空树
var result = [],
queue = [root];
while (queue.length) {
let width = queue.length; //需要先把width存下来,用于for循环,for循环里面直接操作了数组
let curNodes = [];
for (let i = 0; i < width; i++) {
let node = queue.shift();
curNodes.push(node.val);
node.left ? queue.push(node.left) : "";
node.right ? queue.push(node.right) : "";
}
result.push(curNodes);
}
return result;
};
二叉树反向层次遍历
var levelOrderBottom = function(root) {
if (root === null) return []; //空树
var result = [],
queue = [root];
while (queue.length) {
let width = queue.length; //需要先把width存下来,用于for循环,for循环里面直接操作了数组
let curNodes = [];
for (let i = 0; i < width; i++) {
let node = queue.shift();
curNodes.push(node.val);
node.left ? queue.push(node.left) : "";
node.right ? queue.push(node.right) : "";
}
result.unshift(curNodes);//从头部插入,先插入顶部的,后插入底部的额
}
return result;
};
先序遍历
//思路
和中序其他都相似,要先访问根节点要后访问左节点,所以在根节点入栈的时候加入result数组
特殊情况:根节点为空,返回空数组
利用栈来存放访问过的根节点——定义一个指针指向要访问的节点,先访问根节点。将遍历到的结点值存入result数组,结点存入栈中。
stack存放遍历过的根,左节点,以便回溯访问右节点
然后指针一直向下访问左节点
当根节点没有左节点时,将根节点退栈,然后指针指向右节点,访问右节点
直到栈为空而且指针为空,所有节点遍历完成,退出循环,返回结果
var preorderTraversal = function(root) {
if (root === null) return []; //空树
var result = [],
stack = []; //result储存结果,stack存放遍历过的根,左节点,以便回溯访问右节点
var p = root; //p指向当前遍历的节点
//当有未访问的右节点(stack不空)||还有左节点没有访问(p不空)时进入循环
while (stack.length != 0 || p != null) {
//还有左节点没有访问(p不空)
if (p != null) {
result.push(p.val); //遍历根节点
stack.push(p); //把遍历过的节点放入stack保管
p = p.left; //访问左节点
}
//左节点访问完
else {
p = stack.pop().right; //栈顶节点退栈,访问右节点
}
}
return result;
};
中序遍历
//思路
和前序其他都相似,左节点要先访问根节点要后访问,所以在根节点出栈的时候加入result数组
特殊情况:根节点为空,返回空数组
利用栈来存放访问过的根节点——定义一个指针指向要访问的节点,先访问根节点。,结点存入栈中。
stack存放遍历过的根,左节点,以便回溯访问右节点
然后指针一直向下访问左节点
当根节点没有左节点时,将根节点退栈,将遍历到的结点值存入result数组,然后指针指向右节点,访问右节点
直到栈为空而且指针为空,所有节点遍历完成,退出循环,返回结果
var inorderTraversal = function(root) {
if (root === null) return []; //空树
var result = [],
stack = []; //result储存结果,stack存放遍历过的根,左节点,以便回溯访问右节点
var p = root; //p指向当前遍历的节点
//当有未访问的右节点(stack不空)||还有左节点没有访问(p不空)时进入循环
while (stack.length != 0 || p != null) {
//还有左节点没有访问(p不空)
if (p != null) {
stack.push(p); //把遍历过的节点放入stack保管
p = p.left; //访问左节点
}
//左节点访问完
else {
let node = stack.pop();//栈顶节点退栈,
result.push(node.val); //遍历左节点-根节点
p = node.right; //访问右节点
}
}
return result;
};
后序遍历
//思路
特殊情况:根节点为空,返回空数组
利用栈来存放访问过的根节点——定义一个指针指向要访问的节点,先访问根节点。,结点存入栈中。
stack存放遍历过的根,右节点,以便回溯访问左节点
然后指针一直向下访问右节点
当根节点没有右节点时,将根节点退栈,将遍历到的结点值存入result数组,然后指针指向右节点,访问右节点
直到栈为空而且指针为空,所有节点遍历完成,退出循环,返回结果
var postorderTraversal = function(root) {
if (root === null) return []; //空树
var result = [],
stack = []; //result储存结果,stack存放遍历过的根,左节点,以便回溯访问右节点
var p = root; //p指向当前遍历的节点
//当有未访问的左节点(stack不空)||还有右节点没有访问(p不空)时进入循环
while (stack.length != 0 || p != null) {
//还有左节点没有访问(p不空)
if (p != null) {
stack.push(p); //把遍历过的节点放入stack保管
result.unshift(p.val); //从result数组头部插入根节点-右节点-左节点
//最后result顺序为左节点-右节点-根节点
p = p.right; //访问右节点
}
//右节点访问完
else {
p = stack.pop().left; //栈顶节点退栈,访问左节点
}
}
return result;
};
重建二叉树
//思路:二叉树前序遍历第一个点为根节点,中序遍历顺序为先左子树然后根节点最后右子树。所以先通过前序遍历找出根节点,然后将中序遍历分为左右子树两组,最后对于每个子树依次递归调用。
function reConstructBinaryTree(pre, vin) {
if (pre.length === 0 || vin.length === 0) return null; // 前序/中序又一个为空,就返回空值
let root = new TreeNode(pre[0]); //新建节点,作为根节点
if (pre.length === 1) return root; //是叶节点直接返回root,不需要计算子树
//不是叶节点,先递归得到左右节点
let rootIdx = vin.indexOf(pre[0]); //根节点在中序的位置
root.left = reConstructBinaryTree(
// 递归调用得到左子树的根节点
pre.slice(1, rootIdx + 1),
vin.slice(0, rootIdx)
);
root.right = reConstructBinaryTree(
// 递归调用得到右子树的根节点
pre.slice(rootIdx + 1),
vin.slice(rootIdx + 1)
);
return root;
}
二叉树镜像
//思路:先将根的左右节点互换,然后就是递归调用,对左右子树进行分别处理
function Mirror(root)
{
// write code here
if(root==null) return null;
//首先先将左右节点互换
var tmp = root.left;
root.left=root.right;
root.right=tmp;
//递归
Mirror(root.left);
Mirror(root.right);
}
平衡二叉树
//在这里,由于我们是以递归的形式,所以,我们会先遍历左节点,再遍历右节点,最后再遍历根节点(后序遍历)
同时,我们判断左右树的高度差是否超过 1,如果是,则进行中断,返回 false;否则,继续递归。
最后,我们将遍历的结果与 -1 进行比较,返回 true 或者 false
let root = {
val: 3,
left: { val: 9, left: null, right: null },
right: {
val: 20,
left: { val: 3, left: null, right: null },
right: {
val: 7,
left: null,
right: { val: 2, left: null, right: null },
},
},
}
var isBalanced = function(root) {
let ergodic = function(root) {
if (!root) {
return 0;
}
let left = ergodic(root.left);
if (left === -1) {
return -1;
}
let right = ergodic(root.right);
if (right === -1) {
return -1;
}
return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
}
return ergodic(root) != -1;
};
console.log(isBalanced(root));
二叉树深度
//题目:
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
//思路
特殊情况 ,根节点为null,深度为0,return 0
递归法,递归法获取左右子树的深度,
root的深度是左右子树的深度最大值+1,返回深度
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function TreeDepth(pRoot) {
// write code here
if (!pRoot) return 0; //根节点为空,深度为0
var left = TreeDepth(pRoot.left); //左深度等于左子树深度+1
var right = TreeDepth(pRoot.right); //右深度等于右子树深度+1
return 1 + Math.max(left, right); //该节点深度为左右深度中的max,返回
}
二叉树最大深度
//思路
我们判断 root 是否还存在 left 和 right:如果不存在,那么树到了最底层,终止递归;如果存在,那么 depth 深度 + 1,并且遍历它的左树和右树。
同时,我们在树存在 left 和 right 的时候,将 depth 和 longest 比较,把最深的树给记录下来。
返回最长深度 longest。
var maxDepth = function(root) {
return root === null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};
二叉树最小深度
//思路
假设我是一只蜘蛛,我在一颗大树最底下(根节点),开始往上爬。
每经过 1 米(1 个 val 节点),我就留下一个分身。
当我爬到最顶的时候,我就进行最后标记,并告诉分身,前面凉凉了,开始报数!
于是从我为 1 开始,一直到根节点的长度,就是这个分支的高度。
const root = {
val: 3,
left: { val: 9, left: null, right: null },
right: {
val: 20,
left: { val: 15, left: null, right: null },
right: { val: 7, left: null, right: null },
},
}
var minDepth = function(root) {
if (!root) {
return 0;
}
if (!root.left) {
return minDepth(root.right) + 1;
}
if (!root.right) {
return minDepth(root.left) + 1;
}
return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
};
minDepth(root);
翻转二叉树
var invertTree = function(root) {
if (!root) {
return null;
}
return {
val: root.val,
left: invertTree(root.right) || null,
right: invertTree(root.left) || null,
}
};
二叉树下一个节点
//题目
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
//思路
特殊情况 空节点
情况一,有右子节点,下一个访问右子树中最左边的第一个节点。通过while循环,寻找右子树的左叶子节点可以找到。
情况二,没有右子结点,有父节点,当前节点是父节点的左节点,下一个访问父节点
情况三,没有右子结点,有父节点,当前节点是父节点的右节点,此时父节点一下都已经访问完,下一个访问父节点的父节点中,满足从左边连接的第一个
情况二和情况三可以归为一类——父节点存在时,设一个指针cur指向pnode父节点,
如果pnode是cur的左节点,返回;
如果pnode是cur的右节点,pnode向上指,
如果退出循环,查找到根节点都没有返回,就表示没有满足条件的节点,返回null
function GetNext(pNode) {
// write code here
if (!pNode) return null; //空节点
var cur = null;
//有右节点,下一个访问右子树里的最左边节点
if (pNode.right) {
cur = pNode.right;
while (cur.left) {
cur = cur.left;
}
return cur;
}
//没有右节点,有父节点,
while (pNode.next) {
cur = pNode.next; //cur指向父节点
//如果pnode是cur的左节点,下一个访问的就是cur
if (cur.left === pNode) return cur;
//如果pnode不是cur的左节点,pnode指向父节点,向上继续查找
pNode = cur;
}
return null; //查找到根节点也没有符合条件的节点
}
最大二叉树
//题目:
给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下:
二叉树的根是数组中的最大元素。
左子树是通过数组中最大值左边部分构造出的最大二叉树。
右子树是通过数组中最大值右边部分构造出的最大二叉树。
通过给定的数组构建最大二叉树,并且输出这个树的根节点。
//思路
递归法实现构建二叉树
递归出口,数组长度为0,return null,返回空节点
根节点为传入数组的max值,
左节点为递归调用,传入以max为分割点的左边数组的返回值,
右节点为递归调用,传入以max为分割点的右边数组的返回值,
slice(left,right)分割数组,索引为left的元素划分进去,索引为right的元素不划分进去。
var constructMaximumBinaryTree = function(nums) {
if (nums.length <= 0) return null;
let max = Math.max(...nums);
let i = nums.indexOf(max);
let root = new TreeNode(max);
root.left = constructMaximumBinaryTree(nums.slice(0, i));
root.right = constructMaximumBinaryTree(nums.slice(i + 1));
return root;
};
序列化二叉树
//题目描述
请实现两个函数,分别用来序列化和反序列化二叉树
//思路:
序列化,将节点值存入数组中,空节点则使用特殊标记存入数组中。
反序列化,从数组中获取元素,为number类型则生成节点,为特殊标记,则为空节点
var arr=[];
function Serialize(pRoot)
{
// write code here
if(pRoot==null){
arr.push('#')
return;
}
arr.push(pRoot.val);
Serialize(pRoot.left)
Serialize(pRoot.right)
}
function Deserialize(s)
{
// write code here
if(arr==null){
return null;
}
if(arr.length<1){
return null;
}
var root=null;
var temp=arr.shift();
if(typeof temp=='number'){
root=new TreeNode(temp);
root.left=Deserialize(arr);
root.right=Deserialize(arr);
}
return root;
}
二叉树中和为某一值的路径
//思路
因为要先访问最长的路径,所以考虑`
递归深度遍历:对根节点调用递归函数
curPath保存当前路径,sum保存当前和,每访问一个节点,更新路径和和,
更新之后,判断是否满足叶节点+和为指定值,如果满足,压入结果数组
注意:因为 curPath是一个引用类型,它的值一直在变化,所以压入数组时需要先进行深拷贝,如果不拷贝会导致结果输出为最后的curPath空数组。
左右节点存在时,对左右节点递归遍历
当前节点和所有子节点遍历完成,当前节点退出路径
function FindPath(root, expectNumber) {
// write code here
if (!root) return [];
var res = [], //所有满足条件路径
curPath = [], //当前路径
p = root, //访问指针
sum = 0; //当前路径和
dfs(p, curPath, sum, res, (exp = expectNumber));
return res;
}
function dfs(p, curPath, sum, res, exp) {
if(!p&&curPath.length===0) return;//所有节点访问完毕,退出递归
curPath.push(p.val); //节点值加入路径
sum += p.val; //更新和
if (!p.left && !p.right && sum === exp) {
//栈顶节点是叶节点且路径满足条件
res.push(curPath.slice(0)); //路径加入结果
}
p.left ? dfs(p.left, curPath, sum, res, exp) : "";
p.right ? dfs(p.right, curPath, sum, res, exp) : "";
curPath.pop(); //节点和所有子树访问完毕,退出路径
}
求根到叶子节点数字之和
//题目:
给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。例如,从根到叶子节点路径 1->2->3 代表数字 123。计算从根到叶子节点生成的所有数字之和。
//方法一:递归法
//思路
递归输入当前节点和上一层的和,初始为root和0
如果节点空返回0
更新sum,是叶节点的话直接返回这个值
不是叶节点再加上左右节点的递归返回值
var sumNumbers = function(root) {
return dfs(root,0);
};
var dfs = function(p,sum) {
if(!p) return 0;
sum=sum*10+p.val;
if(!p.left&&!p.right) return sum;
return dfs(p.left,sum)+dfs(p.right,sum);
};
二叉树所有路径
//思路:
使用递归方法来解决此问题。对于根节点,如果根节点为空,则返回空数组[],如果为叶子节点,则返回包含此节点的值的数组(这个数组只有一个元素)。若不为空也不是叶子节点,则对左右子树递归调用,将两个结果数组拼接起来,最后使用 map 函数来对数组的每个元素进行字符串的操作。
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
var binaryTreePaths = function(root) {
if (root === null) return [];
if (root.left === null && root.right === null) {
return [root.val.toString()];
}
var left = binaryTreePaths(root.left),
right = binaryTreePaths(root.right);
return left.concat(right).map(x => root.val + '->' + x);
};
树的子结构
//题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
//思路:比较B是不是A的子树,B是不是A的右子树的子树,B是不是A的左子树的子树。如果根元素相同,则开始判断左子树和右子树
function isSubtree(pRoot1,pRoot2){
if (pRoot2 == null) return true;//pRoot2为null,表示子树已经遍历完
if (pRoot1 == null) return false;
if(pRoot1.val==pRoot2.val){
return isSubtree(pRoot1.left,pRoot2.left) && isSubtree(pRoot1.right,pRoot2.right);
}else{
return false;
}
}
function HasSubtree(pRoot1, pRoot2)
{
// write code here
if(pRoot1==null||pRoot2==null) return false;
return isSubtree(pRoot1,pRoot2)||HasSubtree(pRoot1.left,pRoot2)||HasSubtree(pRoot1.right,pRoot2);
}
二叉搜索树最近公共祖先
//思路:
首先确保p的值小于q,若不是,则互换。这样有助于判断root、p、q三者的位置。
三种情况
root最小,说明p和q的最近公共祖先一个在root的右边,使用root.right递归调用
root在中间,说明最近公共祖先只能是root,返回root
root最大,说明p和q的最近公共祖先一个在root的左边,使用root.left递归调用
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
var lowestCommonAncestor = function(root, p, q) {
if (p.val > q.val) [p, q] = [q, p]; // 让p小于q,方便判断
if (root.val >= p.val && root.val <= q.val) {
return root;
} else if (root.val <= p.val && root.val <= q.val) {
return lowestCommonAncestor(root.right, p, q);
} else if (root.val >= p.val && root.val >= q.val) {
return lowestCommonAncestor(root.left, p, q);
}
};
二叉搜索树的后序遍历
//思路
后续遍历我们可以知道,最右边的是根节点r。
2.通过根节点r我们可以判断左子树和右子树。
3.判断左子树中的每个值是否小于r,右子树的每个值是否大于r.
4.对左、右子树递归判断。
function VerifySquenceOfBST(sequence)
{
// write code here
if(sequence.length<=0) return;
return test(sequence,0,sequence.length-1)
}
function test(sequence,start,end){
if(start>=end) return true;
var i=end-1;
while(i>=start && sequence[i]>sequence[end]){
i--;
}
for(var j=i;j>=start;j--){
if(sequence[j]>sequence[end]){
return false;
}
}
return test(sequence,start,i)&&test(sequence,i+1,end-1)
}
二叉搜索树的第k个结点
//思路:二叉搜索树,若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;任意节点的左、右子树也分别为二叉查找树;
所以采用中序遍历的方法,遍历后的结果就是从小到大顺序的结果
function KthNode(pRoot, k)
{
// write code here
var arr=[];
if(pRoot===null||k<1){
return null;
}
function midInorder(root){
if(root.left!==null){
midInorder(root.left);
}
arr.push(root);
if(root.right!==null){
midInorder(root.right);
}
}
midInorder(pRoot);
return arr[k-1];
}
将有序数组转换为二叉搜索树
var sortedArrayToBST = function(nums) {
if (!nums.length) return null;
let mid = Math.floor(nums.length / 2);
let root = {
val: nums[mid],
left: sortedArrayToBST(nums.slice(0, mid)),
right: sortedArrayToBST(nums.slice(mid + 1)),
}
return root;
};
深度优先遍历
该方法是以纵向的维度对dom树进行遍历,从一个dom节点开始,一直遍历其子节点,直到它的所有子节点都被遍历完毕之后在遍历它的兄弟节点。//递归
function deepFirstSearch(node,nodeList) {
if (node) {
nodeList.push(node);
var children = node.children;
for (var i = 0; i < children.length; i++)
//每次递归的时候将 需要遍历的节点 和 节点所存储的数组传下去
deepFirstSearch(children[i],nodeList);
}
return nodeList;
}
//deepFirstSearch接受两个参数,第一个参数是需要遍历的节点,第二个是节点所存储的数组,并且返回遍历完之后的数组,该数组的元素顺序就是遍历顺序,调用方法:
let root = document.getElementById('root')
deepTraversal(root,nodeList=[])
//非递归
function deepFirstSearch(node) {
var nodes = [];
if (node != null) {
var stack = [];
stack.push(node);
while (stack.length != 0) {
var item = stack.pop();
nodes.push(item);
var children = item.children;
for (var i = children.length - 1; i >= 0; i--)
stack.push(children[i]);
}
}
return nodes;
}
广度优先遍历
该方法是以横向的维度对dom树进行遍历,从该节点的第一个子节点开始,遍历其所有的兄弟节点,再遍历第一个节点的子节点,完成该遍历之后,暂时不深入,开始遍历其兄弟节点的子节点。
//非递归版本
function breadthFirstSearch(node) {
var nodes = [];
if (node != null) {
var queue = [];
queue.unshift(node);
while (queue.length != 0) {
var item = queue.shift();
nodes.push(item);
var children = item.children;
for (var i = 0; i < children.length; i++)
queue.push(children[i]);
}
}
return nodes;
}