二叉树是我们比较熟悉的一个数据结构,遍历更是二叉树的最基本的一种操作,所谓遍历二叉树,就是按照一定的规则和顺序走遍二叉树的所有结点,使每一个结点都被访问一次,而且只被访问一次。由于二叉树是非线性结构,因此,树的遍历实质上是将二叉树的各个结点转换成为一个线性序列来表示。
二叉树的遍历方式:
设LDR分别表示遍历左子树、访问根节点和遍历右子树,则对一棵树的遍历有三种情况:
- DLR(前序遍历):先访问根节点,再遍历左子树,最后遍历右子树。
- LDR(中序遍历):先遍历左子树,再访问根节点,最后遍历右子树。
- LRD(后序遍历):先遍历左子树,再遍历右子树,最后访问根节点。
下面我们来看一例子,来看一下下面这棵树的前中后序遍历结果:
- 前序遍历:A B D E C F G
- 中序遍历:D B E A G C G
- 后序遍历:D E B F G C A
下面我们就以上面这棵树为例介绍二叉树的构建和遍历,首先,我们先来定义以下树中结点的结构:
/*---结点中元素类型---*/
typedef char ElementType;
/*---树结点---*/
typedef struct Node {
ElementType _val;
struct Node* _left;
struct Node* _right;
} Node;
首先,我们先写三个递归遍历树的代码,以便验证构建出来的树是否正确:
/*---前序遍历(递归)---*/
void preTraverse(Node* root) {
if (root == NULL) {
return;
}
printf("%c ", root->_val);
preTraverse(root->_left);
preTraverse(root->_right);
}
/*---中序遍历(递归)---*/
void inTraverse(Node* root) {
if (root == NULL) {
return;
}
inTraverse(root->_left);
printf("%c ", root->_val);
inTraverse(root->_right);
}
/*---后序遍历(递归)---*/
void postTraverse(Node* root) {
if (root == NULL) {
return;
}
postTraverse(root->_left);
postTraverse(root->_right);
printf("%c ", root->_val);
}
下面,我们根据这棵树的前序序列在内存中构建出这棵树:
/*---前序构建树---*/
Node* buildTreeByPre(ElementType pre[], int size, int* used) {
if (size == 0) {
*used = 0;
return NULL;
}
if (pre[0] == '#') {
*used = 1;
return NULL;
}
Node* root = (Node*)malloc(sizeof(Node));
root->_val = pre[0];
int left_used;
root->_left = buildTreeByPre(pre + 1, size - 1, &left_used);
int right_used;
root->_right = buildTreeByPre(pre + 1 + left_used,
size - 1 - left_used, &right_used);
*used = left_used + right_used + 1;
return root;
}
这里,我们写个测试代码,看看这个函数是否能够完成我们的需求:
void BinaryTreeTest() {
// 注意这里的前序序列需要考虑左右子树为空的情况
// 例如D的左右子树为空,所有其后是两个#表示空
char* pre = "ABD##E##CF##G";
int size = strlen(pre);
int used = 0;
Node* root = buildTreeByPre(pre, size, &used);
// 前序遍历(递归)
printf("The preorder is: \t");
preTraverse(root);
// 中序遍历(递归)
printf("\nThe inorder is: \t");
inTraverse(root);
// 后序遍历(递归)
printf("\nThe postorder is: \t");
postTraverse(root);
printf("\n");
}
运行结果如下:
可以看到,和我们前面自己写的前中后序完全一致,说明根据前序序列构建树的代码是正确的。
虽然,上面根据一个前序序列就可以构建树,但是它需要将前面叶子结点的左右空孩子补为#,下面我们看一下不需要补空的构建方式:
- 根据前序和中序构建树。
- 根据中序和后序构建树。
我们看到根据前序和中序、中序和后序都可以构建树,那么根据前序和后序能不能构建树呢?
- 答案是不行的。因为前序和中序、中序和后序都有中序,我们知道中序的特点是左子树在根结点的左边,右子树在根结点的右边。
- 根据这个特点,我们可以根据在前序或后序中拿到的结点值去中序中找根结点所在下标,以此来将中序序列拆分为左右子树,依次拆分下去就可以构建二叉树。
下面,我们来看一下,如何根据前序和中序构建二叉树,代码如下:
/*---查找元素在数组中的下标---*/
int eleFind(ElementType in[], ElementType root_val, int size) {
for (int i = 0; i < size; ++i) {
if (in[i] == root_val) {
return i;
}
}
return -1;
}
/*---前序和中序构建二叉树---*/
Node* buildTreeByPreIn(ElementType pre[], ElementType in[], int size) {
if (size == 0) {
return NULL;
}
ElementType root_val = pre[0];
int left_size = eleFind(in, root_val, size);
Node* root = (Node*)malloc(sizeof(Node));
root->_val = root_val;
root->_left = buildTreeByPreIn(pre + 1, in, left_size);
root->_right = buildTreeByPreIn(pre + 1 + left_size,
in + 1 + left_size, size - 1 - left_size);
return root;
}
同样的,我们写一个测试代码:
void BinaryTreeTest() {
// 前序
char* pre = "ABDECFG";
// 中序
char* in = "DBEAFCG";
Node* root = buildTreeByPreIn(pre, in, strlen(pre));
// 前序遍历(递归)
printf("The preorder is: \t");
preTraverse(root);
// 中序遍历(递归)
printf("\nThe inorder is: \t");
inTraverse(root);
// 后序遍历(递归)
printf("\nThe postorder is: \t");
postTraverse(root);
printf("\n");
}
运行结果如下:
结果没问题,代码是对的。
下面,再看一下根据中序和后序构建二叉树的代码:
/*---查找元素在数组中的下标---*/
int eleFind(ElementType in[], ElementType root_val, int size) {
for (int i = 0; i < size; ++i) {
if (in[i] == root_val) {
return i;
}
}
return -1;
}
/*---中序和后序构建二叉树---*/
Node* buildTreeByInPost(ElementType in[], ElementType post[], int size) {
if (size == 0) {
return NULL;
}
ElementType root_val = post[size - 1];
int left_size = eleFind(in, root_val, size);
Node* root = (Node*)malloc(sizeof(Node));
root->_val = root_val;
root->_left = buildTreeByInPost(in, post, left_size);
root->_right = buildTreeByInPost(in + 1 + left_size,
post + left_size, size - 1 - left_size);
return root;
}
最后,我们来看一下二叉树前中后序遍历的非递归实现方式:
/*---前序遍历(非递归)-- - */
void preTarverseNoRecursion(Node* root) {
std::stack<std::pair<Node*, bool>> s;
s.push(std::make_pair(root, false));
bool visited;
while (!s.empty()) {
root = s.top().first;
visited = s.top().second;
s.pop();
if (root == NULL) {
continue;
}
if (visited) {
printf("%c ", root->_val);
} else {
s.push(std::make_pair(root->_right, false));
s.push(std::make_pair(root->_left, false));
s.push(std::make_pair(root, true));
}
}
}
同样的,我们写一个测试代码,使用根据中序和后序构建的二叉树,分别使用递归和非递归进行前序遍历:
void BinaryTreeTest() {
// 中序
char* in = "DBEAFCG";
// 后序
char* post = "DEBFGCA"
Node* root = buildTreeByInPost(pre, in, strlen(pre));
// 前序遍历(递归)
printf("The recursion preorder is: \t");
preTraverse(root);
// 前序遍历(非递归)
printf("\nThe Unrecursion preorder is: \t");
preTarverseNoRecursion(root);
printf("\n");
}
运行结果如下:
/*---中序遍历(非递归)-- - */
void inTraverseNoRecursion(Node* root) {
std::stack<std::pair<Node*, bool>> s;
s.push(std::make_pair(root, false));
bool visited;
while (!s.empty()) {
root = s.top().first;
visited = s.top().second;
s.pop();
if (root == NULL) {
continue;
}
if (visited) {
printf("%c ", root->_val);
} else {
s.push(std::make_pair(root->_right, false));
s.push(std::make_pair(root, true));
s.push(std::make_pair(root->_left, false));
}
}
}
同样的,我们写一个测试代码,我们使用前序构建的二叉树,对其使用递归和非递归的方式进行中序遍历:
void BinaryTreeTest() {
char* pre = "ABD##E##CF##G";
int size = strlen(pre);
int used = 0;
Node* root = buildTreeByPre(pre, size, &used);
// 中序遍历(递归)
printf("The recursion inorder is: \t");
inTraverse(root);
// 中序遍历(非递归)
printf("\nThe Unrecursion inorder is: \t");
inTraverseNoRecursion(root);
printf("\n");
}
运行结果如下:
/*---后序遍历(非递归)-- - */
void postTraverseNoRecursion(Node* root) {
std::stack<std::pair<Node*, bool>> s;
s.push(std::make_pair(root, false));
bool visited;
while (!s.empty()) {
root = s.top().first;
visited = s.top().second;
s.pop();
if (root == NULL) {
continue;
}
if (visited) {
printf("%c ", root->_val);
} else {
s.push(std::make_pair(root, true));
s.push(std::make_pair(root->_right, false));
s.push(std::make_pair(root->_left, false));
}
}
}
同样的,我们写一个测试代码,我们使用前序构建的二叉树,对其使用递归和非递归的方式进行后序遍历:
void BinaryTreeTest() {
char* pre = "ABD##E##CF##G";
int size = strlen(pre);
int used = 0;
Node* root = buildTreeByPre(pre, size, &used);
// 后序遍历(递归)
printf("The recursion postorder is: \t");
postTraverse(root);
// 后序遍历(非递归)
printf("\nThe Unrecursion postorder is: \t");
postTraverseNoRecursion(root);
printf("\n");
}
运行结果如下: