深度优先搜索算法实现整理
深度优先搜索(DFS-Depth First Search)是很基础的二叉树搜索算法。很多二叉树的题型,都是基于此演变而来。
在这里整理一下三种遍历方式的递归与非递归实现,方便收藏。
三种遍历
-
前序遍历:按照父节点、左节点、右节点的顺序遍历
-
中序遍历:按照左节点、父节点、右节点的顺序遍历
-
后序遍历:按照左节点、右节点、父节点的顺序遍历
所谓的前中后,实际上是父节点在遍历顺序中出现的位置是在前中后来区分的。
递归实现
这三种遍历,用递归都会非常简单。无非是根据前序、中序、后序将递归方法放到不同的位置。
/**
* 递归遍历
*
* @param root
*/
public void recursiveTraversal(TreeNode root) {
// 前序
List<Integer> preorderList = new ArrayList<>();
preorderTraversal(root, preorderList);
// 中序
List<Integer> inorderList = new ArrayList<>();
inorderTraversal(root, inorderList);
// 后序
List<Integer> postorderList = new ArrayList<>();
postorderTraversal(root, postorderList);
}
/**
* 前序优先遍历(递归)
*
* @param root
* @param list
*/
public void preorderTraversal(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
list.add(root.val);
preorderTraversal(root.left, list);
preorderTraversal(root.right, list);
}
/**
* 中序优先遍历(递归)
*
* @param root
* @param list
*/
public void inorderTraversal(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
inorderTraversal(root.left, list);
list.add(root.val);
inorderTraversal(root.right, list);
}
/**
* 后序优先遍历(递归)
*
* @param root
* @param list
*/
public void postorderTraversal(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
postorderTraversal(root.left, list);
postorderTraversal(root.right, list);
list.add(root.val);
}
非递归实现
前序遍历
前序遍历利用栈结构来实现。因为前序遍历的顺序是中左右,所以在压入栈的时候,需要记录到数组中,之后再继续将做节点压入栈。
/**
* 前序遍历(非递归)
* 使用栈,先记录当前节点,之后入栈。出栈的时候不记录。
*
* @param root
*/
public List<Integer> preorderTraversal2(TreeNode root) {
List<Integer> inorderList = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode current = root;
while (current != null || !stack.isEmpty()) {
// 当前指针的节点不为空,则入栈
while (current != null) {
// 先记录当前节点父节点
inorderList.add(current.val);
// 当前节点入栈
stack.push(current);
// 因为是前序遍历,顺序为父节点、左子节点、右子节点,因此指针移动到左节点
current = current.left;
}
// 当前节点为空,则说明本侧子树遍历到底了,需要将父节点出栈
current = stack.pop();
// 将右节点入栈
current = current.right;
}
return inorderList;
}
中序遍历
中序遍历利用栈结构来实现。因为中序遍历的顺序是左中右,所以要一直把左节点压入栈,之后出栈的时候再记录到数组中。这里和前序遍历是不一样的。
/**
* 中序优先遍历(非递归)
* 使用栈,一直先遍历左节点,左节点到头后,出栈记录父节点,然后再从右节点开始继续遍历右节点的左子节点
*
* @param root
*/
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> inorderList = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode current = root;
while (current != null || !stack.isEmpty()) {
// 当前指针的节点不为空,则入栈
while (current != null) {
stack.push(current);
// 一直将左指针压入栈
current = current.left;
}
// 当前节点为空,则说明本侧子树遍历到底了,需要将父节点出栈
current = stack.pop();
// 记录父节点
inorderList.add(current.val);
// 将右节点入栈
current = current.right;
}
return inorderList;
}
后序遍历
也是使用栈,但是要稍微复杂一些。因为你在记录当前节点时,需要考虑,当前节点的右节点是否已经记录过。
/**
* 后序优先遍历(非递归)
* 也是使用栈
*
* @param root
* @return
*/
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> postorderList = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode current = root;
// 后续在判断当前节点是否有右子节点时,需要用一个指针辅助判断是否已经记录过他的右子节点
TreeNode pre = null;
while (current != null || !stack.isEmpty()) {
while (current != null) {
// 持续将左节点入栈
stack.push(current);
current = current.left;
}
// 左节点到头,再看是否有右节点。同时要保证右节点没有记录过
current = stack.peek();
if (current.right != null && current.right != pre) {
current = current.right;
} else {
// 如果没有右节点,则记录当前节点
postorderList.add(current.val);
// 出栈
pre = stack.pop();
// 这里置为空,可以在下次循环时,不进入子循环
current = null;
}
}
return postorderList;
}