一、二叉树的特性
性质1
若规定根节点的层次为0,则一棵非空二叉树的第 i 层上最多有 (i >= 0)个结点
性质2
若规定空二叉树树的深度为-1(即根结点的深度为0),则深度为k的二叉树的最大结点数是 (k>=-1)个
性质3
具有n个结点的完全二叉树的深度k为不超过lb(n+1)-1的最大整数
性质4
对于一颗非空的二叉树,如果叶结点个数为,度为2的结点树为
,则有
=
+1
性质5
对于具有n个结点的完全二叉树,如果按照从上至下和从左至右的顺序对所有结点从0开始顺序编号,则对于序号为 i 的结点有:
1.如果 i>0,则序号为i的结点的双亲结点的序号为 (i-1)/2(/表示整除);如果i=0,则序号为i的结点为根结点,无双亲结点。
2.如果 2*i+1<n,则序号为 i 结点的左孩子结点的序号为 2*i+1;如果2*i+1>=n,则序号为i的结点无左孩子结点。
3.如果 2*i+2<n,则序号为 i 结点的右孩子结点的序号为 2*i+2;如果2*i+2>=n,则序号为i的结点无右孩子结点。
二、以结点类为基础的二叉树设计
在二叉树存储结构下,一个二叉树是由若干个结点组成的。
二叉树的实现有两种基本方法:
1.首先设计二叉树结点类,然后在二叉树结点类的基础上,用static成员函数实现二叉树的操作。
2.在二叉树结点类的基础上,再设计二叉树类。
这里说的是第一种。
1.二叉树的结点类代码
package com.sid.model;
public class BiThreeNode {
private BiThreeNode leftChild;
private BiThreeNode rightChild;
public Object data;
BiThreeNode(){
leftChild = null;
rightChild = null;
}
BiThreeNode(Object item,BiThreeNode left,BiThreeNode right){
data = item;
leftChild = left;
rightChild = right;
}
public BiThreeNode getLeft(){
return leftChild;
}
public BiThreeNode getRight(){
return rightChild;
}
public Object getData(){
return data;
}
}
2.二叉树的遍历(一次性遍历)
一次性遍历,使用递归方式。
例子:
前序遍历(DLR)递归算法为:
若二叉树为空则算法结束;否则:
(1)访问根结点
(2)前序遍历根结点的左子树
(3)前序遍历根节点的右子树
对于例子中的二叉树,前序遍历访问结点的次序为:
A B D G C E F
中序遍历(LDR)递归算法为:
若二叉树为空则算法结束;否则:
(1)中序遍历根节点的左子树
(2)访问根节点
(3)中序遍历根节点的右子树
对于例子中的二叉树,中序遍历访问结点的次序为:
D G B A E C F
后序遍历(LRD)递归算法为:
若二叉树为空则算法结束;否则:
(1)后序遍历根节点的左子树
(2)后序遍历根节点的右子树
(3)访问根节点
对于例子中的二叉树,后序遍历访问结点的次序为:
G D B E F C A
层序遍历
层序遍历要求的时按二叉树的层序次序(即从根结点层至叶结点层),同一层中按先左子树再右子树的次序遍历二叉树。
算法:
(1)初始化设置一个队列
(2)把根结点指针入队列
(3)当队列非空时,循环执行步骤1到步骤3:
步骤1 出队列取得当前队列头结点,访问该头结点
步骤2 若该结点的左孩子结点非空,则将该结点的左孩子结点指针入队列
步骤3 若该结点的右孩子结点非空,则将该结点的右孩子结点指针入队列
(4)结束
对于例子中的二叉树,层序遍历访问结点的次序为:
A B C D E F G
3.二叉树的遍历方法和二叉树的结构
由于二叉树时非线性结构,每个结点会有零个、一个或者两个孩子结点,所以一个二叉树的遍历序列不能决定一个二叉树。
有可能出现两个不同的二叉树,但他们的前序遍历序列是一样的。
但,某些不同的遍历序列组合可以唯一地确定一颗二叉树。
可以证明,给定一棵二叉树的前序遍历序列和中序遍历序列,可以唯一确定一颗二叉树的结构。
4.二叉树遍历操作的代码实现
public class Traverse {
class Visit{
public void print(Object item){
System.out.println(item+" ");
}
}
//前序遍历
public static void preOrder(BiThreeNode t,Visit vs){
if(t != null){
vs.print(t);
preOrder(t.getLeft(),vs);
preOrder(t.getRight(),vs);
}
}
//中序遍历
public static void inOrder(BiThreeNode t,Visit vs){
if(t != null){
inOrder(t.getLeft(),vs);
vs.print(t);
inOrder(t.getRight(),vs);
}
}
//后序遍历
public static void postOrder(BiThreeNode t,Visit vs){
if(t != null){
postOrder(t.getLeft(),vs);
postOrder(t.getRight(),vs);
vs.print(t);
}
}
//层序遍历
public static void levelOrder(BiThreeNode t,Visit vs) throws Exception{
LinQueue q = new LinQueue(); //创建链式队列类对象
if(t == null){
return;
}
BiThreeNode curr;
q.append(t); //根结点入队列
while (q!=null){ //当前队列非空时循环
curr = (BiThreeNode) q.delete(); //出队列
vs.print(t); //访问该结点
if(t.getLeft() != null){
q.append(t.getLeft()); //左孩子结点入队列
}
if(t.getRight() != null){
q.append(t.getRight()); //右孩子结点入队列
}
}
}
}
三、二叉树类
这是二叉树设计的另一种方法,在二叉树结点类的基础上再设计一个二叉树类。
代码:
package com.sid.model;
public class BiTree {
private BiTreeNode root ; //根指针
//前序遍历
private void preOrder(BiTreeNode t, Traverse.Visit vs){
if(t != null){
vs.print(t);
preOrder(t.getLeft(),vs);
preOrder(t.getRight(),vs);
}
}
//中序遍历
private void inOrder(BiTreeNode t, Traverse.Visit vs){
if(t != null){
inOrder(t.getLeft(),vs);
vs.print(t);
inOrder(t.getRight(),vs);
}
}
//后序遍历
private void postOrder(BiTreeNode t, Traverse.Visit vs){
if(t != null){
postOrder(t.getLeft(),vs);
postOrder(t.getRight(),vs);
vs.print(t);
}
}
BiTree(){
root = null;
}
BiTree(Object item,BiTree left,BiTree right){
BiTreeNode l,r;
if(left == null){
l = null;
}else {
l = left.root;
}
if(right == null){
r = null;
}else{
r = right.root;
}
root = new BiTreeNode(item,l,r);
}
public void preOrder(Traverse.Visit vs){
preOrder(root,vs);
}
public void inOrder(Traverse.Visit vs){
inOrder(vs);
}
public void postOrder(Traverse.Visit vs){
postOrder(vs);
}
}
四、二叉树的遍历(分步遍历)
二叉树的遍历有两种情况:
1.一次性遍历,如前面讨论的一次性遍历显示二叉树结点的数据元素值
2.分步遍历,规定了一颗二叉树的遍历方法后,每次只访问当前结点的数据元素值,然后使用当前结点为当前结点的后继结点,直到达到二叉树的最后一个结点为止。
1.二叉树游标类
基类
首先我们要设计一个基类,再把二叉树中序游标类、二叉树前序游标类、二叉树后序游标类和二叉树层序游标类设计为该基类的派生类。
package com.sid.model;
public class BiTreeInterator {
BiTreeNode root; //根指针
BiTreeNode current; //当前结点
int iteComplete; //到达尾部标记
BiTreeInterator(){
}
BiTreeInterator(BiTreeNode tree){
root = tree;
current = tree;
iteComplete = 1;
}
public void reset(){ //重置
}
public void next(){ //下一个结点
}
public boolean endOfBiTree(){ //结束否
return iteComplete == 1;
}
public Object getData(){ //取数据元素
if(current == null){
return null;
}else {
return current.data;
}
}
}
二叉树中序游标类设计思想
借助一个堆栈模仿系统的运行时栈,则有非递归的二叉树中序遍历算法:
(1)设置一个堆栈并初始化
(2)使指针 t 等于二叉树根指针,如 t 非空令结束标记为0;否则为1
(3)当 t 的左孩子结点不空时循环;否则转向步骤(4)
1.把 t 入堆栈
2. t 等于 t 的左孩子结点
(4)如果 t 为空则令结束标记为1
(5)如果结束标记为1转步骤8,否则继续执行
(6)访问 t 结点
(7)如果 t 的右孩子结点非空,则使 t 等于 t 的右孩子结点,转到步骤3;否则如果堆栈不为空,则退栈使 t 等于栈顶结点,转向步骤5;否则令结束标记为1,转向步骤5
(8)算法结束
代码
package com.sid.model;
public class BiTrInIterator extends BiTreeInterator{
private LinStack s = new LinStack(); //创建堆栈类对象s
BiTrInIterator(BiTreeNode t){
super(t);
}
private BiTreeNode goFarLeft(BiTreeNode t){ //寻找最左孩子结点
if(t == null){
return null;
}
while (t.getLeft() != null){
s.push(t);
t = t.getLeft();
}
return t;
}
public void reset(){ //覆盖基类的重置成员函数
if(root == null){
iteComplete = 1; //置结束标记
}else {
iteComplete = 0;
}
if(root == null){
return;
}
current = goFarLeft(root);
}
public void next(){ //覆盖基类的下一个结点成员函数
if(iteComplete == 1){
System.out.println("已到二叉树尾!");
return;
}
if(current.getRight() != null){
current = goFarLeft(current.getRight()); //寻找当前结点右孩子结点的最左孩子结点
}else if (!s.isEmpty()){
try{
current = (BiTreeNode) s.pop(); //退栈
}catch (Exception e){
e.printStackTrace();
}
}else{
iteComplete = 1; //置结束标记
}
}
}
二叉树层序游标类代码
package com.sid.model;
public class BiTrLeIterator extends BiTreeInterator{
LinQueue q = new LinQueue();
BiTrLeIterator(BiTreeNode t){
super(t);
}
public void reset(){ //覆盖基类的重置成员函数
if(root == null){
iteComplete = 1; //置结束标记
}else {
iteComplete = 0;
}
if(root == null){
return;
}
current = root;
try {
if(root.getLeft() != null){
q.append(root.getLeft()); //左孩子结点入列
}
if(root.getRight() != null){
q.append(root.getRight()); //右孩子结点入列
}
}catch (Exception e){
e.printStackTrace();
}
}
public void next(){ //覆盖基类的下一个结点成员函数
if(iteComplete == 1){
System.out.println("已到二叉树尾");
return;
}
if(!q.isEmpty()){
try {
current = (BiTreeNode) q.delete(); //出队列
if(current.getLeft() != null){
q.append(current.getLeft()); //左孩子结点入队列
}
if(current.getRight() != null){
q.append(current.getRight()); //右孩子结点入队列
}
}catch (Exception e){
e.printStackTrace();
}
}
else {
iteComplete = 1;
}
}
}
2.线索二叉树
线索二叉树是另一种分步遍历二叉树的方法。
二叉树游标类只能从前往后分步遍历二叉树,
而线索二叉树既可以从前向后分步遍历二叉树,又可以从后向前分布遍历二叉树。
结构:
其中leftThread和rightThread是线索标志位,线索标志位的定义如下:
leftThread = 0 则 leftChild指向结点的左孩子结点
leftThread = 1 则 leftChild指向结点的前驱结点
rightThread = 0 则 rightChild指向结点的右孩子结点
rightThread = 1 则 rightChild指向结点的后继结点
对二叉树以某种方法(如前序、中序或后序方法)遍历使其变为线索二叉树的过程称为按该方法对二叉树进行的线索化。
例子:
二叉树:
中序线索二叉树:
前序线索二叉树
后序线索二叉树
把一棵二叉树变成线索二叉树的方法:在遍历的过程中给每个结点添加线索。
五、树与二叉树的转换
1.树转换为二叉树
树转换为二叉树的方法是:
(1)树中所有相同双亲结点的兄弟结点之间加一条连线。
(2)对树中不是双亲结点第一个孩子的结点,只保留新添加的该结点与左兄弟结点之间的连线,删去该结点与双亲结点之间的连线。
(3)整理所有保留的和添加的连线,使每个结点的第一个孩子结点连线位于左孩子指针位置,使每个结点的右兄弟结点连线位于右孩子指针位置
例子:
树:
第二步:相邻兄弟加连线
第三步:删除双亲与非第一个孩子的连线
最后形成了二叉树:
原来的孩子结点只剩左孩子结点
原来的兄弟结点变成了右孩子结点
2.二叉树还原为树
二叉树还原为树的方法是:
(1)若某结点是其双亲结点的左孩子结点,则把该结点的右孩子、右孩子的右孩子......都与该结点的双亲结点用线连起来
(2)删除原二叉树中所有双亲结点与右孩子结点的连线
(3)整理所有保留的和添加的连线,使每个结点的所有孩子结点位于相同层次高度
这里就不画图了,就是树变二叉树的逆操作。
六、树的遍历
例子:
1.先根遍历
树的先根遍历递归算法为:
(1)访问根节点
(2)按照从左到右的次序先根遍历根结点的每一刻子树
例子先根遍历得到的结点序列为:
A B E J F C G K L D H I
2.后根遍历
树的后根遍历递归算法为:
(1)按照从左到右的次序后根遍历根结点的每一个棵子树
(2)访问根结点
例子后根遍历的到的结点序列为:
J E F B K L G C H I D A
注意:树的后根遍历序列一定和该树转换的二叉树的中序遍历序列相同。