二叉树的递归遍历实现非常简单,而根据根节点的访问顺序分为前中后三种遍历顺序,下面是递归遍历实现的代码:
#include <stdio.h>
#include <stdlib.h>
typedef struct treeNode {
int data;
struct treeNode *left;
struct treeNode *right;
} treeNode;
void printTreeRecursiveFirst(treeNode *root)
{
// 先序遍历
printf("%d\n", root->data);
if (root->left) {
printTreeRecursive(root->left);
}
if (root->right) {
printTreeRecursive(root->right);
}
}
void printTreeRecursiveMiddle(treeNode *root)
{
if (root->left) {
printTreeRecursive(root->left);
}
// 中序遍历
printf("%d\n", root->data);
if (root->right) {
printTreeRecursive(root->right);
}
}
void printTreeRecursiveLast(treeNode *root)
{
if (root->left) {
printTreeRecursive(root->left);
}
if (root->right) {
printTreeRecursive(root->right);
}
// 后序遍历
printf("%d\n", root->data);
}
那有没有办法不用递归来实现二叉树的遍历呢,实际上函数调用的实现主要借助是栈这种后进先出的数据结构,我们自然会想到使用栈结构来实现二叉树的非递归遍历。
实际上,对于如下的一棵二叉树,利用栈来遍历,思路主要如下:
第一步我们从根节点开始,不停的遍历左节点,并把每次遍历到的左节点压入栈中,直到没有左节点结束,对应上图1、2、4、6四个节点被压入栈中。
第二步,依次弹出第一步中压入的节点,每次弹出一个节点,访问它的右节点,此时对应上图中的节点6被弹出,它的右节点是9。
第三步,每次从栈中弹出一个节点,访问该节点的右节点,并把这个右节点看成一棵新的树,再次应用第一步和第二步的操作。
第四步,重复执行第三步,直到栈中的节点全部弹出,此时二叉树的所有节点都被遍历到了。
对于这种利用栈的遍历方式,不同的打印节点值的时机也就决定了我们是用什么顺序在遍历。
先序遍历:先打印节点的值,然后再压入栈中
中序遍历:节点弹出栈的时候打印节点的值
后序遍历:节点弹出栈,此时需要等到把它的右节点也处理完才能打印,所以需要把这个节点再压回去,等到该节点的右节点都处理完第二次弹出栈的时候打印。
相应的代码如下:
typedef struct linkNode {
treeNode *tNode;
struct linkNode *next;
} linkNode;
typedef struct stack {
linkNode head;
} stack;
stack *stack_init()
{
stack *s = malloc(sizeof(stack));
s->head.next = NULL;
return s;
}
void stack_push(stack *s, treeNode *tNode)
{
linkNode *lNode = malloc(sizeof(linkNode));
lNode->tNode = tNode;
if (s->head.next) {
lNode->next = s->head.next;
s->head.next = lNode;
} else {
s->head.next = lNode;
lNode->next = NULL;
}
}
treeNode *stack_pop(stack *s) {
if (s->head.next == NULL) {
return NULL;
}
linkNode *tmp = s->head.next;
s->head.next = tmp->next;
treeNode *tNode = tmp->tNode;
free(tmp);
return tNode;
}
int stack_is_empty(stack *s)
{
return s->head.next == NULL;
}
// 先序遍历
void printTreeFirst(treeNode *root)
{
treeNode *tmp = root;
stack *s = stack_init();
while(tmp) {
printf("%d\n", tmp->data);
stack_push(s, tmp);
tmp = tmp->left;
}
while(!stack_is_empty(s)) {
tmp = stack_pop(s);
tmp = tmp->right;
while (tmp) {
printf("%d\n", tmp->data);
stack_push(s, tmp);
tmp = tmp->left;
}
}
}
// 中序遍历
void printTreeMiddle(treeNode *root)
{
treeNode *tmp = root;
stack *s = stack_init();
while(tmp) {
stack_push(s, tmp);
tmp = tmp->left;
}
while(!stack_is_empty(s)) {
tmp = stack_pop(s);
printf("%d\n", tmp->data);
tmp = tmp->right;
while (tmp) {
stack_push(s, tmp);
tmp = tmp->left;
}
}
}
后序遍历有两种实现思路,第一种就如上所说,节点第二次弹出栈的时候打印节点的值,那如何知道节点是否是第二次弹出呢,我们可以给节点加个标志位,标志位的初始值为0,在第二次压栈前把标志位的值置为1,这样每次在弹出节点的时候判断一下这个标志位的值,如果是1就打印节点的值。
typedef struct treeNode {
int data;
int isSecond; // 增加标志位,初始值赋为0
struct treeNode *left;
struct treeNode *right;
} treeNode;
// 后序遍历1
void printTreeLast(treeNode *root)
{
treeNode *tmp = root;
stack *s = stack_init();
while(tmp) {
stack_push(s, tmp);
tmp = tmp->left;
}
while(!stack_is_empty(s)) {
tmp = stack_pop(s);
if(tmp->isSecond) {
printf("%d\n", tmp->data);
} else {
tmp->isSecond = 1;
stack_push(s, tmp);
tmp = tmp->right;
while (tmp) {
stack_push(s, tmp);
tmp = tmp->left;
}
}
}
}
另一种思路:
我们知道先序遍历是:根-->左-->右,而后序遍历是:左-->右-->根,其实我们只要把先序遍历稍微修改变成:根-->右-->左,就发现它正好是后序遍历的倒序,那么我们只要根据 根-->右-->左遍历,把打印节点变成压入一个临时的栈中,结束后再对这个临时的栈依次弹栈打印节点,最终就是我们要的后序遍历。
// 后序遍历2
void printTreeLast(treeNode *root)
{
treeNode *tmp = root;
stack *s = stack_init();
stack *s2 = stack_init();
while(tmp) {
stack_push(s2, tmp); // 打印改为压栈,临时保存起来
stack_push(s, tmp);
tmp = tmp->right;
}
while(!stack_is_empty(s)) {
tmp = stack_pop(s);
tmp = tmp->left;
while (tmp) {
stack_push(s2, tmp);
stack_push(s, tmp);
tmp = tmp->right;
}
}
// 再依次打印栈中的节点
while(!stack_is_empty(s2)) {
treeNode *tmp = stack_pop(s2);
printf("%d\n", tmp->data);
}
}
That’s all!