數據結構編程筆記十五:第六章 樹和二叉樹 樹和二叉樹的轉換算法實現

上次已經介紹了遞歸算法以及二叉樹的基本操作,最重要的就是二叉樹的遍歷算法。這次主要是介紹樹的孩子兄弟表示法以及樹和二叉樹的轉換。

還是老規矩:

程序在碼雲上可以下載。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git

本次的程序重點是實現樹和二叉樹的轉換。雖然沒有把樹的全部操作都實現,但是還是要貼出樹的ADT定義,以便大家瞭解樹的定義。

ADT Tree{ 
 數據對象D:D是具有相同特性的數據元素的集合。  
  數據關係R:若D爲空集,則稱爲空樹; 
       若D僅含有一個數據元素,則R爲空集,否則R={H},H是如下二元關係: 
       (1)在D中存在唯一的稱爲根的數據元素root,它在關係H下無前驅;  
       (2)若D-{root}≠Φ,則存在D-{root}的一個劃分D1,D2,D3, „,Dm(m>0),
          對於任意j≠k(1≤j,k≤m)有Dj∩Dk=Φ,且對任意的i(1≤i≤m),
          唯一存在數據元素xi∈Di有<root,xi>∈H;
       (3)對應於D-{root}的劃分,H-{<root,xi>,„,<root,xm>}有唯一的一個劃分
          H1,H2,„,Hm(m>0),對任意j≠k(1≤j,k≤m)有Hj∩Hk=Φ,且對任意i
          (1≤i≤m),Hi是Di上的二元關係,(Di,{Hi})是一棵符合本定義的樹,
          稱爲根root的子樹。 
 基本操作P: 
    InitTree(&T); 
      操作結果:構造空樹T。 
    DestroyTree(&T); 
      初始條件:樹T存在。 
      操作結果:銷燬樹T。 
    CreateTree(&T,definition); 
      初始條件:definition給出樹T的定義。 
      操作結果:按definition構造樹T。 
    ClearTree(&T); 
      初始條件:樹T存在。 
      操作結果:將樹T清爲空樹。 
    TreeEmpty(T); 
      初始條件:樹T存在。 
      操作結果:若T爲空樹,則返回TRUE,否則返回FALSE。 
    TreeDepth(T); 
      初始條件:樹T存在。 
      操作結果:返回T的深度。 
    Root(T); 
      初始條件:樹T存在。 
      操作結果:返回T的根。 
    Value(T,cur_e); 
      初始條件:樹T存在,cur_e是T中某個結點。 
      操作結果:返回cur_e的值。 
    Assign(T,cur_e,value); 
      初始條件:樹T存在,cur_e是T中某個結點。 
      操作結果:結點cur_e賦值爲value。 
    Parent(T,cur_e); 
      初始條件:樹T存在,cur_e是T中某個結點。 
      操作結果:若cur_e是T的非根結點,則返回它的雙親,否則函數值爲“空”。 
    LeftChild(T,cur_e); 
      初始條件:樹T存在,cur_e是T中某個結點。 
      操作結果:若cur_e是T的非葉子結點,則返回它的最左孩子,否則返回“空”。 
    RightSibling(T,cur_e); 
      初始條件:樹T存在,cur_e是T中某個結點。 
      操作結果:若cur_e有右兄弟,則返回它的右兄弟,否則返回“空”。 
    InsertChild(&T,&p,I,c); 
      初始條件:樹T存在,p指向T中某個結點,1≤i≤p指結點的度+1,
              非空樹c與T不相交。 
      操作結果:插入c爲T中p指結點的第i棵子樹。 
    DeleteChild(&T,&p,i); 
      初始條件:樹T存在,p指向T中某個結點,1≤i≤p指結點的度。 
      操作結果:刪除T中p所指結點的第i棵子樹。 
    TraverseTree(T,visit()); 
      初始條件:樹T存在,visit是對結點操作的應用函數。 
      操作結果:按某種次序對T的每個結點調用函數visit( )一次且至多一次。
               一旦visit( )失敗,則操作失敗。 
}ADT Tree

二叉樹的ADT定義和實現請參考上一篇文章:
數據結構編程筆記十四:第六章 樹和二叉樹 二叉樹基本操作及四種遍歷算法的實現
http://blog.csdn.net/u014576141/article/details/77518855

樹採用孩子兄弟表示法,孩子兄弟表示法使用的是二叉鏈表存儲結構,二叉樹也採用二叉鏈表的存儲結構。所以樹和二叉樹的轉換就是基於相同存儲結構的不同翻譯。樹轉換爲二叉樹需要將孩子結點翻譯爲二叉樹的左孩子,將兄弟結點翻譯成二叉樹的右孩子。原理如圖所示:
這裏寫圖片描述

本次的代碼用到了二叉樹的二叉鏈表實現,順序棧的實現和鏈隊列的實現。和上次的程序一樣,這些源文件必須在同一目錄下編譯。我把其他代碼放在總結後面,想看的童鞋自己去看。

接下來看看樹和二叉樹轉換的代碼:

//>>>>>>>>>>>>>>>>>>>>>>>>>引入頭文件<<<<<<<<<<<<<<<<<<<<<<<<<<<<

#include <stdio.h>       //使用了標準庫函數 
#include <stdlib.h>      //使用了動態內存分配函數 
#include "BiTree.cpp"    //引入二叉樹的實現 

//>>>>>>>>>>>>>>>>>>>>>>>自定義符號常量<<<<<<<<<<<<<<<<<<<<<<<<<< 

#define OVERFLOW -2          //內存溢出錯誤常量
#define OK 1                 //表示操作正確的常量 
#define ERROR 0              //表示操作錯誤的常量
#define TRUE 1               //表示邏輯真的常量 
#define FALSE 0              //表示邏輯假的常量

//>>>>>>>>>>>>>>>>>>>>>>>自定義數據類型<<<<<<<<<<<<<<<<<<<<<<<<<<

typedef int  Status;      //函數返回值狀態碼類型 
typedef char TElemType;   //樹中結點元素類型 

