二叉树以及遍历算法

这两天看了很多关于二叉树的资料,遂来写个总结。
1>什么是二叉树?

  1. 树:树不同于数组Array以及链表 List ,前面两种都是线性结构。而树是介于线性和非线性之间的半线性结构。实质上就是链表的链表,即一个节点可以发散出多条链表,形成树状结构,如下图:
    这里写图片描述
    那么我们如何在计算机中表达这种结构呢?
    这里有一种很经典的表示法:长子+兄弟 表示法。
    长子+兄弟:每个节点都有:parent域,data域,firstChild长子域,nextSibling兄弟域,这样上图就变成了:
    这里写图片描述

  2. 二叉树:
    二叉树,顾名思义就是每个节点度,最多为2的树,分别为lChild和rChild。
    节点个数 n,与树的高度h之间具有如下关系:
    h 小于 n 小于 2^(h+1)

    可不可以用二叉树表示普通的树?
    试着想象一下,把上图用长子兄弟法表示的一课普通的树,每个节点都逆时针旋转45°,是不是就变成一颗二叉树了呢?这也许就是二叉树应用最广的原因吧。
    一个二叉树的节点如何在计算机中表示出来呢?

public class BinNode {
    /*
     * 一个二叉树节点 需要 什么呢?
     * 1.数据域 data
     * 2.父节点域 parent
     * 3.左节点域 lChild
     * 4.右节点域 rChild
     * 5.节点的高度 height ,用于二叉搜索树       
     * 6.节点的颜色 color  ,用于红黑树 (这里只考虑普通的二叉树)
     * */

    public BinNode(int data,BinNode parent){
        this.data=data;
        this.parent=parent;
    }
    BinNode parent,lChild,rChild;
    int data;
    int height;
}

2>二叉树的遍历算法

  1. 前序,中序和后序的递归遍历算法
    前序:
public void preTraverseRev(BinNode x){
    if(x==null){
        return;
    }
    System.out.println(x.data);  
    traverseRevcursion(x.lChild);
    traverseRevcursion(x.rChild);   
}

中序:

public void inTraverseRev(BinNode x){
    if(x==null){
        return;
    }
    traverseRevcursion(x.lChild);
    System.out.println(x.data);  
    traverseRevcursion(x.rChild);   
}

后序:

public void postTraverseRev(BinNode x){
    if(x==null){
        return;
    }
    traverseRevcursion(x.lChild);
    traverseRevcursion(x.rChild);   
    System.out.println(x.data);  
}

递归的算法,特别简单,但是效率来说,每个递归的空间消耗可能会有栈溢出的风险,所以应该尽量把递归改成迭代形式。
一般来说,尾递归都是可以很轻松转化成迭代形式的,只要把递归语句改成迭代的最后一句就可以了。如:

//递归形式:
int fun(n){
    if(n<1)
        n=1;
    return n*fun(n-1);
}
//迭代形式
int fun(n){
    int sum=1;
    while(n>0){
        sum*=n;
        n--;
    }
    return sum;
}

//求斐波切纳数
//递归
int fib(n){
    return (n<2)?n:(fib(n-1)+fib(n-2));
}

//迭代
int fib(n){
    int a=0;int b=1;
    while(n>0){
        b=a+b;
        a=b-a;
        n--;
    }
    return b;
}
//这里用到了动态规划的方法,把前一个阶段的值存储起来,用于后一个阶段的计算。
  1. 前序,中序,后序的迭代算法。
    前序:
    前序遍历的顺序如图所示:
    这里写图片描述
    A->B->D->E->F->C
    我们能看出什么呢?
    A B D 是一条以A为根的链表, E F 是一条以E为根的链表 C是一个以C为根的链表。实际上,我们找到这三个根,实际上就是遍历了整颗树。
    但是找寻是有顺序的,A->E->C,A不必说,肯定是先找到的,那么怎么实现E C 的顺序呢,可以注意到,E C分别都是A链元素的右子树,且C 为先,但C又必须后遍历,先入后出,栈!!所以:
