樹形結構是一類重要的非線性資料結構,其中以樹和二叉樹最為常用。
二叉樹是每個結點最多有兩個子樹的有序樹。通常子樹的根被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。二叉樹常被用作二叉尋找樹和二元堆積或是二叉排序樹。二叉樹的每個結點至多隻有二棵子樹(不存在度大於2的結點),二叉樹的子樹有左右之分,次序不能顛倒。二叉樹的第i層至多有2的 i -1次方個結點;深度為k的二叉樹至多有2^(k) -1個結點;對任何一棵二叉樹T,如果其終端結點數(即葉子結點數)為n0,度為2的結點數為n2,則n0 = n2 + 1。
二叉樹的鏈式儲存結構是一類重要的資料結構,其形式定義如下:
//二叉樹結點typedef struct BiTNode{//資料char data;//左右孩子指標struct BiTNode *lchild,*rchild;}BiTNode,*BiTree;
二叉樹的建立:
通過讀入一個字串,建立二叉樹的演算法如下:
//按先序序列建立二叉樹int CreateBiTree(BiTree &T){char data;//按先序次序輸入二叉樹中結點的值(一個字元),‘#’表示空樹scanf("%c",&data);if(data == '#'){T = NULL;}else{T = (BiTree)malloc(sizeof(BiTNode));//產生根結點T->data = data;//構造左子樹CreateBiTree(T->lchild);//構造右子樹CreateBiTree(T->rchild);}return 0;}
二叉樹的遍曆:
遍曆是對樹的一種最基本的運算,所謂遍曆二叉樹,就是按一定的規則和順序走遍二叉樹的所有結點,使每一個結點都被訪問一次,而且只被訪問一次。由於二叉樹是非線性結構,因此,樹的遍曆實質上是將二叉樹的各個結點轉換成為一個線性序列來表示。
遞迴演算法:
//輸出void Visit(BiTree T){if(T->data != '#'){printf("%c ",T->data);}}//先序遍曆void PreOrder(BiTree T){if(T != NULL){//訪問根節點Visit(T);//訪問左子結點PreOrder(T->lchild);//訪問右子結點PreOrder(T->rchild);}}//中序遍曆void InOrder(BiTree T){if(T != NULL){//訪問左子結點InOrder(T->lchild);//訪問根節點Visit(T);//訪問右子結點InOrder(T->rchild);}}//後序遍曆void PostOrder(BiTree T){if(T != NULL){//訪問左子結點PostOrder(T->lchild);//訪問右子結點PostOrder(T->rchild);//訪問根節點Visit(T);}}
非遞迴演算法:
<1>先序遍曆:
【思路】:訪問T->data後,將T入棧,遍曆左子樹;遍曆完左子樹返回時,棧頂元素應為T,出棧,再先序遍曆T的右子樹。
/* 先序遍曆(非遞迴) 思路:訪問T->data後,將T入棧,遍曆左子樹;遍曆完左子樹返回時,棧頂元素應為T,出棧,再先序遍曆T的右子樹。*/void PreOrder2(BiTree T){stack<BiTree> stack;//p是遍曆指標BiTree p = T;//棧不空或者p不空時迴圈while(p || !stack.empty()){if(p != NULL){//存入棧中stack.push(p);//訪問根節點printf("%c ",p->data);//遍曆左子樹p = p->lchild;}else{//退棧p = stack.top();stack.pop();//訪問右子樹p = p->rchild;}}//while}
<2>中序遍曆
【思路】:T是要遍曆樹的根指標,中序遍曆要求在遍曆完左子樹後,訪問根,再遍曆右子樹。
先將T入棧,遍曆左子樹;遍曆完左子樹返回時,棧頂元素應為T,出棧,訪問T->data,再中序遍曆T的右子樹。
void InOrder2(BiTree T){stack<BiTree> stack;//p是遍曆指標BiTree p = T;//棧不空或者p不空時迴圈while(p || !stack.empty()){if(p != NULL){//存入棧中stack.push(p);//遍曆左子樹p = p->lchild;}else{//退棧,訪問根節點p = stack.top();printf("%c ",p->data);stack.pop();//訪問右子樹p = p->rchild;}}//while}
<3>後序遍曆
【思路】:T是要遍曆樹的根指標,後序遍曆要求在遍曆完左右子樹後,再訪問根。需要判斷根結點的左右子樹是否均遍曆過。
//後序遍曆(非遞迴)typedef struct BiTNodePost{BiTree biTree;char tag;}BiTNodePost,*BiTreePost;void PostOrder2(BiTree T){stack<BiTreePost> stack;//p是遍曆指標BiTree p = T;BiTreePost BT;//棧不空或者p不空時迴圈while(p != NULL || !stack.empty()){//遍曆左子樹while(p != NULL){BT = (BiTreePost)malloc(sizeof(BiTNodePost));BT->biTree = p;//訪問過左子樹BT->tag = 'L';stack.push(BT);p = p->lchild;}//左右子樹訪問完畢訪問根節點while(!stack.empty() && (stack.top())->tag == 'R'){BT = stack.top();//退棧stack.pop();BT->biTree;printf("%c ",BT->biTree->data);}//遍曆右子樹if(!stack.empty()){BT = stack.top();//訪問過右子樹BT->tag = 'R';p = BT->biTree;p = p->rchild;}}//while}
<4>層次遍曆
【思路】:按從頂向下,從左至右的順序來逐層訪問每個節點,層次遍曆的過程中需要用隊列。
//層次遍曆void LevelOrder(BiTree T){BiTree p = T;//隊列queue<BiTree> queue;//根節點入隊queue.push(p);//隊列不空迴圈while(!queue.empty()){//對頭元素出隊p = queue.front();//訪問p指向的結點printf("%c ",p->data);//退出隊列queue.pop();//左子樹不空,將左子樹入隊if(p->lchild != NULL){queue.push(p->lchild);}//右子樹不空,將右子樹入隊if(p->rchild != NULL){queue.push(p->rchild);}}}
測試案例:
輸入:
ABC##DE#G##F###
輸出:
代碼:
#include<iostream>#include<stack>#include<queue>using namespace std;//二叉樹結點typedef struct BiTNode{//資料char data;//左右孩子指標struct BiTNode *lchild,*rchild;}BiTNode,*BiTree;//按先序序列建立二叉樹int CreateBiTree(BiTree &T){char data;//按先序次序輸入二叉樹中結點的值(一個字元),‘#’表示空樹scanf("%c",&data);if(data == '#'){T = NULL;}else{T = (BiTree)malloc(sizeof(BiTNode));//產生根結點T->data = data;//構造左子樹CreateBiTree(T->lchild);//構造右子樹CreateBiTree(T->rchild);}return 0;}//輸出void Visit(BiTree T){if(T->data != '#'){printf("%c ",T->data);}}//先序遍曆void PreOrder(BiTree T){if(T != NULL){//訪問根節點Visit(T);//訪問左子結點PreOrder(T->lchild);//訪問右子結點PreOrder(T->rchild);}}//中序遍曆 void InOrder(BiTree T){ if(T != NULL){ //訪問左子結點 InOrder(T->lchild); //訪問根節點 Visit(T); //訪問右子結點 InOrder(T->rchild); } } //後序遍曆void PostOrder(BiTree T){if(T != NULL){//訪問左子結點PostOrder(T->lchild);//訪問右子結點PostOrder(T->rchild);//訪問根節點Visit(T);}}/* 先序遍曆(非遞迴) 思路:訪問T->data後,將T入棧,遍曆左子樹;遍曆完左子樹返回時,棧頂元素應為T,出棧,再先序遍曆T的右子樹。*/void PreOrder2(BiTree T){stack<BiTree> stack;//p是遍曆指標BiTree p = T;//棧不空或者p不空時迴圈while(p || !stack.empty()){if(p != NULL){//存入棧中stack.push(p);//訪問根節點printf("%c ",p->data);//遍曆左子樹p = p->lchild;}else{//退棧p = stack.top();stack.pop();//訪問右子樹p = p->rchild;}}//while}/* 中序遍曆(非遞迴) 思路:T是要遍曆樹的根指標,中序遍曆要求在遍曆完左子樹後,訪問根,再遍曆右子樹。 先將T入棧,遍曆左子樹;遍曆完左子樹返回時,棧頂元素應為T,出棧,訪問T->data,再中序遍曆T的右子樹。*/void InOrder2(BiTree T){stack<BiTree> stack;//p是遍曆指標BiTree p = T;//棧不空或者p不空時迴圈while(p || !stack.empty()){if(p != NULL){//存入棧中stack.push(p);//遍曆左子樹p = p->lchild;}else{//退棧,訪問根節點p = stack.top();printf("%c ",p->data);stack.pop();//訪問右子樹p = p->rchild;}}//while}//後序遍曆(非遞迴)typedef struct BiTNodePost{BiTree biTree;char tag;}BiTNodePost,*BiTreePost;void PostOrder2(BiTree T){stack<BiTreePost> stack;//p是遍曆指標BiTree p = T;BiTreePost BT;//棧不空或者p不空時迴圈while(p != NULL || !stack.empty()){//遍曆左子樹while(p != NULL){BT = (BiTreePost)malloc(sizeof(BiTNodePost));BT->biTree = p;//訪問過左子樹BT->tag = 'L';stack.push(BT);p = p->lchild;}//左右子樹訪問完畢訪問根節點while(!stack.empty() && (stack.top())->tag == 'R'){BT = stack.top();//退棧stack.pop();BT->biTree;printf("%c ",BT->biTree->data);}//遍曆右子樹if(!stack.empty()){BT = stack.top();//訪問過右子樹BT->tag = 'R';p = BT->biTree;p = p->rchild;}}//while}//層次遍曆void LevelOrder(BiTree T){BiTree p = T;//隊列queue<BiTree> queue;//根節點入隊queue.push(p);//隊列不空迴圈while(!queue.empty()){//對頭元素出隊p = queue.front();//訪問p指向的結點printf("%c ",p->data);//退出隊列queue.pop();//左子樹不空,將左子樹入隊if(p->lchild != NULL){queue.push(p->lchild);}//右子樹不空,將右子樹入隊if(p->rchild != NULL){queue.push(p->rchild);}}}int main(){BiTree T;CreateBiTree(T);printf("先序遍曆:\n");PreOrder(T);printf("\n");printf("先序遍曆(非遞迴):\n");PreOrder2(T);printf("\n");printf("中序遍曆:\n");InOrder(T);printf("\n");printf("中序遍曆(非遞迴):\n");InOrder2(T);printf("\n");printf("後序遍曆:\n");PostOrder(T);printf("\n");printf("後序遍曆(非遞迴):\n");PostOrder2(T);printf("\n");printf("層次遍曆:\n");LevelOrder(T);printf("\n"); return 0;}