//-------------------樹的孩子兄弟表示法----------------------- 
typedef struct CSNode{
     TElemType data;  //數據域,存儲結點名稱 
     struct CSNode *firstchild, *nextsibling;  //孩子指針域和兄弟指針域 
} CSNode, *CSTree;

//-------------------------------樹的主要操作--------------------------------

/*
    函數:CreateCSTree
    參數:CSTree &CT 樹的引用 
    返回值:狀態碼,操作成功返回OK,否則返回ERROR 
    作用:按先根次序輸入樹中結點的值(一個字符),空格字符表示空樹,
           構造二叉鏈表表示樹T
*/
Status CreateCSTree(CSTree &CT){

    //ch保存從鍵盤接收的字符 
    char ch;

    //從鍵盤接收字符 
    ch = getchar();

    //用戶輸入了空格,表示空子樹 
    if(ch == ' ') { 
        CT = NULL;
    }//if 
    else{ //用戶輸入的不是空格,需要生成新的結點

        //分配根結點空間 
        //if(!(CT = (CSNode *)malloc(sizeof(CSNode))))
        //等效於
        //CT = (CSNode *)malloc(sizeof(CSNode))
        //if(!CT) <=> if(CT == NULL)
        if(!(CT = (CSNode *)malloc(sizeof(CSNode)))){
            printf("內存分配失敗!\n");
            exit(OVERFLOW); 
        }//if

        //生成根結點 
        CT->data = ch;

        //構建左子樹
        CreateCSTree(CT->firstchild); 

        //構建右子樹 
        CreateCSTree(CT->nextsibling);
    }//else

    //操作成功 
    return OK; 
}//CreateCSTree

/*
    函數:ExchangeToBiTree
    參數:CSTree &CT 樹的引用
          BiTree &T 二叉樹的引用 
    返回值:狀態碼,操作成功返回OK,否則返回ERROR 
    作用:將一棵用二叉鏈表表示的樹轉換爲二叉樹
*/
Status ExchangeToBiTree(CSTree &CT, BiTree &T){

    //若樹的根結點爲空,那麼轉換成的二叉樹根結點也是空 
    if(!CT) { //if(CT) <=> if(CT != NULL)
        T = NULL;
    }//if 
    else{
        //分配新的結點空間
        //if(!(T = (BiNode *)malloc(sizeof(BiNode))))
        //相當於以下兩行代碼
        //T = (BiNode *)malloc(sizeof(BiNode));
        //if(!T) <=> if(T == NULL) 
        if(!(T = (BiNode *)malloc(sizeof(BiNode)))){
            printf("內存分配失敗!\n");
            exit(OVERFLOW); 
        }//if

        //拷貝樹中對應結點到二叉樹 
        T->data = CT->data;

        //將樹的孩子轉換爲二叉樹的左孩子     
        ExchangeToBiTree(CT->firstchild, T->lchild);

        //將樹的兄弟轉換爲二叉樹的右孩子  
        ExchangeToBiTree(CT->nextsibling,T->rchild);  
    }//else 

    //操作成功 
    return OK; 
}//ExchangeToBiTree

/*
    函數:DestoryTree
    參數:CSTree &CT 樹的引用 
    返回值:無
    作用:按照樹的定義遞歸地銷燬樹
*/
void DestoryCSTree(CSTree &CT){

    //非空樹
    if(CT){  //if(CT) <=> if(CT != NULL)

        //孩子子樹非空,遞歸的銷燬孩子子樹 
        //if(CT->firstchild) <=> if(CT->firstchild != NULL) 
        if(CT->firstchild) {
           DestoryCSTree(CT->firstchild);
        }//if

        //兄弟子樹非空,遞歸的銷燬兄弟子樹
        //if(CT->nextsibling) <=> if(CT->nextsibling != NULL)
        if(CT->nextsibling) { 
           DestoryCSTree(CT->nextsibling);
        }//if

        //釋放根結點
        free(CT);

        //指針置空 
        CT = NULL; 
    }//if  
}//DestoryTree

/*
    函數:DestoryBiTree
    參數:BiTree &T 二叉樹的引用 
    返回值:無 
    作用:按照二叉樹定義遞歸地銷燬二叉樹
*/
void DestoryBiTree(BiTree &T){

    //非空樹 
    if(T){  //if(T) <=> if(T != NULL)

        //左子樹非空,遞歸的銷燬左子樹
        if(T->lchild)  {
            DestoryBiTree(T->lchild);
        }//if 

        //右子樹非空,遞歸的銷燬右子樹
        if(T->rchild) {
            DestoryBiTree(T->rchild);
        }//if 

        //釋放根結點
        free(T);

        //指針置空 
        T = NULL; 
    }//if 
}//DestoryTree

/*
    函數:DestoryTree
    參數:CSTree &CT 樹的引用 
          BiTree &T  二叉樹的引用 
    返回值:無
    作用:銷燬樹和二叉樹
*/
void DestoryTree(CSTree &CT, BiTree &T){

    //銷燬樹 
    DestoryCSTree(CT);

    //銷燬二叉樹 
    DestoryBiTree(T);

    printf("\n->生成的樹和二叉樹已被銷燬!"); 
}//DestoryTree

