新年新气象! 祝大家兔年 财源滚滚! 万事胜意!
二叉树也是笔试中常考的知识点,大家要掌握及时回顾噢~
树是一种非线性的数据结构,它是由n个有限结点组成的一个具有层次关系的集合,像一棵倒挂的树,根在上,叶子在下.
如上图,是一颗二叉树,因为每个结点最多有两个孩子结点.
以下介绍节点的基本情况
1.根节点: 根节点没有前驱节点
2.叶子节点: 没有孩子结点的节点
3.树中同一棵树的子树与子树之间不能有交集.
4.每棵树除了根节点都只能有一个父亲节点.
5.一棵树如果有N个节点,那么他就有N-1条边.
如下图
第一个,C与D同是A的子树,C与D之间不能有交集.
第二个,E只能有一个父亲节点,不能有B,C两个父亲节点.
第三个,也是G不能有A,D两个父亲节点.
1.节点的度: 一个节点含有子树的个数.二叉树中,节点的度可以为0,1,2.叶子结点的度就是0.
2…树的度: 所有节点的度的最大值为树的度
3.结点的层次: 根节点为第一层,依次类推递增
4.树的高度或深度: 树中节点的最大层次为树的高度.
5.兄弟节点: 父亲节点相同的节点互为兄弟节点.
6.节点的子孙: 以该节点为根节点的子树中的任意节点都是该节点的子孙.树中所有节点都是根节点的子孙.
二叉树是节点的有限结合.
二叉树可以为空,也可以只有一个根节点,每个结点最多可以有两个孩子节点.
所以二叉树有以下四种基本情况.
1.满二叉树,如下图,每层的节点都达到最大值.满满登登的.
满二叉树,第K层的节点数目为
2.完全二叉树,如下图,树从左到右要连续的,从第一个节点到最后一个节点之间不能有为空的节点.
1.一棵非空二叉树,以根节点为第一层,第N层节点最大数目为2的n-1次幂.
2.一颗深度为K的非空二叉树,总共节点最大数目为2的K次幂-1.
原理如下,一个深度为4的树,最大节点总数计算过程如下.
3.一棵二叉树,设其叶子节点个数为n0,度为2(有两个孩子节点)的节点个数为n2,则n0 = n2 + 1.
4.具有N个节点的完全二叉树的深度为log2(n+1)向上取整.
5.若第一个节点从0开始编号,第i个节点(除了根节点),他的父亲节点编号为(i-1)/2.
6.若第一个节点从0开始编号,第i个节点,若它有左右孩子节点,它的左孩子节点编号为2i+1,右孩子编号为2i+2.
例题
解析:
叶子节点为n0,n0 = n2 +1,n0 = 200.
解析:
完全二叉树有如下两种情况.
第一种,节点个数为偶数,只有一个度为1的节点,其余都是度为0和度为2的节点
第二种,节点个数为奇数,全都是度为2和度为0的节点.
本题节点个数为偶数,是第一种情况,只有一个度为1的节点.
节点总数 = n0 + n1 + n2
解析:
这道题与上道题类似,个数为奇数,只有度为0和度为2的节点.
767 = n0 + n2
767 = n0 + n0 -1
n0 = 384
k = log2^(531 +1)
k = 10
二叉树可以有顺序存储和链式存储.我们这节先介绍链式存储.
二叉树是由结点和结点与结点之间的的引用构成.
链式存储有二叉三叉表示方法.
第一种是孩子表示法.
class TreeNode{
public TreeNode left;//节点左孩子
public TreeNode right;//节点右孩子
public int value;//节点值
}
第二种是双亲(孩子和父亲)表示法.
class TreeNode{
public TreeNode left;
public TreeNode right;
public TreeNode parent;//节点的父亲节点
public int value;
}
本节介绍第一种表示方式.
我们先用简单的方式构造一棵二叉树.
public void createTree(){
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');
root = A;
A.left = B;
A.right = C;
B.left = D;
B.right = E;
C.left = F;
C.right = G;
}
遍历二叉树根据遍历顺序不同有三种遍历方式: 前序遍历,中序遍历,后序遍历.
前序遍历的顺序为: 根结点->左子树->右子树.
如下图,具体操作是
代码实现
public void preOrder(TreeNode root){
//根左右
if(root == null){
return;
}
System.out.print(root.val + " ");
preOrder(root.left);
preOrder(root.right);
}
中序遍历的顺序是左子树->根节点->右子树
public void inOrder(TreeNode root){
//左根右
if(root == null){
return;
}
inOrder(root.left);
System.out.print(root.val);
inOrder(root.right);
}
后序遍历的顺序是 左子树->右子树->根
如下图
1.先遍历根节点A的左子树BDE,以B为根节点,以左右根的顺序遍历,结果是DEB.
2.左子树遍历完了,再遍历根节点A的右子树CFG,以左右根的顺序,遍历结果为FGC
3.A的左右子树遍历完毕,最后遍历根节点A
如下图,后续遍历顺序为DEBFGCA
代码实现
public void postOrder(TreeNode root){
//左右根
if(root == null){
return;
}
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val + " ");
}
如下图,按照从左到右,从上到下的顺序遍历.遍历顺序为ABCDEFG
解答:树为完全二叉树,中间是没有空缺的树的.所以A是根节点,B是A的左孩子节点,C是A的右孩子节点.
DE分别为B的左右孩子.GH分别为C的左右孩子.所以这棵树前序遍为:ABDHECFG,选A.
解答:很简单哈,先序遍历的第一个节点就是根节点,选A
解答:
1.先看后续节点的倒数第一个节点A,这个节点为根节点.
2.根据中序遍历中根节点的位置,根节点左侧结点为根节点的左子树.根节点右侧结点为根节点的右子树.
3.再看后序遍历的倒数第二个节点C,这是右子树的根.(因为遍历完右子树才会轮到根节点,所以倒数第二节点就是右子树的根)
4.对应中序遍历中C的位置,C的左侧结点为C的左子树,C右侧节点的C的右子树.
对应这题,后序遍历的最后一个结点是根节点,所以,这颗树的根节点是A.
中序遍历根节点左侧是左子树,根节点右侧是右子树.所以,B是根节点的左子树.
再看后序遍历倒数第二个节点C,它是右子树的根.
对应中序遍历,C的左侧节点是它的左子树.C的右侧节点是他的右子树.所以,D是C的左孩子,E是C的右孩子.
综上,画出二叉树,选D
解答:
根节点为后序遍历最后一个节点F为根节点
对应中序遍历,F左侧为F的左子树,所以,F只有左子树,无右子树.
因为无右子树,后序遍历倒数第二个节点E为左子树的根节点
对应中序遍历,E的左侧为E的左子树.
以此类推,画出的树如下
层序遍历: FECBA,选A.
public int nodeCount(TreeNode root){
if(root == null){
return 0;
}
int tmp = nodeCount(root.left) + nodeCount(root.right) + 1;
return tmp;
}
树中叶子节点的特点是无左右孩子结点.
与上一题相似,这道题只有符合要求的节点就加1.
public static int LeafNum(TreeNode root){
//看节点的左孩子是否为空,再看右孩子是否为空.全部符合则count++.
if(root == null){
return 0;
}
int count = 0;
if(root.left == null && root.right == null){
return 1;
}
return LeafNum(root.left) + LeafNum(root.right);
}
反转二叉树,需要将根的左子树与右子树交换,再将每颗小树的左孩子与右孩子交换.
public TreeNode reverseTree(TreeNode root){
if(root == null){
return null;
}
TreeNode tmp = root.left;
root.left = root.right;
root.right = root.left;
reverseTree(root.left);
reverseTree(root.right);
return root;
}
//判断一棵树是完全二叉树
//队列实现,直接放,出完的时候全是null,就是完全二叉树
//先用之前的层序遍历把所有元素放进去再弹出,出完的时候全是null,就是完全二叉树
public boolean IsCompleteTreeTravel(TreeNode root){
Queue<TreeNode> queue = new LinkedList<>();
if(root == null){
return true;
}
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()){
TreeNode cur = queue.poll();
if(cur != null){
return false;
}
}
return true;
}
//判断两树相同,结构相同,数相同。时间复杂度O( min(r, s) )
public static boolean sameTree(TreeNode root1, TreeNode root2){
if(root1 == null && root2 != null){
return false;
}
if(root1 != null && root2 == null){
return false;
}
if(root1 == null && root2 == null){
return true;
}
if(root1.val != root2.val){
return false;
}
boolean b = sameTree(root1.left,root2.left);
boolean b2 = sameTree(root1.right,root2.right);
return b && b2;
}
只要树2等于树1的任意子树就为真
//看树是不是别的树的子树,时间复杂度,O(r * s)
public static boolean subtreeJudge(TreeNode root1, TreeNode root2){
if(root1 == null || root2 == null){
return false;
}
if(sameTree(root1,root2)){
return true;
}
//注意这里,当root1为空的时候,取不到root1的left,造成空指针异常
if(subtreeJudge(root1.left,root2)){
return true;
}
if(subtreeJudge(root1.right,root2)){
return true;
}
return false;
}
//查找value是否存在
public TreeNode lookupValue(TreeNode root,char val){
if(root == null){
return null;
}
if(root.val == val){
return root;
}
TreeNode ret = lookupValue(root.left,val);
if(ret != null){
return ret;
}
TreeNode ret2 = lookupValue(root.right,val);
if(ret2 != null) {
return ret2;
}
//树的左右结点都走完了,都没找到,返回空
return null;
}
多巩固,多复习.祝前程似锦!