第五章 树与二叉树
5.1 树的基本概念:
5.1.1 树的定义:
树是由n(n≥0)个结点组成的有限集合(记为T)。其中,
如果n=0,它是一棵空树,这是树的特例;
如果n>0,这n个结点中存在(有仅存在)一个结点作为树的根结点,简称为根(root),其余结点可分为m (m>0)个互不相交的有限集T1,,T2,…,Tm,
其中每一棵子集本身又是一棵符合本定义的树,称为根root的子树。
树形表示法。这是树的最基本的表示,使用一棵倒置的树表示树结构,非常直观和形象。下图就是采用这种表示法。
文氏图表示法。使用集合以及集合的包含关系描述树结构。下图就是树的文氏图表示法。
凹入表示法。使用线段的伸缩描述树结构。下图是树的凹入表示法
5.1.3 树的基本术语
1. 结点的度与树的度:树中某个结点的子树的个数称为该结点的度。树中各结点的度的最大值称为树的度,通常将度为m的树称为m次树。
2. 分支结点与叶结点:度不为零的结点称为非终端结点,又叫分支结点。度为零的结点称为终端结点或叶结点。在分支结点中,每个结点的分支数就是该结点的度。如对于度为1的结点,其分支数为1,被称为单分支结点;对于度为2的结点,其分支数为2,被称为双分支结点,其余类推。
3.路径与路径长度:对于任意两个结点ki和kj,若树中存在一个结点序列ki,ki1,ki2,…,kin,kj,使得序列中除ki外的任一结点都是其在序列中的前一个结点的后继,则称该结点序列为由ki到kj的一条路径,用路径所通过的结点序列(ki,ki1,ki2,…,kj)表示这条路径。路径的长度等于路径所通过的结点数目减1(即路径上分支数目)。可见,路径就是从ki出发“自上而下”到达kj所通过的树中结点序列。显然,从树的根结点到树中其余结点均存在一条路径。
4. 孩子结点、双亲结点和兄弟结点:在一棵树中,每个结点的后继,被称作该结点的孩子结点(或子女结点)。相应地,该结点被称作孩子结点的双亲结点(或父母结点)。具有同一双亲的孩子结点互为兄弟结点。进一步推广这些关系,可以把每个结点的所有子树中的结点称为该结点的子孙结点,从树根结点到达该结点的路径上经过的所有结点被称作该结点的祖先结点。
5. 结点的层次和树的高度:树中的每个结点都处在一定的层次上。结点的层次从树根开始定义,根结点为第1层,它的孩子结点为第2层,以此类推,一个结点所在的层次为其双亲结点所在的层次加1。树中结点的最大层次称为树的高度(或树的深度)。
6. 有序树和无序树:若树中各结点的子树是按照一定的次序从左向右安排的,且相对次序是不能随意变换的,则称为有序树,否则称为无序树。
7. 森林:n(n>0)个互不相交的树的集合称为森林。森林的概念与树的概念十分相近,因为只要把树的根结点删去就成了森林。反之,只要给n棵独立的树加上一个结点,并把这n棵树作为该结点的子树,则森林就变成了树。
5.2 二叉树
1.二叉树的定义
二叉树也称为二次树或二分树,它是有限的结点集合,这个集合或者是空,或者由一个根结点和两棵互不相交的称为左子树和右子树的二叉树组成。
二叉树的定义是一种递归定义。
二叉树有五种基本形态,如下图所示,任何复杂的二叉树都是这五种基本形态的复合。
2.满二叉树定义
在一棵二叉树中,如果所有分支结点都有左孩子结点和右孩子结点,并且叶结点都集中在二叉树的最下一层,这样的二叉树称为满二叉树。
下图所示就是一棵满二叉树。可以对满二叉树的结点进行连续编号,约定编号从树根为1开始,按照层数从小到大、同一层从左到右的次序进行。
图中每个结点外边的数字为对该结点的编号。
3.完全二叉树的定义
若二叉树中最多只有最下面两层的结点的度数可以小于2,并且最下面一层的叶结点 都依次排列在该层最左边的位置上,则这样的二叉树称为完全二叉树
5.2.2 二叉树的性质
性质1 非空二叉树上第i层上至多有2i-1个结点
性质2 深度为k的二叉树至多有2k-1个结点(k≥1)。
性质3 具有n个结点的完全二叉树的深度为
5.2.3 二叉树的存储结构
1. 二叉树的顺序存储结构
二叉树的顺序存储结构中结点的存放次序是:对该树中每个结点进行编号,其编号从小到大的顺序就是结点存放在连续存储单元的先后次序。若把二叉树存储到一维数组中,则该编号就是下标值加1(注意,C/C++语言中数组的起始下标为0)。树中各结点的编号与等高度的完全二叉树中对应位置上结点的编号相同
2. 二叉树的链式存储结构
在二叉树的链接存储中,结点的结构如下:
package ch05;
/**
*
* 二叉链式存储结构下的二叉树结点
*
*/
public class BiTreeNode {
private Object data;// 结点的数据元素private BiTreeNode lchild, rchild; // 左右孩子
public BiTreeNode() {// 构造一个空结点
this(null);
}public BiTreeNode(Object data) {// 构造一棵左右孩子为空的结点
this(data, null, null);
}public BiTreeNode(Object data, BiTreeNode lchild, BiTreeNode rchild) {// 构造一棵数据元素和左右孩子都不为空的结点
this.data = data;
this.lchild = lchild;
this.rchild = rchild;
}public Object getData() {
return data;
}public BiTreeNode getLchild() {
return lchild;
}public BiTreeNode getRchild() {
return rchild;
}public void setData(Object data) {
this.data = data;
}public void setLchild(BiTreeNode lchild) {
this.lchild = lchild;
}public void setRchild(BiTreeNode rchild) {
this.rchild = rchild;
}}
3.二叉链式存储结构下二叉树类的描述
package ch05;
/**
*
*二叉链式存储结构下的二叉树
*
*/
import ch03.LinkQueue;
import ch03.LinkStack;public class BiTree {
private BiTreeNode root;// 树的根结点
public BiTree() {// 构造一棵空树
this.root = null;
}public BiTree(BiTreeNode root) {// 构造一棵树
this.root = root;
}// 由先根遍历的数组和中根遍历的数组建立一棵二叉树
// 其中参数preOrder是整棵树的 先根遍历,inOrder是整棵树的中根遍历,preIndex是
// 先根遍历从preOrder字符串中的开始位置,inIndex是中根遍历从字符串inOrder中的开始位置,count表示树结点的个数
public BiTree(String preOrder, String inOrder, int preIndex, int inIndex,
int count) {
if (count > 0) {// 先根和中根非空
char r = preOrder.charAt(preIndex);// 取先根字符串中的第一个元素作为根结点
int i = 0;
for (; i < count; i++)
// 寻找根结点在中根遍历字符串中的索引
if (r == inOrder.charAt(i + inIndex))
break;root = new BiTreeNode(r);// 建立树的根结点
root.setLchild(new BiTree(preOrder, inOrder, preIndex + 1, inIndex,
i).root);// 建立树的左子树
root.setRchild(new BiTree(preOrder, inOrder, preIndex + i + 1,
inIndex + i + 1, count - i - 1).root);// 建立树的右子树
}
}// 由标明空子树的先根遍历序列建立一棵二叉树
private static int index = 0;// 用于记录preStr的索引值public BiTree(String preStr) {
char c = preStr.charAt(index++);// 取出字符串索引为index的字符,且index增1
if (c != '#') {// 字符不为#
root = new BiTreeNode(c);// 建立树的根结点
root.setLchild(new BiTree(preStr).root);// 建立树的左子树
root.setRchild(new BiTree(preStr).root);// 建立树的右子树
} else
root = null;
}// 先根遍历二叉树基本操作的递归算法
public void preRootTraverse(BiTreeNode T) {
if (T != null) {
System.out.print(T.getData()); // 访问根结点
preRootTraverse(T.getLchild());// 访问左子树
preRootTraverse(T.getRchild());// 访问右子树
}
}// 先根遍历二叉树基本操作的非递归算法
public void preRootTraverse() {
BiTreeNode T = root;
if (T != null) {
LinkStack S = new LinkStack();// 构造栈
S.push(T);// 根结点入栈
while (!S.isEmpty()) {
T = (BiTreeNode) S.pop();// 移除栈顶结点,并返回其值
System.out.print(T.getData()); // 访问结点
while (T != null) {
if (T.getLchild() != null) // 访问左孩子
System.out.print(T.getLchild().getData()); // 访问结点if (T.getRchild() != null)// 右孩子非空入栈
S.push(T.getRchild());T = T.getLchild();
}
}
}
}// 中根遍历二叉树基本操作的递归算法
public void inRootTraverse(BiTreeNode T) {
if (T != null) {
inRootTraverse(T.getLchild());// 访问左子树
System.out.print(T.getData()); // 访问根结点
inRootTraverse(T.getRchild());// 访问右子树
}
}// 中根遍历二叉树基本操作的非递归算法
public void inRootTraverse() {
BiTreeNode T = root;
if (T != null) {
LinkStack S = new LinkStack();// 构造链栈
S.push(T); // 根结点入栈
while (!S.isEmpty()) {
while (S.peek() != null)
// 将栈顶结点的所有左孩子结点入栈
S.push(((BiTreeNode) S.peek()).getLchild());
S.pop(); // 空结点退栈
if (!S.isEmpty()) {
T = (BiTreeNode) S.pop();// 移除栈顶结点,并返回其值
System.out.print(T.getData()); // 访问结点
S.push(T.getRchild());// 结点的右孩子入栈
}
}
}
}// 后根遍历二叉树基本操作的递归算法
public void postRootTraverse(BiTreeNode T) {
if (T != null) {
postRootTraverse(T.getLchild());// 访问左子树
postRootTraverse(T.getRchild());// 访问右子树
System.out.print(T.getData()); // 访问根结点
}
}// 后根遍历二叉树基本操作的非递归算法
public void postRootTraverse() {
BiTreeNode T = root;
if (T != null) {
LinkStack S = new LinkStack();// 构造链栈
S.push(T); // 根结点进栈
Boolean flag;// 访问标记
BiTreeNode p = null;// p指向刚被访问的结点
while (!S.isEmpty()) {
while (S.peek() != null)
// 将栈顶结点的所有左孩子结点入栈
S.push(((BiTreeNode) S.peek()).getLchild());
S.pop(); // 空结点退栈
while (!S.isEmpty()) {
T = (BiTreeNode) S.peek();// 查看栈顶元素
if (T.getRchild() == null || T.getRchild() == p) {
System.out.print(T.getData()); // 访问结点
S.pop();// 移除栈顶元素
p = T;// p指向刚被访问的结点
flag = true;// 设置访问标记
} else {
S.push(T.getRchild());// 右孩子结点入栈
flag = false;// 设置未被访问标记
}
if (!flag)
break;
}
}
}
}// 层次遍历二叉树基本操作的算法(自左向右)
public void levelTraverse() {
BiTreeNode T = root;
if (T != null) {
LinkQueue L = new LinkQueue();// 构造队列
L.offer(T);// 根结点入队列
while (!L.isEmpty()) {
T = (BiTreeNode) L.poll();
System.out.print(T.getData()); // 访问结点
if (T.getLchild() != null)// 左孩子非空,入队列
L.offer(T.getLchild());
if (T.getRchild() != null)// 右孩子非空,入队列
L.offer(T.getRchild());
}
}}
public BiTreeNode getRoot() {
return root;
}public void setRoot(BiTreeNode root) {
this.root = root;
}// 统计叶结点数目
public int countLeafNode(BiTreeNode T) {
int count = 0;
if (T != null) {
if (T.getLchild() == null && T.getRchild() == null) {
++count;// 叶结点数增1
} else {
count += countLeafNode(T.getLchild()); // 加上左子树上叶结点数
count += countLeafNode(T.getRchild());// 加上右子树上的叶结点数
}
}
return count;
}// 统计结点的数目
public int countNode(BiTreeNode T) {
int count = 0;
if (T != null) {
++count;// 结点数增1
count += countNode(T.getLchild()); // 加上左子树上结点数
count += countNode(T.getRchild());// 加上右子树上的结点数
}
return count;
}}
5.3 二叉树的遍历
5.3.1 二叉树的遍历方法及其实现
1.二叉树的遍历方法
1.先序遍历
先序遍历二叉树的过程是:
(1) 访问根结点;
(2) 先序遍历左子树;
(3) 先序遍历右子树。
2. 中序遍历
中序遍历二叉树的过程是:
(1) 中序遍历左子树;
(2) 访问根结点;
(3) 中序遍历右子树。
3. 后序遍历
后序遍历二叉树的过程是:
(1) 后序遍历左子树;
(2) 后序遍历右子树;
(3) 访问根结点。
2.二叉树遍历的一个应用
3.二叉树遍历操作实现的递归算法
算法5.1 //先根遍历二叉树基本操作的递归算法
public void preRootTraverse(BiTreeNode T) {
if (T != null) {
System.out.print(T.getData()); // 访问根结点
preRootTraverse(T.getLchild());// 访问左子树
preRootTraverse(T.getRchild());// 访问右子树
}
}
算法5.2// 中根遍历二叉树基本操作的递归算法
public void inRootTraverse(BiTreeNode T) {
if (T != null) {
inRootTraverse(T.getLchild());// 访问左子树
System.out.print(T.getData()); // 访问根结点
inRootTraverse(T.getRchild());// 访问右子树
}
}
算法5.3// 后根遍历二叉树基本操作的递归算法
public void postRootTraverse(BiTreeNode T) {
if (T != null) {
postRootTraverse(T.getLchild());// 访问左子树
postRootTraverse(T.getRchild());// 访问右子树
System.out.print(T.getData()); // 访问根结点
}
}
4. 二叉树遍历操作实现的非递归算法
1. 先根遍历的实现
1.1 创建一个栈对象,根结点入栈
算法5.4 先根遍历操作实现的非递归算法
// 先根遍历二叉树基本操作的非递归算法
public void preRootTraverse() {
BiTreeNode T = root;
if (T != null) {
LinkStack S = new LinkStack();// 构造栈
S.push(T);// 根结点入栈
while (!S.isEmpty()) {
T = (BiTreeNode) S.pop();// 移除栈顶结点,并返回去值
System.out.print(T.getData()); // 访问结点
while (T != null) {
if (T.getLchild() != null) // 访问左孩子
System.out.print(T.getLchild().getData()); // 访问结点if (T.getRchild() != null)// 右孩子非空入栈
S.push(T.getRchild());T = T.getLchild();
}
}
}
}
算法5.5 中根遍历操作实现的非递归算法
// 中根遍历二叉树基本操作的非递归算法
public void inRootTraverse() {
BiTreeNode T = root;
if (T != null) {
LinkStack S = new LinkStack();// 构造链栈
S.push(T); // 根结点入栈
while (!S.isEmpty()) {
while (S.peek() != null)
// 将栈顶结点的所有左孩子结点入栈
S.push(((BiTreeNode) S.peek()).getLchild());
S.pop(); // 空结点退栈
if (!S.isEmpty()) {
T = (BiTreeNode) S.pop();// 移除栈顶结点,并返回去值
System.out.print(T.getData()); // 访问结点
S.push(T.getRchild());// 结点的右孩子入栈
}
}
}
}
算法5.6 后根遍历操作实现的非递归算法
// 后根遍历二叉树基本操作的非递归算法
public void postRootTraverse() {
BiTreeNode T = root;
if (T != null) {
LinkStack S = new LinkStack();// 构造链栈
S.push(T); // 根结点进栈
Boolean flag;// 访问标记
BiTreeNode p = null;// p指向刚被访问的结点
while (!S.isEmpty()) {
while (S.peek() != null)
// 将栈顶结点的所有左孩子结点入栈
S.push(((BiTreeNode) S.peek()).getLchild());
S.pop(); // 空结点退栈
while (!S.isEmpty()) {
T = (BiTreeNode) S.peek();// 查看栈顶元素
if (T.getRchild() == null || T.getRchild() == p) {
System.out.print(T.getData()); // 访问结点
S.pop();// 移除栈顶元素
p = T;// p指向刚被访问的结点
flag = true;// 设置访问标记
} else {
S.push(T.getRchild());// 右孩子结点入栈
flag = false;// 设置未被访问标记
}
if (!flag)
break;
}
}
}
}
算法5.7 层次遍历操作实现的非递归算法
// 层次遍历二叉树基本操作的算法(自左向右)
public void levelTraverse() {
BiTreeNode T = root;
if (T != null) {
LinkQueue L = new LinkQueue();// 构造队列
L.offer(T);// 根结点入队列
while (!L.isEmpty()) {
T = (BiTreeNode) L.poll();
System.out.print(T.getData()); // 访问结点
if (T.getLchild() != null)// 左孩子非空,入队列
L.offer(T.getLchild());
if (T.getRchild() != null)// 右孩子非空,入队列
L.offer(T.getRchild());
}
}
}
下面给出二叉树BiTree类的程序代码,此程序完成的功能是先建立如图5.12 所示的二叉树的不带头结点的二叉链式存储结构,然后分别输出先根,中根,后根和层次遍历二叉权所得到的遍历序列。
public class DebugBiTree {
// 构建如图5.10(a)所示的二叉树
public BiTree createBiTree() {
BiTreeNode d = new BiTreeNode('D');
BiTreeNode g = new BiTreeNode('G');
BiTreeNode h = new BiTreeNode('H');
BiTreeNode e = new BiTreeNode('E', g, null);
BiTreeNode b = new BiTreeNode('B', d, e);
BiTreeNode f = new BiTreeNode('F', null, h);
BiTreeNode c = new BiTreeNode('C', f, null);
BiTreeNode a = new BiTreeNode('A', b, c);
return new BiTree(a);
// 创建根结点为a的二叉树,其中二叉树类BitTree的定义紧接在DebugBiTree类的后面给出
}
public static void main(String[] args) {
DebugBiTree debugBiTree = new DebugBiTree();
BiTree biTree = debugBiTree.createBiTree();
BiTreeNode root = biTree.getRoot();// 取得树的根结点
// 调试先根遍历
System.out.print("(递归)先根遍历序列为:");
biTree.preRootTraverse(root);
System.out.println(); // 输出换行
System.out.print("(非递归)先根遍历序列为:");
biTree.preRootTraverse();
System.out.println();
// 调试中根遍历
System.out.print("(递归)中根遍历序列为:");
biTree.inRootTraverse(root);
System.out.println();
System.out.print("(非递归)中根遍历序列为:");
biTree.inRootTraverse();
System.out.println();
// 调试后根遍历
System.out.print("(递归)后根遍历序列为:");
biTree.postRootTraverse(root);
System.out.println();
System.out.print("(非递归)后根遍历序列为:");
biTree.postRootTraverse();
System.out.println();
// 调试层次遍历
System.out.print("层次遍历序列为:");
biTree.levelTraverse();
System.out.println();
}
}
// 运行结果:
// (递归)先根遍历序列为:ABDEGCFH
// (非递归)先根遍历序列为:ABDEGCFH
// (递归)中根遍历序列为:DBGEAFHC
// (非递归)中根遍历序列为:DBGEAFHC
// (递归)后根遍历序列为:DGEBHFCA
// (非递归)后根遍历序列为:DGEBHFCA
// 层次遍历序列为:ABCDEFGH
算法5.8 二叉树上的查找算法
public BiTreeNode searchNode(BiTreeNode T,Object x) {
// 在以T为根结点的二叉树中查找值为x的结点,若找到,则返回该结点,否则返回空值
if (T != null) {
if (T.getData().equals(x))
return T;
else {
BiTreeNode lresult= searchNode(T.getLchild(),x); // 在左子树上查找
return (lresult!=null?lresult:searchNode(T.getRchild(),x)) ;
// 若左子树上没找到,则到右子树上找
}
}
return null;
}
Package ch05;
/**
*
* 【例5-2】 编写成员函数计算二叉树中结点的个数
*
*/
public class Example5_2 {
// 采用先根遍历的方式对树进行遍历,计算树中的结点数
public int countNode(BiTreeNode T) {
int count = 0;
if (T != null) {
++count;// 根结点增1
count += countNode(T.getLchild()); // 加上左子树上结点数
count += countNode(T.getRchild());// 加上右子树上的结点数
}
return count;
}
public static void main(String[] args) {
BiTree biTree = new BiTreeCreator().createBiTree();// 创建一棵树
BiTreeNode root = biTree.getRoot();// 取得树的根结点
// 调试【例5-1 】编写成员函数完成在二叉树中查找数据元素值为x的结点的操作
Example5_2 e = new Example5_2();
System.out.println("树中的结点个数为: " + e.countNode(root));
}
}
// 运行结果:
// 树中的结点个数为: 13
算法5.9 统计二叉树中结点个数的算法
public int countNode(BiTreeNode T) {int count = 0;
if (T != null) {
++count;// 根结点增1
count += countNode(T.getLchild()); // 加上左子树上结点数
count += countNode(T.getRchild());// 加上右子树上的结点数
}
return count;
}
例5.3 求二叉树的深度:编写算法完成求二叉树的深度的操作
5.3.3 建立二叉树