//-----------------------------主函數----------------------------------- 
int main(int argc,char *argv[]){
    printf("----------------------------------  樹的應用  ----------------------------------\n");
    BiTree T=NULL;     //聲明一棵二叉樹
    CSTree CT=NULL;    //聲明一棵普通樹
    printf("         ---------------------------樹的建立----------------------              \n");
    printf("->請按樹的先根次序輸入序列,如有空子樹,用空格填充,完成後輸入回車確認\n"); 
    CreateCSTree(CT);
    printf("         ---------------------------樹的轉換----------------------              \n");
    printf("->正在將輸入的樹轉換爲其對應的二叉樹...\n");
    ExchangeToBiTree(CT,T); 
    printf("->轉換操作執行完畢!\n");
    printf("\n         -------------------------二叉樹的遍歷--------------------              ");
    printf("\n\n先序遍歷遞歸  算法結果:"); PreOrderTraverse(T,PrintElement);
    printf("\n\n中序遍歷遞歸  算法結果:"); InOrderTraverse(T,PrintElement);
    printf("\n\n後序遍歷遞歸  算法結果:"); PostOrderTraverse(T,PrintElement); 
    printf("\n\n先序遍歷非遞歸算法結果:"); PreOrderTraverse1(T,PrintElement);
    printf("\n\n中序遍歷非遞歸算法結果:"); InOrderTraverse1(T,PrintElement);
    printf("\n\n後序遍歷非遞歸算法結果:"); PostOrderTraverse1(T,PrintElement);
    printf("\n\n層序遍歷非遞歸算法結果:"); LevelOrderTraverse1(T,PrintElement); 
    printf("\n         -------------------------二叉樹的信息--------------------              ");
    printf("\n該二叉樹的高度:%d",BiTreeDepth(T)); 
    LeafNodeNum(T);
    printf("\n二叉樹中葉子結點的個數:%d", LNM);
    printf("\n二叉樹總結點數:%d",NodeSubNum(T) );
    printf("\n\n         -------------------------  樹的銷燬  --------------------              ");
    DestoryTree(CT, T); 
    printf("\n->算法演示結束!"); 
    system("pause"); 
    return 0;
}//main

程序測試使用的是這樣一棵樹:
這裏寫圖片描述

大家可以手工寫出這棵樹轉換成二叉樹之後的遍歷結果,然後檢驗這個結果對不對。

以下是程序測試時的輸入和輸出:

----------------------------------  樹的應用  ----------------------------------

         ---------------------------樹的建立----------------------

->請按樹的先根次序輸入序列,如有空子樹,用空格填充,完成後輸入回車確認
ABE*F**C*DGHI*J*K******↙   
    //說明:此處的*是空格,爲方便確認輸入了幾個空格將空格替換成*,測試輸入時請將*改回空格
            ↙表示回車確認    輸入(可直接複製,不要複製↙):ABE F  C DGHI J K      ↙
         ---------------------------樹的轉換----------------------

->正在將輸入的樹轉換爲其對應的二叉樹...

->轉換操作執行完畢!

         -------------------------二叉樹的遍歷--------------------


先序遍歷遞歸  算法結果: A  B  E  F  C  D  G  H  I  J  K

中序遍歷遞歸  算法結果: E  F  B  C  I  J  K  H  G  D  A

後序遍歷遞歸  算法結果: F  E  K  J  I  H  G  D  C  B  A

先序遍歷非遞歸算法結果: A  B  E  F  C  D  G  H  I  J  K

中序遍歷非遞歸算法結果: E  F  B  C  I  J  K  H  G  D  A

後序遍歷非遞歸算法結果: F  E  K  J  I  H  G  D  C  B  A

層序遍歷非遞歸算法結果: A  B  E  C  F  D  G  H  I  J  K

         -------------------------二叉樹的信息--------------------

該二叉樹的高度:9
二叉樹總結點數:11

         -------------------------  樹的銷燬  --------------------
->生成的樹和二叉樹已被銷燬!
->算法演示結束!請按任意鍵繼續. . .

總結:樹和二叉樹的轉換其實就是基於相同存儲結構的不同翻譯。

下次的文章將介紹線索二叉樹的實現。希望大家繼續關注我的博客。再見!

附:
二叉樹實現精簡版。源文件:BiTree.cpp

//>>>>>>>>>>>>>>>>>>>>>>>>>自定義符號常量<<<<<<<<<<<<<<<<<<<<<<<<<<<< 

#define STACK_INIT_SIZE 50   //順序棧存儲空間初始分配量 
#define STACKINCREMENT 10    //順序棧存儲空間分配增量  
#define OVERFLOW -2          //內存溢出錯誤常量
#define OK 1                 //表示操作正確的常量 
#define ERROR 0              //表示操作錯誤的常量
#define TRUE 1               //表示邏輯真的常量 
#define FALSE 0              //表示邏輯假的常量

//>>>>>>>>>>>>>>>>>>>>>>>>>自定義數據類型<<<<<<<<<<<<<<<<<<<<<<<<<<<<

typedef int  Status;      //狀態碼爲int類型,用於保存操作結果(1成功0失敗) 
typedef char TElemType;   //二叉樹節點數據域的元素類型 

//----------------二叉樹的二叉鏈表存儲表示-------------------- 
typedef struct BiNode{
    TElemType  data;
    struct BiNode  *lchild,*rchild;   //孩子結點指針 
}BiNode,*BiTree;

//--------引入棧和隊列的實現(實際上應該放在頭部,由於編譯原因,只好這樣了)---------------- 
#include "Queue.cpp"     //引入隊列的實現 
#include "Stack.cpp"     //引入棧的實現 

//---------------------二叉樹的主要操作--------------------------

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>1.構造二叉樹<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 

/*
    函數:CreateBiTree
    參數:BiTree &T 二叉樹引用 
    返回值:狀態碼,操作成功返回OK,否則返回ERROR 
    作用:按先序次序輸入二叉樹中結點的值(一個字符),空格字符表示空樹,
          遞歸的構造二叉鏈表表示二叉樹T
*/
Status CreateBiTree(BiTree &T){ 

    //ch存儲從鍵盤接收的字符 
    char ch;

    //從鍵盤接收字符 
    ch = getchar();

    //判斷輸入的字符是否是空格 
    if(ch == ' ') { //輸入空格表示結點爲空 
        T = NULL;
    }//if 
    else{ //不是空格,按正常結點對待

        //申請結點空間
        //if(!(T = (BiNode *)malloc(sizeof(BiNode))))
        //等效於以下兩行代碼
        //T = (BiNode *)malloc(sizeof(BiNode));
        //if(!(T = (BiNode *)malloc(sizeof(BiNode)))) 
        if(!(T = (BiNode *)malloc(sizeof(BiNode)))){
            printf("內存分配失敗!\n");
            exit(OVERFLOW); 
        }//if

        //生成根結點
        T->data = ch;

        //遞歸的構建左子樹      
        CreateBiTree(T->lchild);

        //遞歸的構建右子樹 
        CreateBiTree(T->rchild);
    }//else

    //操作成功 
    return OK; 
}//CreateBiTree

