参考书籍:数据结构C语言-严蔚敏、吴伟民 Java程序员面试宝典-何昊等
参考链接:http://www.cnblogs.com/bmrs/archive/2010/08/19/SloveTree.html
二叉树的基本概念
度:结点所拥有的子树的数目称为该结点的度。
叶结点:度为0的结点称为叶结点。
分支结点:度不为0的结点称为分支结点。一个树除叶结点外,其余都是分支结点。
树的深度:树中所有结点的最大层树称为树的深度。
树的度:树中各结点度的最大值称为该树的度。叶子结点的度为0
结点的层数:规定根结点的层数为1,其余结点的层数等于他的双亲结点的层树加1。
满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层上,这样的一棵树称为满二叉树。
完全二叉树:一棵深度为k的有n个结点的二叉树,对树中的结点从上至下,从左到右进行编号,如果编号为i(1<=i<=n)的结点与满二叉树中的位置相同,那么这棵二 叉树就是完全二叉树。
完全二叉树的特点是:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。满二叉树肯定是完全二叉树,完全二叉树不一定是满二叉树。
二叉树的性质
性质1:一个非空二叉树第i层上最多有2^(i-1)个结点(i>=1);
性质2:一棵深度为k的二叉树中,最多具有2^k-1个结点,最少有k个结点。
性质3:一棵非空二叉树,度为0的结点(叶子结点)总是比度为2的结点多一个,即如果叶子结点数为n0,度为2的结点数为n2,那么n0 = n2 + 1;
证明:n0:度为0的结点总数,n1:度为1的结点总数 ,n2:度为2的结点总数,则n = n0+n1+n2,根据二叉树和树的性质,可知n = n1 + 2*n2+1(所有结点的度数之和加1 = 结点总数),所以:n0+n1+n2 = n1+2*n2+1,可以得到n0 = n2 + 1;
性质4:具有n个结点的完全二叉树深度为[log2(n)]向下取整+1或[log2(n+1)]向上取整。
性质5:对于具有n个结点的完全二叉树,如果按照从上至下和从左到右的顺序对二叉树中的所有结点从1开始顺序编号,则对于任意的序号为i的结点,有:
1、如果i>1,那么序号为i的结点的双亲结点的序号为i/2;如果i=1,那么序号为i的结点是根结点,无双亲结点。
2、如果2i<=n,那么序号为i的结点的左孩子结点的序号为2i;如果2i > n,那么序号为i的结点无左孩子。
3、如果2i+1 <=n,那么序号为i的结点的右孩子结点的序号为2i+1;如果2i +1> n,那么序号为i的结点无右孩子。
练习
1 、一棵完全二叉树有1001个结点,其中叶子结点的个数是多少?
二叉树的公式:n = n0+n1+n2 = n0+n1+(n0-1) = 2*n0+n1-1,又因为在完全二叉树中,n1只能取0或1,如果n1 = 1,则2*n0 = 1001得到n0为小数,不符合,所以n1为0,此时2*n0 - 1 = 1001,得到n0为501.
2、如果根的层次为1,具有61个结点的完全二叉树的高度为多少?
答案:6
3、在具有100个结点的树中,其边的数目为多少?
在一棵树中,除了根结点外,每一个结点都有一条入边,因此总边数应该是100-1,即99,所以答案是99.
二叉树的常见操作
package com.Howard.test11.BinaryTree;
public class Node {
public int data;
public Node left;
public Node right;
//以下两个用于计算结点间最大距离
//左子树距根结点的最大距离
public int leftMaxDistance;
//右子树距根结点的最大距离
public int rightMaxDistance;
public Node (int data) {
this.data = data;
this.left = null;
this.right = null;
}
}
package com.Howard.test11.BinaryTree;
import java.util.LinkedList;
import java.util.Queue;
/**
* 二叉树
* @author Howard
* 2017年4月22日
*/
public class BinaryTree {
private Node root;
public BinaryTree() {
root = null;
}
/**
* 将data插入到排序二叉树中
* @param data
*/
public void insert(int data) {
Node newNode = new Node(data);
if (root == null) {
root = newNode;
}else {
Node current = root;
Node parent;
while(true) {
parent = current;
if (data < current.data) {
current = current.left;
if (current == null) {
parent.left = newNode;
return;
}
}else {
current = current.right;
if (current == null) {
parent.right = newNode;
return;
}
}
}
}
}
/**
* 构建二叉树
* @param data
*/
public void buildTree (int[] data) {
for (int i = 0; i < data.length; i++) {
insert(data[i]);
}
}
/**
* 中序遍历
* @param localRoot
*/
public void inOrder(Node localRoot) {
if (localRoot != null) {
inOrder(localRoot.left);
System.out.print(localRoot.data + " ");
inOrder(localRoot.right);
}
}
public void inOrder() {
this.inOrder(this.root);
}
/**
* 先序遍历
* @param localRoot
*/
public void preOrder(Node localRoot) {
if (localRoot != null) {
System.out.print(localRoot.data + " ");
preOrder(localRoot.left);
preOrder(localRoot.right);
}
}
public void preOrder() {
this.preOrder(this.root);
}
/**
* 后序遍历
* @param localRoot
*/
public void postOrder(Node localRoot) {
if (localRoot != null) {
postOrder(localRoot.left);
postOrder(localRoot.right);
System.out.print(localRoot.data + " ");
}
}
public void postOrder() {
this.postOrder(this.root);
}
/**
* 层序遍历
* 利用队列实现
* 先将根结点放入队列中,然后每次都从队列中取出一个结点打印该结点的值,若这个结点有子结点,则将他
* 的子结点放入队列尾,直到队列为空
*/
public void layerTranverse() {
if (this.root == null) {
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(this.root);
while (!queue.isEmpty()) {
Node node = queue.poll();
System.out.print(node.data + " ");
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
public int maxlen = 0;
private int getMax(int a,int b) {
return a > b ? a : b;
}
/**
* 求二叉树中结点的最大距离
* 结点的距离是值这两个结点之间边的个数 求一棵二叉树中相距最远的两个结点之间的距离
* 思路:首先求左子树距根结点的最大距离 ,其次求右子树距根结点的最大距离
* 则二叉树中结点的最大距离是两者之和
* 可以用递归实现
* @param root
*/
public void FindMaxDistance(Node root) {
if (root == null) {
return;
}
if (root.left == null) {
root.leftMaxDistance = 0;
}
if (root.right == null) {
root.rightMaxDistance = 0;
}
if (root.left != null) {
FindMaxDistance(root.left);
}
if (root.right != null) {
FindMaxDistance(root.right);
}
//计算左子树中距离根结点的最大距离
if (root.left != null) {
root.leftMaxDistance = getMax(root.left.leftMaxDistance, root.left.rightMaxDistance) + 1;
}
//计算右子树中距离根结点的最大距离
if (root.right != null) {
root.rightMaxDistance = getMax(root.right.leftMaxDistance, root.right.rightMaxDistance) + 1;
}
if (root.leftMaxDistance + root.rightMaxDistance > maxlen) {
maxlen = root.leftMaxDistance + root.rightMaxDistance;
}
}
/**
* 测试
* @param args
*/
public static void main(String[] args) {
BinaryTree biTree = new BinaryTree();
int[] data = {2,8,7,4,9,3,1,6,7,5};
biTree.buildTree(data);
System.out.println("中序遍历:");
biTree.inOrder();
System.out.println();
System.out.println("先序遍历:");
biTree.preOrder();
System.out.println();
System.out.println("后序遍历:");
biTree.postOrder();
System.out.println();
System.out.println("层序遍历:");
biTree.layerTranverse();
System.out.println();
biTree.FindMaxDistance(biTree.root);
System.out.println(biTree.maxlen);
}
}
另外,对于二叉树还有一种常见的题,即:
已知中序遍历和先序(或后序),求解树。
(如果只知道树的先序和后序,是无法完整的求出中序的)
一、已知二叉树的前序序列和中序序列,求解树。
1、确定树的根节点。树根是当前树中所有元素在前序遍历中最先出现的元素。
2、求解树的子树。找出根节点在中序遍历中的位置,根左边的所有元素就是左子树,根右边的所有元素就是右子树。若根节点左边或右边为空,则该方向子树为空;若根节点左边和右边都为空,则根节点已经为叶子节点。
3、递归求解树。将左子树和右子树分别看成一棵二叉树,重复1、2、3步,直到所有的节点完成定位。
二、已知二叉树的后序序列和中序序列,求解树。
1、确定树的根。树根是当前树中所有元素在后序遍历中最后出现的元素。
2、求解树的子树。找出根节点在中序遍历中的位置,根左边的所有元素就是左子树,根右边的所有元素就是右子树。若根节点左边或右边为空,则该方向子树为空;若根节点左边和右边都为空,则根节点已经为叶子节点。
3、递归求解树。将左子树和右子树分别看成一棵二叉树,重复1、2、3步,直到所有的节点完成定位。
举例说明:根据已知求解二叉树
中序序列 HLDBEKAFCG
后序序列 LHDKEBFGCA
1、在后序序列LHDKEBFGCA中最后出现的元素为A,HLDBEK|
A|FCG
2、在后序序列LHDKEB中最后出现的元素为B,HLD|
B|EK|A|FCG
3、在后序序列LHD中最后出现的元素为D,HL|
D|B|EK|A|FCG
4、在后序序列LH中最后出现的元素为H,
H|L|D|B|EK|A|FCG
5、在后序序列KE中最后出现的元素为E,H|L|D|B|
E|K|A|FCG
5、在后序序列FGC中最后出现的元素为C,H|L|D|B|E|K|A|F|
C|G
6、所有元素都已经定位,二叉树求解完成。
A
/ \
B C
/ \ / \
D E F G
/ \
H K
\
/ \
B C
/ \ / \
D E F G
/ \
H K
\
L