二叉树的遍历

复习数据结构的二叉树遍历时,想到以前对这一块真的是头晕,有一部分原因是上课没好好听讲,也有一部分原因是自己研究代码不透彻,大概就知道是先序DLR、中序LDR、后序LRD,今天沉下心就来搞清楚这个问题。


1、二叉树顺序存储方式

想搞清楚二叉树的遍历就要从二叉树的顺序存储开始,我在前面的有写过:二叉树的顺序存储结构就是使用一维数组存储二叉树中的结点,并且结点的存储位置,就是数组的下标索引。(建议直接看图,清晰明了)

img

这里我们要记住:子节点的表示方法(左节点,右节点):(2i+1,2i+2)

2、递归实现

二叉树遍历说明

记住这个口诀(前序DLR、中序LDR、后序LRD)不是没有道理,记住子节点的表示方法也不是没有道理,我们将在递归实现中完美的展示出来,话不多说直接看代码:

/**
 * create by wzm
 * 递归的实现
 */
public class BinaryTree {
    //先序遍历
    private static void preOrder(int[] arr, int index){ 
        if (index>=arr.length){
            return;
        }
        System.out.println(arr[index]); //先输出根节点D
        preOrder(arr, index*2+1); //输出左子树L
        preOrder(arr,index*2+2); //输出右子树R
    }
    //中序遍历
    private static void inOrder(int[] arr,int index){
        if (index>=arr.length){
            return;
        }
        inOrder(arr, index*2+1); //L
        System.out.println(arr[index]); //D
        inOrder(arr,index*2+2); //R
    }

    //后序遍历
    private static void postOder(int[] arr,int index){
        if (index>=arr.length){
            return;
        }
        postOder(arr, index*2+1); //L
        postOder(arr,index*2+2); //R
        System.out.println(arr[index]); //D
    }
	
    //测试
    public static void main(String[] args) { //index为顺序存储中根结点所在位置
        int[] arr = {0,1,2,3,4,5,6,7,8,9};
        preOrder(arr,0);
        System.out.println("---------------------");
        inOrder(arr,0);
        System.out.println("---------------------");
        postOder(arr,0);
    }
}

结合上面提到的顺序存储,简直一目了然。

3、非递归实现

看了一些别人的博客,实现起来还是有些绕,可以运用栈的思想,当然也可以运用队列的思想,我在这里主要用栈的思想,把完成的注解写自在了代码中,边看边理解:

import java.util.Stack;
import java.util.ArrayDeque;
import java.util.Queue;

/**
 * create by wzm
 * 非递归的实现
 */
public class BinaryTree {
	
    //Node节点
    static class Node {
        int data; //节点值
        Node leftChild; //左孩子
        Node rightChild; //右孩子
        public Node() {
        }
        public Node(int data) {
            this.data = data;
        }
    }

    // create binaryTree from arrays
    private static Node creatTree(int[] element, int i) {
        if (i >= element.length || element[i] == -1)
            return null;
        Node root = new Node(element[i]);
        root.leftChild = creatTree(element, i * 2 + 1);
        root.rightChild = creatTree(element, i * 2 + 2);
        return root;
    }


    /**
     * 非递归先序遍历的思路如下:
     *     1.先将根节点入栈
     *     2.访问根节点
     *     3.如果根节点存在右孩子,则将右孩子入栈
     *     4.如果根节点存在左孩子,则将左孩子入栈(注意:一定是右孩子先入栈,然后左孩子入栈)
     *     5.重复2~4
     *
     * @param root
     */
    private static void preOrder(Node root) {
        if (root==null)
            return;

        Node tmp = root;
        Stack<Node> s = new Stack<>();
        //1.根节点入栈
        s.push(tmp);
        //5.重复2~4
        while (!s.empty()) {
            //2.访问根节点
            Node p = s.pop();
            System.out.print(p.data+" ");
            //3.如果根节点存在右孩子,则将右孩子入栈
            if (p.rightChild!=null) {
                s.push(p.rightChild);
            }
            //4.如果根节点存在左孩子,则将左孩子入栈
            if (p.leftChild!=null) {
                s.push(p.leftChild);
            }
        }
        System.out.println();
    }