//>>>>>>>>>>>>>>>>>>>>2.二叉樹的遍歷(4種方法)<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:Print
    參數:TElemType e 被訪問的元素 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:訪問元素e的函數,通過修改該函數可以修改元素訪問方式,
          該函數使用時需要配合遍歷函數一起使用。 
*/
Status PrintElement(TElemType e) {

    //採用控制檯輸出的方式訪問元素 
    printf(" %c ", e);

    //操作成功 
    return OK;
}//PrintElement

//------------------------遞歸算法----------------------------- 

/*
    函數:PreOrderTraverse
    參數:BiTree T 二叉樹T
          Status(* Visit)(TElemType) 函數指針,指向元素訪問函數 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:採用二叉鏈表存儲結構,Visit是對數據元素操作的應用函數
          先序遍歷二叉樹T的遞歸算法,對每個數據元素調用函數Visit 
*/
Status PreOrderTraverse(BiTree T, Status(* Visit)(TElemType)){

    //根節點存在 
    //if(T) <=> if(T != NULL)
    if(T){

        //1.訪問根結點
        //if(Visit(T->data)) <=> if(Visit(T->data) != ERROR)
        if(Visit(T->data)) {

            //2.訪問左孩子(左子樹) 
            if(PreOrderTraverse(T->lchild, Visit)) {

                //3.訪問右孩子(訪問右子樹) 
                if(PreOrderTraverse(T->rchild, Visit)) {
                    return OK;
                }//if 
            }//if
        }//if

        return ERROR;
    }//if
    else { 
        return OK;
    }//else 
}//PreOrderTraverse

/*
    函數:InOrderTraverse
    參數:BiTree T 二叉樹T
          Status(* Visit)(TElemType) 函數指針,指向元素訪問函數 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:採用二叉鏈表存儲結構,Visit是對數據元素操作的應用函數
          中序遍歷二叉樹T的遞歸算法,對每個數據元素調用函數Visit 
*/
Status InOrderTraverse(BiTree T, Status(* Visit)(TElemType)){

    //根節點存在 
    if(T){  //if(T)  <=>  if(T != NULL)

        //1.訪問左子樹 
        if(InOrderTraverse(T->lchild,Visit)) {

            //2.訪問根節點 
            //if(Visit(T->data)) <=> if(Visit(T->data) != ERROR)
            if(Visit(T->data)) {

                //3.訪問右子樹 
                if(InOrderTraverse(T->rchild,Visit)) {

                    return OK;
                }//if
            }//if 
        }//if 

        return ERROR;
    }//if
    else {
        return OK;
    }//else 
}//InOrderTraverse 

/*
    函數:PostOrderTraverse
    參數:BiTree T 二叉樹T
          Status(* Visit)(TElemType) 函數指針,指向元素訪問函數 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:採用二叉鏈表存儲結構,Visit是對數據元素操作的應用函數
          後序遍歷二叉樹T的遞歸算法,對每個數據元素調用函數Visit 
*/
Status PostOrderTraverse(BiTree T, Status(* Visit)(TElemType)){

    //根結點存在 
    if(T){  //if(T)  <=>  if(T != NULL)

        //1.訪問左子樹 
        if(PostOrderTraverse(T->lchild, Visit)) {

            //2.訪問右子樹 
            if(PostOrderTraverse(T->rchild, Visit)) {

                //3.訪問根結點 
                //if(Visit(T->data)) <=> if(Visit(T->data) != ERROR)
                if(Visit(T->data)) {
                    return OK;
                }//if
            }//if
        }//if

        return ERROR;
    }//if
    else return OK;
}//PostOrderTraverse

//-----------------------非遞歸遍歷算法---------------------------

/*
    函數:PreOrderTraverse1
    參數:BiTree T 二叉樹T
          Status(* Visit)(TElemType) 函數指針,指向元素訪問函數 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:採用二叉鏈表存儲結構,Visit是對數據元素操作的應用函數
          先序遍歷二叉樹T的非遞歸算法,對每個數據元素調用函數Visit 
*/
Status PreOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){

    //二叉樹非遞歸遍歷需要借用棧來保存回溯點 
    Stack S;

    //初始化棧 
    InitStack(S);

    //工作指針p指向二叉樹根結點 
    BiTree p = T;

    //遍歷繼續的條件:工作指針p不爲空或棧不爲空
    //while(p || !(StackIsEmpty(S))) 
    //<=> while(p != NULL || StackIsEmpty(S) != 1)
    while(p || !(StackIsEmpty(S))){

        //根結點存在 
        if(p){

            //1.訪問根結點 
            //if(Visit(p->data)) <=> if(Visit(p->data) != ERROR)
            if(!Visit(p->data)) { 
                return ERROR;
            }//if 

            //根指針進棧
            Push(S, p);

            //2.遍歷左子樹
            p = p->lchild;
        }//if
        else{
            //根指針退棧
            Pop(S, p);

            //3.遍歷右子樹
            p = p->rchild;
        }//else
    }//while

    //銷燬棧
    DestoryStack(S); 

    //操作成功 
    return OK;
} //PreOrderTraverse1

