二叉树
一、二叉树的遍历
二叉树的遍历(搜索)方式主要有两种,一种是深度优先搜索(DFS)、另一种是广度优先搜索(BFS)。深度优先搜索又分为三种遍历方式:前序遍历、中序遍历、后序遍历。
1. 前序遍历(DFS)
- 递归
public static void preOrderTraveral(Node rootNode) {
if (rootNode == null) {
return;
}
System.out.println("Node:" + rootNode.value);
preOrderTraveral(rootNode.leftNode);
preOrderTraveral(rootNode.rightNode);
}
- 非递归
// 方法一
public static void preOrderTraveralWithStack(Node rootNode) {
Stack<Node> stack = new Stack<>();
stack.push(rootNode);
while (!stack.isEmpty()) {
Node curNode = stack.pop();
System.out.println("Node:" + curNode.value);
if (curNode.rightNode != null) {
stack.push(curNode.rightNode);
}
if (curNode.leftNode != null) {
stack.push(curNode.leftNode);
}
}
}
// 方法二
public static void preOrderTraveralWithStack(Node rootNode) {
Stack<Node> stack = new Stack<>();
Node curNode = rootNode;
while (curNode != null || !stack.isEmpty()) {
while (curNode != null) {
System.out.println("Node:" + curNode.value);
stack.push(curNode);
curNode = curNode.leftNode;
}
if (!stack.isEmpty()) {
Node node = stack.pop();
curNode = node.rightNode;
}
}
}
2. 中序遍历(DFS)
- 递归
public static void inOrderTraveral(Node rootNode) {
if (rootNode == null) {
return;
}
inOrderTraveral(rootNode.leftNode);
System.out.println("Node:" + rootNode.value);
inOrderTraveral(rootNode.rightNode);
}
- 非递归
public static void inOrderTraveralWithStack(Node rootNode) {
Stack<Node> stack = new Stack<>();
Node curNode = rootNode;
while (curNode != null || !stack.isEmpty()) {
while (curNode != null) {
stack.push(curNode);
curNode = curNode.leftNode;
}
if (!stack.isEmpty()) {
Node node = stack.pop();
System.out.println("Node:" + node.value);
curNode = node.rightNode;
}
}
}
3. 后序遍历(DFS)
- 递归
public static void postOrderTraveral(Node rootNode) {
if (rootNode == null) {
return;
}
postOrderTraveral(rootNode.leftNode);
postOrderTraveral(rootNode.rightNode);
System.out.println("Node:" + rootNode.value);
}
- 非递归
二叉树从左往右的后序遍历,可以理解为是从右往左的前序遍历
。
public static void postOrderTraveralWithStack(Node rootNode) {
Stack<Node> stack1 = new Stack<>();
Stack<Node> stack2 = new Stack<>();
Node curNode = rootNode;
while (curNode != null || !stack1.isEmpty()) {
while (curNode != null) {
stack1.push(curNode);
stack2.push(curNode);
curNode = curNode.rightNode;
}
if (!stack1.isEmpty()) {
Node popNode = stack1.pop();
curNode = popNode.leftNode;
}
}
while (!stack2.isEmpty()) {
System.out.println("Node:" + stack2.pop().value);
}
}
4. 层序遍历(BFS)
广度优先搜索利用队列来实现层序遍历。
public static void BFSTraveral(Node rootNode) {
LinkedList<Node> queue = new LinkedList<>();
queue.offer(rootNode);
while (!queue.isEmpty()) {
Node popNode = queue.poll();
System.out.println("Node:" + popNode.value);
if (popNode.leftNode != null) {
queue.offer(popNode.leftNode);
}
if (popNode.rightNode != null) {
queue.offer(popNode.rightNode);
}
}
}
二、二叉树的构建
如构建一个如下结构的二叉树。
前序遍历: [4, 2, 1, 3, 6, 5, 7]
中序遍历: [1, 2, 3, 4, 5, 6, 7]
后序遍历: [1, 3, 2, 5, 7, 6. 4]
各个数组的特点:
- 前序遍历数组:数组的首个元素即为当前的Node节点。
- 中序遍历数组:用于计算出当前Node节点的左右子树个数。
- 后序遍历数组:数组的最后一个元素即为当前的Node节点。
1. 已知前序遍历和中序遍历顺序
实现原理:
根据前序遍历数组的顺序,以及中序数组计算出的左右子树的个数来构建二叉树。因为构建二叉树的过程是先构建根节点,再构建左右节点,这与二叉树前序遍历递归写法的思想是一致的。
以上面的二叉树为例来推到实现步骤:
- 因为前序遍历数组的首个元素4为根节点,所以先构建根节点Node。
- 计算4在中序遍历数组的下标位置。此处下标为3,所以跟节点4左侧的子树节点个数为3个 (即前序遍历对应左子树的值为 [2, 1, 3] );右侧子树节点个数为3 (即前序遍历对应右子树的值为 [6, 5, 7] )。
- 计算根节点4的左节点:利用分治的思想进行递归。我们可以理解为根节点4的左节点2为其自身左右节点(节点1和节点3)的父节点。
- 相当于求子数组对应的二叉树。子数组前序遍历:[2, 1, 3];子数组中序遍历:[1, 2, 3]。
- 计算根节点4的右节点:利用分治的思想进行递归。我们可以理解为根节点4的右节点6为其自身左右节点(节点5和节点7)的父节点。
- 返回根节点Node。
public static Node buildTree(int[] preOrder, int[] inOrder) {
int len = preOrder.length;
// 利用Map来记录中序遍历数组中每个元素的位置。
HashMap indexMap = new HashMap<Integer, Integer>();
for (int i = 0; i < len; i++) {
indexMap.put(inOrder[i], i);
}
return buildTree(preOrder, inOrder, 0, len - 1, 0, len - 1, indexMap);
}
public static Node buildTree(int[] preOrder, int[] inOrder,
int preOrderLeft, int preOrderRight,
int inOrderLeft, int inOrderRight,
HashMap<Integer, Integer> indexMap) {
// 退出条件
if (preOrderLeft > preOrderRight) {
return null;
}
// 1.先把根节点建立出来。
Node root = new Node(preOrder[preOrderLeft]);
int inOrderRoot = indexMap.get(root.value);
// 2.得到子树中的节点数目。
int subtreeSize = inOrderRoot - inOrderLeft;
// 3.递归地构造左子树,并连接到根节点。
// 先序遍历中「从 左边界+1 开始的 subtreeSize」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素。
root.leftNode = buildTree(preOrder, inOrder,
preOrderLeft + 1, preOrderLeft + subtreeSize,
inOrderLeft, inOrderRoot - 1, indexMap);
// 4.递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素。
root.rightNode = buildTree(preOrder, inOrder,
preOrderLeft + subtreeSize + 1, preOrderRight,
inOrderRoot + 1, inOrderRight, indexMap);
return root;
}
2. 已知中序遍历和后序遍历顺序
实现原理:
根据后序遍历数组的顺序,以及中序数组计算出的左右子树的个数来构建二叉树。这与利用前序和中序来构建二叉树的方法类似,不同点在于后续遍历数组中根节点的位置在数组最后一个(而不是数组第一个元素)。
以上面的二叉树为例来推到实现步骤:
- 因为后序遍历数组的最后一个元素4为根节点,所以先构建根节点Node。
- 计算4在中序遍历数组的下标位置。此处下标为3,所以跟节点4左侧的子树节点个数为3个 (即后序遍历对应左子树的值为 [1, 3, 2] );右侧子树节点个数为3 (即后序遍历对应右子树的值为 [5, 7, 6] )。
- 计算根节点4的左节点:利用分治的思想进行递归。我们可以理解为根节点4的左节点2为其自身左右节点(节点1和节点3)的父节点。
- 相当于求子数组对应的二叉树。子数组前序遍历:[1, 3, 2];子数组中序遍历:[1, 2, 3]。
- 计算根节点4的右节点:利用分治的思想进行递归。我们可以理解为根节点4的右节点6为其自身左右节点(节点5和节点7)的父节点。
- 返回根节点Node。
public static Node buildTree(int[] postOrder, int[] inOrder) {
int len = postOrder.length;
// 利用Map来记录中序遍历数组中每个元素的位置。
HashMap indexMap = new HashMap<Integer, Integer>();
for (int i = 0; i < len; i++) {
indexMap.put(inOrder[i], i);
}
return buildTree(postOrder, inOrder, 0, len - 1, 0, len - 1, indexMap);
}
/**
* postOrder:后序遍历
* inOrder:中序遍历
*/
public static Node buildTree(int[] postOrder, int[] inOrder,
int postOrderLeft, int postOrderRight,
int inOrderLeft, int inOrderRight,
HashMap<Integer, Integer> indexMap) {
// 退出条件
if (postOrderLeft > postOrderRight) {
return null;
}
// 1.先把根节点建立出来。
Node root = new Node(preOrder[postOrderRight]);
int inOrderRoot = indexMap.get(root.value);
// 2.得到子树中的节点数目。
int subtreeSize = inOrderRoot - inOrderLeft;
// 3.递归地构造左子树,并连接到根节点。
// 后序遍历中「从 左边界 开始的 subtreeSize」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素。
root.leftNode = buildTree(postOrder, inOrder,
postOrderLeft, (postOrderLeft + subtreeSize - 1),
inOrderLeft, inOrderRoot - 1, indexMap);
// 4.递归地构造右子树,并连接到根节点
// 后序遍历中「从 左边界+左子树节点数目 开始到 右边界-1」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素。
root.rightNode = buildTree(postOrder, inOrder,
postOrderLeft + subtreeSize, postOrderRight - 1,
inOrderRoot + 1, inOrderRight, indexMap);
return root;
}
3. 已知前序遍历和后序遍历顺序
无法构建二叉树,因为无法确定左右子树的个数。