    /**
     * 非递归中序遍历的思路如下:
     *     1.先将根节点入栈
     *     2.将当前节点的所有左孩子入栈,直到左孩子为空
     *     3.访问栈顶元素,如果栈顶元素存在右孩子,则继续第2步
     *     4.重复第2、3步,直到栈为空并且所有的节点都被访问
     *
     * @param root
     */
    private static void inOrder(Node root) {
        if (root==null)
            return;

        Node tmp = root;
        Stack<Node> s = new Stack<>();
        while (tmp!=null || !s.empty()) {
            //1.先将根节点入栈
            //2.将所有左孩子入栈
            while (tmp!=null) {
                s.push(tmp);
                tmp = tmp.leftChild;
            }
            //3.访问栈顶元素
            tmp = s.pop();
            System.out.print(tmp.data+" ");
            //若存在右孩子,将右孩子入栈
            if (tmp.rightChild!=null) {
                tmp = tmp.rightChild;
            }
            //否则,将tmp置为null,表示下次要访问的是栈顶元素
            else {
                tmp = null;
            }
        }
        System.out.println();
    }

    /**
     *  非递归后续遍历的实现思路:
     *     1.根节点入栈
     *     2.将根节点的左子树入栈,直到最左,没有左孩子为止
     *     3.得到栈顶元素的值,先不访问,判断栈顶元素是否存在右孩子,如果存在并且没有被访问,则将右孩子入栈,否则,就访问栈顶元素
     *
     * @param root
     */
    public static void postOrder(Node root) {
        if (root==null)
            return;

        Node tmp = root;
        Node prev = null; //上一次访问的结点
        Stack<Node> s = new Stack<>();
        while (tmp!=null || !s.empty()) {
            //1.根节点及左孩子入栈
            while (tmp!=null) {
                s.push(tmp);
                tmp = tmp.leftChild;
            }
            if (!s.empty()) {
                //2.获取栈顶元素
                tmp = s.peek();
                //3.没有右孩子,或者右孩子已经被访问过
                if (tmp.rightChild==null || tmp.rightChild==prev) {
                    //则访问栈顶元素
                    tmp = s.pop();
                    System.out.print(tmp.data+" ");
                    //标记上一次访问的结点
                    prev = tmp;
                    tmp = null;
                }
                //4.存在没有被访问的右孩子
                else {
                    tmp = tmp.rightChild;
                }
            }
        }
        System.out.println();
    }
	
    //测试,比对递归实现,完全正确
    public static void main(String[] args) {
        int[] arrays = { 0, 1, 2, 3, 4, 5, 6, 7,8, 9};
        Node tree = creatTree(arrays, 0);
        System.out.println("测试结果:");

        preOrder(tree);
        System.out.println();

        inOrder(tree);
        System.out.println();

        postOrder(tree);
        System.out.println();
        
        //levelOrder(tree);
    }
}

推荐一篇结合了图片写的清晰的: https://blog.csdn.net/Benja_K/article/details/88389039 ,实现的方法有一些不一样。

4、层序遍历

从上往下打印出二叉树的每个节点,同层节点从左至右打印,最简单运用队列的思想:将本代码加入上述(非递归实现)中便可以测试

    /**
     *  层序遍历的实现思路:
     *     1.根节点出队
     *     2.左孩子不为空,则左孩子出队
     *     3.之后,右孩子不为空,则右孩子出队
     *     4.循环直到队空
     *
     * @param root
     */
    public static void levelOrder(Node root) {
        if (root == null) return;

        Queue<Node> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            root = queue.poll(); //逐一出队
            System.out.print(root.data + " ");
            if (root.leftChild != null)
                queue.offer(root.leftChild);
            if (root.rightChild != null)
                queue.offer(root.rightChild);
        }
    }	

5、C语言实现

考试的考点,还是自己跟着动手一下,C语言实现代码借鉴于该博客 http://data.biancheng.net/tree/

①递归遍历

#include <stdio.h>
#include<stdlib.h>
#include <string.h>
#define TElemType int
//构造结点的结构体
typedef struct BiTNode{
    TElemType data;//数据域
    struct BiTNode *lchild,*rchild;//左右孩子指针
}BiTNode,*BiTree;

//初始化树的函数
void CreateBiTree(BiTree *T){
    *T=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->data=1;
    (*T)->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild=(BiTNode*)malloc(sizeof(BiTNode));
  
    (*T)->lchild->data=2;
    (*T)->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->rchild->data=5;
    (*T)->lchild->rchild->lchild=NULL;
    (*T)->lchild->rchild->rchild=NULL;
    (*T)->rchild->data=3;
    (*T)->rchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild->lchild->data=6;
    (*T)->rchild->lchild->lchild=NULL;
    (*T)->rchild->lchild->rchild=NULL;
    (*T)->rchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild->rchild->data=7;
    (*T)->rchild->rchild->lchild=NULL;
    (*T)->rchild->rchild->rchild=NULL;
    (*T)->lchild->lchild->data=4;
    (*T)->lchild->lchild->lchild=NULL;
    (*T)->lchild->lchild->rchild=NULL;
}