/*
    函數:InOrderTraverse1
    參數:BiTree T 二叉樹T
          Status(* Visit)(TElemType) 函數指針,指向元素訪問函數 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:採用二叉鏈表存儲結構,Visit是對數據元素操作的應用函數
          中序遍歷二叉樹T的非遞歸算法,對每個數據元素調用函數Visit 
*/
Status InOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){ 

    //二叉樹非遞歸遍歷需要借用棧來保存回溯點
    Stack S;

    //初始化棧 
    InitStack(S);

    //工作指針p指向根結點 
    BiTree p = T;

    //遍歷繼續的條件:工作指針p不爲空或棧不爲空
    //while(p || !(StackIsEmpty(S))) 
    //<=> while(p != NULL || StackIsEmpty(S) != 1)
    while(p || !(StackIsEmpty(S))) {

        //根結點不爲空 
        if(p){

            //根指針進棧
            Push(S, p); 

            //1.遍歷左子樹
            p = p->lchild;
        }//if
        else{
            //根指針退棧
            Pop(S, p);

            //2.訪問根結點
            //if(Visit(p->data)) <=> if(Visit(p->data) != ERROR)
            if(!Visit(p->data)) {  
                return ERROR;
            }//if

            //3.遍歷右子樹
            p = p->rchild; 
        }//else
    }//while

    //銷燬棧
    DestoryStack(S); 

    //操作成功 
    return OK;
} //InOrderTraverse1

/*
    函數:PostOrderTraverse1
    參數:BiTree T 二叉樹T
          Status(* Visit)(TElemType) 函數指針,指向元素訪問函數 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:採用二叉鏈表存儲結構,Visit是對數據元素操作的應用函數
          後序遍歷二叉樹T的非遞歸算法,對每個數據元素調用函數Visit 
*/
Status PostOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){

    //p和q都是工作指針
    //p指向當前遍歷的結點,q指向p最近一次遍歷的結點 
    BiTree p = T, q = NULL;

    //二叉樹非遞歸遍歷需要借用棧來保存回溯點
    Stack s;

    //初始化棧 
    InitStack(s);

    //遍歷繼續的條件:工作指針p不爲空或棧不爲空
    //while(p || !StackIsEmpty(S)) 
    //<=> while(p != NULL || StackIsEmpty(S) != 1)
    while(p || !StackIsEmpty(s)) {

        //順着樹的根,一直走左分支,直到遇到最左分支的盡頭(葉子節點的左孩子)。 
        while(p){

            //根結點入棧 
            Push(s, p);

            //訪問左子樹 
            p = p->lchild;
        }//while

        //重置指針q的值爲NULL 
        q = NULL;

        //棧不爲空 
        while(!StackIsEmpty(s)){

            //p指向棧頂元素 
            GetTop(s, p);

            //這個條件表示p指向了葉子結點或者p的左右子樹均被遍歷過 
            if(p->rchild == NULL || p->rchild == q){

                //訪問根結點 
                //if(Visit(p->data)) <=> if(Visit(p->data) != ERROR)
                if(!Visit(p->data)) { 
                    return ERROR;    
                }//if

                if(p == T) {
                    return ERROR;
                }//if

                //q指向的是p的上一次遍歷過的結點
                q = p;

                //根指針出棧 
                Pop(s, p);
            }//if
            else{

                //訪問右子樹 
                p = p->rchild;

                //退出內層循環 
                break;              
            }//else
        }//while
    }//while

    //銷燬棧
    DestoryStack(s); 

    //操作成功 
    return OK;
} //PostOrderTraverse1

/*
    函數:LevelOrderTraverse1
    參數:BiTree T 二叉樹T
          Status(* Visit)(TElemType) 函數指針,指向元素訪問函數 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:採用二叉鏈表存儲結構,Visit是對數據元素操作的應用函數
          層序遍歷二叉樹T的算法,對每個數據元素調用函數Visit 
*/
Status LevelOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){ 

    //層序遍歷需要用到隊列 
    Queue Q;

    //工作指針p指向根結點 
    BiTree p = T;

    //根結點不爲空 
    if(T){ //if(T) <=>  if(T != NULL)
        //初始化隊列
        InitQueue(Q);

        //根結點入隊列
        EnQueue(Q, T);

        //隊列不空 
        //while(!QueueEmpty(Q)) <=> while(QueueEmpty(Q) == 0)
        while(!QueueEmpty(Q)){

            //根結點出隊 
            DeQueue(Q, p);

            //訪問根結點 
            if(!Visit(p->data)) { 
               return ERROR;
            }//if

            //左孩子不爲空 
            if(p->lchild) {
                //左孩子入隊列 
                EnQueue(Q, p->lchild);   
            }//if

            if(p->rchild) {
                //右孩子入隊列 
                EnQueue(Q, p->rchild);
            }//if 
       }//while

       //輸出換行,使顯示美觀 
       printf("\n");

       //隊列用完之後要銷燬,釋放其內存空間 
       DestoryQueue(Q); 
    }//if

    //操作成功 
    return OK;
} //LevelOrderTraverse1

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>3.二叉樹的信息<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:BiTreeDepth
    參數:BiTree T 二叉樹T
    返回值:若二叉樹T存在,返回T的深度(高度),否則返回0
    作用:若二叉樹T存在,遞歸地求二叉樹T的深度 
*/
int BiTreeDepth(BiTree T){ 

    //Thigh是二叉樹高度,leftThigh是左子樹高度,rightThigh是右子樹高度
    int Thigh, leftThigh, rightThigh;

    //根結點爲空,樹高爲0 
    if(!T) {
        return 0;
    }//if
    else{
        //根結點不爲空,則遞歸的計算樹的高度

        //遞歸的求出左子樹高度
        leftThigh = BiTreeDepth(T->lchild);

        //遞歸的求出右子樹高度 
        rightThigh = BiTreeDepth(T->rchild);

        //左右子樹可能高度不相等,按照樹的高度定義
        //應取左子樹和右子樹中高度較大者作爲樹的高度 
        if(leftThigh >= rightThigh) { 
           Thigh = leftThigh + 1;
        }//if 
        else { 
           Thigh = rightThigh + 1;
        }//else
    }//else

    //返回樹的高度 
    return Thigh;
}//BiTreeDepth

//全局變量LNM記錄了二叉樹葉子節點的個數 
int LNM = 0;

