树的遍历及二叉树实现

来源:互联网 发布:石头人升级数据 编辑:程序博客网 时间:2024/05/16 23:44

一、树的遍历

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

(一)深度优先遍历

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

(二)广度优先遍历

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

(三)C语言实现

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

(1)递归实现

//头文件:Tree.h#ifndef TREE_H_INCLUDED#define TREE_H_INCLUDEDstruct 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)就又开始向左迭代找最左的元素,但这也符合逻辑,即先左子树后右子树的规定,因为把右子树单独看作一棵树,这颗树也有自己的左子树,如果想不清楚的话,按照程序多画画树,就好理解了。

原创粉丝点击