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;
}