/*
    函數:LeafNodeNum
    參數:BiTree T 二叉樹T
    返回值:若二叉樹T存在,返回T的葉子結點個數,否則返回0
    作用:遞歸求二叉樹葉子結點的個數 
*/
int LeafNodeNum(BiTree T){ 

    //葉子結點的特徵是:左孩子和右孩子指針域均爲NULL 
    if(T->lchild == NULL && T->rchild == NULL) { //當前結點是葉子結點 
        LNM++;
    }//if
    else {
        //左孩子不爲空 
        if(T->lchild != NULL) {

            //遞歸的統計左子樹中葉子結點的數目 
            LeafNodeNum(T->lchild);
        }//if

        //右孩子不爲空 
        if(T->rchild != NULL) {

            //遞歸的統計右子樹中葉子結點的數目 
            LeafNodeNum(T->rchild);
        }//if
    }//else
}//LeafNodeNum

/*
    函數:NodeSubNum
    參數:BiTree T 二叉樹T
    返回值:若二叉樹T存在,返回T的總結點個數,否則返回0
    作用:統計二叉樹的總結點個數
*/
int NodeSubNum(BiTree T){

    if(!T) {
        return 0;  //空樹或空子樹
    }//if 
    else {
        //二叉樹總結點數 = 左子樹總結點數 + 右子樹總結點數 + 自身(1) 
        return NodeSubNum(T->lchild) + NodeSubNum(T->rchild) + 1;
    } 
}//NodeSubNum

順序棧的實現精簡版。源文件:Stack.cpp

//-------------------棧的順序存儲表示------------------------- 

typedef BiTree SElemType;   //棧的元素爲二叉樹指針類型 
typedef struct {          //棧的順序存儲表示                        
    SElemType *base;            //棧底指針,在棧構造之前和銷燬之後,base的值爲NULL 
    SElemType *top;             //棧頂指針
    int stacksize;              //當前已分配的存儲空間,以元素爲單位 
}Stack; 

//--------------------------棧的相關函數(供非遞歸後序遍歷使用)----------------------------
/*
    函數:InitStack_Sq
    參數:Stack &S 順序棧引用  
    返回值:狀態碼,OK表示操作成功 
    作用:構造一個空的順序棧 
*/
Status InitStack(Stack &S){

    //動態申請順序棧的內存空間,並檢查內存空間是否成功分配
    //if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType))))
    //這句代碼相當於以下兩行代碼:
    //S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
    //if(!S.base)  <=>  if(S.base == NULL) 
    if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType)))){
        printf("內存分配失敗,程序即將退出!\n");
        exit(OVERFLOW);
    }//if

    //由於剛動態分配完的棧是空棧,所以棧頂指針和棧底指針都指向棧底   
    S.top = S.base;

    //棧的大小就是棧的初始化大小參數STACK_INIT_SIZE 
    S.stacksize = STACK_INIT_SIZE;

    //操作成功 
    return OK; 
}//InitStack_Sq

/*
    函數:DestoryStack_Sq
    參數:Stack &S 順序棧引用  
    返回值:狀態碼,OK表示操作成功 
    作用:釋放順序棧S所佔內存空間 
*/
Status DestoryStack(Stack &S){

    //棧底指針保存的是順序棧內存空間的首地址 
    free(S.base);

    //操作成功 
    return OK; 
}//DestoryStack_Sq

/*
    函數:StackIsEmpty_Sq
    參數:Stack S 順序棧S 
    返回值:若順序棧S是空棧返回1,否返回0 
    作用:判斷順序棧S是否爲空棧
*/
Status StackIsEmpty(Stack S){

    //棧頂指針和棧底指針都指向棧底表示此棧是空棧 
    return S.top == S.base; 
}//StackIsEmpty_Sq

/*
    函數:ReallocStack_Sq
    參數:Stack &S 順序棧S引用 
    返回值:狀態碼,操作成功返回OK,否則返回ERRROR
    作用:將棧S擴容,每擴容一次,棧的大小增加STACKINCREMENT
*/
Status ReallocStack(Stack &S){

    //爲順序棧重新分配內存(擴容),擴展的空間大小是STACKINCREMENT
    /*if(!(S.base = (SElemType *)realloc(S.base, 
                (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType))))
      這句代碼相當於:
      S.base = (SElemType *)realloc(S.base, 
                        (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType));
      if(!S.base) <=> if(S.base == NULL)
    */
    if(!(S.base = (SElemType *)realloc(S.base, 
               (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType)))){
        printf("內存分配失敗,程序即將退出!\n");
        exit(OVERFLOW);
    }//if

    //由於擴容前棧已經滿了,所以棧頂指針位置就是棧底指針+原來棧的大小 
    S.top = S.base + S.stacksize;

    //擴容後,棧的大小增加了STACKINCREMENT 
    S.stacksize += STACKINCREMENT;

    //操作成功 
    return OK; 
}//ReallocStack_Sq

/*
    函數:Push_Sq
    參數:Stack &S 順序棧引用
          SElemType e 被插入的元素e 
    返回值:成功獲取順序棧S棧頂元素的值後返回OK,否則返回ERRROR
    作用:(入棧、壓棧)插入元素e爲新的棧頂元素
*/
Status Push(Stack &S, SElemType e){ 

    //入棧時發現棧滿了,就要追加存儲空間(擴容) 
    if(S.top - S.base >= S.stacksize) {  

        //調用擴容函數
        ReallocStack(S);
    }//if

    //插入前,棧頂指針指向當前棧頂元素的下一個位置 
    //將e賦值給棧頂指針所指存儲空間(插入元素e),棧頂指針後移
    //*S.top++ = e;  <=>  *(S.top) = e;  S.top++; 
    *S.top++ = e;

}//Push_Sq

/*
    函數:Pop_Sq
    參數:Stack &S 順序棧引用
          SElemType &e 帶回被刪除的元素值e 
    返回值:刪除成功返回OK,否則返回ERRROR
    作用:(出棧,彈棧)若棧不空,則刪除S的棧頂元素,用e返回其值
*/
Status Pop(Stack &S, SElemType &e){ 

    //在空棧中執行出棧操作沒有意義,所以要判斷棧是否爲空
    //注意棧是否爲空和棧是否存在不是一個概念,所以不可以用 
    //S.base != NULL判斷棧是否爲空 
    if(StackIsEmpty(S)) { 
         return ERROR;
    }//if

    //刪除前,棧頂指針指向當前棧頂元素的下一個位置
    //--S.top;之後,棧頂指針剛好指向被刪除元素 
    //棧頂指針前移,保存被刪除的元素值到e
    //e=*--S.top;  <=>  --S.top;   e=*(S.top);
    e = *--S.top;

    //操作成功 
    return OK; 
}//Pop_Sq

