树的定义:
树是一种非线性的数据结构,它是由有限个(n>=0)节点组成一个具有层次关系的集合。
树最顶端的顶点称为根节点,根节点没有前期节点。(如下A点就被称为根节点)
子树就是整一颗树的其中一个枝头。如下:
树形结构中,子树之间不能有交集,否则就不是树形结构。比如:
树的相关概念:
结点的度:一个结点含有子树的个数称为该结点的度; 如上图:A的度为3 。 树的度:一棵树中,所有结点度的最大值称为树的度; 如上图:树的度为3 。 叶子结点或终端结点:度为0的结点称为叶结点; 如上图:F、I、J、K...等节点为叶结点 。 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是C的父结点 。 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:C是A的孩子结点 。 根结点:一棵树中,没有双亲结点的结点;如上图:A 点。 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推 。 树的高度或深度:树中结点的最大层次; 如上图:树的高度为4 。 非终端结点或分支结点:度不为0的结点; 如上图:C、E、G、H等节点为分支结点 。 兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:C,D是兄弟结点 。 堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:I,K互为堂兄弟结点 。 结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙 。 森林:由m(m>=0)棵互不相交的树组成的集合称为森林 。
树的表现形式:
如果树的度太大,那么我们一般用孩子兄弟表示法。
public class Tree {
private int value;
private Tree firstChild;//第一个孩子
private Tree nextBrother;//下一个兄弟的引用
}
二叉树:
二叉树是所有节点最大的度为2的树。
任意两个二叉树都是由以下几种情况复合而成的:
两种特殊的二叉树:
1.满二叉树:
一颗二叉树,每层节点的个数都达到最大值。如下:
它每一层的节点个数为2^(h-1),h为第h层的意思。总节点个数为2^k-1,k为层数。
2.完全二叉树:
简单来讲就是在h<k的位置是满二叉树 ,在h==k的那层叶子节点是从左往右没有空缺的。如下图:
二叉树的性质:
1. 若规定根结点的层数为1,则一棵非空二叉树第i层上最多有 (i>0)个结点 。 2. 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (k>=0) 。 3. 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1 。 4. 具有n个结点的完全二叉树的深度k为 log2(n + 1)上取整 。 5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i 的结点有: 若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点 。 若2i+1<n,左孩子序号:2i+1,否则无左孩子 。 若2i+2<n,右孩子序号:2i+2,否则无右孩子 。
二叉树的存储结构:
1.顺序存储
顺序存储就是用数组来存储,比如:
这颗树的顺序存储形式就是:
一般顺序存储比较适合完全二叉树,因为不会有空间浪费:
二叉树顺序存储在物理上是一个数组,在逻辑上是一颗树。
2.链式存储
链式存储是用链表来表示一颗二叉树,通常的方法是每个节点包含3个域,左指针域、右指针域、数据域:
static class TreeNode {
public char val;
public TreeNode left;//左孩子的引用
public TreeNode right;//右孩子的引用
public TreeNode(char val) {
this.val = val;
}
}
左指针域来表示左孩子,右指针域来表示右孩子。
等到后面高阶数据结构还会出现三叉链,就是在原来二叉链的基础上多出一个父指针域,在红黑树中会使用到。
二叉树的遍历:
遍历就是沿着某种路线对树进行访问,是二叉树十分重要的功能。
二叉树的前序遍历:
// 前序遍历
public void preOrder(TreeNode root) {
if(root == null) {//如果该节点为null,返回
return;
}
System.out.print(root.val + " ");//先访问父节点
preOrder(root.left);//再访问左节点
preOrder(root.right);//最后访问右节点
}
非递归:
public void PreOrderNoRecursion(TreeNode root) {
if (root == null) {//如果根节点为空就不运行了
return;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);//先把头节点放入栈中
while (!stack.empty()) {
TreeNode cur = stack.pop();
System.out.print(cur.val + " ");//先打印栈顶元素
if (cur.right != null) {
stack.push(cur.right);//注意这里要先放右节点!!!
}
if (cur.left != null) {
stack.push(cur.left);//再放左节点
}
}
System.out.println();
}
二叉树的中序遍历:
// 中序遍历
void inOrder(TreeNode root) {
if(root == null) {//如果该节点为null,返回
return;
}
inOrder(root.left);//先访问左节点
System.out.print(root.val + " ");//再访问父节点
inOrder(root.right);//最后访问右节点
}
非递归:
//中序变量无递归版
public void inOrderNoRecursion(TreeNode root) {
if (root == null ) {
return;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
System.out.print(cur.val + " ");
cur = cur.right;
}
System.out.println();
}
二叉树的后序遍历:
// 后序遍历
void postOrder(TreeNode root) {
if(root == null) {//如果该节点为null,返回
return;
}
postOrder(root.left);//先访问左节点
postOrder(root.right);//再访问右节点
System.out.print(root.val + " ");//最后访问父节点
}
非递归:
public void postOrderNoRecursion(TreeNode root) {
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode prev = null;//记录着前一个被打印的节点
while (cur != null || !stack.empty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
if (top.right == null || top.right == prev) {
//打印的条件是1.这个节点的左儿子为空或者被打印过且
//2.右儿子为空或者有被打印过
//第一个条件可以不显式的写出来,因为当程序执行到TreeNode top = stack.peek();时
//会被自动筛掉
System.out.print(top.val + " ");
prev = top;//更新prev
stack.pop();//别忘记打印了的要出栈
} else {
//这种情况时这个节点还有右孩子且右孩子没有被打印
cur = top.right;
}
}
System.out.println();
}
二叉树的层序遍历:
//层序遍历
void levelOrder(TreeNode root) {
if(root == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int size = 1;
while(!queue.isEmpty()) {
while (size -- > 0) {
TreeNode cur = queue.poll();
System.out.print(cur.val + " ");
if(cur.left != null) {
queue.offer(cur.left);
}
if(cur.right != null) {
queue.offer(cur.right);
}
}
size = queue.size();
System.out.println();
}
}
获取二叉树的一些属性:
获取树中节点的个数:
//方法1
public static int nodeSize;//利用静态成员变量
void size(TreeNode root) {
if(root == null) {
return;
}
nodeSize++;
size(root.left);
size(root.right);
}
//方法2
int size2(TreeNode root) {
if (root == null) {
return 0;
}
int sizeleft = size2(root.left);
int sizeright = size2(root.right);
return sizeleft + sizeright + 1;
}
获取叶子节点的个数:
//方法1
public static int leafSize = 0;//利用静态成员变量
void getLeafNodeCount1(TreeNode root) {
if(root == null) {
return;
}
if(root.left == null && root.right == null) {
leafSize++;
}
getLeafNodeCount1(root.left);
getLeafNodeCount1(root.right);
}
//方法2
int getLeafNodeCount2(TreeNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
return getLeafNodeCount2(root.left) + getLeafNodeCount2(root.right);
}
获取第K层节点的个数:
int getKLevelNodeCount(TreeNode root, int k) {
if(root == null) {
return 0;
}
if(k == 1) {//只有第k层的节点才加上
return 1;//注意这里是1,表示已经到达了第k层了。
}
int sizeLeft = getKLevelNodeCount(root.left, k - 1);
int sizeRight = getKLevelNodeCount(root.right, k - 1);
return sizeLeft + sizeRight;
}
获取二叉树的高度:
/*
获取二叉树的高度
时间复杂度:O(N)
*/
int getHeight(TreeNode root) {
if(root == null) {
return 0;
}
int sizeLeft = getHeight(root.left);
int sizeRight = getHeight(root.right);
return sizeLeft > sizeRight ? (sizeLeft + 1) : (sizeRight + 1);
}
检测值为value的元素是否存在:
// 检测值为value的元素是否存在
TreeNode find(TreeNode root, char val) {
if (root == null) {
return null;
}
if (root.val == val) {
return root;//如果找到了,返回这个节点
}
TreeNode cur1 = find(root.left, val);//创建一个节点来存储左子树之前返回的结果
if (cur1 != null) {//如果结果不为空则表示找到了
return cur1;
}
TreeNode cur2 = find(root.left, val);//创建一个节点来存储右子树之前返回的结果
return cur2;//执行到这一步表示左子树没有找到,那么无论有没有找到都返回右子树的解
}
判断一棵树是不是完全二叉树:
// 判断一棵树是不是完全二叉树
boolean isCompleteTree(TreeNode root) {
if(root == null) {
return true;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty())
{
TreeNode cur = queue.poll();
if(cur != null) {
queue.offer(cur.left);
queue.offer(cur.right);
}
else {
break;
}
}
while (!queue.isEmpty()) {
if (queue.poll() != null) {
return false;
}
}
return true;
}
练习:
选择:
某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为()A: ABDHECFG B: ABCDEFGH C: HDBEAFCG D: HDEBFGCA
2.二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为()A: E B: F C: G D: H
3.设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为()A: adbce B: decab C: debac D: abcde
4.某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为()A: FEDCBA B: CBAFED C: DEFCBA D: ABCDEF
答案:1.A 2.A 3.D 4.A
编程题:
1. 检查两颗树是否相同。100. 相同的树 - 力扣(LeetCode)
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q != null || p != null && q == null) {
return false;
}
if (p == null && q == null) {
return true;
}
if (p.val != q.val) {
return false;
}
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
2. 另一颗树的子树。572. 另一棵树的子树 - 力扣(LeetCode)
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q != null || p != null && q == null) {
return false;
}
if (p == null && q == null) {
return true;
}
if (p.val != q.val) {
return false;
}
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) { //如果走到空了都没找到返回false
return false;
}
if (isSameTree(root, subRoot)) {
return true;//看看这颗子树是不是相同的
}
if (isSubtree(root.left, subRoot)) {
return true;//不相同的话去左子树找
}
if (isSubtree(root.right, subRoot)) {
return true;//还是不相同的话去右子树找
}
return false;//都找过了,都找不到返回false
}
}
3. 翻转二叉树。226. 翻转二叉树 - 力扣(LeetCode)
class Solution {
public void invert(TreeNode root) {
if (root == null) {
return;
}
TreeNode l = root.left;
TreeNode r = root.right;
root.left = r;
root.right = l;
invert(root.left);
invert(root.right);
}
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
invert(root);
return root;
}
}
4. 判断一颗二叉树是否是平衡二叉树。110. 平衡二叉树 - 力扣(LeetCode)
class Solution {
public int getHeight(TreeNode root) {
if (root == null) {
return 0;
}
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
public boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
}
if (Math.abs(getHeight(root.left) - getHeight(root.right)) > 1) {
return false;
}
return isBalanced(root.left) && isBalanced(root.right);
}
}
5. 对称二叉树。101. 对称二叉树 - 力扣(LeetCode)
class Solution {
public boolean isSym(TreeNode l, TreeNode r) {
if (l == null && r == null) {
return true;
} else if (l == null && r != null || l != null && r == null) {
return false;
}
if (l.val != r.val) {
return false;
}
if (l.left == null && r.right != null || l.left != null && r.right == null) {
return false;
}
if (l.right == null && r.left != null || l.right != null && r.left == null) {
return false;
}
if (l.left != null && r.right != null && l.left.val != r.right.val) {
return false;
}
if (l.right != null && r.left != null && l.right.val != r.left.val) {
return false;
}
return isSym(l.left, r.right) && isSym(l.right, r.left);
}
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
return isSym(root.left, root.right);
}
}
6. 二叉树的构建及遍历。二叉树遍历_牛客题霸_牛客网 (nowcoder.com)
import java.util.Scanner;
class TreeNode {
char val;
TreeNode left;
TreeNode right;
public TreeNode (char value) {
val = value;
}
}
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
static int i;
public static TreeNode CreateTree(String str) {
TreeNode cur = null;
if (str.charAt(i) != '#') {
cur = new TreeNode(str.charAt(i));
i++;
cur.left = CreateTree(str);
cur.right = CreateTree(str);
}
else {
i++;
}
return cur;
}
static public void inOder(TreeNode root) {
if (root == null) {
return;
}
inOder(root.left);
System.out.print(root.val + " ");
inOder(root.right);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.next();
TreeNode root = CreateTree(str);
inOder(root);
}
}
7. 二叉树的分层遍历 。102. 二叉树的层序遍历 - 力扣(LeetCode)
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root == null) {
return list;
}
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
LinkedList<Integer> newlist = new LinkedList<>();
while (size-- > 0) {
TreeNode top = queue.poll();
if (top.left != null) {
queue.offer(top.left);
}
if (top.right != null) {
queue.offer(top.right);
}
newlist.add(top.val);
}
list.add(newlist);
}
return list;
}
}
8. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。236. 二叉树的最近公共祖先 - 力扣(LeetCode)
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
if (root == p || root == q) {
return root;
}
root.left = lowestCommonAncestor(root.left, p, q);
root.right = lowestCommonAncestor(root.right, p, q);
if (root.left != null && root.right != null) {
return root;
} else if (root.left != null) {
return root.left;
} else if (root.right != null) {
return root.right;
}
return null;
}
}
9. 根据一棵树的前序遍历与中序遍历构造二叉树。 105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
class Solution {
public int i = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
return PreIn(preorder, inorder, 0, preorder.length - 1);
}
public TreeNode createByPreIn(int[] preorder, int[] inorder, int begin, int end) {
if (begin > end) {
return null;
}
TreeNode root = new TreeNode(preorder[i]);
int index = findIndex(inorder, preorder[i]);
i ++;
root.left = PreIn(preorder, inorder, begin, index - 1);
root.right = PreIn(preorder, inorder, index + 1, end);
return root;
}
public static int findIndex(int[] inorder, int key) {
for (int j = 0; j < inorder.length; j++) {
if (inorder[j] == key) {
return j;
}
}
return -1;
}
}
10. 根据一棵树的中序遍历与后序遍历构造二叉树。106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
class Solution {
public int i = 0;
public TreeNode buildTree(int[] inorder, int[] postorder) {
i = inorder.length - 1;
return createByInPost(inorder, postorder, 0, inorder.length - 1);
}
public TreeNode createByInPost(int[] inorder, int[] postorder, int begin, int end) {
if(begin > end) {
return null;
}
TreeNode root = new TreeNode(postorder[i]);
int index = FindIndex(inorder, begin, end, postorder[i]);
i--;
root.right = createByInPost(inorder, postorder, index + 1, end);
//一定要先创建右子树再创建左子树,因为postoder从后往前遍历是根->右->左
root.left = createByInPost(inorder, postorder, begin, index - 1);
return root;
}
public int FindIndex(int[] inorder, int ibegin, int iend, int key) {
for(int j = ibegin; j <= iend; j++) {
if(inorder[j] == key) {
return j;
}
}
return -1;
}
}
11. 二叉树创建字符串。606. 根据二叉树创建字符串 - 力扣(LeetCode)
class Solution {
public String tree2str(TreeNode root) {
StringBuffer str = new StringBuffer();
dfs(root, str);
return str.toString();
}
public void dfs(TreeNode root, StringBuffer str) {
if (root == null) {
return;
}
str.append(root.val);
if (root.left == null && root.right == null) {
return;
}
str.append("(");
dfs(root.left, str);
str.append(")");
if (root.right != null) {
str.append("(");
dfs(root.right, str);
str.append(")");
}
}
}
小总结:二叉树的问题普遍都很难,大家要好好消化,这部分内容非常重要!!!