二叉树及相关题目
一、树
树是一种在实际编程中经常遇到的数据结构。它的逻辑很简单:除了根结点之外每个结点只有一个父结点;除了叶结点之外所有结点都有一个或多个子结点,叶结点没有子结点。父结点和子结点之间用指针链接。由于树的操作一般涉及大量的指针,因此一般与树有关的题目都稍显复杂。
二、二叉树
二叉树是树中最常见的一种。所谓二叉树也就是树的一种特殊结构,在二叉树中每个结点最多只能有两个子结点。一棵二叉树具有以下特点:
- 每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点;
- 左子树和右子树是有顺序的,次序不能任意颠倒;
- 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。
另外,还有以下一些特殊的二叉树。
1)斜树
所有结点都只在左子树上的二叉树叫左斜树。所有结点都只有右子树的二叉树叫右斜树。
图1 左斜树
图2 右斜树
2)满二叉树
在一棵二叉树中,如果所有的分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树为满二叉树。
满二叉树的特点有:
- 叶子只能出现在最下一层、出现在其它层就不可能达成平衡;
- 非叶子结点的度一定是2;
- 在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
图3 满二叉树
3)完全二叉树
对一棵具有n个结点的二叉树按层编号,如果编号为i(i<=i<=n)的结点与同样深度的满二叉树中的编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
图4 完全二叉树
特点:
- 叶子结点只能出现在最下层和次下层;
- 最下层的叶子结点集中在树的左部;
- 倒数第二层若存在叶子结点,一定在右部连续位置;
- 如果结点度为1,则该节点只有左子树,即没有右子树;
- 同样节点数目的二叉树,完全二叉树深度最小。
注:满二叉树一定是完全二叉树,但反过来不一定成立。
4)二叉搜索树
二叉搜索树是一种特殊的二叉树,最主要的特征是在二叉搜索树中左子结点总是小于或等于根结点,右子结点总是大于或者等于根结点。如图5所示:
图5 二叉搜索树
另外,比较特殊的二叉树是堆和红黑树等。堆分为最大堆和最小堆;在最大堆中根结点的值最大,在最小堆中根结点的值最小;有很多需要快速找到最大值或者最小值的问题都可以用堆来解决。红黑树是把树中的节点定义为红、黑两种颜色,并通过规则确保从跟结点到叶结点的最长路径不超过最短路径的两倍。
5)树的遍历
在二叉树中最重要的操作,莫过于遍历,即按照某一顺序访问树中的所有结点,通常树有以下几种遍历方式:
- 前序遍历:顾名思义,即最先访问根结点。具体顺序为:先访问根结点,再访问左子结点,最后访问右子结点。图5中的二叉树前序遍历的顺序为:10、6、4、8、14、12、16。
- 中序遍历:顾名思义,即中间访问根结点。具体顺序为:先访问左子结点,再访问根结点,最后访问右子结点。图5中的二叉树中序遍历的顺序为:4、6、8、10、12、14、16。
- 后序遍历:顾名思义,即最后访问根结点。具体顺序为:先访问左子结点,再访问右子结点,最后访问根结点。图5中的二叉树的后序遍历的顺序为:4、8、6、12、16、14、10。
- 宽度优先遍历:先访问树的第一层结点,再访问树的第二层结点……一直访问到最下面一层结点。在同一层结点中,从左到右的顺序依次访问。我们可以对包括二叉树在内的所有树进行宽度优先遍历。图5中的二叉树的宽度优先遍历顺序是:10、6、14、4、8、12、16。
三、相关题目
问题一:重建二叉树
题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。加入输入前序遍历序列{1、2、4、7、3、6、6、8}和中序遍历序列{4、7、2、1、5、3、8、6},重建二叉树,并输出它的头结点。
二叉树结点的定义代码如下:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
题目分析:在二叉树的前序遍历序列中,第一个数字总是树的根结点的值。但在中序遍历序列中,根结点的值在序列的中间,左子树的结点的值位于根结点的值得左边,而右子树的结点的值位于根结点的值得右边。因此我们需要扫描中序遍历序列,才能找到根结点的值。
前序遍历序列的第一个数字1就是根结点的值。扫描中序遍历序列,就能确定根结点值的位置。根据中序遍历特点,在根结点的值1前面的3个数字都是左子树结点的值,位于1后面的数字都是右子树上的值。
由于在中序遍历序列中,有3个数字是左子树结点的值,因此左子树总共有3个左子结点。同样,在前序遍历的序列中,根结点后面的3个数字就是3个左子树结点的值,再后面的所有数字都是右子结点的值。这样,我们在前序遍历和中序遍历两个序列中,分别找到了左右子树对应的子序列。
既然我们已经分别找到了左、右子树的前序遍历序列和中序遍历序列,我们可以用同样的方法分别取构建左右子树。也就是说,接下来的事情可以用递归的方法去完成。
在分析完成整个过程之后,可以开始编写代码:
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
return re(pre,0,pre.length-1,in,0,in.length-1);
}
public TreeNode re(int[] pre,int preL,int preR,int[] in,int inL,int inR){
if (preL>preR||inL>inR){
return null;
}
TreeNode root=new TreeNode(pre[preL]);
for (int i=inL;i<=inR;i++){
if (in[i]==pre[preL]){
root.left=re(pre,preL+1,preL+i-inL,in,inL,i-1);
root.right=re(pre,preL+1+i-inL,preR,in,i+1,inR);
}
}
return root;
}
}