在線性結構中數據元素之間的邏輯關係爲一對一的線性關係,而在樹形結構中,數據元素之間具有一對多的邏輯關係,它反應了數據元素之間的層次關係,和一個數據元素可以有多個後繼但只能有一個前驅的特點。
樹:有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); //右孩子非空,入隊列
}
}
}
}