数据结构复习笔记特别系列4 —— 二叉树
Posted Putarmor
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构复习笔记特别系列4 —— 二叉树相关的知识,希望对你有一定的参考价值。
1.树相关概念
树是一种非线性的数据结构,由n(n>=0)有限个节点组成一个具有层次关系的集合,这里的树是一棵倒挂形状的树。
- 节点的度:一个节点含有子树的个数为该节点的度
- 一颗树的度:树中最大节点的度称为树的度
- 叶子节点:度为0的节点称为叶子节点
- 双亲节点或父节点:一个节点含有子树,则称为这个节点为其子树根节点的父节点
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点
- 根节点:一棵树中没有父节点的节点称为根节点
- 节点的层次:从根节点开始定义,根为第一层,跟的子节点为第二层,以此类推。。。
- 树的高度:该节点到叶子结点之间距离
- 树的深度:该节点到这棵树根节点之间的距离
- 非终端节点或分支节点:度- 不为0的节点
- 兄弟节点:具有相同父节点的节点称为兄弟节点 堂兄弟节点:父亲节点在同一层的节点称为堂兄弟节点
- 节点的祖先:从树的根到该节点所经分支上的所有节点
- 节点的子孙:以某个节点为根的子树中任一节点都称为该节点的子孙
- 森林:由m(m>=0)棵互不相交的树的集合称为森林
树的表现形式:
树的表现形式有很多,比如双亲表示法、孩子表示法、孩子兄弟表示法等,这里我们简单了解一下孩子兄弟表示法。
class Node{
int value; //节点的值
Node child; //孩子节点引用
Node brother; //兄弟节点引用
}
如图所示,给一棵树,child代表的是孩子节点,brother表示兄弟节点。
2.二叉树基本概念
二叉树是节点的有限结合,集合或者为空,或者由一个根结点加上两棵别称为左子树和右子树的二叉树组成。
特点:
1.每个结点的度为2;
2.二叉树的子树有左右之分,子树的次序不能颠倒,二叉树是有序树;
3.二叉树基本形态有五种:空树、只有根结点的二叉树、结点只有左子树、结点只有右子树、节点的左右子树都存在。
特殊二叉树:
- 满二叉树:一棵二叉树每一层结点数都达到最大值,这个树就是满二叉树;假设满二叉树的层数为k,该树的结点总数是2^k-1;
- 完全二叉树:完全二叉树效率很高,它是由满二叉树引申出来的,对于深度为k,有n个结点的二叉树,当每一个结点与深度为k的满二叉树结点编号完全对应时,这棵树就称为完全二叉树。满二叉树是特殊的完全二叉树。
二叉树性质此处不再赘述,以前的博客中讲了,这里我们看一道考察二叉树性质的习题:
题目:假设一棵完全二叉树中总共有1000个结点,则该二叉树中有多少个叶子结点?多个个非叶子结点?几个结点只有左孩子?几个结点只有右孩子?
这里我画图解析一下这道题:
3.二叉树的存储
二叉树的存储分为:顺序存储(一般针对于完全二叉树)和类似于链表的链式存储,二叉树的链式存储是通过一个个结点的引用连接起来的。常见的表示方法有二叉和三叉表示方法,我们一般用二叉去表示:
//二叉表示法
class Node{
int val; //数据域
Node left; //左孩子引用,一般代表左孩子为根的整棵左子树
Node right; //右孩子引用,一般代表右孩子为根的整棵右子树
}
4.二叉树遍历
遍历概念:沿着某一条搜索路径,依次对树中的每个结点有且仅做一次访问。如果N代表根结点,L代表根结点的左子树,R代表根结点的右子树,根据根结点遍历的先后顺序,将二叉树遍历分为三种方式:
- 前序遍历NLR:先访问根结点,再访问左子树,再访问右子树
- 中序遍历LNR:先访问根的左子树,再访问根结点,再访问根的右子树
- 后序遍历LRN:先访问根的左子树,再访问根的右子树,再访问根结点
牛刀小试,写出下列这棵完全二叉树的前序遍历、中序遍历以及后序遍历的结果。
前序遍历:ABDEHCFG
中序遍历:DBEHAFCG
后序遍历:DHEBFGCA
5.二叉树习题汇总
二叉树操作习题:
/**
* 二叉树的存储
*/
class BTNode{
char val; //结点数值
BTNode left; //结点左孩子引用
BTNode right; //结点右孩子引用
public BTNode(char val) {
this.val = val;
}
}
/**
* 二叉树操作方法
*/
class BinaryTree{
public BTNode createTree(){
BTNode A = new BTNode('A');
BTNode B = new BTNode('B');
BTNode C = new BTNode('C');
BTNode D = new BTNode('D');
BTNode E = new BTNode('E');
BTNode F = new BTNode('F');
BTNode G = new BTNode('G');
BTNode H = new BTNode('H');
A.left = B;
A.right = C;
B.left = D;
B.right = E;
E.right = H;
C.left = F;
C.right = G;
return A; //返回根结点A
}
//前序遍历
public void preOrderTraversal(BTNode root){
if(root == null) return ;
System.out.print(root.val);
inOrderTraversal(root.left);
inOrderTraversal(root.right);
}
//中序遍历
public void inOrderTraversal(BTNode root){
if(root == null) return ;
inOrderTraversal(root.left);
System.out.print(root.val);
inOrderTraversal(root.right);
}
//后序遍历
public void posterOrderTraversal(BTNode root){
if(root == null) return ;
posterOrderTraversal(root.left);
posterOrderTraversal(root.right);
System.out.println(root.val);
}
//遍历思路求结点个数(根据前中后序遍历方式)
static int size = 0;
public void getSize(BTNode root){
if(root == null) return ;
size++;
getSize(root.left);
getSize(root.right);
}
//子问题思路求结点个数 左树结点个数 + 右树结点个数 + 1
// *重点分析一下递归执行过程
public int getSize1(BTNode root){
if(root == null){
return 0;
}
return getSize1(root.left) + getSize1(root.right) + 1;
}
//遍历思路求叶子结点个数
static int leafSize = 0;
public void getLeafSize(BTNode root){
if(root == null) return ;
if(root.left == null && root.right == null){
leafSize++;
}
getLeafSize(root.left);
getLeafSize(root.right);
}
//子问题思路求叶子结点个数 *重点关注一下
public int getLeafSize1(BTNode root){
if(root == null) return 0;
if(root.left == null && root.right == null){
return 1;
}
return getLeafSize1(root.left) + getLeafSize1(root.right) + 1;
}
//求树的高度
public int height(BTNode root){
if(root == null) return 0;
return Math.max(height(root.left),height(root.right)) + 1;
}
//时间复杂度O(n)
public int height1(BTNode root){
if(root == null) return 0;
int leftHeight = height(root.left);
int rightHeight = height(root.right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
//求第k层结点个数
public int getKLevelSize(BTNode root, int k){
if(root == null){
return 0;
}
//k为1表明没有左右子树了,直接返回1
if(k == 1){
return 1;
}
return getKLevelSize(root.left,k-1) + getKLevelSize(root.right,k-1);
}
//查找结点 根据遍历方式查找!
public BTNode findNode(BTNode root, int val){
if(root == null){
return null;
}
if(root.val == val){
return root;
}
BTNode leftRet = findNode(root.left,val);
if(leftRet != null) return leftRet;
BTNode rightRet = findNode(root.right,val);
if(rightRet != null) return rightRet;
//左边与右边都没找到结点,返回null
return null;
}
}
测试用例:
public class TestDemo1 {
public static void main(String[] args) {
BinaryTree binaryTree = new BinaryTree(); //创建BinaryTree对象
BTNode root = binaryTree.createTree(); //构建二叉树
System.out.print("前序遍历:");
binaryTree.preOrderTraversal(root);
System.out.println();
System.out.print("中序遍历:");
binaryTree.inOrderTraversal(root);
System.out.println();
System.out.print("后序遍历:");
binaryTree.posterOrderTraversal(root);
System.out.println();
binaryTree.getSize(root);
System.out.println("遍历思路求结点个数:"+BinaryTree.size); //结点个数
System.out.println("子问题思路求结点个数:"+binaryTree.getSize1(root)); //结点个数
System.out.println("求第三层结点个数:"+binaryTree.getKLevelSize(root,3)); //第三层结点个数为4
System.out.println("树的高度:"+binaryTree.height(root));
//System.out.println(binaryTree.height1(root));
System.out.println("查找结点:"+binaryTree.findNode(root,'F').val); //查找结点
}
}
测试用例执行结果:
二叉树基础习题:
1.判断两棵树是不是相同的树
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
解决思路:子问题思路
先判断根,再递归左子树,然后递归右子树;当两棵树root都为null时,认为这两棵树是相同的;当一棵树root为null而另一棵不为null时,返回false;当两棵树root不为null但是root结点值不相等时返回false;此时对根的判断已经结束,然后递归左子树判断两棵树左子树是否相同,再递归右子树,判断两棵树右子树是否相同,当左右子树都相同时返回true表明两棵树相等,否则返回false表明两棵树不相等。
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null){
return true;
}
//当p和q有一个为空,另一个不为空时直接返回false
if(p == null && q != null || p != null && q == null){
return false;
}
//当p与q的值不相等时直接返回false;
if(p.val != q.val){
return false;
}
//判断左树和右树是否一致
if(isSameTree(p.left,q.left) && isSameTree(p.right,q.right)){
return true;
}
return false;
}
2.判断一棵树是不是另一棵树的子树
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
思路:先判断两颗树s和t是否有一个为空,如果有返回false,然后判断两棵树是否为相同的树,如果不相等就去递归判断s左子树与t,左子树不相等就去递归判断s右子树和t。
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root == null || subRoot == null){
return false;
}
//先判断t树和s树是不是相等
if(isSameTree(root,subRoot)){
return true;
}
if(isSubtree(root.left,subRoot)){
return true;
}
if(isSubtree(root.right,subRoot)){
return true;
}
return false;
}
//判断两棵树是不是相同的树
public boolean isSameTree(TreeNode p, TreeNode q){
if(p == null && q == null){
return true;
}
if(p == null || q == null){
return false;
}
if(p.val != q.val){
return false;
}
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
3.对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
return isSymmetricChild(root.left, root.right);
}
//子问题求解
//传入左子树 传入右子树
public boolean isSymmetricChild(TreeNode leftTree, TreeNode rightTree){
if(leftTree == null && rightTree == null){
return true;
}
if(leftTree == null || rightTree == null){
return false;
}
if(leftTree.val == rightTree.val){
return isSymmetricChild(leftTree.left, rightTree.right) &&
isSymmetricChild(leftTree.right, rightTree.left);
}else{
return false;
}
}
4.判断一棵树是否为平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
思想:子问题子问题子问题!!!先判断根结点左右子树高度之差是否小于等于1,再递归左子树和右子树。
时间复杂度:O(n平方)
public boolean isBalanced(TreeNode root) {
if(root == null) return true;
int leftLength = maxDepth(root.left);
int rightLength = maxDepth(root.right);
//子问题思路,先判断当前结点左右树高度差,再判断左子树和右子树
return Math.abs(leftLength - rightLength) <= 1 &&
isBalanced(root.left) && isBalanced(root.right);
}
//求二叉树最大高度
public int maxDepth(TreeNode root){
if(root == null){
return 0;
}
return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
}
时间复杂度:O(n)
核心思想:从下边往上边走,每次判断结点左右子树高度绝对值之差,高度差大于1时直接返回false,小于等于1时继续向上判断。
public int height(TreeNode root){
if(root == null) return 0以上是关于数据结构复习笔记特别系列4 —— 二叉树的主要内容,如果未能解决你的问题,请参考以下文章