/*
    函數:GetTop
    參數:Stack S 順序棧S
    返回值:成功獲取順序棧S棧頂元素的值後返回OK,否則返回ERRROR
    作用:用e返回棧頂元素的值,但是棧頂元素不做出棧操作 
*/
Status GetTop(Stack S, SElemType &e){

    //空棧沒有棧頂元素,所以要先判斷棧是否爲空 
    //注意棧是否爲空和棧是否存在不是一個概念,所以不可以用 
    //S.base != NULL判斷棧是否爲空 
    if(StackIsEmpty(S)) { 
         return ERROR;
    }//if

    //注意:棧頂指針指向棧頂元素的下一個位置
    e = *(S.top - 1);  
    /*   注意:此處不能使用“e = *(--S.top); ”的原因 
         1. --S.top自減操作改變了棧頂指針本身的指向,使得該指針向前移動一位,相當於刪除了原來棧中的最後一個元素(最後一個元素出棧); 
         2. S.top-1 僅僅表示棧頂指針的上一個位置,並沒有改變S.top的值,*(S.top-1)表示取棧頂指針前一個位置的值,即棧頂元素的值  
         3. 這兩種寫法造成的結果是不同的,如果是純代數運算,兩者沒有差別,但在指向數組
            (順序結構在C語言中是用一維數組描述的)的指針變量運算中,這兩個表達式有特殊含義 
            在指針運算中,“指針變量-1 ”表示該指針變量所指位置的前一個位置,
            這種做法並不改變指針變量本身的值。 
            --指針變量   不僅使得該指針指向原來所指位置的上一個位置, 還修改了指針變量本身的值
            在棧中,棧頂指針和棧底指針所指向的位置有特殊的含義,故兩者不等價。       
     */ 

     //操作成功 
     return OK; 
}//GetTop_Sq

/*
    函數:StackLength_Sq
    參數:Stack S 順序棧S 
    返回值:若順序棧S是空棧返回1,否返回0 
    作用:判斷順序棧S是否爲空棧
*/
Status StackLength(Stack S){

    //棧的長度就是棧頂指針和棧底指針之間的元素個數 
    return (S.top - S.base); 
}//StackLength_Sq

/*
    函數:Print
    參數:ElemType e 被訪問的元素 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:訪問元素e的函數,通過修改該函數可以修改元素訪問方式,
          該函數使用時需要配合遍歷函數一起使用。 
*/
Status Print_Stack(SElemType e){
    printf("%5d  ", e);
    return OK;
}//Print 

/*
    函數:StackTraverse_Sq
    參數:Stack S 順序棧S 
          Status(* visit)(SElemType) 函數指針,指向元素訪問函數。 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:調用元素訪問函數按出棧順序完成順序棧的遍歷,但並未真正執行出棧操作 
*/
Status StackTraverse(Stack S, Status(* visit)(SElemType)) {

    //在空棧中執行遍歷操作沒有意義,所以要判斷棧是否爲空
    //注意棧是否爲空和棧是否存在不是一個概念,所以不可以用 
    //S.base != NULL判斷棧是否爲空 
    if(StackIsEmpty(S)) {
        printf("此棧是空棧"); 
        return ERROR;
    }//if

    //調用元素訪問函數依次訪問棧中的每個元素
    for(int i = 0; i < StackLength(S); ++i){

        //調用元素訪問函數,一旦訪問失敗則退出  
        if(!visit(S.base[i])) {
            return ERROR;
        }//if 
    }//for

    //輸出換行,是控制檯顯示美觀 
    printf("\n");

    //操作成功 
    return OK;
}//StackTraverse_Sq

鏈隊列實現精簡版。對應源文件:Queue.cpp

//------------------隊列的鏈式存儲表示----------------------- 
typedef BiTree QElemType;   //隊列元素爲二叉樹指針類型

typedef struct  QNode{  //鏈隊列的C語言表示                  
     QElemType data;        //數據域 
     struct QNode * next;   //指針域 
}QNode,* QueuePtr;

typedef struct{
    QueuePtr front;  //隊頭指針 
    QueuePtr rear;   //隊尾指針 
}Queue; 

//--------------------------隊列的相關函數(供非遞歸層序遍歷使用)----------------------------
/*
    函數:MallocQNode
    參數:無 
    返回值:指向新申請結點的指針 
    作用:爲鏈隊列結點申請內存的函數 
*/
QueuePtr MallocQNode(){

    //工作指針p,指向新申請的結點 
    QueuePtr p;

    //if(!(p = (QueuePtr)malloc(sizeof(QNode))))  相當於以下兩行代碼: 
    //p = (QueuePtr)malloc(sizeof(QNode));
    //if(!p)  <=>  if(p != NULL) 
    //申請結點的內存空間,若失敗則提示並退出程序
    if(!(p = (QueuePtr)malloc(sizeof(QNode)))){
        printf("內存分配失敗,程序即將退出!\n");
        exit(OVERFLOW);
    }//if

    //返回新申請結點的地址 
    return p;
}//MallocQNode 

/*
    函數:InitQueue
    參數:Queue &Q 鏈隊列引用 
    返回值:狀態碼,操作成功返回OK 
    作用:構建一個空隊列 Q
*/
Status InitQueue(Queue &Q) {

    //申請頭結點的內存空間,並使隊頭和隊尾指針同時指向它
    Q.front = Q.rear = MallocQNode();

    //由於頭結點剛剛初始化,後面還沒有元素結點 
    Q.front->next = NULL;

    //頭結點數據域記錄了鏈隊列長度
    //由於此時鏈隊列沒有數據節點,所以將頭結點數據域設爲0 
    Q.front->data = 0;

    //操作成功 
    return OK;
}//InitQueue 

