樹以及樹的遍歷和搜索

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 廣度優先

樹的廣度優先搜索策略是從根節點開始往下,逐層遍歷訪問,其實現方式是藉助隊列FIFO特性實現。
// 廣度優先搜索(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;
}


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