树的遍历及二叉树实现

一、树的遍历

  树的遍历有两大类四种方式,分别是深度优先遍历广度优先遍历

(一)深度优先遍历

  • 先序遍历
      (1)访问根结点;
      (2)先序遍历左子树;
      (3)先序遍历右子树;
  • 中序遍历(二叉树独有)
      (1)中序遍历左子树;
      (2)访问根结点;
      (3)中序遍历右子树;
  • 后序遍历
      (1)后序遍历左子树;
      (2)后序遍历右子树;
      (3)访问根结点;

(二)广度优先遍历

  • 层次遍历:一层一层的访问,从上到下,从左到右的顺序。

(三)C语言实现

  使用递归实现这些遍历比较简单,重点理解递归的用法即可,而使用非递归方式则比较麻烦,这里递归与非递归方式都会给出代码,为了方便,被遍历的树为一颗二叉搜索树,而对树结点的操作为打印输出操作。

(1)递归实现
//头文件:Tree.h
#ifndef TREE_H_INCLUDED
#define TREE_H_INCLUDED
struct BinarySearchTree{
    int element;
    struct BinarySearchTree* left;
    struct BinarySearchTree* right;
};
typedef struct BinarySearchTree* Tree;

Tree insertEle(Tree, int);

void preOrderByRec(Tree);
void inOrderByRec(Tree);
void postOrderByRec(Tree);
void levelOrder(Tree);

#endif // TREE_H_INCLUDED
//遍历实现文件:Tree.c
#include <stdio.h>
#include <stdlib.h>
#include "Tree.h"

//插入新结点
Tree insertEle(Tree tree, int data){

    if(tree){

        if(tree->element > data){
            tree->left = insertEle(tree->left, data);
        }else if(tree->element < data){
            tree->right = insertEle(tree->right, data);
        }else{
            printf("树中已经有该元素,不再插入\n");
        }
    }else{

        tree = (Tree)malloc(sizeof(struct BinarySearchTree));
        tree->element = data;
        tree->left = tree->right = NULL;
    }

    return tree;//注意这里一定要返回该指针!!
}

/*
  递归实现三种深度优先遍历时,代码基本一致
  不同的是对元素操作的时机不同
*/
void preOrderByRec(Tree tree){
    if(tree){
        printf("%d\n", tree->element);//最先访问
        preOrderByRec(tree->left);//遍历左子树
        preOrderByRec(tree->right);//遍历右子树
    }
}

void inOrderByRec(Tree tree){
    if(tree){
        inOrderByRec(tree->left);//先遍历左子树
        printf("%d\n", tree->element);//访问结点
        inOrderByRec(tree->right);//最后遍历右子树
    }
}

void postOrderByRec(Tree tree){
    if(tree){
        postOrderByRec(tree->left);//先遍历左子树
        postOrderByRec(tree->right);//再遍历右子树
        printf("%d\n", tree->element);//最后访问结点
    }
}

/*层次遍历需要使用一个队列,这里只给出伪代码*/
/*
void levelOrder(Tree tree){
    Queue Q;
    Tree T;
    if(!tree) return;//空树则直接返回
    Q = createQueue();//创建空队列Q
    addQ(Q, tree);
    //利用了队列先进先出的性质
    //往队列里添加第k层时,第k层由第k-1层的结点生出来
    //依次添加k-1层每个结点的左右子结点
    //比如,一颗树按层划分为(12)-(5,14)-(4,8,17)-(23)
    //那么往队列添加删除的顺序为(加号为添加,减号为删除):
    //+12, -12, +5, +14, -5, +4, +8, -14, +17, -4, -8, -17, +23, -23
    while(!isEmpty(Q)){
        T = deleteQ(Q);
        printf("%d\n", T->element);
        if(T->left){
            addQ(Q, T->left);
        }
        if(T->right){
            addQ(Q, T->right);
        }
    }
}
*/
//main入口:main.c
#include <stdio.h>
#include <stdlib.h>
#include "Tree.h"

