二叉樹的遞歸遍歷實現非常簡單,而根據根節點的訪問順序分爲前中後三種遍歷順序,下面是遞歸遍歷實現的代碼:
#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!