//模拟操作结点元素的函数,输出结点本身的数值
void displayElem(BiTNode* elem){
    printf("%d ",elem->data);
}

//先序遍历
void PreOrderTraverse(BiTree T){
    if (T) {
        displayElem(T);//调用操作结点数据的函数方法
        PreOrderTraverse(T->lchild);//访问该结点的左孩子
        PreOrderTraverse(T->rchild);//访问该结点的右孩子
    }
    //如果结点为空,返回上一层
    return;
}

//中序遍历
void INOrderTraverse(BiTree T){
    if (T) {
        INOrderTraverse(T->lchild);//遍历左孩子
        displayElem(T);//调用操作结点数据的函数方法
        INOrderTraverse(T->rchild);//遍历右孩子
    }
    //如果结点为空,返回上一层
    return;
} 

//后序遍历
void PostOrderTraverse(BiTree T){
    if (T) {
        PostOrderTraverse(T->lchild);//遍历左孩子
        PostOrderTraverse(T->rchild);//遍历右孩子
        displayElem(T);//调用操作结点数据的函数方法
    }
    //如果结点为空,返回上一层
    return;
}

int main() {
    BiTree Tree;
    CreateBiTree(&Tree);
    printf("先序遍历: \n");
    PreOrderTraverse(Tree) ;
    printf("\n");
    printf("中序遍历: \n");
    INOrderTraverse(Tree);
    printf("\n");
    printf("后序遍历: \n");
    PostOrderTraverse(Tree);
}

②非递归遍历

#include <stdio.h>
#include<stdlib.h>
#include <string.h>
#define TElemType int
int top=-1;//top变量时刻表示栈顶元素所在位置
//构造结点的结构体
typedef struct BiTNode{
    TElemType data;//数据域
    struct BiTNode *lchild,*rchild;//左右孩子指针
}BiTNode,*BiTree;
//初始化树的函数
void CreateBiTree(BiTree *T){
    *T=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->data=1;
    (*T)->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->data=2;
    (*T)->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->rchild->data=5;
    (*T)->lchild->rchild->lchild=NULL;
    (*T)->lchild->rchild->rchild=NULL;
    (*T)->rchild->data=3;
    (*T)->rchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild->lchild->data=6;
    (*T)->rchild->lchild->lchild=NULL;
    (*T)->rchild->lchild->rchild=NULL;
    (*T)->rchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild->rchild->data=7;
    (*T)->rchild->rchild->lchild=NULL;
    (*T)->rchild->rchild->rchild=NULL;
    (*T)->lchild->lchild->data=4;
    (*T)->lchild->lchild->lchild=NULL;
    (*T)->lchild->lchild->rchild=NULL;
}
//前序和中序遍历使用的进栈函数
void push(BiTNode** a,BiTNode* elem){
    a[++top]=elem;
}
//弹栈函数
void pop( ){
    if (top==-1) {
        return ;
    }
    top--;
}
//模拟操作结点元素的函数,输出结点本身的数值
void displayElem(BiTNode* elem){
    printf("%d ",elem->data);
}
//拿到栈顶元素
BiTNode* getTop(BiTNode**a){
    return a[top];
}

//后序遍历非递归算法
typedef struct SNode{
    BiTree p;
    int tag;
}SNode;

//后序遍历使用的进栈函数
void postpush(SNode *a,SNode sdata){
    a[++top]=sdata;
}

//先序遍历非递归算法
void PreOrderTraverse(BiTree Tree){
    BiTNode* a[20];//定义一个顺序栈
    BiTNode * p;//临时指针
    push(a, Tree);//根结点进栈
    while (top!=-1) {
        p=getTop(a);//取栈顶元素
        pop();//弹栈
        while (p) {
            displayElem(p);//调用结点的操作函数
            //如果该结点有右孩子,右孩子进栈
            if (p->rchild) {
                push(a,p->rchild);
            }
            p=p->lchild;//一直指向根结点最后一个左孩子
        }
    }
}

