前言
二叉树主要有两种遍历方式:深度遍历和广度遍历,其中深度优先遍历分为:
- 前序遍历
- 中序遍历
- 后序遍历 每种遍历又有递归法和迭代法两种,而这里的前、中、后其实指的是中间节点的遍历顺序,例如:
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中 对照图片如果我们能顺利整理出正确的读取顺序,那么就算理解了
深度遍历我们一般采取递归的方式解决,但是其实栈就是递归的一种实现结构,所以深度优先遍历我们也可以借助栈而非递归的方式来实现。
另外二叉树的定义一般如下
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
递归遍历
递归需要掌握三个必要条件
- 确定递归函数的输入和输出值:确定哪些值是要在递归中处理的,以及递归函数需要返回哪些值
- 确定递归的终止条件:递归要有终止递归的条件,也就是在逻辑中要定义满足什么条件后终止循环调用自身,不然无限制调用下去会内存栈溢出
- 确定递归的单层逻辑:确定每一层处理的过程也就是递归需要重复调用的逻辑
前序遍历
- 首先确定一下递归函数的参数以及返回值:首先要知道递归的节点值,另外需要一个数组用来储存递归的值,如果传入了数组用来储存递归的值,那么返回值的话就并不是那么重要了,可以选择把数组返回出来
/**
* @param {TreeNode} root
* @param {number[]} array
*
*/
function traversal(root, array)
- 要确定终止条件:什么时候终止当前递归,那么就是当当前的root节点是空节点的时候,那么就不需要继续往节点内塞值,就可以终止当前递归了
if(root === null) return array;
- 确定当前递归的逻辑:前序遍历是中左右循环,所以在单层递归逻辑,要先取中节点数值,之后再递归左节点然后再递归右节点
array.push(root.val)
traversal(root.left, array)
traversal(root.right, array)
单层递归逻辑按照中左右,这样前序遍历就完成了
完整代码如下:
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function (root) {
let array = []
return traversal(root, array)
};
/**
* @param {TreeNode} root
* @param {number[]} array
* @return {number[]}
*/
function traversal(root, array){
if(root === null) return array;
array.push(root.val)
traversal(root.left, array)
traversal(root.right, array)
return array
}
以此类推
中序遍历
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function (root) {
let array = []
return traversal(root, array)
};
/**
* @param {TreeNode} root
* @param {number[]} array
* @return {number[]}
*/
function traversal(root, array){
if(root === null) return array;
traversal(root.left, array)
array.push(root.val)
traversal(root.right, array)
return array
}
后序遍历
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function (root) {
let array = []
return traversal(root, array)
};
/**
* @param {TreeNode} root
* @param {number[]} array
* @return {number[]}
*/
function traversal(root, array){
if(root === null) return array;
traversal(root.left, array)
traversal(root.right, array)
array.push(root.val)
return array
}
迭代遍历
使用迭代的方法对二叉树进行深度遍历,和递归法会有所不同,但是同样会用到栈操作
前序遍历
我们知道前序遍历的顺序是中左右,所以我们第一步是取出来栈顶的节点取出值,然后再放进去节点的右子节点,最后再放节点的左子节点,先放右子节点再放左子节点的原因是因为,栈是先进后出,所以先放右再放左
代码如下
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function (root) {
let stack = []
let result = []
if (!root) return result
stack.push(root)
while (stack.length > 0) {
const top = stack.pop()
result.push(top.val)
if (top.right) stack.push(top.right)
if (top.left) stack.push(top.left)
}
return result
};
中序遍历
使用迭代法中序遍历的方式和前序遍历的方式并不一致,本质原因是因为迭代先访问的节点是中间节点,而前序遍历先操作的也是中间节点,所以前序遍历的迭代法,访问和操作的顺序是一致的,为什么中序遍历和前序遍历不一致呢?
中序操作迭代先访问的也是中间节点,但是中序操作需要先处理的是左子节点,所以就会造成访问和处理的数据不一致,这个时候我们可以用一个指针用来标记节点的左节点,当指针指向null的时候就证明到达最底部的左节点了,就可以开始向栈弹出,之后再将指针指向弹出的节点的右节点,再循环指向左子节点,最后就可以按照中序遍历的顺序遍历所有节点
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function (root) {
let stack = []
let array = []
let cur = root
while (cur || stack.length > 0) {
if (cur !== null) {
stack.push(cur)
cur = cur.left
} else {
const node = stack.pop()
array.push(node.val)
cur = node.right
}
}
return array
};
后序遍历
后序遍历和前序遍历类似,只需要针对前序遍历的顺序稍加修改就可以结局,把前序遍历的中左右顺序改成中右左顺序,之后再整体翻转就可得出后序遍历的顺序
- 前序遍历:中->左->右 ——》 中->右->左 ——》 左->右->中
- 后序遍历:左->右->中
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function (root) {
let stack = []
let array = []
if (!root) return array
stack.push(root)
while (stack.length > 0) {
const node = stack.pop()
array.push(node.val)
if (node.left) stack.push(node.left)
if (node.right) stack.push(node.right)
}
return array.reverse()
};
统一迭代法
如上迭代法,前序遍历、中序遍历、后序遍历是三种不同的思路,那么如何用同一种思路解决上面的三种遍历呢,其实是有的,我们可以在遍历的过程中,在要取出来的节点前加上一个null的标记,这样当我们读到标记的节点时,就知道需要把后一个节点的数值取出来了
前序遍历
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function (root) {
let stack = []
let result = []
if (!root) return result
stack.push(root)
while (stack.length > 0) {
const node = stack.pop()
if (node !== null) {
if (node.right) stack.push(node.right)
if (node.left) stack.push(node.left)
stack.push(node)
stack.push(null)
} else {
const st = stack.pop()
result.push(st.val)
}
}
return result
};
中序遍历
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function (root) {
let stack = []
let result = []
if (!root) return result
stack.push(root)
while (stack.length > 0) {
const node = stack.pop()
if (node !== null) {
if (node.right) stack.push(node.right)
stack.push(node)
stack.push(null)
if (node.left) stack.push(node.left)
} else {
const st = stack.pop()
result.push(st.val)
}
}
return result
};
后序遍历
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function (root) {
let stack = []
let result = []
if (!root) return result
stack.push(root)
while (stack.length > 0) {
const node = stack.pop()
if (node !== null) {
stack.push(node)
stack.push(null)
if (node.right) stack.push(node.right)
if (node.left) stack.push(node.left)
} else {
const st = stack.pop()
result.push(st.val)
}
}
return result
};