作者:disappearedgod
时间:2014-4-24
前记
正文
2.1 介绍
2.2 定义
二叉树是由n(n≥0)结点的有限集合,此集合或者为空,或者由一个根结点加上两棵分别称为左、右子树的,互不相交的二叉树组成
二叉树可以为空集,因此根可以有空的左子树或者右子树,亦或者左、右子树皆为空
从二叉树定义中可以看出,二叉树结构与一般树结构区别如下:
(1)二叉树可以为空树,即不包含任何结点;一般树至少应有一个结点。
(2)二叉树区别于度数为2的有序树,在二叉树中允许某些结点只有右子树而没有左子树;而有序树中,一个结点如果没有第一子树就不可能有第二子树的存在。
二叉树可以为空集,因此根可以有空的左子树或者右子树,亦或者左、右子树皆为空
从二叉树定义中可以看出,二叉树结构与一般树结构区别如下:
(1)二叉树可以为空树,即不包含任何结点;一般树至少应有一个结点。
(2)二叉树区别于度数为2的有序树,在二叉树中允许某些结点只有右子树而没有左子树;而有序树中,一个结点如果没有第一子树就不可能有第二子树的存在。
2.3 性质
二叉树性质
- 性质1 二叉树第i(i≥1)层上的结点数最多为2^(i-1 )
- 性质2 高度为k的二叉树最多有2^k-1个结点
- 性质3 对任何二叉树T,设n0、n1、n2分别表示度数为0、1、2的结点个数,则n0=n2+1
- 性质4 具有n个结点的完全二叉树(包括满二叉树)的高度为 log2N(向下取整)+1 或者 log2(N+1)向下取整
- 性质5 (满二叉树原理 )非空满二叉树的叶结点数等于其分支结点数加1
- 性质6 一棵非空二叉树空子树的数目等于其结点数目加1
2.4 实现
创建二叉树时,可以根据需要而采用实际的数据类型。成员函数包括返回元素的值,返回左、右结点指针,设置元素的值,以了标志该结点是否为叶结点。
interface BinNode { //二叉树结点的抽象数据类型
//返回并设置元素值
public Object element();
public Object setElement(Object v);
//返回并设置左孩子
public Binnode left();
public Binnode setLeft(BinNode p);
//返回并设置右孩子
public Binnode right();
public Binnode setRight(BinNode p);
//判断是否为叶结点
public boolean isLeaf();
}//interface BinNode
2.4.1 存储结构
2.4.1.1 顺序存储结构
二叉树的顺序存储结构是把二叉树的所有结点按照一定的次序顺序存储到一组包含n个存储单元的空间中
二叉树顺序存储的原则是:不管给定的二叉树是不是完全二叉树,都看作完全二叉树,即按完全二叉树的层次次序(从上到下,从左到右)把各结点依次存入数组中在顺序存储结构中,由某结点的存储单元地址可以推出其父亲、左儿子、右儿子及兄弟的地址,假设给定结点的地址为I,则:
(1)若I=1,则该结点是为根结点,无父亲。
(2)若I≠1,则该结点的父亲结点地址为I/2的整数部分。
(3)若2×I≤n,则该结点的左儿子结点地址为2×I,否则该结点无左儿子。
(4)若2×I+1≤n,则该结点的右儿子结点地址为2×I+1,否则该结点无右儿子。
(5)若I为奇数(不为1),则该结点的左兄弟为I-1。
(6)若I为偶数(不为n),则该结点的右兄弟为I+1。
二叉树顺序存储的原则是:不管给定的二叉树是不是完全二叉树,都看作完全二叉树,即按完全二叉树的层次次序(从上到下,从左到右)把各结点依次存入数组中在顺序存储结构中,由某结点的存储单元地址可以推出其父亲、左儿子、右儿子及兄弟的地址,假设给定结点的地址为I,则:
(1)若I=1,则该结点是为根结点,无父亲。
(2)若I≠1,则该结点的父亲结点地址为I/2的整数部分。
(3)若2×I≤n,则该结点的左儿子结点地址为2×I,否则该结点无左儿子。
(4)若2×I+1≤n,则该结点的右儿子结点地址为2×I+1,否则该结点无右儿子。
(5)若I为奇数(不为1),则该结点的左兄弟为I-1。
(6)若I为偶数(不为n),则该结点的右兄弟为I+1。
以数组方式实现二叉树
先依次序输入元素值,一一建立二叉树数组,其中根结点的下标为1,其余结点的建立则遵循左小(level*2)右大(level*2+1)的原则,最后输出所建立二叉树的结点内容。
先依次序输入元素值,一一建立二叉树数组,其中根结点的下标为1,其余结点的建立则遵循左小(level*2)右大(level*2+1)的原则,最后输出所建立二叉树的结点内容。
import ConsoleReader.*; //引入数据输入类
public class bitree01
{
public static void main (String args[]){
int i; //循环变量
int Index=1; //数组下标变量
int Data; //读取输入值的临时变量
BiTreeArray BiTree=new BitreeArray(); //声明二叉树数组
System.out.printin(“请输入二叉树数据元素(输入0退出!):”);
ConsoleReader console=new ConsoleReader(System.in);
do //依次序读取结点值
{
System.out.print(“Data”+Index+” : ”);
Data=console.readInt();
Bitree.Create(Data); //建立二叉树
Index++;
}while(Data!=0);
BiTree.PrintAll(); //输出二叉树的结点值
}
}
class BiTreeArray
{
int MaxSize=16;
int[] ABiTree=new int[MaxSize];
public void BiTreeArray()
{
int i;
for (i=0;i<MaxSize;i++)
ABiTree[i]=0;
}
//建立二叉树
public void Create(int Data)
{
int i;
int Level; //树的层数
Level=1; //从层1开始建立
while(ABiTree[Level]!=0) //判断是否存在子树
{
if Data<ABiTree[Level]) //判断是左子树?还是右子树?
Level=Level*2; //左子树
else
Level=Level*2+1; //右子树
}
ABiTree[Level]=Data; //将元素值插入结点
//输出二叉树结点值
public void PrintAll()
{
int i;
System.out.println(“二叉树结点值依次是:”);
for (i=1;i<MaxSize;i++)
{
System.out.print(“Node”+i);
System.out.println(“:[“+ABiTree[i]+”]”);
}
}
}
2.4.1.2 链接存储结构
二叉树的链接存储中每个结点由数据域和指针域两部分组成
二叉树每个结点的指针域有两个,一个指向左儿子,一个指向右儿子
还需一个链表的头指针指向根结点
二叉树的链接存储结构也称为二叉链表
若二叉树为空,则根结点为NULL
二叉树每个结点的指针域有两个,一个指向左儿子,一个指向右儿子
还需一个链表的头指针指向根结点
二叉树的链接存储结构也称为二叉链表
若二叉树为空,则根结点为NULL
数组方式实现二叉树的链接存储
以下示例为以结点数组方式建立二叉树,并输出结点内容。依次序输入结点值,并存入数组中,再一一建立成二叉树数组,其中根结点的下标为0,其余结点的建立则遵守左字段存左子结点的下标,右字段存右子结点下标的原则,最后输出所建立二叉树的结点值。
以下示例为以结点数组方式建立二叉树,并输出结点内容。依次序输入结点值,并存入数组中,再一一建立成二叉树数组,其中根结点的下标为0,其余结点的建立则遵守左字段存左子结点的下标,右字段存右子结点下标的原则,最后输出所建立二叉树的结点值。
import ConsoleReader.*; //引入己定义的数据输入类
public class bitree02
{
public static void main(String argS[]) {
int i; //循环变量
int index=l; //数组下标变量
int data; //输入值所使用的临时变量
BiTreeArray BiTree=new BiTreeArray(); //声明二叉树数组
System.out.println(’’请输入二叉树结点值(输入0退出0):”);
ConsolReader console=new ConsoleReader(SyStem.in);
System.out.print(“Data “+index+” : “);
Data=console.readInt();
BiTree.TreeData[0]=data;
index++;
while (true) //读取输入值
{
System.out.print(“Data “+index+” : “);
data=console.readInt();
if (data==0)
break;
BiTree.Create(data); //建立二叉树
index++;
}
BiTree.PrintAll(); //输出二叉树的内容
}
}
class BiTreeArray
{
int MaxSize=16;
int[] TreeData=new int[MaxSize];
int[] RightNode=new int[MaxSize];
int[] LeftNode=new int[MaxSize];
public BiTreeArray()
{
int i; //循环变量
for (i=0; i<MaxSize; i++)
{
TreeData[i]=0;
RightNode[i]=-1;
LeftNode[i]=-1;
}
}
//建立二叉树
public void Create(int data)
{
int i;
int level=0; //树的层数
int Position=0;
for (i=0; TreeData[i]!=0; i++)
TreeData[i]=data;
while (true) //寻找结点位置
{
//判断是左子树还是右子树
if (data > TreeData[Level])
//右子树是否有下一层
if (RightNode[level]!=-1)
level=RightNode[level];
else
{
Position=-1; //设置为右子树
break;
}
else
{
//左子树是否有下一阶层
if (LeftNode[level]!=-1)
level=LeftNode[level];
else
{
Position=1; //设置为左子树
break;
}
}
}
if (Position==1) //建立结点的左右连结
LeftNode[level]=i; //连结左子树
else
RightNode[level]=i; //连结右子树
}
//打印所有二叉树结点值
public void PrintAll()
{
int i;
System.out.println(“二叉树结点值:”);
System.out.println(“ [lchild] [data] [rchild]”);
for (i=0;i<MaxSize;i++) {
if(TreeData[i]!=0)
{
System.out.print(“Node”+i);
System.out.print(“ [“+LeftNode[i]+”]”);
System.out.print(“ [“+TreeData[i]+”]”);
System.out.println(“ [”+RightNode[i]+”]”);
}
}
}
}
2.5 二叉树的遍历
2.5.1 二叉树的前序遍历
“遍历”是抽取数据结构中的各个数据值
前序遍历(Preorder traversal)是先遍历根结点,再遍历左子树,最后才遍历右子树
即若二叉树非空,则依次进行如下操作:
Java语言实现示例:
前序遍历(Preorder traversal)是先遍历根结点,再遍历左子树,最后才遍历右子树
即若二叉树非空,则依次进行如下操作:
- 访问根结点;
- 前序遍历左子树;
- 前序遍历右子树。
if 指向根结点的指针=NULL then
此为空树,遍历结束
else
(1) 处理当前的结点
(2) 往左走,递归处理preorder(root->left)
(3) 往右走,递归处理preorder(root->right)
Java语言实现示例:
void preorder(BinNode rt) //rt是子树的根
{
if (rt == null) return; //空子树
visit(rt);
preorder(rt.left());
preorder(rt.right());
}
2.5.2 二叉树的中序遍历
中序遍历(Inorder traversal)是先遍历左子树,再遍历根结点,最后才遍历右子树
若二叉树非空,则依次序进行如下操作:
若二叉树非空,则依次序进行如下操作:
- 中序遍历左子树;
- 访问根结点;
- 中序遍历右子树。
中序遍历(inorder)的递归算法如下:
Java语言实现示例:
if 指向根结点的指针=NULL then
此为空树,遍历结束
else
(1) 往左走,递归处理inorder(root->left)
(2) 处理当前的结点
(3) 往右走,递归处理inorder(root->right)
Java语言实现示例:
void inorder(BinNode rt) //rt是子树的根
{
if (rt == null) return; //空子树
inorder(rt.left());
visit(rt);
inorder(rt.right());
}
2.5.3 二叉树的后序遍历
后序遍历(Postorder traversal)是先遍历左子树,再遍历右子树,最后才遍历根结点
若二叉树非空,则依次序进行如下操作:
Java语言实现示例:
若二叉树非空,则依次序进行如下操作:
- 后序遍历左子树;
- 后序遍历右子树;
- 访问根结点。
if 指向根结点的指针=NULL then
此为空树,遍历结束
else
(1) 往左走,递归处理postorder(root->left)
(2) 往右走,递归处理postorder(root->right)
(3) 处理目前的结点
Java语言实现示例:
void postorder(BinNode rt) //rt是子树的根
{
if (rt == null) return; //空子树
postorder(rt.left());
postorder(rt.right());
visit(rt);
}
2.6 应用
2.7 评价
实现二叉树使用了遍历函数,这些函数无论是否递归,他们都隐式或显式的使用栈,来存储当前没有处理完的节点信息。在递归函数中使用了运行时栈。在非递归的算法变体中,显式定义和使用了由用户维护的栈。程序需要花费额外的时间来维护栈,还要为栈留出更多的空间。在最坏的情况中,当树以不利于程序运行的方式"倾斜"着,栈将保存树中几乎所有节点的信息对于很大的树来说,这是一个严重的问题。
这个问题解决办法之一就是线索树。线索树的一大个思想是:使栈成为树的一部分。在给定的节点中引入线索(threads)。