int main()
{
    Tree tree = NULL;
    tree = insertEle(tree, 12);
    tree = insertEle(tree, 5);
    tree = insertEle(tree, 14);
    tree = insertEle(tree, 4);
    tree = insertEle(tree, 8);
    tree = insertEle(tree, 23);
    tree = insertEle(tree, 17);

    printf("先序遍历结果:\n");
    preOrderByRec(tree);
    printf("\n");

    printf("中序遍历结果:\n");
    inOrderByRec(tree);
    printf("\n");

    printf("后序遍历结果:\n");
    postOrderByRec(tree);
    printf("\n");

    return 0;
}

//先序遍历结果:
//12
//5
//4
//8
//14
//23
//17

//中序遍历结果:
//4
//5
//8
//12
//14
//17
//23

//后序遍历结果:
//4
//8
//5
//17
//23
//14
//12
(2)非递归实现

  因为递归本质上使用了一个栈,所以将递归化为非递归,一般额外需要使用一个栈,用该栈来模拟递归,此时循环的条件除了递归里的条件,还有一个就是栈不为空,二叉树的先序遍历和中序遍历化为非递归的主要代码差不多,所以先说这两个,这里也只给出伪代码。

void traverse(Tree tree){
    Stack stack = createStack(Maxsize);
    Tree T = tree;//注意,这里需要用一个局部指针来代替传进来的tree,以免把原树的结构改变了
    //循环的条件二选一:一是结点不为空,二是栈不为空,任何一个满足都可以继续循环
    while(T || !isEmpty(stack)){
        if(T){
            //printf("%d\n", T->element);//先序遍历
            push(stack, T);
            T = T->left;
        }else{
            pop(stack, T);
            printf("%d\n", T->element);//中序遍历
            T = T->right;
        }
    }
}

  后序遍历的非递归实现稍微麻烦一点,这里需要使用一个栈、一个记录当前访问的结点,一个记录上次访问的结点,后序遍历里,对于任意一个结点,只有其右子结点被访问过了,该结点才会被访问,所以我们可以利用这个条件,先出栈一个结点,如果该节点的右子结点没有被访问,那么这个结点再进栈,然后进入右子结点遍历。


这里写图片描述
后序遍历说明图

  如上图,先依次将个左子结点入栈,此时栈Stack = [1,2],然后开始出栈,结点2没有右结点,因此打印输出,然后继续出栈,结点1有右子结点并且此时右子结点并没有访问,所以结点1再次进栈,并将结点3入栈,此时栈Stack = [1,3],此时没有结点可以入栈了,于是结点3出栈,结点3没有右子结点,因此打印输出,然后结点1出栈,因为此时上一次访问的结点是右子节点,因此此时结点1打印,此时栈空,停止程序。伪代码如下。

//后序遍历非递归实现伪代码
void postTraverse(Tree tree){
    Tree curTree = tree;//当前操作的结点
    Tree lastTree = NULL;//上次打印输出的结点
    Stack stack = createStack(MaxSize);//建栈
    //向左遍历进入最左边的元素
    while(curTree){
        push(stack, curTree);
        curTree = curTree->left;
    }
    //考虑上图的思路过程,判断条件为栈非空
    while(!isEmpty(stack)){
        pop(stack, curTree);
        //访问根结点的条件是该结点没有右子结点或者右子结点已经访问过
        if(curTree->right == NULL || curTree->right == lastTree){
            printf("%d\n", curTree->element);
            lastTree = curTree;
        }else{
            push(stack, curTree);//根结点再次入栈
            //右子树入栈
            curTree = curTree->right;
            while(curTree){
                push(stack, curTree);
                curTree = curTree->left;
            }
        }
    }
}
(3)总结

  二叉树的非递归遍历程序里关于循环条件有点绕,因为每个结点既可以看作是根结点、也可以看作子结点,这种灵活性增加了思考的难度,但总的来说,不管哪种遍历方式,都需要在整棵树的前提下,先向左找到最左的结点,这里形成了一条路径,然后沿着该路径回溯,判断出栈操作或向右遍历,而向右遍历的时候看起来只操作了一次(T = T->right)就又开始向左迭代找最左的元素,但这也符合逻辑,即先左子树后右子树的规定,因为把右子树单独看作一棵树,这颗树也有自己的左子树,如果想不清楚的话,按照程序多画画树,就好理解了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值