1. 二叉樹
一、建立一個如下圖所示的二叉樹並打印出來。
圖 1
- 它的前序遍歷順序為:621438
- 它的中序遍歷順序為:123468
- 它的後序遍歷順序為:134286
- 它的層次遍歷順序為:628143
二、二叉樹的建立
由於二叉樹的定義是遞歸的,所以用遞歸的思想建立二叉樹是很自然的想法。
1. 以前序遍歷的方式
/* 以前序遍歷的方式建立 */ BinaryTreeNode* create() { BinaryTreeNode *pRoot = nullptr; char c; cin >> c; if(c == ‘#‘) { return nullptr; } else { pRoot = new BinaryTreeNode; pRoot->value = c; pRoot->lChild = create(); pRoot->rChild = create(); } return pRoot; }
補:只要輸入序列621##43###8##,就會建立一個如圖1所示的二叉樹。序列中的‘#‘表示空結點,可用其作為遞歸終止的條件。
2. 根據中序序列和後序序列建立二叉樹
如果只給出前序序列、中序序列或後序序列中的一種,則不能唯一確定一棵二叉樹。但是如果給出中序序列和後序序列或者前序序列和中序序列就可以唯一確定一棵二叉樹。
圖1所示的二叉樹的中序遍歷序列是123468,後序遍歷序列是134286,我們可通過輸入這兩個序列來建立一個如圖1所示的二叉樹。
/* 根據中序序列和後序序列來創建二叉樹 */ BinaryTreeNode* create(int InOrd[], int PostOrd[], int n) { if(n == 0){ // 遞歸終止條件 return nullptr; // 此處應是一個空結點 } BinaryTreeNode *pRoot = new BinaryTreeNode; pRoot->value = PostOrd[n-1]; // 根結點的值 int lChildNum = 0; // 左子樹的結點數 int rChildNum = 0; // 右子樹的結點數 for(; lChildNum < n; ++lChildNum) { if(InOrd[lChildNum] == pRoot->value) break; } rChildNum = n - lChildNum - 1; // 遞歸創建左右子樹 pRoot->lChild = create(InOrd, PostOrd, lChildNum); pRoot->rChild = create(InOrd + lChildNum + 1, PostOrd + lChildNum, rChildNum); return pRoot; }
分析:後序序列的最後一個元素一定是這棵樹的根結點,由此結點的值去遍歷中序序列,可得到左子樹和右子樹的結點個數,據此,再分別傳入左子樹和右子樹的中序序列和後序序列來創建當前根結點的左孩子結點和右孩子結點。如此遞歸,便可建立一棵如圖1所示的二叉樹。
三、用遞歸的方式實現二叉樹的前/中/後序遍歷
/* 前序遍歷 */ void PreOrderTraverse(BinaryTreeNode *pRoot) { if(pRoot == nullptr) return; cout << pRoot->value; PreOrderTraverse(pRoot->lChild); PreOrderTraverse(pRoot->rChild); } /* 中序遍歷 */ void InOrderTraverse(BinaryTreeNode *pRoot) { if(pRoot == nullptr) return; InOrderTraverse(pRoot->lChild); cout << pRoot->value; InOrderTraverse(pRoot->rChild); } /* 後序遍歷 */ void PostOrderTraverse(BinaryTreeNode *pRoot) { if(pRoot == nullptr) return; PostOrderTraverse(pRoot->lChild); PostOrderTraverse(pRoot->rChild); cout << pRoot->value; }
四、用非遞歸的方式實現二叉樹的前/中/後序遍歷
1. 前序遍歷
先從棧頂取出結點,如果該結點不為空,則訪問該結點,同時把該結點的右子樹和左子樹依次入棧。
void PreOrderTraverse(BinaryTreeNode *pRoot) { stack<BinaryTreeNode*> sck; sck.push(pRoot); BinaryTreeNode *pNode = nullptr; while(!sck.empty()) { pNode = sck.top(); sck.pop(); if(pNode != nullptr) { cout << pNode->value; sck.push(pNode->rChild); sck.push(pNode->lChild); } } }
2. 中序遍歷
先把根結點入棧,然後再一直把左子樹入棧,直到左子樹為空,此時停止入棧。棧頂結點就是我們需要訪問的結點,取棧頂結點p並訪問。然後該結點可能有右子樹,所以訪問完結點p後還要判斷p的右子樹是否為空,如果為空則接下來要訪問的結點在棧頂,所以將p賦值為null。如果不為空則將p賦值為其右子樹的值。
/* 中序遍歷 */ void InOrderTraverse(BinaryTreeNode *pRoot) { stack<BinaryTreeNode*> sck; BinaryTreeNode *pCur = pRoot; while(!sck.empty() || pCur != nullptr) { while(pCur != nullptr) { sck.push(pCur); pCur = pCur->lChild; } pCur = sck.top(); sck.pop(); cout << pCur->value; if(pCur->rChild != nullptr) pCur = pCur->rChild; else pCur = nullptr; } }
3. 後序遍歷
對於任一結點P,將其入棧,然後沿其左子樹一直往下搜索,直到搜索到沒有左孩子的結點,此時該結點出現在棧頂,但是此時不能將其出棧並訪問, 因此其右孩子還未被訪問。所以接下來按照相同的規則對其右子樹進行相同的處理。當訪問完其右孩子時,該結點又出現在棧頂,此時可以將其出棧並訪問。這樣就保證了正確的訪問順序。可以看出,在這個過程中,每個第一次不能出棧的結點都將兩次出現在棧頂,並且只有在第二次出現在棧頂時,才能訪問它。因此需要多設置一個變量標識該結點是否是第一次出現在棧頂。
/* 後序遍歷 */ struct BTNode { BinaryTreeNode *pNode; bool isFirst; }; void PostOrderTraverse(BinaryTreeNode *pRoot) { stack<BTNode*> sck; BTNode *temp = nullptr; BinaryTreeNode *pCur = pRoot; while(pCur != nullptr || !sck.empty()) { while(pCur != nullptr) { BTNode *pBtn = new BTNode; pBtn->pNode = pCur; pBtn->isFirst = true; sck.push(pBtn); pCur = pCur->lChild; } temp = sck.top(); sck.pop(); if(temp->isFirst == true) { sck.push(temp); temp->isFirst = false; pCur = temp->pNode->rChild; } else { cout << temp->pNode->value; pCur = nullptr; } } }
分析,以圖1所示的二叉樹為例,如果不加入輔助變量來判斷結點是否第一次出現在棧頂,那麽將輸出1348,其中有右子樹的結點都將因第一次出現在棧頂而被彈出棧,然而第一次出現時並不會輸出,所以結果輸出將沒有它們。
五、層次遍歷
1. 使用隊列的循環實現
/* 層次遍歷 */ void LevelTraverse(BinaryTreeNode *pRoot) { queue<BinaryTreeNode*> que; que.push(pRoot); BinaryTreeNode *pNode = nullptr; while(!que.empty()) { pNode = que.front(); que.pop(); cout << pNode->value; if(pNode->lChild != nullptr) que.push(pNode->lChild); if(pNode->rChild != nullptr) que.push(pNode->rChild); } }
2. 使用遞歸實現
/* 層次遍歷 */ void PrintNodeAtLevel(BinaryTreeNode *pRoot, int level) { if(pRoot == nullptr || level < 1) // 空樹或層級不合理 return; if(level == 1) { cout << pRoot->value; return; } // 左子樹的 level - 1 級 PrintNodeAtLevel(pRoot->lChild, level - 1); // 右子樹的 level - 1 級 PrintNodeAtLevel(pRoot->rChild, level - 1); } void LevelTraverse(BinaryTreeNode *pRoot) { if(pRoot == nullptr) return; int height = GetHeight(pRoot); // 二叉樹的高度 // 逐層打印 for(int i = 1; i <= height; i++) { PrintNodeAtLevel(pRoot, i); cout << endl; } }
分析:該解法得到的既是層次遍歷的結果,又是二叉樹分層輸出的結果。
六、求樹的高度
1. 使用遞歸實現
/* 求樹的高度 */ int GetHeight(BinaryTreeNode *pRoot) { if(pRoot == nullptr) return 0; int leftHeight = GetHeight(pRoot->lChild); int rightHeight = GetHeight(pRoot->rChild); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; }
2. 使用循環實現
利用循環求二叉樹高度的思想是:一層一層地出隊列。在我們每次訪問完畢一層時,這時隊列中存儲的剛好是下一層的所有元素。所以在下一次循環開始時,首先記錄該層的元素個數,一次性訪問完這一層的所有元素。
/* 求樹的高度 */ int GetHeight(BinaryTreeNode *pRoot) { if(pRoot == nullptr) return 0; int height = 0; // 樹的高度 queue<BinaryTreeNode*> que; que.push(pRoot); BinaryTreeNode *pCur = nullptr; // 實際上當每次循環開始時,隊列中存儲的剛好是將要訪問的那一層的所有元素 while(!que.empty()) { height++; int curLevelNum = que.size(); // 當前層的結點數 // 彈出當前層所有元素 while(curLevelNum-- > 0) { pCur = que.front(); que.pop(); // 將下一層的元素壓入隊列 if(pCur->lChild != nullptr) que.push(pCur->lChild); if(pCur->rChild != nullptr) que.push(pCur->rChild); } } return height; }
七、總代碼
#include <iostream> #include <stack> #include <queue> using namespace std; struct BinaryTreeNode { int value; BinaryTreeNode *lChild; BinaryTreeNode *rChild; }; struct BTNode { BinaryTreeNode *pNode; bool isFirst; }; int GetHeight(BinaryTreeNode *pRoot); /* 根據中序序列和後序序列來創建二叉樹 */ BinaryTreeNode* create(int InOrd[], int PostOrd[], int n) { if(n == 0){ // 遞歸終止條件 return nullptr; // 此處應是一個空結點 } BinaryTreeNode *pRoot = new BinaryTreeNode; pRoot->value = PostOrd[n-1]; // 根結點的值 int lChildNum = 0; // 左子樹的結點數 int rChildNum = 0; // 右子樹的結點數 for(; lChildNum < n; ++lChildNum) { if(InOrd[lChildNum] == pRoot->value) break; } rChildNum = n - lChildNum - 1; // 遞歸創建左右子樹 pRoot->lChild = create(InOrd, PostOrd, lChildNum); pRoot->rChild = create(InOrd + lChildNum + 1, PostOrd + lChildNum, rChildNum); return pRoot; } /* 非遞歸先序遍歷 */ void PreOrderTraverse(BinaryTreeNode *pRoot) { stack<BinaryTreeNode*> sck; sck.push(pRoot); BinaryTreeNode *pNode = nullptr; while(!sck.empty()) { pNode = sck.top(); sck.pop(); if(pNode != nullptr) { cout << pNode->value; sck.push(pNode->rChild); sck.push(pNode->lChild); } } } /* 非遞歸中序遍歷 */ void InOrderTraverse(BinaryTreeNode *pRoot) { stack<BinaryTreeNode*> sck; BinaryTreeNode *pCur = pRoot; while(!sck.empty() || pCur != nullptr) { while(pCur != nullptr) { sck.push(pCur); pCur = pCur->lChild; } pCur = sck.top(); sck.pop(); cout << pCur->value; if(pCur->rChild != nullptr) pCur = pCur->rChild; else pCur = nullptr; } } /* 非遞歸後序遍歷 */ void PostOrderTraverse(BinaryTreeNode *pRoot) { stack<BTNode*> sck; BTNode *temp = nullptr; BinaryTreeNode *pCur = pRoot; while(pCur != nullptr || !sck.empty()) { while(pCur != nullptr) { BTNode *pBtn = new BTNode; pBtn->pNode = pCur; pBtn->isFirst = true; sck.push(pBtn); pCur = pCur->lChild; } temp = sck.top(); sck.pop(); if(temp->isFirst == true) { sck.push(temp); temp->isFirst = false; pCur = temp->pNode->rChild; } else { cout << temp->pNode->value; pCur = nullptr; } } } /* 遞歸層次遍歷 */ void PrintNodeAtLevel(BinaryTreeNode *pRoot, int level) { if(pRoot == nullptr || level < 1) // 空樹或層級不合理 return; if(level == 1) { cout << pRoot->value; return; } // 左子樹的 level - 1 級 PrintNodeAtLevel(pRoot->lChild, level - 1); // 右子樹的 level - 1 級 PrintNodeAtLevel(pRoot->rChild, level - 1); } void LevelTraverse(BinaryTreeNode *pRoot) { if(pRoot == nullptr) return; int height = GetHeight(pRoot); // 二叉樹的高度 // 逐層打印 for(int i = 1; i <= height; i++) PrintNodeAtLevel(pRoot, i); } /* 循環求樹的高度 */ int GetHeight(BinaryTreeNode *pRoot) { if(pRoot == nullptr) return 0; int height = 0; // 樹的高度 queue<BinaryTreeNode*> que; que.push(pRoot); BinaryTreeNode *pCur = nullptr; // 實際上當每次循環開始時,隊列中存儲的剛好是將要訪問的那一層的所有元素 while(!que.empty()) { height++; int curLevelNum = que.size(); // 當前層的結點數 // 彈出當前層所有元素 while(curLevelNum-- > 0) { pCur = que.front(); que.pop(); // 將下一層的元素壓入隊列 if(pCur->lChild != nullptr) que.push(pCur->lChild); if(pCur->rChild != nullptr) que.push(pCur->rChild); } } return height; } int main() { int InOrd[6] = {1, 2, 3, 4, 6, 8}; int PostOrd[6] = {1, 3, 4, 2, 8, 6}; BinaryTreeNode *pTree = create(InOrd, PostOrd, 6); cout << "提示:二叉樹創建完畢!" << endl; cout << "提示:先序遍歷二叉樹..." << endl; PreOrderTraverse(pTree); cout << endl; cout << "提示:中序遍歷二叉樹..." << endl; InOrderTraverse(pTree); cout << endl; cout << "提示:後序遍歷二叉樹..." << endl; PostOrderTraverse(pTree); cout << endl; cout << "提示:層次遍歷二叉樹..." << endl; LevelTraverse(pTree); cout << endl; cout << "提示:樹的高度為" << GetHeight(pTree) << endl; return 0; }
1. 二叉樹