目录
一.概念
二叉树是一种树状结构,其中每个节点最多有两个子节点,被称为左子节点和右子节点。二叉树通常具有以下特点:
-
根节点:二叉树的最顶层节点被称为根节点。它没有父节点,是整个树的起点。
-
子节点:每个节点最多有两个子节点,分别称为左子节点和右子节点。左子节点在树结构中位于父节点的左侧,右子节点在右侧。
-
叶节点:没有子节点的节点被称为叶节点,也被称为终端节点。叶节点位于树的最底层。
-
父节点:每个节点的上一层节点被称为父节点。每个节点除了根节点都有一个父节点。
-
兄弟节点:拥有相同父节点的节点被称为兄弟节点。
-
深度:节点的深度是指从根节点到该节点的路径上的节点数。
-
高度:节点的高度是指从该节点到树的最底层叶节点的最长路径。
-
子树:节点及其子节点以及与之相关的边所构成的树称为子树。
二叉树在计算机科学中具有广泛的应用,例如在搜索树和排序算法中的使用。因为二叉树具有简单的结构和快速的搜索能力,所以它被广泛应用于许多领域
二.构建二叉树节点类TreeNode
package 树.二叉树;
/**
* 普通二叉树
*/
public class TreeNode {
int value;
TreeNode left;
TreeNode right;
public TreeNode(int value,TreeNode left,TreeNode right){
this.value = value;
this.left = left;
this.right = right;
}
@Override
public String toString() {
return "TreeNode{" +
"value=" + value +
", left=" + left +
", right=" + right +
'}';
}
}
三.二叉树的遍历
1.前序遍历preOrder
/**
* 前序遍历
*
* @param treeNode
*/
static void preOrder(TreeNode treeNode) {
if (treeNode == null) {
return;
}
System.out.print(treeNode.value + "\t");
preOrder(treeNode.left); //左
preOrder(treeNode.right); //右
}
2.中序遍历medOrder
/**
* 中序遍历
*
* @param treeNode
*/
static void medOrder(TreeNode treeNode) {
if (treeNode == null) {
return;
}
medOrder(treeNode.left); //左
System.out.print(treeNode.value + "\t");
medOrder(treeNode.right); //右
}
3.后序遍历postOrder
/**
* 后序遍历
*
* @param treeNode
*/
static void postOrder(TreeNode treeNode) {
if (treeNode == null) {
return;
}
postOrder(treeNode.left); //左
postOrder(treeNode.right); //右
System.out.print(treeNode.value + "\t");
}
分别打印一下:
public static void main(String[] args) {
/**
* 1
* / \
* 2 3
* / / \
* 6 9 8
*/
TreeNode root = new TreeNode(
1,
new TreeNode(2, new TreeNode(6, null, null), null),
new TreeNode(3, new TreeNode(9, null, null), new TreeNode(8, null, null))
);
System.out.println("前序遍历");
preOrder(root);
System.out.println();
System.out.println("中序遍历");
medOrder(root);
System.out.println();
System.out.println("后序遍历");
postOrder(root);
}
运行:
前序遍历
1 2 6 3 9 8
中序遍历
6 2 1 9 3 8
后序遍历
6 2 9 8 3 1
进程已结束,退出代码0
4.非递归遍历
我们可以使用栈(stack)来实现二叉树的遍历,栈可以模拟递归
/**
* 通过迭代实现三种遍历
*/
@Test
public void testAllOrder(){
/**
* 1
* / \
* 2 3
* / / \
* 6 9 8
*/
TreeNode root = new TreeNode(
1,
new TreeNode(2, new TreeNode(6, null, null), null),
new TreeNode(3, new TreeNode(9, null, null), new TreeNode(8, null, null))
);
//创建栈
LinkedListStack<TreeNode> stack = new LinkedListStack<>(10);
//定义指针变量,初始指向root节点
TreeNode node = root;
TreeNode pop = null;
//遍历,只要node不为空,并且栈中有元素
while (node != null || !stack.isEmpty()) {
//先找左孩子
if (node != null) {
//压入栈
stack.push(node);
System.out.println("前序:"+node.value);
//指向左孩子
node = node.left;
} else {
//弹出元素
TreeNode peek = stack.peek();
// 没有右子树
if (peek.right == null ) {
System.out.println("中序遍历:"+peek.value);
//弹出
pop = stack.pop();
System.err.println("后序:"+pop.value);
}
//右子树处理完成
else if ( peek.right == pop) {
pop = stack.pop();
System.err.println("后序:"+pop.value);
}
//待处理右子树
else {
System.out.println("中序遍历:"+peek.value);
//指向栈顶元素的右孩子
node = peek.right;
}
}
}
}
测试一下:
前序:1
前序:2
前序:6
后序:6
中序遍历:6
后序:2
中序遍历:2
后序:9
中序遍历:1
后序:8
前序:3
前序:9
中序遍历:9
后序:3
中序遍历:3
后序:1
前序:8
中序遍历:8
三.深度
1.概念
二叉树的深度是指从根节点到最远叶子节点的路径上的节点个数。可以通过递归或迭代的方式来计算二叉树的深度。
递归方法:
- 如果二叉树为空,返回深度为 0。
- 否则,分别计算左子树和右子树的深度。
- 树的深度为左子树深度和右子树深度中的较大值加 1。
迭代方法(层次遍历):
- 如果根节点为空,返回深度为 0。
- 创建一个队列,并将根节点入队。
- 初始化深度为 0。
- 循环执行以下步骤直到队列为空:
- 获取当前层的节点个数,记为 count。
- 将当前层的节点依次出队,并将它们的左子节点和右子节点依次入队。
- 将 count 减去当前层节点个数,如果 count 大于 0,则深度加 1。
- 返回深度的值。
无论使用递归还是迭代的方式,都可以得到二叉树的深度。
2.递归求最大深度
/**
* 递归调研求最大深度
* @param node
* @return
*/
static int maxDepth(TreeNode node){
if(node == null){
return 0;
}
if(node.left == null && node.right == null){
return 1;
}
int d1 = maxDepth(node.left);
int d2 = maxDepth(node.right);
return Integer.max(d1,d2) + 1;
}
3.层序遍历加队列求最大深度
/**
* 通过层序遍历 加 队列 来实现求最大深度
* 思路:
* 每次将一层的节点加入到队列中,然后循环,到下一层取出队列中的元素,如果该
* 元素右左右子树,那么继续加入到队列中去,
* @param root
* @return
*/
static int maxDepthByQueue(TreeNode root){
if(root==null){
return 0;
}
//创建队列
Queue<TreeNode> queue = new LinkedList<>();
//先把根节点加入到队列中去
queue.offer(root);
//初始深度为0
int depth = 0;
//当队列不为空时循环
while (!queue.isEmpty()){
//相当于每一层的个数
for (int i =0;i < queue.size(); i++){
//从队头取出元素
TreeNode poll = queue.poll();
//如果有左孩子,把左孩子加入到队列中
if(poll.left != null){
queue.offer(poll.left);
}
//如果有右孩子,把右孩子加入到队列中
if(poll.right != null){
queue.offer(poll.right);
}
}
//每遍历完一层,让深度加一
depth++;
}
return depth;
}
4.测试
/**
* 1
* / \
* 2 3
* / \
* 4 5
* \
* 6
*
* 左子树最大深度为3,右子树为4,所以最大深度为4
*/
public static void main(String[] args) {
TreeNode root = new TreeNode(1,new TreeNode(2,new TreeNode(4,null,null),null),
new TreeNode(3,null,new TreeNode(5,null,new TreeNode(6,null,null))));
System.out.println("递归法:");
System.out.println(maxDepth(root));
System.out.println("层序队列法:");
System.out.println(maxDepthByQueue(root));
}
运行:
递归法:
4
层序队列法:
4
进程已结束,退出代码0
5.递归求最小深度
/**
* 递归求最小深度
* @param root
* @return
*/
static int minDepth(TreeNode root){
if(root == null){
return 0;
}
int d1 = minDepth(root.left);
int d2 = minDepth(root.right);
//为了防止单边树,如果一边为null,那么应该返回另一边的深度
if(d1 == 0){
return d2 + 1;
}
if(d2 == 0){
return d1 + 1;
}
return Integer.min(d1,d2) + 1;
}
6.层序遍历加队列求最小深度
/**
* 层序遍历找最小深度,
* 思路:
* 如果找到第一个叶子节点,那么该叶子节点所在的层就是最小层
* @param root
* @return
*/
static int minDepthFloorByQueue(TreeNode root){
if(root==null){
return 0;
}
LinkedList<TreeNode> queue = new LinkedList<>();
int depth = 0;
queue.offer(root);
while (!queue.isEmpty()){
int size = queue.size();
depth++;
for (int i = 0; i < size; i++) {
TreeNode poll = queue.poll();
if(poll.left==null && poll.right==null){
return depth;
}
if(poll.left!=null){
queue.offer(poll.left);
}
if(poll.right != null){
queue.offer(poll.right);
}
}
}
return depth;
}
}
7.测试
public static void main(String[] args) {
/**
* 1
* / \
* 2 3
* / \
* 4 5
* \
* 6
*
* 左子树最大深度为3,右子树为4,所以最大深度为4
*/
TreeNode root = new TreeNode(1,new TreeNode(2,new TreeNode(4,null,null),null),
new TreeNode(3,null,new TreeNode(5,null,new TreeNode(6,null,null))));
System.out.println("递归法:");
System.out.println(minDepth(root));
System.out.println("层序队列法:");
System.out.println(minDepthFloorByQueue(root));
}
运行:
递归法:
3
层序队列法:
3
进程已结束,退出代码0
四.对称二叉树
/**
* 对称二叉树
*/
public class D_Eq_Tree {
/**
* 左的左和右的右相等,
* 左的右和右的左相等
*
* @param args
*/
public static void main(String[] args) {
/**
* 1
* / \
* 2 2
* /\ /\
* 3 4 4 3
*/
TreeNode root = new TreeNode(1,new TreeNode(2,new TreeNode(3,null,null),new TreeNode(4,null,null)),
new TreeNode(2,new TreeNode(4,null,null),new TreeNode(3,null,null)));
boolean flag = isD_EqTree(root.left, root.right);
System.out.println(flag);
}
static boolean isD_EqTree(TreeNode left,TreeNode right){
//如果左子树和右子树都为null,则对称
if(left==null && right==null){
return true;
}
//如果左子树或者右子树为Null,则不对称
if(left==null || right==null){
return false;
}
//如果左右两边值不相等,则不对称
if(left.value != right.value){
return false;
}
//左右递归
return isD_EqTree(left.left,right.right) && isD_EqTree(left.right,right.left);
}
}
测试运行:
true
进程已结束,退出代码0
五.翻转二叉树
/**
* 翻转二叉树
*/
public class L226_ReversedTree {
public static void main(String[] args) {
/**
* 1 1
* / \ / \
* 2 3 => 3 2
* / \ / \ / \ / \
* 4 5 6 7 7 6 5 4
*/
TreeNode root = new TreeNode(1,
new TreeNode(2,new TreeNode(4,null,null),new TreeNode(5,null,null)),
new TreeNode(3,new TreeNode(6,null,null),new TreeNode(7,null,null)));
preOrder(root);
System.out.println();
reversed(root);
preOrder(root);
}
/**
* 翻转二叉树
* @param root
*/
static void reversed(TreeNode root){
if(root==null){
return;
}
TreeNode node = root.left;
root.left = root.right;
root.right = node;
//递归左子树
reversed(root.left);
//递归右子树
reversed(root.right);
}
static void preOrder(TreeNode root){
if(root==null){
return;
}
System.out.print(root.value+"\t");
preOrder(root.left);
preOrder(root.right);
}
}
运行:
1 2 4 5 3 6 7
1 3 7 6 2 5 4
进程已结束,退出代码0
六.后缀表达式构造树
/**
* 后缀表达式构造树
*/
public class Last_Expression {
/**
* 中缀: (2-1)*3
* 后缀: 21-3*
*
* 树:
* *
* / \
* - 3
* / \
* 2 1
*
* 后序遍历就能获得 : 21-3*
*
*
* @param args
*/
public static void main(String[] args) {
String[] str = {"2","1","-","3","*"};
LinkedList<TreeStrNode> stack = new LinkedList<>();
for (String c : str) {
switch (c){
case "+":
case "-":
case "*":
case "/":
//先弹出的为右孩子
TreeStrNode right = stack.pop();
//后弹出的为左孩子
TreeStrNode left = stack.pop();
//创建树
TreeStrNode parent = new TreeStrNode(c);
parent.left = left;
parent.right = right;
//最后把父节点压入栈
stack.push(parent);
break;
default:
//如果不是运算符,就压入栈
stack.push(new TreeStrNode(c));
}
}
//最终栈中的节点为树的根节点
TreeStrNode root = stack.peek();
//对他做一个后序遍历
postOrder(root);
}
/**
* 后序遍历
*
* @param treeNode
*/
static void postOrder(TreeStrNode treeNode) {
if (treeNode == null) {
return;
}
postOrder(treeNode.left); //左
postOrder(treeNode.right); //右
System.out.print(treeNode.str + "\t");
}
}
运行:
2 1 - 3 *
进程已结束,退出代码0
七.根据前序遍历和中序遍历还原树
/**
* 根据前序遍历和中序遍历 还原树
*/
public class Pre_In_ToTree {
/**
* 1
* / \
* 2 3
* / / \
* 6 9 8
*
* preOrder = 1, 2, 6, 3, 9, 8
* inOrder = 6, 2, 1, 9 , 3, 8
*
* 思路:
* 前序遍历的第一个是根节点 root = 1
* 然后找到中序遍历中 root所在的位置
* ->左边的是左子树
* ->右边的是右子树
* 中序:
* 左: 6,2
* 右: 9,3,8
* 前序:
* 左: 2,6
* 右: 3,9,8
*
* 这样根据中序的左和前序的左,可以知道 6 是左子树, 2 是父节点
* 根据中序的右和前序的右,可以知道 9是左子树,8是右子树,3是父节点
*
* 最后递归调用继续分割左右数组
*
*
*/
public static void main(String[] args) {
int[] preOrder = {1, 2, 6, 3, 9, 8};
int[] inOrder = {6, 2, 1, 9 , 3, 8};
TreeNode root = getTree(preOrder, inOrder);
preOrder(root);
}
static TreeNode getTree(int[] preOrder,int[] inOrder){
//结束递归条件
if(preOrder.length == 0){
return null;
}
//先找到根节点
int rootValue = preOrder[0];
TreeNode root = new TreeNode(rootValue, null, null);
//在中序中找到根节点的位置
for (int i = 0; i < inOrder.length ; i++) {
if(inOrder[i] == rootValue){
//找到之后切割左子树和右子树
//inOrder.left = 0 ~ i-1
//inOrder.right = i+1 ~ inOrder.length-1;
int[] inLeft = Arrays.copyOfRange(inOrder, 0, i);
int[] inRight = Arrays.copyOfRange(inOrder, i + 1, inOrder.length);
//继续切割preOrder
//preOrder.left = 1 ~ i+1
//preOrder.right = i+2 ~ preOrder.length-1
int[] preLeft = Arrays.copyOfRange(preOrder, 1, i+1);
int[] preRight = Arrays.copyOfRange(preOrder, i + 1, preOrder.length);
//递归调用
root.left = getTree(preLeft,inLeft);
root.right = getTree(preRight,inRight);
break;
}
}
return root;
}
/**
* 前序遍历
*
* @param treeNode
*/
static void preOrder(TreeNode treeNode) {
if (treeNode == null) {
return;
}
System.out.print(treeNode.value + "\t");
preOrder(treeNode.left); //左
preOrder(treeNode.right); //右
}
}
运行:
1 2 6 3 9 8
进程已结束,退出代码0
八.根据后序和中序遍历还原树
/**
* 根据后序和中序遍历还原树
*/
public class Post_In_ToTree {
/**
* 1
* / \
* 2 3
* / / \
* 6 9 8
*
* postOrder = 6,2,9,8,3,1
* inOrder = 6, 2, 1, 9 , 3, 8
*
**/
public static void main(String[] args) {
int[] postOrder = {6,2,9,8,3,1};
int[] inOrder = {6, 2, 1, 9 , 3, 8};
TreeNode root = buildTree(postOrder, inOrder);
preOrder(root);
}
static TreeNode buildTree(int[] postOrder,int[] inOrder){
if(postOrder.length==0){
return null;
}
//先找根节点
int rootValue = postOrder[postOrder.length - 1];
TreeNode root = new TreeNode(rootValue, null, null);
for (int i = 0; i < inOrder.length; i++) {
if(inOrder[i] == rootValue){
//切分
int[] inLeft = Arrays.copyOfRange(inOrder, 0, i);
int[] inRight = Arrays.copyOfRange(inOrder, i + 1, inOrder.length);
int[] postLeft = Arrays.copyOfRange(postOrder, 0, i);
int[] postRight = Arrays.copyOfRange(postOrder, i , postOrder.length - 1);
//递归
root.left = buildTree(postLeft,inLeft);
root.right = buildTree(postRight,inRight);
break;
}
}
return root;
}
}
运行:
1 2 6 3 9 8
进程已结束,退出代码0