在线性结构中数据元素之间的逻辑关系为一对一的线性关系,而在树形结构中,数据元素之间具有一对多的逻辑关系,它反应了数据元素之间的层次关系,和一个数据元素可以有多个后继但只能有一个前驱的特点。
树:有n个节点所构成的有限集合,当n=0时,称为空树。当n>0时,n个结点满足以下条件:1)有且仅有一个称为根的节点。2)其余结点可分为m个互不相交的有限集合,且每一个集合又构成一棵树,这棵树称为根结点的子树。
二叉树:一种特殊的树,它的每个节点最多只有两颗字数,并且这两颗子树也是二叉树。由于二叉树中的两个子树有左右之分,所以二叉树是有序树。二叉树是由n个结点所构成的有限集合。当n=0时,这个集合为空,当n>0时,这个集合是由一个根结点和两个互不相交的分别称为左子树和右子树的二叉树构成。
满二叉树:如果在一颗二叉树中,它的所有结点或者叶结点,或者左、右子树都非空,并且所有叶结点都在同一层上,则称这颗二叉树为满二叉树。
完全二叉树:如果在一颗具有n个结点的二叉树中,它的逻辑结构与满二叉树的前n个结点的逻辑结构相同,则称这样的二叉树为完全二叉树。什么叫逻辑结构相同?对与一颗具有n个结点的二叉树按层次编号,如果i的结点和同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同。
二叉树的存储结构:顺序存储、链式存储。
顺序存储:按照某种顺序依次将二叉树中的各个结点的值存放在一组地址连续的存储单元中。由于二叉树是非线性结构的,所以需要将二叉树中的各个结点先按照一定的顺序排列成一个线性序列,再通过这些结点在线性序列中的相对位置确定二叉树中各个结点之间的逻辑关系。
链式存储:将二叉树中各个结点随机存放在位置任意的内存空间中,各个结点之间的逻辑关系通过指针来反映。二叉链式存储结构的结点类代如下:
public class BiTreeNode {
public Object data; //结点的数据域
public BiTreeNode lchild; //左右孩子域
public BiTreeNode rchild;
public BiTreeNode(){
this(null);
}
public BiTreeNode(Object data){
this.data=data;
lchild=null;
rchild=null;
}
public BiTreeNode(Object data,BiTreeNode lchild,BiTreeNode rchild){
this.data=data;
this.lchild=lchild;
this.rchild=rchild;
}
}
二叉树的遍历:沿着某条搜索路径对二叉树中的结点进行访问,使得每个节点均被访问一次,且仅被访问一次。
二叉树的遍历方法:1、层次遍历(按层次进行访问) 2、先根遍历:若二叉树为空,则为空操作,否则:a、访问根结点 b、先根遍历左子树 c、先根遍历右子树 3、中根遍历 4、后根遍历.
先根遍历操作的非递归算法的主要思想是:从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索的过程中每遇到一个结点就先访问该结点,并将该结点的非空右孩子结点压栈。当左子树结点访问完成后,从栈顶弹出结点的右孩子结点,然后用同样的方法去遍历该结点的右子树,以此类推,直到二叉树中所有的结点都被访问为止。其主要操作过程可描述为:1)创建一个栈对象,根结点入栈 2)当栈为非空时,将栈顶结点弹出栈内并访问该结点 3)对当前访问结点的非空左孩子结点依次访问,并将当前访问结点的非空右孩子结点压入栈内。4)重复执行2)3),直到栈为空为止。
public void preRootTraverse() throws Exception{
BiTreeNode T=root;
if(T!=null){
LinkStack S=new LinkStack();
S.push(T);
while(!S.isEmpty()){
T=(BiTreeNode) S.pop(); //移除栈顶结点,并访问其值
System.out.print(T.data);
while(T!=null){
if(T.lchild!=null)
System.out.print(T.lchild.data);
if(T.rchild!=null)
S.push(T.rchild);
T=T.lchild;
}
}
}
}
中根遍历操作的实现:需要借助一个栈来记载遍历过程中所经历的而未被访问的所有结点,以便遍历完一个结点的左子树后能顺利地返回到它的父结点。
实现中根遍历的非递归算法的主要思想是:从二叉树的根节点出发,沿着该结点的左子树向下搜索,在搜索过程中将所遇到的每一个结点依次压栈,直到二叉树中最左下的结点压栈为止,然后从栈中探出栈顶结点病对其依次进行访问,访问完后再进入该结点的右子树并用上述同样的方法去遍历该结点的右子树,依次类推,直到二叉树中所有的结点都被访问为止。其操作的视线过程如下:
1、创建一个栈对象,根结点进栈
2、若栈非空,则将栈顶结点的非空左孩子相继进栈
3、栈顶结点出栈并访问非空栈顶结点,并使该栈顶结点的非空右孩子结点入栈
4、重复执行(2)和(3)直到栈空为止
public void inRootTraverse() throws Exception{
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()).lchild);
}
S.pop(); //空结点退栈
if(!S.isEmpty()){
T=(BiTreeNode) S.pop(); //移除栈顶结点,并返回其值
System.out.println(T.data); //访问结点
S.push(T.rchild); //结点的右孩子入栈
}
}
}
}
后根遍历:由于后根遍历是先处理左子树,后处理右子树,最后才访问根结点,所以在遍历搜索过程中也是从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中每遇到一个结点判断该结点是否第一次经过,若是,则不立即访问,而是将该结点入栈保存,遍历该结点的左子树;当左子树遍历完毕后再返回到该结点,这时还不能访问该结点,而是继续进入该结点的右子树进行遍历;当左右子树均遍历完毕后,才能从栈顶弹出该结点并访问它。由于在决定栈顶结点是否能访问时,需要知道该结点的右子树是否被遍历完毕,因此为解决这个问题,在算法中引入一个布尔类型的访问标记变量flag和一个结点指针p。其中,flag用来标记当前栈顶结点是否被访问,当值为true时,表示栈顶结点已被访问;当值为false时,表示当前栈顶结点未被访问,指针p指向当前遍历过程中最后一个被访问的结点。若当前栈顶结点的右孩子结点为空,或者就是p指向的结点,则表明当前结点的右子树已遍历完毕,此时就可以访问当前栈顶结点。其操作的实现过程如下:
1)创建一个栈对象,根结点进栈,p赋初始值null
2)若栈非空,则栈顶结点的非空左孩子相继进栈
3)若栈非空,查看栈顶结点,若栈顶结点的右孩子为空,或者与p相等,则将该栈顶结点弹出栈并访问它,同时使p指向该结点,并置flag值为ture;否则,将栈顶结点的右孩子压入栈,并置flag值为false
4)若flag值为true,则重复执行步骤3),否则重复执行步骤2)和3),直到栈为空为止
public void postRootTraverse() throws Exception{
BiTreeNode T=root;
if(T!=null){
LinkStack S=new LinkStack(); //LinkStack为自定义链栈
S.push(T);
boolean flag;
BiTreeNode p=null;
while(!S.isEmpty()){
while(S.peek()!=null) //左孩子相继入栈
S.push(((BiTreeNode)S.peek()).lchild);
}
S.pop(); //空结点退栈
while(!S.isEmpty()){
T=(BiTreeNode) S.peek();
if(T.rchild==null||T.rchild==p){
System.out.print(T.data);
S.pop();
p=T;
flag=true;
}else{
S.push(T.rchild);
flag=false;
}if(!flag){
break;
}
}
}
}
层次遍历操作实现:层次遍历操作的视线过程中需要使用一个队列作为辅助的存储结构,这里使用了链队列类LinkQueue来创建一个队列对象L。在遍历开始时,首先根结点入队,然后每次从队列中取出队首元素进行处理,每处理一个结点,都是先访问该结点,再按从左到右的顺序把它的孩子结点依次入队。这样,上一层的结点总排在下一层结点的前面,从而实现了二叉树的遍历。其操作的实现过程如下:
1)创建一个队列对象,根结点入队。
2)若队列为空,则将队首结点出队并访问该结点,再将该结点的非空左、右孩子结点依次入队。
3)重复执行步骤2),直到队列为空为止。
public void levelTraverse() throws Exception{
BiTreeNode T=root;
if(T!=null){
LinkQueue linkQueue=new LinkQueue(); //构造队列,其中LinkQueue为自定义的,队列一文中有
linkQueue.offer(T); //根结点入队列
while(!linkQueue.isEmpty()){
T=(BiTreeNode) linkQueue.poll();
System.out.println(T.data); //访问结点
if(T.lchild!=null){ //左孩子非空,入队列
linkQueue.offer(T.lchild);
}
if(T.rchild!=null){
linkQueue.offer(T.rchild); //右孩子非空,入队列
}
}
}
}