目录
101. 对称二叉树https://leetcode-cn.com/problems/symmetric-tree/
一、一些概念
1.树:树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
2.空树(empty tree):一个结点都没有的树
3.根节点(root node):一棵树中,没有双亲结点的结点,如上图:A
4.叶子结点(leaf node):度为0的结点称为叶结点; 如上图:B、C、H、I、...等结点为叶子结点
5.非叶子结点(trunk node)
6.结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为6
7.双亲结点(parent)::若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
8.孩子节点(child):一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
9.层次(level):从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
10.高度(height):树中节点的最大层次; 如上图:树的高度为4
二、二叉树(binary tree)是一种特殊的树
2.1概念
1.二叉树的度是小于等于2,换言之二叉树中的结点,如果有孩子,最多只能有两个孩子。结点可以有0、1、2个孩子
2.二叉树是一种有序树
如果我们认为两个图表示的是同一棵树(孩子的顺序不重要),称其为无序树。
如果我们认为两个图表示的不是同一棵树(孩子的顺序很重要),称其为有序树。
2.2二叉树的基本形态
直接将二叉树结点的两个孩子命令为左孩子和右孩子
区分:左孩子 vs 右孩子;左子树 vs 右子树
孩子:结点;子树:结构(哪怕里面只有一个结点或者一个结点都没有)
2.3两种特殊的二叉树
1.满二叉树 结点个数(n) 高度(h) n = 2^h - 1
2.完全二叉树 二叉树的结点上限:满二叉树的节点个数
2.4二叉树的性质
1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i - 1) (i>0)个结点
2. 若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2^k - 1 (k>=0)
3. 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1
4. 具有n个结点的完全二叉树的深度k为 上取整
2.5二叉树结点的代码表示
描述的是结点而不是二叉树本身
public static TreeNode buildTree(){
TreeNode n1 = new TreeNode('A');
TreeNode n2 = new TreeNode('B');
TreeNode n3 = new TreeNode('C');
TreeNode n4 = new TreeNode('D');
n1.left = n2; n1.right = n3;
n2.left = n4; n2.right = null; //(为null的也可以不写,不写的默认为null)
return n1;
}
2.6二叉树的基本操作
2.6.1二叉树的遍历
遍历:按照指定规则针对集合中的每个元素进行指定行为
二叉树中的元素之间没有什么前后关系,因此我们要想遍历,就必须认为的规定一些顺序进行元素之间的遍历。
广度优先的(层序遍历)自上而下,自左向右。
深度优先的遍历:整棵树被分为三部分:根结点,左子树,右子树。规定:左子树的遍历优先于右子树的遍历
1.前序遍历(Preorder Traversal )——访问根结点--->根的左子树--->根的右子树。
2. 中序遍历(Inorder Traversal)——根的左子树--->根节点--->根的右子树。
前序遍历:访问根结点,访问根结点的左子树的前序遍历结果,访问根结点的右子树的前序遍历结果
D的前序遍历结果为:D;H的前序遍历结果为:H;F的前序遍历结果为:F;G的前序遍历结果为:G;E的前序遍历结果为:E H; C的前序遍历结果为:C F G; B的前序遍历结果为:BD+E的前序遍历结果,即为BDEH; A的前序遍历结果为:A BDEH CFG(中序遍历和后序遍历的道理相同)
2.6.2代码实现
首先要写一个TreeNode类
public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val){
this.val = val;
this.left = null;
this.right = null;
}
@Override
public String toString(){
return String.format("TreeNode{%c}",val);
}
}
然后在TestTree中写遍历的代码,其中要先建立起一个二叉树,表明每个结点之间的关系
public class TreeTest1 {
public static TreeNode buildTree(){
// 定义结点
TreeNode a = new TreeNode('A');
TreeNode b = new TreeNode('B');
TreeNode c = new TreeNode('C');
TreeNode d = new TreeNode('D');
TreeNode e = new TreeNode('E');
TreeNode f = new TreeNode('F');
TreeNode g = new TreeNode('G');
TreeNode h = new TreeNode('H');
TreeNode i = new TreeNode('I');
TreeNode j = new TreeNode('J');
TreeNode k = new TreeNode('K');
TreeNode l = new TreeNode('L');
TreeNode m = new TreeNode('M');
TreeNode n = new TreeNode('N');
TreeNode o = new TreeNode('O');
TreeNode p = new TreeNode('P');
TreeNode q = new TreeNode('Q');
// 定义结点之间的关系
a.left = o; a.right = b;
b.right = c;
c.left = d; c.right = e;
d.left = f; d.right = g;
e.right = h;
g.left = i; g.right = j;
l.right = k;
m.left = l;
o.left = p; o.right = q;
q.left = m; q.right = n;
// 返回根结点
return a;
}
public static void main(String[] args) {
TreeNode root = buildTree();
preorder(root);
System.out.println();
inorder(root);
System.out.println();
postorder(root);
System.out.println();
}
// 采用递归方法的形式进行编写
// 打印出各个结点的值即可
// 传入的参数:一棵树的根结点;如果传入了 null,我们要正确地对空树进行前序遍历
//前序遍历
public static void preorder(TreeNode root){
// 二叉树的常见形态
// 1. 空树 -> 一个结点都没有 -> 根结点不存在 -> root == null
// 2. 不是空树 && 根的左右子树为空树
// 3. 不是空树 && 根的左子树为空树 && 根的右子树不为空树
// 4. 不是空树 && 根的左子树不为空树 && 根的右子树为空树
// 5. 不是空树 && 根的左右子树都不为空树
if(root != null){
System.out.printf("%c ",root.val);
preorder(root.left);
preorder(root.right);
}
}
//中序遍历
public static void inorder(TreeNode root) {
if(root != null){
inorder(root.left);
System.out.printf("%c ",root.val);
inorder(root.right);
}
}
//后序遍历
public static void postorder(TreeNode root){
if(root != null){
postorder(root.left);
postorder(root.right);
System.out.printf("%c ",root.val);
}
}
}
2.7与二叉树相关的一些题
2.7.1统计二叉树一共有多少个结点
方法一:对二叉树进行遍历,每经过一个结点(不是null),统计变量++(选用静态属性作为统计变量,因为局部变量会随着方法的一次调用就结束了,我们要使用递归,而且是静态方法,属性也是不适用的,因此最后选择静态属性);遍历方式可以采用:层序、前序、中序、后序
private static int nodeCount;
public static void calcNodeCountVersion1(TreeNode root){
if(root != null){
nodeCount++;
calcNodeCountVersion1(root.left);
calcNodeCountVersion1(root.right);
}
}
public static void main(String[] args) {
TreeNode root = buildTree();
//因为有可能统计很多次,所以每一次nodeCount 都让他的默认值为0,这样就不会造成累计
nodeCount = 0;
calcNodeCountVersion1(root);
System.out.println("结点个数:" + nodeCount);
nodeCount = 0;
calcNodeCountVersion1(root);
System.out.println("结点个数:" + nodeCount);
方法二:一棵树由根和左右子树构成,我们求出左子树的节点个数,记为 leftCount,右子树的结点个数,记为 rightCount,那么整棵树的结点个数 = leftCount + rightCount + 1。这种方法采用了递归的思想,问题的性质没有变,但问题的规模在减小,总有一天规模会减为0(也就是空树),如果是空树了的话,那结点个数就是0。
public static int calcNodeCountVersion2(TreeNode root){
if(root == null){
return 0;
}
int leftCount = calcNodeCountVersion2(root.left);
int rightCount = calcNodeCountVersion2(root.right);
return leftCount + rightCount + 1;
}
public static void main(String[] args) {
TreeNode root = buildTree();
int count = calcNodeCountVersion2(root);
System.out.println("结点个数:" + count);
count = calcNodeCountVersion2(root);
System.out.println("结点个数:" + count);
count = calcNodeCountVersion2(root);
System.out.println("结点个数:" + count);
}
2.7.2求二叉树的叶子结点的个数
方法一:通过遍历的思路,针对每个结点判断该结点是不是叶子结点(左孩子和右孩子都不存在null),统计变量++。
private static int leafCount;
private static void calcLeafCount(TreeNode root){
if(root != null){
if(root.left == null && root.right == null){
leafCount++;
}
calcLeafCount(root.left);
calcLeafCount(root.right);
}
}
public static void main(String[] args) {
TreeNode root = buildTree();
leafCount = 0;
calcLeafCount(root);
System.out.println("叶子结点个数:" + leafCount);
leafCount = 0;
calcLeafCount(root);
System.out.println("叶子结点个数:" + leafCount);
leafCount = 0;
calcLeafCount(root);
System.out.println("叶子结点个数:" + leafCount);
}
方法二:①空树就是0;②整棵树只有根结点的情况就是1③左子树中的叶子结点个数leftLeafCount + 右子树中的叶子结点个数rightLeafCount
private static int calcLeafCount2(TreeNode root){
if(root == null){
return 0;
}
if(root.left == null && root.right == null){
return 1;
}
int leftLeafCount = calcLeafCount2(root.left);
int rightLeafCount = calcLeafCount2(root.right);
return leftLeafCount + rightLeafCount;
}
public static void main(String[] args) {
TreeNode root = buildTree();
int count = 0;
count = calcLeafCount2(root);
System.out.println("叶子结点个数:" + count);
count = calcLeafCount2(root);
System.out.println("叶子结点个数:" + count);
count = calcLeafCount2(root);
System.out.println("叶子结点个数:" + count);
}
2.7.3求给定二叉树第 k 层的结点个数
需要两个参数(树的根结点,k)k >= 1
比如求第k层的结点个数,可以转化为求左子树的第k - 1层结点个数 + 右子树第k - 1层结点个数,最终转化成左子树第一层结点的个数 + 右子树第一层结点的个数,只要这颗树不是空的,在k == 1时,结点个数一定是1。收敛到特殊情况下:树的结点越来越小,k的值也是越来越小的 root == null -> 0; root != null && k ==1 -> 1。
public static int calcKLevelNodeCount(TreeNode root, int k) {
if (root == null) {
return 0;
}
if (k == 1) {
return 1;
}
int leftCount = calcKLevelNodeCount(root.left, k - 1);
int rightCount = calcKLevelNodeCount(root.right, k - 1);
return leftCount + rightCount;
}
public static void main(String[] args) {
TreeNode root = buildTree();
for (int i = 1; i <= 5; i++) {
int count = calcKLevelNodeCount(root, i);
System.out.println("第 " + i + " 层上一共有结点多少个: " + count);
count = calcKLevelNodeCount(root, i);
System.out.println("第 " + i + " 层上一共有结点多少个: " + count);
count = calcKLevelNodeCount(root, i);
System.out.println("第 " + i + " 层上一共有结点多少个: " + count);
System.out.println("========================================");
}
}
2.7.4 求二叉树的高度
求左子树的高度和右子树的高度,再求他俩的最大值 + 1,就是整个二叉树的高度
pubilc static int calcHeight(TreeNode root){
if(root == null){
return 0;
}
int leftHeight = calcHeight(root.left);
int rightHeight = calcHeight(root.right);
return Integer.max(leftHeight,rightHeight) + 1;
}
2.7.5查找 val 所在结点,没有找到返回 null
public static TreeNode find(TreeNode root,int val){
if(root == null){
return null;
}
if(root.val == val){
return root;
}
TreeNode node = find(root.left,val);
if(node != null){
return node;
} else {
return find(root.right,val);
}
}
2.7.6前序遍历返回是List
public List<Integer> preorderTraversal(TreeNode root){
List<Integer> list = new ArrayList<>();
if(root != null){
List<Integer> leftList = preorderTraversal(root.left);
List<Intrger> rightList = preorderTraversal(root.right);
list.add(root.val);
list.addAll(leftList);
list.addAll(rightList);
}
return list;
}
2.7.7 力扣100.相同的树
题目链接:力扣https://leetcode-cn.com/problems/same-tree/
题目描述:给你两棵二叉树的根节点 p
和 q
,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的
分析:整棵树 = 左子树 + 右子树。在两棵树都不为空树的情况下:根的值相等&&p左和q左 is same;根的值相等&&p右和q右 is same。
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null){
return true;
}
if(p == null || q == null){
return false;
}
return p.val == q.val && isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
}
2.7.8 力扣101.对称二叉树
题目链接:
101. 对称二叉树
https://leetcode-cn.com/problems/symmetric-tree/
同 2.7.7力扣100.相同的树 这道题很相似,直接给出代码
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
return isMirror(root.left,root.right);
}
private boolean isMirror(TreeNode p,TreeNode q){
if(p == null && q == null){
return true;
}
if(p == null || q == null){
return false;
}
return p.val == q.val && isMirror(p.left,q.right) && isMirror(p.right,q.left);
}
}
2.7.9 力扣572.另一棵树的子树
题目链接:572. 另一棵树的子树https://leetcode-cn.com/problems/subtree-of-another-tree/
分析:去以 root 为根的二叉树中,查找一个结点,条件是以这个结点为根的子树 和 subRoot 树相等。我们可以把 root 中每个结点作为根结点和subRoot进行比较,这样问题就转化成了查找和判断两棵树是否相等。
class Solution {
// 判断两棵树是否相等
private boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) {
return true;
}
if (p == null || q == null) {
return false;
}
return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
// 查找
private boolean find(TreeNode root, TreeNode subRoot) {
if (root == null) {
return false;
}
if (isSameTree(root, subRoot)) {
return true;
}
if (find(root.left, subRoot)) {
return true;
}
return find(root.right, subRoot);
}
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (subRoot == null) {
return true;
}
return find(root, subRoot);
}
}
2.7.10 力扣110.平衡二叉树
题目链接:110. 平衡二叉树https://leetcode-cn.com/problems/balanced-binary-tree/
题目描述:
给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
分析:
这棵树不是平衡二叉树,因为 左子树的高度为3;右子树的高度为1。两者差的绝对值大于1了,因此不是。
一棵树的构成是 根 + 左子树 + 右子树
一个二叉树,左右两个子树的高度差的绝对值不超过1 && 左子树满足平衡二叉树 && 右子树满足平衡二叉树
这里有两个递归,求高度是递归,判断是否平衡也是递归
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null){
return true;
}
int left = height(root.left);
int right = height(root.right);
int diff = left - right;
if(diff < -1 || diff > 1){
return false;
}
return isBalanced(root.left) && isBalanced(root.right);
}
// 求高度
public int height(TreeNode root){
if(root == null){
return 0;
}
return Integer.max(height(root.left),height(root.right)) + 1;
}
}
2.7.10 二叉树的层序遍历
层序遍历用队列
我们需要定义一个队列,队列中放的元素的类型是 TreeNode 类型。然后把根放进去,开启循环,当队列是 empty 时,循环结束。当队列不是空的时候,不断地从队列中取出元素,取出的元素就是我们下一个要遍历的结点,然后把这个结点的孩子也放入队列中(前提是该结点有孩子)
public static void levelOrder(TreeNode root){
if(root == null){
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
System.out.printf("%c ",node.val);
if(node.left != null){
queue.offer(node.left);
}
if(node.right != null){
queue.offer(node.right);
}
}
System.out.println();
}