public void visitLeftBranch(BinNode x, Stack<BinNode> stack){
        while(x!=null){
            System.out.println(x.data);   //遍历左链上每一个元素
            stack.push(x.rChild);             //右孩子入栈
            x=x.lChild;
        }

    }

public void traversePreIterator(BinNode x){
        Stack<BinNode> stack=new Stack<BinNode>();
        //创立一个栈放所有右孩子。用来得到E C并合理其顺序。
        while(true){
            visitLeftBranch(x, stack);  
            if(stack.isEmpty()) 
                break;
            x=stack.pop();
        }
    }

中序:
中序遍历的顺序如图:
这里写图片描述

D->B->F->E->A->C
有了前面前序的基础,这里可以看出:
D B A 是一条链  F E 是一条链  C 是一条链
但是很明显,我们拿到节点的顺序 肯定是 A B D,那么如何反方向遍历呢,依然是 先入后出 栈。而且 可以看出来 F E 在 A 前面,且E C 对于整棵树来说是右子树。所以依然只要找到A E C 就可以遍历整棵树。
public void visitLeftBranch2(BinNode x, Stack<BinNode> stack1){
        while(x!=null){
            stack1.push(x);
            x=x.lChild;
        }
    }

public void inTraverseIterator(BinNode x){
    Stack<BinNode> stack1=new Stack<BinNode>();
    while(true){
        visitLeftBranch2(x,stack1);
        if(stack1.isEmpty())
            break;
        x=stack1.pop();
        System.out.println(x.data);
        x=x.rChild;       //不管是否有右孩子,假设它有,每次都去检查,如果实际有,那么栈中有增加元素,没有继续出栈。
    }
}

后序:
后序遍历的顺序如图:
这里写图片描述

D->F->E->B->C->A
后序遍历是左右根的关系,那么由于首先访问的是A,但A必须最后访问,那依然可以用栈来处理这个问题,关键点是如何让入栈的顺序依我们遍历相反方向呢?
首先,我们可以确定的是,第一个访问的一定是A B D这个链的最左端的右子树(如果存在的话),但不再是依D B A来访问了,而是会先考虑D的兄弟节点E的链的最左端。
那么可以每次把节点的按 根右左 入栈,直到这个链的最左端:

public void leftBranch(Stack<BinNode> stack){
    BinNode x=stack.peek();
    while(x!=null){
        if(x.lchild!=null){  //如果有左孩子,也有右孩子,先右孩子入栈,再左孩子入栈。
            if(x.rChild!=null)
                stack.push(x.rChild);
            stack.push(x.lChild);
        }else{
            stack.push(x.rChild);
        }
        x=x.lChild;
    }
    stack.pop();//因为 终止这个循环 的时候,必定有一个null 入栈。
}
public void postTraverse(BinNode x){
    Stack<BinNode> stack1=new Stack<BinNode>();
    stack1.push(x);
    while(!stack1.isEmpty()){    
        if(stack1.peek()!=x.parent)  //因为我们入栈是按照一个节点的根 右 左 来入栈的,如果一个节点的下一个节点不是父节点,那一定是它的兄弟节点。
            leftBranch(stack1);
        x=stack1.pop();
        System.out.println(x.data);
    }
}

3.层次遍历
层次遍历,顾名思义,一棵树,从上到下,从左到右依次遍历。
这个比前面的好理解,从上到下,从左到右,把每个节点自身和左右节点依次放入队列中,依次出队列就好了。先入的先出。

public void rankTraverse(BinNode x){
        Queue<BinNode> q=new LinkedList<BinNode>();  //队列是一种特殊的线性表,只允许在表的前段进行删除操作,在表的后端进行插入操作。
        //LinkedList实现了Queue接口,因此可以把这个当Queue用。
        q.offer(x);
        while(!q.isEmpty()){
            x=q.poll();
            System.out.println(x.data);
            if(x.lChild!=null){
                q.offer(x.lChild);
            }
            if(x.rChild!=null){
                q.offer(x.rChild);
            }
        }

    }
参考资料:
清华大学 邓俊辉教授的数据结构教材

猜你喜欢

转载自blog.csdn.net/mooneal/article/details/70157029