1.树
1.1 概念
树是一种非线性的数据结构,它是由n(n>=0)个结点构成的具有层次关系的集合。
- 有一个特殊的节点,称为根节点,根节点没有前驱节点
- 除根节点外,其余节点被分成M(M > 0)个互不相交的集合T1、T2、…、Tm,其中每一个集合 Ti (1 <= i<= m) 又是一棵与树类似的子树。每棵子树的根节点有且只有一个前驱,可以有0个或多个后继
- 树是递归定义的。
1.节点的度:一个节点含有的子树的个数称为该节点的度;
2.树的度:一棵树中,最大的节点的度称为树的度;
3.叶子节点或终端节点:度为0的节点称为叶节点;
4.双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
5.孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
6.根结点:一棵树中,没有双亲结点的结点;
7.节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
8.树的高度或深度:树中节点的最大层次;
9.非终端节点或分支节点:度不为0的节点;
10.兄弟节点:具有相同父节点的节点互称为兄弟节点;点
11堂兄弟节点:双亲在同一层的节点互为堂兄弟;
12.节点的祖先:从根到该节点所经分支上的所有节点;
13.子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
14.森林:由m(m>=0)棵互不相交的树的集合称为森林
2 二叉树
2.1 概念
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉
树组成。
二叉树的特点:
- 每个结点最多有两棵子树,即二叉树不存在度大于 2 的结点。
- 二叉树的子树有左右之分,其子树的次序不能颠倒,因此二叉树是有序树。
2.2 二叉树的基本形态
从左往右依次是:空树、只有根节点的二叉树、节点只有左子树、节点只有右子树、节点的左右子树均存在,一般二叉树都是由上述基本形态结合而形成的。
2.3 两种特殊的二叉树
- 满二叉树: 一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
- 完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
2.4 二叉树的性质
- 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2i-1(i>0)个结点
- 若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数是2k-1 (k>=0)
- 对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1
- 具有n个结点的完全二叉树的深度k为log(n+1) 上取整
- 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i
的结点有:
若i>0,双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
若2i+1<n,左孩子序号:2i+1,否则无左孩子
若2i+2<n,右孩子序号:2i+2,否则无右孩子
2.5 二叉树的存储
二叉树的存储结构分为:顺序存储和类似于链表的链式存储。
2.6 二叉树的基本操作
2.6.1 二叉树的遍历
Node结点
public TreeNode left;
public TreeNode right;
public int val;
TreeNode(int val){
this.val=val;
}
}
递归遍历
- 前序遍历:
public void preOrder(TreeNode root){
if(root!=null){
System.out.println(root.val);
preOrder(root.left);
preOrder(root.right);
}
}
- 中序遍历:
public void inOrder(TreeNode root){
if(root!=null){
inOrder(root.left);
System.out.println(root.val);
inOrder(root.right);
}
}
- 后序遍历:
public void postOrder(TreeNode root){
if(root!=null){
postOrder(root.left);
postOrder(root.right);
System.out.println(root.val);
}
}
非递归调用
- 前序遍历
public void preOrder2(TreeNode root){
if(root==null){
return;
}
Deque<TreeNode>stack=new LinkedList<>();
TreeNode current=root;
while(!stack.isEmpty()||current!=null){
while(current!=null){
System.out.println(current.val);
stack.push(current);
current=current.left;
}
TreeNode node=stack.pop();
current=node.right;
}
}
- 中序遍历
public void inOrder2(TreeNode root){
if(root==null){
return;
}
Deque<TreeNode>stack=new LinkedList<>();
TreeNode current=root;
while(!stack.isEmpty()||current!=null){
while(current!=null){
stack.push(current);
current=current.left;
}
TreeNode node=stack.pop();
System.out.println(node.val);
current=node.right;
}
}
- 后序遍历
public void postOrder1(TreeNode root){
if(root==null){
return;
}
Deque<TreeNode>stack=new LinkedList<>();
TreeNode current=root;
TreeNode last=null;
while(!stack.isEmpty()||current!=null){
while(current!=null){
stack.push(current);
current=current.left;
}
TreeNode node=stack.peek();
if(node.right==null){
last=node;
stack.pop();
System.out.println(node.val);
}else if(node.right==last){
last=node;
stack.pop();
System.out.println(node.val);
}else{
current=node.right;
}
}
}
- 层序遍历
public void levelOrderTraversal(TreeNode root){
if(root==null){
return;
}
Queue<TreeNode>queue=new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
TreeNode node = queue.remove();
System.out.println(node.val);
if(node.left!=null){
queue.add(node.left);
}
if(node.right!=null){
queue.add(node.right);
}
}
}
2.6.2其他操作
- 遍历思路-求结点个数
static int size=0;
public void getSize1(TreeNode root){
if(root!=null){
size++;
getSize1(root.left);
getSize1(root.right);
}
}
- 子问题思路-求结点个数
public int getSize(TreeNode root){
if(root==null){
return 0;
}
return getSize(root.left)+getSize(root.right)+1;
}
- 遍历思路-求叶子结点个数
static int leafSize = 0;
public void getLeafSize1(TreeNode root){
if(root!=null){
if(root.left==null&&root.right==null){
leafSize++;
}
getLeafSize1(root.left);
getLeafSize1(root.right);
}
}
- 子问题思路-求叶子结点个数
public int getLeafSize2(TreeNode root){
if(root==null){
return 0;
}
if(root.left==null&&root.right==null){
return 1;
}
return getLeafSize2(root.left)+getLeafSize2(root.right);
}
- 子问题思路-求第 k 层结点个数
public int getKLevelSize(TreeNode root,int k){
if(root==null){
return 0;
} else if(k==1){
return 1;
}
return getKLevelSize(root.left,k-1)+getKLevelSize(root.right,k-1);
}
- 获取二叉树的高度
public int getHeight(TreeNode root){
if(root==null){
return 0;
}
return 1+Math.max(getHeight(root.left),getHeight(root.right));
}
}
- 查找 val 所在结点,没有找到返回 null
public 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;
}
return find(root.right,val);
}
- 判断一棵树是否是一个完全二叉树
public boolean isCompleteTree(TreeNode root){
if(root==null){
return false;
}
Queue<TreeNode>queue=new LinkedList<>();
queue.add(root);
while(true){
TreeNode node = queue.remove();
if(node==null){
break;
}
queue.add(node.left);
queue.add(node.right);
}
while(!queue.isEmpty()){
TreeNode node=queue.remove();
if(node!=null){
return false;
}
}
return true;
}
3.相关Oj题
- 力扣100.相同的树
链接
给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
来源:力扣(LeetCode)
public boolean isSameTree(TreeNode p, TreeNode q) {
//当两棵树都是空树的时候这两棵树是相同的树
if(p==null&&q==null){
return true;
}
//程序能走到这里说明其中一棵树是空树一棵树不是空树,则不是相同的树
if(p==null||q==null){
return false;
}
//当两颗树的根的val相同,左右子树都相同时是相同的树,否则不是
return p.val==q.val&&isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
}
- 力扣572.另一个树的子树
链接
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
来源:力扣(LeetCode)
public boolean isSameTree(TreeNode p, TreeNode q) {
//当两棵树都是空树的时候这两棵树是相同的树
if(p==null&&q==null){
return true;
}
//程序能走到这里说明其中一棵树是空树一棵树不是空树,则不是相同的树
if(p==null||q==null){
return false;
}
//当两颗树的根的val相同,左右子树都相同时是相同的树,否则不是
return p.val==q.val&&isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
}
public boolean isSubtree(TreeNode s, TreeNode t) {
if(s==null){
return false;
}
//是子树就说明t与s的一部分是相同的树,就从根->左子树->右子树进行判断
if(isSameTree(s,t)){
return true;
}
if(isSubtree(s.left,t)){
return true;
}
return isSubtree(s.right,t);
}
- 力扣110.平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
public int maxDepth(TreeNode root) {
if(root==null){
return 0;
}
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
public boolean isBalanced(TreeNode root) {
if(root==null){
return true;
}
if(Math.abs(maxDepth(root.left)-maxDepth(root.right))>1){
return false;
}
if(!isBalanced(root.left)){
return false;
}
return isBalanced(root.right);
}
- 力扣101.对称二叉树
给定一个二叉树,检查它是否是镜像对称的
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isImageTree(TreeNode p, TreeNode q){
if(p==null&&q==null){
return true;
}
if(p==null||q==null){
return false;
}
return p.val==q.val&&isImageTree(p.left,q.right)&&isImageTree(p.right,q.left);
}
public boolean isSymmetric(TreeNode root) {
if(root==null){
return true;
}
return isImageTree(root.left,root.right);
}
}
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串:ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果
链接
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
public String toString(){
return String.format("TreeNode{%d}",this.val);
}
}
public class Main {
private static List<Character> arrayToList(String str){
char[] chars = str.toCharArray();
List<Character>list=new ArrayList<>();
for(char c:chars){
list.add(c);
}
return list;
}
public static TreeNode buildTree(List<Character> in, List<Character> out){
if(in.isEmpty()){
return null;
}
Character c = in.remove(0);
TreeNode root=new TreeNode(c);
if(c=='#'){
out.addAll(in);
return null;
}
List<Character>innerList=new LinkedList<>();
root.left=buildTree(in,innerList);
root.right=buildTree(innerList,out);
return root;
}
private static void preOrder(TreeNode root){
if(root!=null){
preOrder(root.left);
System.out.printf("%c ", root.val);
preOrder(root.right);
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s = scanner.nextLine();
List<Character> in=arrayToList(s);
List<Character>out=new LinkedList<>();
TreeNode root = buildTree(in, out);
preOrder(root);
}
}
- 力扣102 二叉树的遍历
链接
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
class TreeNode {
public TreeNode left;
public TreeNode right;
public int val;
TreeNode(int val){
this.val=val;
}
}
static class NL{
TreeNode node;
int level;
NL(TreeNode node,int lev){
this.node=node;
this.level=lev;
}
}
public List<List<Integer>> levelOrder(TreeNode root){
List<List<Integer>>list=new LinkedList<>();
if(root==null){
return list;
}
Queue<NL>queue=new LinkedList<>();
queue.add(new NL(root,0));
while(!queue.isEmpty()){
NL node=queue.remove();
int lev=node.level;
TreeNode node1=node.node;
if(list.size()==lev){
list.add(new LinkedList<>());
}
List<Integer> list1=list.get(lev);
list1.add(node1.val);
if(node1.left!=null){
queue.add(new NL(node1.left,node.level+1));
}
if(node1.right!=null){
queue.add(new NL(node1.right,node.level+1));
}
}
return list;
}
- 力扣236.二叉树的最近祖先
链接
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
来源:力扣(LeetCode)
class Solution {
private static boolean search(TreeNode root,TreeNode node){
if(root==null){
return false;
}
if(root==node){
return true;
}
if(search(root.left,node)){
return true;
}
return search(root.right,node);
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == p || root == q) {
return root;
}
boolean pInleft=search(root.left,p);
boolean qInleft=search(root.left,q);
if(qInleft&&pInleft){
return lowestCommonAncestor(root.left,p,q);
}
if(!qInleft&&!pInleft){
return lowestCommonAncestor(root.right,p,q);
}
return root;
}
}