上次已經介紹了遞歸算法以及二叉樹的基本操作,最重要的就是二叉樹的遍歷算法。這次主要是介紹樹的孩子兄弟表示法以及樹和二叉樹的轉換。
還是老規矩:
程序在碼雲上可以下載。
地址: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