二叉树:Binary Tree
- 二叉树n(n≥0) 个结点的有限集,它或者是空集,或者是由一个根和称为左、右子树的两个互不相交的二叉树组成
1.性质
1、若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2 i − 1 2^{i-1} 2i−1个节点
证明:
当 i = 1 , 根 节 点 有 一 个 , 2 i − 1 = 2 0 = 1 , 正 确 假 设 i − 1 成 立 , 即 第 i − 1 层 上 至 多 有 2 i − 2 个 结 点 由 于 二 叉 树 结 点 的 度 最 多 为 2 , 故 在 第 i 层 上 的 最 大 结 点 数 位 第 i − 1 层 上 最 大 节 点 数 的 2 倍 。 即 2 ∗ 2 i − 2 = 2 i − 1 当i = 1,根节点有一个,2^{i-1} = 2^0 = 1,正确 \\ 假设i-1成立,即第i-1层上至多有2^{i-2}个结点 \\ 由于二叉树结点的度最多为2,故在第i层上的最大结点数位第i-1层上最大节点数的2倍。即2*2^{i-2} = 2^{i-1} 当i=1,根节点有一个,2i−1=20=1,正确假设i−1成立,即第i−1层上至多有2i−2个结点由于二叉树结点的度最多为2,故在第i层上的最大结点数位第i−1层上最大节点数的2倍。即2∗2i−2=2i−12、若规定只有根节点的二叉树的深度为1,则深度为k的二叉树的最大节点数是 2 k − 1 2^k-1 2k−1
证明:
当 k = 1 k=1 k=1时,根节点有一个, 2 k − 1 = 2 1 − 1 = 1 2^k-1 = 2^1-1 = 1 2k−1=21−1=1,正确
假设 k − 1 k-1 k−1成立,第 i − 1 i-1 i−1层的结点个数为 2 k − 1 − 1 2^{k-1}-1 2k−1−1
由于二叉树第k层最多有 2 k − 1 2^{k-1} 2k−1个结点。那么二叉树的最大结点个数为: 2 k − 1 + 2 k − 1 − 1 = 2 k − 1 2^{k-1}+2^{k-1}-1=2^{k}-1 2k−1+2k−1−1=2k−1
3、对任何一棵二叉树,如果其叶子节点个数为 n 0 n_0 n0,度为2的非叶子节点个数为 n 2 n_2 n2,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1(注意这里的度是出度)
证明:
已知,叶子节点的个数为 n 0 n_0 n0,度为2的结点数为 n 2 n_2 n2,度为1的结点数为 n 1 n_1 n1
由于二叉树中的所有结点的度只能为0、1、2;故二叉树的结点总数为: n = n 0 + n 1 + n 2 n = n_0+n_1+n_2 n=n0+n1+n2
除了根节点外,其他结点都有一个分支进入,设B为分支总数,则 n = B + 1 n = B+1 n=B+1
由于这些分支均是由度为1或2的结点出发。则
B = n 1 + 2 n 2 B = n_1+2n_2 B=n1+2n2,
综上:
n 0 + n 1 + n 2 = n 1 + 2 n 2 + 1 n_0+n_1+n_2 = n_1+2n_2+1 n0+n1+n2=n1+2n2+1
4、具有n个节点的完全二叉树的深度k为: ⌈ l o g 2 n + 1 ⌉ \lceil log_2{n+1} \rceil ⌈log2n+1⌉或者( ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n \rfloor+1 ⌊log2n⌋+1)
证明:
k − 1 层 满 二 叉 树 结 点 数 < k 层 完 全 二 叉 树 结 点 数 < = k 层 满 二 叉 树 结 点 数 k-1层满二叉树结点数<k层完全二叉树结点数<=k层满二叉树结点数 k−1层满二叉树结点数<k层完全二叉树结点数<=k层满二叉树结点数
2 k − 1 − 1 < n < = 2 k − 1 2^{k-1}-1<n<=2^{k}-1 2k−1−1<n<=2k−1
2 k − 1 < = n < 2 k 2^{k-1}<=n<2^k 2k−1<=n<2k
k − 1 < = l o g 2 n < k k-1<=log_2{n}<k k−1<=log2n<k
k是整数, k = ⌊ l o g 2 n ⌋ + 1 k = \lfloor log_2n \rfloor+1 k=⌊log2n⌋+1
5、对于具有n个节点的完全二叉树,如果按照从上至下从左至右的顺序对所有结点从0开始编号,则对于序号为i的节点有:
(1)如果i>=0,则序号为i节点的双亲结点的序号为 ( i + 1 ) / / 2 (i+1) // 2 (i+1)//2;如果i=0,则序号i节点无双亲结点,i就是根节点
(2)如果2i+1<n,则序号i结点的左孩子的序号为2i+1,右孩子的序号为2i+2;如果2i+1>=n,则序号i节点无孩子节点
例子:编号为3 的子节点是:2×3+1=7,2×3+2=8;编号3的父节点是:(3-1)//2 = 1
2.分类
- 完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
- 注意:值得注意的是:完全二叉树用一个列表就可表示(下方的遍历代码就是以完全二叉树为例)
- 特点:
- 所有的叶子节点都出现在第k层或k-1层
- 若任一结点,如果其右子树的最大层次为i,那么其左子树的最大层次是i或者i+1
- 当完全二叉树的结点数是偶数个时,度为1的结点数是1个
当完全二叉树的结点数是奇数个时,度为1的结点数是0个
- 具有n个节点的完全二叉树有 ⌈ n 2 ⌉ \lceil {n\over 2} \rceil ⌈2n⌉个叶子节点
n为奇数时, n + 1 2 n+1\over 2 2n+1
n为偶数时, n 2 n\over 2 2n
证明:
n为奇数时,度为1的有0个, n = n 0 + n 2 = 2 n 0 − 1 n = n_0+n_2 = 2n_0-1 n=n0+n2=2n0−1
n为偶数时,度为1的有1个, n = n 0 + n 2 + 1 = 2 n 0 − 1 + 1 = 2 n 0 n = n_0+n_2+1 = 2n_0-1+1 = 2n_0 n=n0+n2+1=2n0−1+1=2n0
- 满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。(特殊的完全二叉树)
3.代码
import 栈.Stack;
import 队列.Queue;
/**
* 1. 二叉树的建立——中序与后序、先序与中序、表明空子树
* 2. 深度遍历——前序中序后序(递归算法)
* 3. 深度遍历——前序中序后序(非递归算法)
* 4. 宽度遍历——层次遍历
*
*/
public class BinaryTree {
TreeNode root;
public BinaryTree(){
this.root = null;//用左右指针来管理,因此不需要new TreeNode()
}
public static void main(String[] args) {
BinaryTree t = new BinaryTree();
TreeNode pp = t.enter1("DGBAECHF","GDBEHFCA");
// t.inOrder(pp);
// System.out.println();
// t.inOrderTraveral(pp);
// t.preOrder(pp);
// System.out.println();
// t.preOrderTraveral(pp);
t.postOrder(pp);
System.out.println();
t.postOrderTraveral(pp);
System.out.println();
t.BFS(pp);
// t.preOrder(pp);
// System.out.println();
// t.inOrder(pp);
// System.out.println();
// t.postOrder(pp);
// System.out.println();
// t.BFS(pp);
// TreeNode p2 = t.enter2("ABD...C.E..");
// t.BFS(p2);
// t.preOrder(p2);
// System.out.println();
// t.inOrder(p2);
// System.out.println();
// t.postOrder(p2);
}
/*-----------------------------------------------------确定一颗二叉树-------------------------------------------------*/
/**
* @D:先序、中序确立二叉树
* @param prefix
* @param infix
* @return
*/
public TreeNode enter(String prefix,String infix){
TreeNode p = null; //由根节点开始
int k,n; //k为结点所在位置,n为字符串的长度
String root,presub,insub; //root根节点
n = prefix.length();
if (n > 0) {
root = prefix.substring(0,1); //取出根结点
p = new TreeNode(root); //开始建树
k = infix.indexOf(root); //找出中序根节点所在位置
presub = prefix.substring(1,k+1);
insub = infix.substring(0,k);
p.left = enter(presub,insub);//递归的建立左子树
presub = prefix.substring(k+1,n);
insub = infix.substring(k+1,n);
p.right = enter(presub,insub);
}
return p;//此时返回的p时回溯后的根节点
}
/**
* @D:中序、后续确定一颗二叉树
* @param infix
* @param postfix
* @return
*/
public TreeNode enter1(String infix,String postfix){
TreeNode p = null;
int k,n;
n = postfix.length();
String root,insub,postsub;
if (n >0) {
root = postfix.substring(n-1);
p = new TreeNode(root);
k = infix.indexOf(root);
//递归地建立左子树
insub = infix.substring(0,k);
postsub = postfix.substring(0,k);
p.left = enter1(insub,postsub);
//先递归的建立右子树
insub = infix.substring(k+1,n);
postsub = postfix.substring(k,n-1);
p.right = enter1(insub,postsub);
}
return p;
}
/**
* @D:以标明空子树的方法建立二叉树
* @param prefix
* @return
*/
int count = 0; //这里需要设置一个全局变量
public TreeNode enter2(String prefix){
TreeNode p = null;
if (count < prefix.length()) {
char ch = prefix.charAt(count);
count++;
if (ch != '.') {
p = new TreeNode(ch+"");
p.left = enter2(prefix);
p.right = enter2(prefix);
}else{
return null;
}
}
return p;
}
/*---------------------------------------深度遍历二叉树--------------------------------------------------------------*/
/*---------------------------------------------------递归算法----------------------------------------------------*/
/**
* @D:前序遍历
* @param root
*/
public void preOrder(TreeNode root){
if (root == null) {
return;
}
System.out.print(root.data+" ");
preOrder(root.left);
preOrder(root.right);
}
/**
* @D:中序遍历
* @param root
*/
public void inOrder(TreeNode root){
if (root == null) {
return;
}
inOrder(root.left);
System.out.print(root.data+" ");
inOrder(root.right);
}
/**
* @D:后序遍历
* @param root
*/
public void postOrder(TreeNode root){
if (root == null) {
return;
}
postOrder(root.left);
postOrder(root.right);
System.out.print(root.data+" ");
}
/*---------------------------------------------------非递归算法--------------------------------------------------*/
/**
* @D:非递归先序遍历算法
* @param root
*/
public void preOrderTraveral(TreeNode root){
Stack stack = new Stack(20);
System.out.println("先序遍历");
while(root !=null || !stack.isEmpty()){
if (root != null) {
System.out.print(root.data+" ");
stack.push(root);
root = root.left;
}else{
root = (TreeNode)stack.pop();
root = root.right;
}
}
System.out.println();
}
/**
* @D:非递归的中序遍历算法
* @param root
*/
public void inOrderTraveral(TreeNode root){
Stack stack = new Stack(20);
System.out.println("先序遍历:");
while (root!=null || !stack.isEmpty()){ //root为空时(叶子节点就是空),表明走出了一条路径,此时要回溯
if (root != null) {
stack.push(root); //入栈
root = root.left; //p结点下沉
}else{ //左孩子没有,就输弹出元素(左子树根节点),并走向右节点;或者全空回溯走右孩子
root = (TreeNode)stack.pop();
System.out.print(root.data +" ");
root = root.right;
}
}
System.out.println();
}
/**
* @D:非递归的后序遍历算法
* @param root
*/
public void postOrderTraveral(TreeNode root){
//新添加一个指针用于回溯路径,先序、中序都可以很简单的实行回溯。但后序有些许麻烦。这里执行root==null强制回溯。
//同时,prior指针解决了结点有右孩子,在输出右节点后回溯,此时右孩子部位空,用prior记录其右孩子,代表已经输出,如果在等于prior则直接输出。
TreeNode prior = null;
Stack stack = new Stack(20);
while(root !=null || !stack.isEmpty()){
if (root != null) {
stack.push(root);
root = root.left;
}else{
root = (TreeNode)stack.pop();
if (root.right ==null || root.right == prior) {
System.out.print(root.data+" ");
prior = root;
root = null;
}else{
stack.push(root); //有右孩子就再次入栈并进入右孩子
root = root.right;
}
}
}
}
/*--------------------------------------------------广度遍历二叉树---------------------------------------------------*/
/**
* @D:层次遍历
* @param p:根节点
*/
public void BFS(TreeNode p){
Queue queue = new Queue(25);
while (p!=null){
System.out.print(p.data+" ");
if (p.left != null)
queue.enqueue(p.left);
if (p.right != null)
queue.enqueue(p.right);
p = (TreeNode)queue.dequeue();
}
System.out.println();
}
/*--------------------------------------节点类------------------------------------------------*/
public class TreeNode{
public String data;//data
public TreeNode left,right;
public TreeNode(String data){
this.data = data;
}
}
}