1. 數據結構定義
- 若節點集合爲空集,可以是一棵樹;
- 若節點集合非空,則由樹根(root)以及零個或多個非空的子樹(T1,T2...Tk)組成,root與其每棵子樹的樹根之間有一條邊關聯;
樹的一個節點中除了存儲本身包含的數據外,還要存儲該節點的孩子節點、兄弟節點之間的關係,通常用的存儲結構如下:
// 定義樹節點數據結構
struct TNode {
Element data; // 節點數據
TNode* firstChild; // 第一個孩子節點
TNode* nextSibling; // 相鄰的下一個兄弟節點
};
下圖所示爲一棵樹的邏輯結構和存儲結構:
2. 樹的遍歷
2.1 三種遍歷方式
-
前序遍歷:
先訪問根節點,然後依次訪問子樹; -
中序遍歷:
先訪問根節點的第一個孩子節點,然後訪問根節點,最後依次訪問剩下根節點的孩子節點; -
後序遍歷:
先依次訪問根節點的孩子節點,最後訪問根節點.
2.2 遞歸實現
#include <iostream>
#include <malloc.h>
using namespace std;
typedef char Element;
// 定義樹節點數據結構
struct TNode {
Element data; // 節點數據
TNode* firstChild; // 第一個孩子節點
TNode* nextSibling; // 相鄰的下一個兄弟節點
};
void visitNode(TNode* node) {
cout << node->data << " ";
}
// 遞歸實現三種樹的遍歷方式
// 前序遍歷: 先訪問根節點,再依次訪問孩子節點
void preOrder(TNode* root) {
if (NULL == root)
return;
visitNode(root);
TNode* child = root->firstChild;
while (NULL != child) {
preOrder(child);
child = child->nextSibling;
}
}
// 後序遍歷:先依次訪問孩子節點,最後訪問根節點
void postOrder(TNode* root) {
if (NULL == root) {
return;
}
TNode* child = root->firstChild;
while (NULL != child) {
postOrder(child);
child = child->nextSibling;
}
visitNode(root);
}
// 中序遍歷:先訪問第一個孩子節點,然後訪問根節點,最後一次訪問剩下的孩子節點
void inOrder(TNode* root) {
if (NULL == root) {
return;
}
TNode* child = root->firstChild;
if (NULL != child) {
inOrder(child);
child = child->nextSibling;
}
visitNode(root);
while (NULL != child) {
inOrder(child);
child = child->nextSibling;
}
}
// 銷燬樹
void destory(TNode* root) {
if (NULL == root) {
return;
}
TNode* child = root->firstChild;
TNode* temp;
while (NULL != child) {
temp = child->nextSibling;
destory(child);
child = temp;
}
//cout << "destory " << root->data << endl;
free(root);
}
測試代碼如下:
TNode* initTree() {
TNode * root = (TNode*) malloc(sizeof(TNode));
root->data = 'A';
root->nextSibling = NULL;
TNode *child = (TNode*) malloc(sizeof(TNode));
root->firstChild = child;
child->data = 'B';
child->firstChild = NULL;
child->nextSibling = (TNode*) malloc(sizeof(TNode));
child = child->nextSibling;
child->data = 'C';
child->firstChild = NULL;
child->nextSibling = (TNode*) malloc(sizeof(TNode));
child = child->nextSibling;
child->data = 'D';
child->firstChild = NULL;
child->nextSibling = NULL;
child = root->firstChild->nextSibling;
child->firstChild = (TNode*) malloc(sizeof(TNode));
child = child->firstChild;
child->data = 'E';
child->firstChild = NULL;
child->nextSibling = (TNode*) malloc(sizeof(TNode));
child = child->nextSibling;
child->data = 'F';
child->firstChild = NULL;
child->nextSibling = NULL;
return root;
}
int main() {
TNode * tree = initTree();
cout << "PreOrder traversal Tree: " << endl;
preOrder(tree);
cout << endl << endl;
cout << "PostOrder traversal Tree: " << endl;
postOrder(tree);
cout << endl << endl;
cout << "InOrder traversal Tree: " << endl;
inOrder(tree);
cout << endl << endl;
destory(tree);
}
測試輸出:
PreOrder traversal Tree:
A B C E F D
PostOrder traversal Tree:
B E F C D A
InOrder traversal Tree:
B A E C F D
2.3 非遞歸實現
前序遍歷的非遞歸算法相對簡單,初始化時候將root入棧,對棧進行循環操作:先取棧頂節點爲根節點,訪問該節點並彈棧,然後將其子節點 逆序 入棧即可,如此循環直至棧爲空。
// 非遞歸前序遍歷
void nr_preOrder(TNode* tree) {
if (NULL == tree) {
return;
}
stack<TNode*> majorStack; // 主棧
stack<TNode*> tempStack; // 輔助棧,子樹根節點入主棧前逆序
majorStack.push(tree);
TNode* p;
TNode* child;
while (!majorStack.empty()) {
p = majorStack.top();
majorStack.pop();
visitNode(p); // 訪問該節點
// 將該節點的子節點入棧
child = p->firstChild;
while (NULL != child) {
tempStack.push(child);
child = child->nextSibling;
}
while (!tempStack.empty()) {
majorStack.push(tempStack.top());
tempStack.pop();
}
}
p = child = NULL;
}
後序遍歷的非遞歸算法過程中,從樹根開始,對每個節點先順着第一個孩子節點依次入棧,直到葉子節點,這時,取棧頂節點,訪問該節點並彈棧,然後取該節點的下一個兄弟節點作爲初始節點循環上述操作,直至棧爲空。
// 非遞歸後序遍歷
void nr_postOrder(TNode* tree) {
if (NULL == tree) {
return;
}
//
stack<TNode*>* mStack = new stack<TNode*>; // 主棧
TNode* pNode = tree;
while (pNode != NULL || !mStack->empty()) {
while (pNode != NULL) {
mStack->push(pNode);
pNode = pNode->firstChild;
}
if (!mStack->empty()) {
pNode = mStack->top();
mStack->pop();
visitNode(pNode);
pNode = pNode->nextSibling;
}
}
pNode = NULL;
delete mStack;
}
中序遍歷非遞歸算法實現相對複雜,主要是因爲其父節點和第一個孩子及節點之外的孩子節點之間缺少直接聯繫(連續性)。
需要注意的是:孩子節點要先於根節點處理,所以要標記根節點的孩子節點是否已經處理過,這樣才能避免無限循環。
// 非遞歸中序遍歷
void nr_inOrder(TNode* tree) {
if (NULL == tree) {
return;
}
stack<TNode*> majorStack; // 主棧
stack<TNode*> tempStack; // 輔助棧
stack<bool> flagStack; // 標誌棧,記錄根節點的第一個孩子節點是否處理過
majorStack.push(tree);
flagStack.push(tree->firstChild == NULL);
TNode* p;
TNode* temp;
bool flag;
while (!majorStack.empty()) {
p = majorStack.top();
flag = flagStack.top();
if (!flag) {
// 修改標記,表示該節點的孩子節點已經處理過
flagStack.pop();
flagStack.push(true);
// 將首個孩子節點入棧
temp = p->firstChild;
majorStack.push(temp);
flagStack.push(temp->firstChild == NULL);
continue;
}
// 如果第一個孩子節點已經處理過
visitNode(p);
majorStack.pop();
flagStack.pop();
// 接着處理後序孩子節點
temp = p->firstChild;
if (temp != NULL) {
temp = temp->nextSibling;
}
while (NULL != temp) {
tempStack.push(temp);
temp = temp->nextSibling;
}
while (!tempStack.empty()) {
temp = tempStack.top();
majorStack.push(temp);
flagStack.push(temp->firstChild == NULL);
tempStack.pop();
}
}
p = temp = NULL;
}
3. 樹的搜索
3.1 深度優先
3.2 廣度優先
// 廣度優先搜索(Breadth-first search)
int bfs_search(TNode* tree, Element ele) {
if (NULL == tree) {
return 0;
}
queue<TNode*>* sehQueue = new queue<TNode*>;
TNode* aNode;
TNode* temp;
int flag = 0;
sehQueue->push(tree);
while (!sehQueue->empty()) {
aNode = sehQueue->front();
cout << aNode->data << "\t";
sehQueue->pop();
if (aNode->data == ele) {
flag = 1;
break;
}
temp = aNode->firstChild;
while (NULL != temp) {
sehQueue->push(temp);
temp = temp->nextSibling;
}
}
delete sehQueue;
aNode = temp = NULL;
return flag;
}