/*
    函數:DestoryQueue
    參數:Queue &Q 鏈隊列引用 
    返回值:狀態碼,操作成功返回OK 
    作用:銷燬隊列Q
*/
Status DestoryQueue(Queue &Q){

    //從頭結點開始向後逐個釋放結點內存空間 
    while(Q.front){ //while(Q.front) <=> while(Q.front != NULL)

        //隊尾指針指向被刪除結點的後繼結點 
        Q.rear = Q.front->next;

        //釋放Q.front指向的被刪除結點的空間 
        free(Q.front);

        //隊頭指針後移,指向下一個待刪除結點 
        Q.front = Q.rear; 
    }//while

    //操作成功 
    return OK;
}//DestoryQueue

/*
    函數:QueueEmpty
    參數:Queue Q 鏈隊列Q 
    返回值:狀態碼,若Q爲空隊列,則返回TRUE;否則返回FALSE
    作用:判斷隊列Q是否爲空 
*/
Status QueueEmpty(Queue Q){

    //隊頭指針和隊尾指針均指向鏈隊列頭結點表示鏈隊列爲空 
    if(Q.rear == Q.front){
        return TRUE; 
    }//if
    else {
        return FALSE; 
    }//else
}//QueueEmpty

/*
    函數:EnQueue
    參數:Queue &Q 鏈隊列Q的引用
          QElemType e  被插入的元素e 
    返回值:狀態碼,操作成功後返回OK。 
    作用:插入元素e爲Q的新的隊尾元素
*/
Status EnQueue(Queue &Q, QElemType e){

    //申請一個新的結點,並使p指向這個新結點 
    QueuePtr p = MallocQNode(); 

    //將待插入元素e保存到新結點數據域 
    p->data = e;

    //由於新結點要插在隊尾,後面沒有其他結點,所以後繼指針域的值爲NULL 
    p->next = NULL;

    //將新結點鏈入到隊尾 
    //隊列要求插入操作只能發生在隊尾 
    Q.rear->next = p;

    //修正隊尾指針,使之指向p所指向的新插入的結點 
    Q.rear = p;

    //由於插入一個結點,所以存儲在頭結點中的隊列長度+1 
    Q.front->data++;

    //插入操作成功 
    return OK; 
}//EnQueue 

/*
    函數:DeQueue
    參數:Queue &Q 鏈隊列Q的引用
          QElemType &e 帶回被刪除結點的元素e 
    返回值:狀態碼,操作成功後返回OK。 
    作用:若隊列不空,則刪除Q的隊頭元素,用e返回其值
*/
Status DeQueue(Queue &Q, QElemType &e){

    //注意隊列爲空和隊列不存在的區別,隊列爲空,頭結點一定存在,
    //隊列不存在時頭結點一定不存在
    //對空隊列執行出隊操作沒有意義,出隊操作執行前要先檢查隊列是否爲空 
    if(QueueEmpty(Q)) { //if(QueueEmpty(Q)) <=> if(QueueEmpty(Q) != TRUE)
        return ERROR;
    }//if

    //工作指針p指向隊頭第一個結點(不是頭結點,是頭結點的後繼)
    //隊列要求刪除操作只能發生在隊頭,所以p指向的就是待刪除節點 
    QueuePtr p = Q.front->next;

    //保存被刪除結點的值 
    e = p->data;

    //在刪除操作執行前修正隊頭指針的位置,使之在刪除結點後指向新的隊頭結點 
    Q.front->next = p->next;

    //若被刪除結點恰好是隊尾結點,那麼該結點被刪除後,隊列將會變成空隊列
    //此時剛好滿足空隊列條件:Q.rear == Q.front,所以要修正隊尾指針的位置,使之指向頭結點 
    if(Q.rear == p) { 
        Q.rear = Q.front;
    }//if

    //在隊頭指針和隊尾指針的位置都調整好了之後就可以
    //放心地釋放p指向的結點的內存空間了 
    free(p);

    //由於從隊列中刪除了一個結點,頭結點存儲的隊列長度應當-1 
    Q.front->data--;

    //操作成功 
    return OK; 
}//DeQueue

/*
    函數:Print
    參數:ElemType e 被訪問的元素 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:訪問元素e的函數,通過修改該函數可以修改元素訪問方式,
          該函數使用時需要配合遍歷函數一起使用。 
*/
Status Print_Queue(QElemType e){

    //指定元素的訪問方式是控制檯打印輸出 
    printf("%6.2f    ",e);

    //操作成功 
    return OK;
}//Print

/*
    函數:QueueTraverse
    參數:Queue Q 鏈隊列Q 
          Status (* visit)(QElemType) 函數指針,指向元素訪問函數。 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:調用元素訪問函數按出隊順序完成鏈隊列的遍歷,但並未真正執行出隊操作 
*/
Status QueueTraverse(Queue Q, Status (* visit)(QElemType)) {   

    //對空隊列進行遍歷操作沒有意義,所以遍歷操作前要先判斷隊列是否爲空 
    //注意隊列爲空和隊列不存在的區別,隊列爲空,頭結點一定存在,
    //隊列不存在時頭結點一定不存在  
    if(QueueEmpty(Q)) { //if(QueueEmpty(Q)) <=> if(QueueEmpty(Q) != TRUE)
        return ERROR;
    }//if

    //工作指針p指向隊頭結點 
    QueuePtr p = Q.front->next;

    //從隊頭結點開始依次訪問每個結點,直到隊尾 
    while(p) { //while(p)  <=>  while(p != NULL) 

        //調用元素訪問函數 
        if(!visit(p->data)) { 
            printf("輸出發生錯誤!\n");
            return ERROR;
        }//if

        //工作指針p後移,指向下一個元素 
        p = p->next;
    }//while

    //輸出換行,使結果清楚美觀 
    printf("\n");

    //操作成功 
    return OK; 
}//QueueTraverse
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章