二叉树的遍历--先序、中序、后序、层次遍历

所谓二叉树的遍历,是指按照某条搜索路径访问树中的每个结点,使得每个结点均被访问依次,而且仅被访问一次。
由二叉树的递归定义可知,遍历一棵二叉树便要决定对根结点N、左子树L和右子树R的访问顺序。按照先遍历左子树再遍历右子树的原则,常见的遍历次序有先序(NLR)、中序(LNR)和后序(LRN)三种遍历算法。其中,序指的是根结点在何时被访问。

typedef int ElemType;
typedef struct BiTNode {
    ElemType data;
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

1 先序遍历(PreOrde)

如果二叉树为空,什么也不做。否则:
1)访问根结点;
2)先序遍历左子树;
3)先序遍历右子树。
对应的递归算法如下:

void PreOrder(BiTree T) {
    if (T != NULL) {
        visit(T);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

2 中序遍历(InOrder)

如果二叉树为空,什么也不做。否则:
1)中序遍历左子树;
2)访问根结点;
3)中序遍历右子树。
对应的递归算法如下:

void InOrder(BiTree T) {
    if (T != NULL) {
        InOrder(T->lchild);
        visit(T);
        InOrder(T->rchild);

    }
}

3 后序遍历(PostOrder)

如果二叉树为空,什么也不做。否则:
1)后序遍历左子树;
2)后序遍历右子树;
3)访问根结点。
对应的递归算法如下:

void PostOrder(BiTree T) {
    if (T != NULL) {
        PostOrder(T->lchild);
        PostOrder(T->rchild);
        visit(T);
    }
}

不管采用哪种遍历算法,每个结点都访问一次且仅访问一次,故时间复杂度都是O(n)。在递归遍历中,递归工作栈的栈深恰好为树的深度,所以在最坏的情况下,二叉树是有n个结点且深度为n的单支树,遍历算法的空间复杂度为O(n)。

4 递归算法和非递归算法的转换

可以借助栈,将二叉树的递归遍历算法转换为非递归算法。
1)先序遍历的非递归算法:

void PreOrder2(BiTree T) {
    BiTNode *stack[MAXSIZE] = {NULL};
    int top = -1;
    BiTNode *p = T, *q;
    stack[++top] = T; //根结点入栈
    while (top > -1) {//栈不为空时循环
        p = stack[top--];//退栈
        visit(p);//访问p
        if (p->rchild) {//如果右孩子不为空,进栈
            stack[++top] = p->rchild;
        }
        if (p->lchild) {//如果左孩子不为空,进栈
            stack[++top] = p->lchild;
        }
    }
}

2)中序遍历的非递归算法
先扫描(并非访问)根结点的所有左结点并将它们一一进栈。然后出栈一个结点p(显然p没有左孩子或者左孩子结点均已访问过),则访问它。然后扫描该结点的右孩子结点,将其进栈,再扫描该右孩子结点的所有左结点并一一进栈,如此继续,直到栈空为止。

void inOrder2(BiTree T) {
    BiTNode *stack[MAXSIZE];
    BiTNode *p = T;
    int top = -1;
    while (p || top > -1) { //栈不空或p不空时循环
        if (p) {//p不为空时,进栈
            stack[++top] = p;
            p = p->lchild;//先把所有左子树进栈
        } else {
            p = stack[top--];//p为空时,出栈一个到p
            visit(p);//访问p
            p = p->rchild;//往右子树走
        }
    }
}

3)后序遍历的非递归算法
在后序遍历中,根结点是最后被访问的。因此,在遍历过程中,当搜索指针指向某一结点时,不能立即访问,要先遍历其左子树,此时根结点进栈。当其左子树遍历完再搜索到该根结点时,还是不能访问,还需遍历其右子树,所以,此根结点还需再次进栈,当其右子树遍历完后再退栈到该根结点时,才能被访问。
因此,设立一个状态标志变量tag:tag=0时,结点暂不能访问;tag=1时,结点可以被访问。
其次,设立两个堆栈S1,S2,S1保存结点,S2保存结点的状态标志变量tag。S1和S2共用一个栈顶指针。
设T是指向根结点的指针变量,非递归算法是:若二叉树为空,则返回;否则,另p=T;
(1)第一次经过根结点p,不访问;p进栈S1,tag赋值0,进栈S2,p=p->Lchilrd。
(2)若p不为空,转(1),否则,取状态标志tag。
(3)若tag=0,对栈S1,不访问,不出栈;修改S2栈顶元素值(tag赋1),取S1栈顶元素的右子树,即p=S1[top]->Rchilrd,转(1);
(4)若tag=1,S1退栈,访问该节点;直到栈空为止。

void posOrder2(BiTNode *T) {
    BiTNode *S1[MAXSIZE], *p = T;
    int S2[MAXSIZE], top = -1;
    if (T == NULL) {
        printf("Tree is Empty!\n");
    } else {
        do {
            while (p != NULL) {//若p不为空,一直扫描左子树
                S1[++top] = p;//第一次经过根结点p,不访问;p进栈S1
                S2[top] = 0;//tag赋值0,进栈S2
                p = p->lchild;
            }
            if (S2[top] == 0) {//若tag=0,对栈S1,不访问,不出栈
                p = S1[top]->rchild;//取S1栈顶元素的右子树
                S2[top] = 1;//修改S2栈顶元素值(tag赋1)
            } else {//如果tag=1
                p = S1[top--];//S1退栈
                visit(p);//访问该结点
                p = NULL;//使循环继续进行不至于死循环
            }
        } while (top > -1);
    }
}

测试代码:

int main() {
    BiTree A = (BiTNode *) malloc(sizeof(BiTNode));
    BiTree B = (BiTNode *) malloc(sizeof(BiTNode));
    BiTree C = (BiTNode *) malloc(sizeof(BiTNode));
    BiTree D = (BiTNode *) malloc(sizeof(BiTNode));
    BiTree E = (BiTNode *) malloc(sizeof(BiTNode));
    A->data = 1;
    B->data = 2;
    C->data = 3;
    D->data = 4;
    E->data = 5;
    A->lchild = B;
    A->rchild = C;
    B->lchild = D;
    B->rchild = NULL;
    C->lchild = NULL;
    C->rchild = E;
    D->lchild = NULL;
    D->rchild = NULL;
    E->lchild = NULL;
    E->rchild = NULL;
//    PreOrder(A);
//    InOrder(A);
//    PostOrder(A);
//    PreOrder2(A);
//    inOrder2(A);
//    posOrder2(A);
    return 0;
}

5 层次遍历

要进行层次遍历需要借助一个队列。先将二叉树根结点入队,然后出队,访问该结点,如果它有左子树,则将左子树根结点入队;如果它有右子树,则将右子树根结点入队。然后出队,对出队结点访问,如此反复,直到队列为空。



二叉树的层次遍历算法如下:

void levelorderTraverse(BiTNode *T) {
    BiTNode *Queue[MAXSIZE], *p = T;
    int front = -1, rear = -1;
    if (p != NULL) {
        Queue[++rear] = p;//根结点入队
        while (front < rear) {
            p = Queue[++front];
            visit(p);
            if (p->lchild != NULL) {
                Queue[++rear] = p->lchild;//左结点入队
            }
            if (p->rchild != NULL) {
                Queue[++rear] = p->rchild;//右结点入队
            }
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容