二叉树的遍历
关于二叉树的各种(递归,迭代,morris)遍历,前序,中序,遍历,请看我的前一篇的文章。(juejin.cn/post/703486… )
判断是否为搜索二叉树
搜索二叉树是一种特殊有序的二叉树。满足下面的条件即为搜索二叉树:
- 它的根节点左子树不为空,且它左子树上面的所有节点的值都小于它的根节点的值。
- 它的右子树不为空,那么它右子树任意节点的值都大于他的根节点的值,
- 它的左右子树也是二叉搜索树。 如果对搜索二叉树中序遍历,则可以得到一个有序的(从小到大)的序列。如图:
递归版本
递归思路:
- 调用函数,参数传入上界和下届为系统最大值和最小值
- 如果节点为空,判断为true,即为搜索二叉树
- 判断当前节点是否都在上下届 (min, max) 的范围内
- 递归判断节点的左树和右树是否为搜索二叉树,调用子函数的时候,根据当前节点的值作为边界值传给子函数(判断节点左树的函数中传入tree.val作为子函数的上界,判断节点右树的函数中传入tree.val作为子函数的下界)
function isSBT (tree, min, max) {
if (!tree) {
return true
}
return isBST(tree.left, min, tree.val) && isBST(tree.right, tree.val, max) && (max > tree.val) && (min < tree.val)
}
isBST(tree, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)
迭代版本
迭代版本实现基础基于上面二叉树的特点:如果对搜索二叉树中序遍历,则可以得到一个有序的(从小到大)的序列。 思路:
- 定义一个val,为系统最小值
- morris中序遍历,每次遍历到一个节点,判断当前节点值是否 > val,如果为false,则该二叉树不为搜索二叉树。如为true,更新val值,进如下一次循环
- 遍历结束,返回true
function isSBT2 (tree) {
let val = Number.MIN_SAFE_INTEGER
let cur = tree
function getMostRight (tree) {
let cur = tree
cur = cur.left
while (cur && cur.right && cur.right !== tree) {
cur = cur.right
}
return cur
}
let mostRight = null
while (cur) {
if (cur.left) {
mostRight = getMostRight(cur)
if (mostRight.right !== cur) {
mostRight.right = cur
cur = cur.left
continue
}
if (val > cur.val) {
return false
}
val = cur.val
mostRight.right = null
} else {
if (val > cur.val) {
return false
}
val = cur.val
}
cur = cur.right
}
return true
}
判断为完全二叉树
完全二叉树:非最后一层的节点都有左右子节点,在最后一层,并不是所有节点都有两个子节点,这类二叉树又称为完全二叉树(Complete Binary Tree),入下图:
在我们之前实现的堆结构中,就是用到了完全二叉树( juejin.cn/post/703145… )
完全二叉树的判断,用层序遍历判断:
迭代版本
思路:
- 定义一个变量 isLeaf 表示是否遍历到了叶节点
- 层序遍历每个节点,当遇到节点有右子节点,没有左子节点,直接返回false,该树不为完全二叉树
- 层序遍历每个节点,当遇到节点有左子节点,没有右子节点,isLeaf置为true
- isLeaf = true 后,每个遍历到的节点必须是叶节点(无左右节点)
function isCBT (tree) {
let queue = [tree]
let isLeaf = false
while (queue.length) {
tree = queue.shift()
if (tree.right && !tree.left) {
return false
}
if (isLeaf && (tree.left || tree.right)) {
return false
}
if (tree.left && tree.right) {
queue.push(tree.left)
queue.push(tree.right)
continue
}
tree.left && queue.push(tree.left)
!isLeaf && (isLeaf = true)
}
return true
}
判断为满二叉树
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
判断是否满二叉树,可以通过求得二叉树的高度h和节点数nums,满足nums = (2^k) -1即可。
递归版本
思路:
- 递归函数返回结构,{nums: Number, height: Number},表示当前树的节点数和高度
- 如果tree为空,返回{nums: 0, height: 1}
- 分别求解左右子树
- 组合当前节点的信息并返回
- 根据nums = (2^k) -1 判断是否为满二叉树
function isFBT (tree) {
function process (tree) {
if (!tree) {
return {nums: 0, height: 1}
}
const left = process(tree.left)
const right = process(tree.right)
return {
nums: left.nums + right.nums + 1,
height: left.height + 1,
}
}
const res = process(tree)
return res.nums === (Math.pow(2, res.height - 1) - 1)
}
迭代版本
思路:
- morris前序遍历获取树的节点树(每遍历到一个节点,nums++)
- morris前序遍历,会先遍历树的左边界,可以得到树的高度k
- 根据nums = (2^k) -1 判断是否为满二叉树
function isFBT (tree) {
let height = 1
let cur = tree
let flag = false
let nodeNums = 0
let mostRight = null
function getMostRight (tree) {
let cur = tree.left
while (cur && cur.right && cur.right !== tree) {
cur = cur.right
}
return cur
}
while (tree) {
if (tree.left) {
!flag && height++
mostRight = getMostRight(tree)
if (mostRight.right === tree) {
mostRight.right = null
tree = tree.right
} else {
nodeNums++
mostRight.right = tree
tree = tree.left
}
} else {
flag = true
nodeNums++
tree = tree.right
}
}
return nodeNums === (Math.pow(2, height) - 1)
}
判断平衡搜索二叉树
递归版本
function isBST(tree, min, max) {
if (!tree) {
return {
isBST: true,
height: 0
}
}
const left = isBST(tree.left, min, tree.val)
const right = isBST(tree.right, tree.val, max)
return {
isBST: left.isBST && right.isBST && (max > tree.val) && (min < tree.val) && (Math.abs(left.height - right.height) <= 1),
height: Math.max(left.height, right.height) + 1
}
}
二叉树leecode题目
上面判断搜索二叉树,满二叉树,平衡搜索二叉树的递归版本都是二叉树dp的实现。
顾名思义,树型动态规划就是在“树”的数据结构上的动态规划,平时作的动态规划都是线性的或者是建立在图上的,线性的动态规划有二种方向既向前和向后,相应的线性的动态规划有二种方法既顺推与逆推,而树型动态规划是建立在树上的,所以也相应的有二个方向:
1、叶->根:在回溯的时候从叶子节点往上更新信息
2、根 - >叶:往往是在从叶往根dfs一遍之后(相当于预处理),再重新往下获取最后的答案。
不管是 从叶->根 还是 从 根 - >叶,两者都是根据需要采用,没有好坏高低之分。树形DP简单理解,先确定递归函数的返回值结构,然后递归过程中,获取递归子函数(递归求左右子节点)的值,合并子函数返回结果加上对当前树的判断返回一个新的结果。 下面这道《 二叉树的最近公共祖先》,也可以用树形dp的思路求解。
leetcode 二叉树的最近公共祖先
var lowestCommonAncestor = function(root, p, q) {
if (!root) {
return null
}
if (root === p || root === q) {
return root
}
const left = lowestCommonAncestor(root.left, p, q)
const right = lowestCommonAncestor(root.right, p, q)
if (left && right) {
return left === right ? left : root
}
return left || right
};
剑指 Offer II 053. 二叉搜索树中的中序后继
思路: 分两种情况讨论:
- p节点有右节点,那么p的后继节点很快的可以求出(右树下的最左子节点)
- p节点没有右节点,只能从根节点开始遍历(遍历终止条件是遍历到当前p节点) 根据搜索二叉树的特点(所有的右树节点 > 当前节点,所有左树节点 < 当前节点)可以不断向下遍历,用一个变量保存 > P的最小节点即为答案。
var inorderSuccessor = function(root, p) {
let cur = p.right
if (cur) {
while (cur && cur.left) {
cur = cur.left
}
return cur
}
let child = root
let inorderNode = null
while (child !== p) {
if (child.val > p.val) {
inorderNode = !inorderNode || child.val < inorderNode.val ? child : inorderNode
}
child = p.val > child.val ? child.right : child.left
}
return inorderNode
};