视频(3)
子树的概念
平衡二叉树(AVL树)
平衡二叉树又叫作AVL树,所谓平衡二叉树是指对于这棵树中的任意根结点,他的左子树和右子树的高度是平衡的,即高度差是0或者1,何为子树的高度?所谓树的高度是指从根结点开始到叶子结点的最长的路径上的结点的数目(与书上有所不同但是以此为准),因此如果一棵二叉树,上面的任何根结点的左右子树的高度差小于1,那么这棵树就是平衡二叉树。直观的来看,如果某个结点开始的左边的最长路径与右边的最长路径的长度差大于1,那么这个结点就是不平衡的,即以该结点为根的树是不平衡的;反之,如果任何一个结点的左右最长路径都小于等于1,那么这棵树就是平衡的二叉树。
Tree5:平衡二叉树判断练习题
题目:有一棵二叉树,请设计一个算法判断这棵二叉树是否为平衡二叉树。给定二叉树的根结点root,请返回一个bool值,代表这棵树是否为平衡二叉树。
思路:根据定义,要判断一棵二叉树是否是平衡二叉树需要判断树上的每一个结点的左右子树的高度差是否小于等于1,显然需要遍历左右的根结点。其实对于二叉树而言,遍历时使用的遍历方式无非是先序遍历、中序遍历、后序遍历、按层遍历,选取合适的遍历方式即可。其中,先序遍历实际上是从上到下遍历结点的,由于是先根结点再左右子树,因此大致上是从上到下进行的;中序遍历是先左子树再中间根结点再右子树,因此大致上是从下开始的;后序遍历是先左结点再右结点再根结点,因此大致上是从下向上的,按层遍历特征太明显,从上到下,从左到右。本题中,要判断是否是平衡二叉树,需要对每个结点进行判断,对于每个结点又要得到它的左右子树的高度,如果从上到下遍历会对下部的子树进行多次的重复判断因此应该从下往上遍历子树判断子树是否平衡,即先判断左子树是否平衡,并求出其最大的高度,如果不平衡则整棵树不平衡;同理再求右子树是否平衡以及求出最大的高度,如果不平衡则整棵树不平衡;如果左右子树都平衡,那么比较2左右子树是否平衡即可,显然这是一个递归的过程。由于是先遍历左子树,再遍历右子树再遍历根结点,因此显然应该对后续遍历进行改造。
二叉树难的地方在于很多时候需要使用递归,而且是需要携带返回值的递归,此时高度抽象,但是不要灰心,需要多理解,多练习,找找感觉。
很巧妙,很简单:
其实还是一个递归的过程,构造一个递归函数,函数的功能是给定一个一棵子树的根结点root,返回这棵树的高度:递推关系是:对于任何一个跟结点,它的高度是其左右子树高度+1,即比较得到2个子树的高度,选取较大的高度+1即为当前结点的高度,于是求某个结点的高度转化为求它的子树的高度,这显然是一个递推的过程,递推的边界条件是当一直向下递推时,如果某个结点的子树为null,那么这棵子树的高度为0,同时表示到达叶子结点,递归结束return即可。由于需要判断是否是平衡二叉树,于是在求出左右子树的高度同时稍微改编加一些逻辑即可:
写一个递归函数,输入一个根结点,判断这个根结点所在的树是否是平衡二叉树,如果是则返回这棵树的高度,如果不是就返回-1;
①递归的递推条件:判断一棵二叉树是否平衡并求出高度,可以转化为分别判断左右二叉树是否平衡并求出其高度,如果有某一棵子树不是平衡的就直接return -1,如果2子树都不返回-1,那么判断2子树返回的高度差是否<=1,如果不是则返回-1,如果是就返回2棵子树中高度较大的值作为当前子树的高度。
②基准情形:当root为null时,说明是求null结点的高度,显然是0,直接返回0即可。
基准情形也可以理解为边界条件,因为此时的结果是明显的,不需要递归调用,可以直接返回一个明确的值,或者不返回值而是直接return,例如之前遍历时当stack==null时直接return也可以认为是基准情形。
关键是根据逻辑要求构造出一个递归函数。
importjava.util.*;
//判断一棵二叉树是否是平衡二叉树,使用递归,以子树的高度作为返回值
publicclass CheckBalance {
public boolean check(TreeNode root) {
//调用递归方法来判断是否是平衡的
//如果返回-1表不平衡,如果返回一个具体的值,说明树平衡,返回的是树的高度值
booleanresult=this.getHeight(root)==-1?false:true;
return result;
}
//这是一个递归的方法,用于返回一棵二叉树的高度,如果平衡返回高度,如果不平衡返回-1
private int getHeight(TreeNode root){
//基准情形
if(root==null) return 0;
//先求左子树的高度
intleftHeight=this.getHeight(root.left);
//判断左子树是否平衡,调用递归方法后总是认为这个方法已经全部执行完毕
if(leftHeight==-1) return -1;
//再求右子树的高度
intrightHeight=this.getHeight(root.right);
//判断右子树是否平衡
if(rightHeight==-1) return -1;
//判断高度差是否过大
if(Math.abs(leftHeight-rightHeight)>1) return -1;
//执行到此处说明二叉树平衡,返回此树的高度(子树较大值+1)
returnMath.max(leftHeight,rightHeight)+1;
}
}
搜索二叉树
搜索二叉树又叫作二叉树查找树或者二叉排序树,所谓搜索二叉树是指对于任何一个结点,它的左子树的所有结点都比这个根结点要小,它的右子树的所有结点都比这个根结点要大。注意是根结点与左右子树上所有的结点进行比较而不是仅仅与左右孩子结点进行比较,因此根据这个定义,那么当按照中序遍历来遍历一棵搜索二叉树时,必然是单调递增的,即是有序的序列,反之,如果一棵二叉树按照中序遍历得到的序列时有序的,那么这棵二叉树一定是搜索二叉树,因此对于搜索二叉树通常总是进行中序遍历的操作。
红黑树、平衡搜索二叉树(AVL树)等其实都是搜索二叉树的不同实现,它们都努力使得搜索二叉树的搜索效率更高,调整代价更小。
题目:判断一棵二叉树是否是搜索二叉树
思路:根据定义检查在进行中序遍历时结点值是否递增即可,如果一旦出现减小,必然不是搜索二叉树。
可以使用递归也可以不使用递归,当使用递归时注意,由于每次遍历到的结点需要与上一个结点进行比较,因此要保留上一个结点的值,因此在递归外面要设置一个成员变量temp(不能是局部变量)用来记住上一次遍历到的结点值,如果本次结点>=temp表示符合要求,再将temp替换为当前的值即可。并且,为了使得当遇到 packagecom.caicainiao.nowcoder; //主函数 publicclass SearchBinaryTree { public static void main(String[] args){ TreeNode node1=newTreeNode(1); TreeNode node2=newTreeNode(2); TreeNode node3=newTreeNode(3); TreeNode node4=new TreeNode(4); TreeNode node5=newTreeNode(5); TreeNode node6=newTreeNode(6); TreeNode node7=newTreeNode(7); node4.left=node2; node4.right=node6; node2.left=node1; node2.right=node3; node6.left=node5; node6.right=node7; new SearchBinaryTree().checkIsSearch(node4); } //辅助变量:成员变量,所有递归栈共享 int temp; boolean flag=true; //判断是否是搜索二叉树,中序遍历 public void checkIsSearch(TreeNodetreeNode ){ temp=Integer.MIN_VALUE; this.inOrderRecru(treeNode); if(flag){ System.out.println("是搜索二叉树"); }else{ System.out.println("不是搜索二叉树"); } } //中序遍历,同时判断是否递增 private void inOrderRecru(TreeNode treeNode){ //这是一个递归方法,要有结束的边界条件,当没有子节点时返回 if(treeNode==null) return; //①先遍历子树的左子树 this.inOrderRecru(treeNode.left); //②与前一个结点值比较 if(treeNode.val flag=false; return; } temp=treeNode.val; //③遍历子树的右子树 this.inOrderRecru(treeNode.right); } } 满二叉树与完全二叉树 所谓的满二叉树是指一棵二叉树中除了最后一层的结点没有任何子节点之外,剩下每一层上的结点都有2个子节点,即直观地看,满二叉树没有任何确实的结点。对于满二叉树,它的结点数目与层数存在直接的对应关系,如果层数为L,那么满二叉树的结点数目为N=2^L-1,反之L=log2(N+1) 所谓完全二叉树是指除了最后一层之外,其他每一层的结点数目都是满的,如果最后一层也满了就是满二叉树,如果最后一层不满那么结点全部集中在左侧。满二叉树是一棵特殊的完全二叉树,对于完全二叉树这种特殊的结构,它可以使用数组来表示各个结点,此时每个结点与它的子节点的位置下标之间存在直接的对应关系,即从数组中i=1开始存放元素,于是对于某个结点i,它的左孩子下标是2*i,它的右孩子结点时2*i+1,它的父节点下标是i/2。堆(优先队列)就是一种完全二叉树结构。