//中序遍历非递归算法
void InOrderTraverse(BiTree Tree){
    BiTNode* a[20];//定义一个顺序栈
    BiTNode * p;//临时指针
    push(a, Tree);//根结点进栈
    while (top!=-1) {//top!=-1说明栈内不为空,程序继续运行
        while ((p=getTop(a)) &&p){//取栈顶元素,且不能为NULL
            push(a, p->lchild);//将该结点的左孩子进栈,如果没有左孩子,NULL进栈
        }
        pop();//跳出循环,栈顶元素肯定为NULL,将NULL弹栈
        if (top!=-1) {
            p=getTop(a);//取栈顶元素
            pop();//栈顶元素弹栈
            displayElem(p);
            push(a, p->rchild);//将p指向的结点的右孩子进栈
        }
    }
}

//后序遍历函数
void PostOrderTraverse(BiTree Tree){
    SNode a[20];//定义一个顺序栈
    BiTNode * p;//临时指针
    int tag;
    SNode sdata;
    p=Tree;
    while (p||top!=-1) {
        while (p) {
            //为该结点入栈做准备
            sdata.p=p;
            sdata.tag=0;//由于遍历是左孩子,设置标志位为0
            postpush(a, sdata);//压栈
            p=p->lchild;//以该结点为根结点,遍历左孩子
        }
        sdata=a[top];//取栈顶元素
        pop();//栈顶元素弹栈
        p=sdata.p;
        tag=sdata.tag;
        //如果tag==0,说明该结点还没有遍历它的右孩子
        if (tag==0) {
            sdata.p=p;
            sdata.tag=1;
            postpush(a, sdata);//更改该结点的标志位,重新压栈
            p=p->rchild;//以该结点的右孩子为根结点,重复循环
        }
        //如果取出来的栈顶元素的tag==1,说明此结点左右子树都遍历完了,可以调用操作函数了
        else{
            displayElem(p);
            p=NULL;
        }
    }
}

int main(){
    BiTree Tree;
    CreateBiTree(&Tree);
    printf("先序遍历: \n");
    PreOrderTraverse(Tree);
    printf("\n");	 
	printf("中序遍历: \n");
    InOrderTraverse(Tree);
    printf("\n");
    printf("后序遍历: \n");
    PostOrderTraverse(Tree);
    printf("\n");
}

③层序遍历

#include <stdio.h>
#include<stdlib.h>
#define TElemType int
//初始化队头和队尾指针开始时都为0
int front=0,rear=0;

typedef struct BiTNode{
    TElemType data;//数据域
    struct BiTNode *lchild,*rchild;//左右孩子指针
}BiTNode,*BiTree;
void CreateBiTree(BiTree *T){
    *T=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->data=1;
    (*T)->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild=(BiTNode*)malloc(sizeof(BiTNode));
   
    (*T)->lchild->data=2;
    (*T)->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->rchild->data=5;
    (*T)->lchild->rchild->lchild=NULL;
    (*T)->lchild->rchild->rchild=NULL;
   
    (*T)->rchild->data=3;
    (*T)->rchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild->lchild->data=6;
    (*T)->rchild->lchild->lchild=NULL;
    (*T)->rchild->lchild->rchild=NULL;
   
    (*T)->rchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild->rchild->data=7;
    (*T)->rchild->rchild->lchild=NULL;
    (*T)->rchild->rchild->rchild=NULL;
   
    (*T)->lchild->lchild->data=4;
    (*T)->lchild->lchild->lchild=NULL;
    (*T)->lchild->lchild->rchild=NULL;
}
//入队函数
void EnQueue(BiTree *a,BiTree node){
    a[rear++]=node;
}
//出队函数
BiTNode* DeQueue(BiTNode** a){
    return a[front++];
}
//输出函数
void displayNode(BiTree node){
    printf("%d ",node->data);
}
int main() {
    BiTree tree;
    //初始化二叉树
    CreateBiTree(&tree);
    BiTNode * p;
    //采用顺序队列,初始化创建队列数组
    BiTree a[20];
    //根结点入队
    EnQueue(a, tree);
    //当队头和队尾相等时,表示队列为空
    while(front<rear) {
        //队头结点出队
        p=DeQueue(a);
        displayNode(p);
        //将队头结点的左右孩子依次入队
        if (p->lchild!=NULL) {
            EnQueue(a, p->lchild);
        }
        if (p->rchild!=NULL) {
            EnQueue(a, p->rchild);
        }
    }
    return 0;
}
posted @ 2020-03-30 22:14  我在吃大西瓜呢  阅读(382)  评论(0编辑  收藏  举报