定义
二叉树:一个空树或一个跟节点和两颗分别为左子树、右子树的二叉树组成
就是最多两个子节点的树
特性:
- 二叉树的第i层上,最多有 2^(i - 1)个节点,i >= 1
- 高度为k的二叉树,最多的节点数:2^(k) - 1, k >= 1
- 在任意一颗二叉数中,若叶子节点数为n_0,度为2的节点数是n_2,则:
$n_0 = n_2 + 1$
证明如下:假设度为1的节点数$n_1$,则
$n = n_0 + n_1 + n_2$
在二叉数中,除根节点外,每个节点都存在一根链接父节点的线,假设线的总数是 B,则
$n = B + 1$
这些线也是 度为1 和 度为2的节点链接子节点的线,那么
$B = n_1 + 2 * n_2 $带入上面
$n = n_1 + 2 * n_2 + 1$
$n_0 + n_1 + n_2 = n_1 + 2 * n_2 + 1$
得出
$n_0 = n_2 + 1$
二叉树的遍历
- 广度遍历,从上往下,从左到右
- 深度遍历,深度遍历分为以下三种
- 前序遍历:根左右
- 中序遍历:左根右
- 后序遍历:左右根
其中深度优先遍历指的的前序遍历
递归法实现三种深度遍历
// 前序遍历 根左右,递归法
const preorder = (root) => {
if (root) {
console.log('前序遍历:', root.val)
preorder(root.left)
preorder(root.right)
}
}
// 中序遍历 左根右,递归法
const indorder = (root) => {
if (root) {
indorder(root.left)
console.log('中序遍历:', root.val)
indorder(root.right)
}
}
// 后序遍历 左右根,递归法
const postorder = (root) => {
if (root) {
postorder(root.left)
postorder(root.right)
console.log('后序遍历:', root.val)
}
}
非递归法实现三种深度遍历
// 前序遍历 根左右,非递归法
/**
* 思路:
* 1. 首先将root放到stack中
* 2. 循环直至stack空
* 3. stack pop出顶部节点,然后将期右节点,左节点push
* 因为是 根左右,所以push的时候先push右再push左
*/
const preorder = (root) => {
if (!root) {
return
}
const stack = [root]
while(stack.length > 0) {
const node = stack.pop()
console.log('前序遍历:', node.val)
if (node.right) {
stack.push(node.right)
}
if (node.left) {
stack.push(node.left)
}
}
}
// 中序遍历 左根右,递归法
/**
* 思路:
* 利用栈stack 和 node 两个进行控制
* 1. node = root
* 2. 循环直至stack为空并且 node也为kong
* 3. 通过node去走左边,直至左边为空,那么栈中保存的就是左节点
* 3. stack pop 一个节点,然后访问一下,然后node = node.right
* (访问到节点node的时候,表示node的左子树已经完成遍历,然后遍历右子树即可)
*/
const indorder = (root) => {
const stack = []
let node = root
while(stack.length > 0 || node) {
// 遍历左边
if (node) {
stack.push(node)
node = node.left
} else {
// 左边没有了,访问一下,然后遍历右边
node = stack.pop()
console.log('中序遍历:', node.val)
node = node.right
}
}
}
// 后序遍历 左右根,递归法
/**
* 思路:
* 后序遍历比中序遍历再复杂一点,因为需要左边访问之后再访问右边最后访问根,跟节点存在2此入栈,需要记录一下方向
* 入栈的数据结构记录为: { node: 节点, direction: 访问方向 }
* 1. pos 指针,用来访问 pos = root
* 2. 直至stack空,并且pos为空停止循环
* 3. 一直访问左子树,并入栈
*/
const postorder = (root) => {
const stack = []
let pos = root
while(stack.length > 0 || pos) {
// 一直访问左子树
if (pos) {
stack.push({ node: pos, direction: 'left' })
pos = pos.left
} else {
// 左边访问完成,pop一下栈顶元素
const { node, direction } = stack.pop()
// 如果栈顶元素表示 direction === 'right'表示已经访问过右子树了,直接访问节点
if (direction === 'right') {
console.log('后序遍历:', node.val)
// 注意,需要把pos置为空,进行下一个循环
pos = null
} else {
// 访问右子树
pos = node.right
// 再次入栈,然后 方向设置为 right
stack.push({ node, direction: 'right' })
}
}
}
}
广度遍历
const bfs = (root) => {
const queue = [root]
while(queue.length > 0) {
const node = queue.shift()
console.log('深度遍历:', node.val)
if (node.left) {
queue.push(node.left)
}
if (node.right) {
queue.push(node.right)
}
}
}
根据前序遍历和中序遍历可以确定一颗二叉树
const createTree = (preorder, inorder) => {
if (preorder.length <= 0) {
return null
}
const root = { val: preorder[0], left: null, right: null }
let i = 0
for (; i < inorder.length; i++) {
if (inorder[i] === preorder[0]) {
break
}
}
root.left = createTree(preorder.slice(1, i + 1), inorder.slice(0, i))
root.right = createTree(preorder.slice(i + 1), inorder.slice(i + 1))
return root
}