二叉树的创建和遍历

 

一、结点类的定义

这里只是进行了简单的定义,结点类包括结点数据域和左右孩子引用,结点类是静态内部类。

static final class Node{
		Integer data = null;
		Node right;//左子树
		Node left;//右子树
		Node(Integer data){
			this.data = data;
		}
		Node(){
			this(null);
		}
		public String toString() {
			return data+"";
		}
	}

二、根据先序遍历的顺序构造二叉树

假如有一个如下图的二叉树,按照先序遍历的顺序建树需要提供该树的先序遍历结果,并且子树为空时要用特殊字符表示。假设用0表示子树为空,则提供的结果应该为pre={1,2,3,0,0,4,0,0,5,0,0}。然后根据pre来建树。

先序遍历的顺序递归建树:

//root是树根结点
public BinaryTree(List<Integer> t){
    root = initBiTree(root,t);
}

public Node initBiTree(Node p,List<Integer> t){
    if(!t.isEmpty()){    //t不为空说明还有结点,树还没建完,这个判断应该可以不要
        int e = t.get(0);    //取出第一个数,这个数就是当前根结点的值
        t.remove(0);    
        if(e!=0){    //e为0表示该结点不为空
            p = new Node(e);    //创建根结点
            p.left = initBiTree(p.left,t);    //递归建立左子树
            p.right = initBiTree(p.right,t);    //递归建立右子树
        }
        else{    //e为0表示该结点为空
         p = null;
        }
    }
    return p;
}

三、先序遍历二叉树

1、递归遍历

public void preOrder(Node t){
    if(t!=null){
        System.out.println(t);
        preOrder(t.left);
        preOrder(t.right);
    }
}

2、非递归遍历

非递归遍历使用栈实现,首先将根结点入栈,并且入栈的时候就输出,这样就实现了根先输出。然后将左结点入栈,直到左结点为空时,弹出栈顶元素p,并且将p的右结点入栈,循环往复即可。

/*
 * 先序遍历二叉树,非递归实现
 * 1.先将结点p输出并入栈,然后p指向其左结点pl,如果pl不是空,则也输出并入栈。(这里就是输出根,然 
 *后找左子树,又输出根)
 * 2.如果pl为空,那么从栈中弹出一个结点,这个结点一定是pl的双亲结点p,然后将p指向p的右子树pr,这 
 *时又回到了第一步
 */
public void preOrderN(){
    Node p = root;
    Stack<Node> s = new Stack<>();
    /*1.p!=null的作用
	 *当栈里面的最后一个结点p弹出时,表示以p结点为根的树的左子树已经输出完毕了,但是右子树还没有 
     *输出,当p=p.right后如果p==null说明右子树为空,则不用输出,否则要继续输出
	 * 2.!s.isEmpty()的作用
	 * 明显当结点处于栈中时,说明他的左子树也没输出完毕
	 */
    while(p!=null||!s.isEmpty()){
        if(p!=null){
            System.out.printf(p);//将根节点输出
            s.push(p);            //将根节点入栈
            p = p.left;            //指向其左结点
        }
        else{
            p = s.pop();    //弹出的结点为原来p的双亲结点
            p = p.right;    //指向p的右结点
        }
    }
}

四、中序遍历输出

1、递归遍历

public void inOrder(Node t){
    if(t!=null){
        inOrder(t.left);
        System.out.printf(t);
        inOrder(t.right);
    }
}

2、非递归遍历

中序遍历非递归实现与先序非递归实现类似,只需要改变输出根节点的时间,在结点弹出时输出。

1、先将结点p入栈,然后将p指向其左结点pl,如果pl不为空,则又将pl入栈(根入栈,寻找左子树,左子树根再入栈)

2、如果pl为空,那么从栈中弹出一个结点p并输出(因为入栈时是先入的根节点然后入的左子树根节点,所以弹出时先弹出的是根的左结点),然后将p指向p的右结点,回到1。

public void inOrderN(){
    Node p = root;
    Stack<Node> s = new Stack<>();
    while(p!=null||!s.isEmpty()){
        if(p!=null){
            s.push(p);    //将根结点入栈
            p = p.left();    //指向右结点
        }
        else{
            p = s.pop();    //由于左结点比根节点后入栈,所以左结点会先弹出
            System.out.printf(p);    //输出
            p = p.right;    //指向右结点
        }
    }
}

四、后序遍历

1、递归实现

public void postOrder(Node t){
    if(t!=null){
        postOrder(t.left);
        postOrder(t.right);
        System.out.printf(t);
    }
}

2、非递归实现

后序遍历的递归实现

public void postOrderN(){
    Node p = root;
    Stack<Node> s = new Stack<>();
    Node last = null;    //表示上次输出的结点
    if(p==null)
        return;
    do{
        while(p!=null){    //将左结点入栈
            s.push(p);
            p = p.left();
        }
        bool flag = true;//可以看做是左结点为空的标记
        //到这里已经可以保证栈顶结点的左结点为空,所以后面只需要考虑它的右结点
        while(!s.isEmpty()&&flag){
            p = s.peek();    //当前结点等于栈顶结点
         /*
		  * 如果该结点的右结点为空,或者上次访问的是它的右结点则表示它的左右结点都已经访问完了,
		  * 现在就可以开始输出该结点了
		  */
            if(p.right == null||last = p.right){
                last = s.pop();
                System.out.printf(p);
            }
            else{
                p = p.right;    //如果右结点没被访问,那么处理右结点
                flag = false;
            }
        }
    }while(!s.isEmpty());
}

五、根据先序和中序遍历的结果构建树

假设二叉树如下,它的先序遍历结果为pre={A,B,C,D,E},中序遍历结果为in={C,B,D,A,E}

1、通过pre可以知道A为根结点

2、然后在in数组中找到A的位置,A的左边(CBD,下标从0到2)为左子树,A的右边(E,下标从4到4)为右子树

3、通过先序的第二个为B,可以得到CBD的根结点为B,以此类推。 

这里通过递归实现。

List<Node> pre,in;    //pre中存的是先序遍历结果,in中存的是中序遍历结果

public void piInit(List<Node> pre,List<Node> in){
    this.pre = pre;
    this.in = in;
    root = initByPi(0,pre.size(),p);    //一开始的边界是从0到pre的长度
}

private Node initByPi(int l,int r,Node p){
    int vis=-1;
    for(int i=l;i<r;i++){    //找到根节点的位置,并将位置赋值给vis
        if(pre.get(0)==in.get(i)){
            vis = i;
            break;
        }
    }
    p = new Node(pre.get(0));    //创建根节点
    pre.remove(0);    

    if(l<vis)    //l<vis说明p的左子树存在
        p.left = initByPi(l,vis,p.left);
    if(vis+1<r)    //说明p的右子树存在
        p.right = initByPi(vis+1,r,p.right);
    
    return p;
            
}

六、根据中序和后序遍历的结果构建树

假设二叉树同上,它的中序遍历结果为pre={C,B,D,A,E},后序遍历结果为post={C,D,B,E,A},过程与五类似

1、通过post可以知道A为根结点

2、然后在in数组中找到A的位置,A的左边(CBD,下标从0到2)为左子树,A的右边(E,下标从4到4)为右子树

3、通过先序的第二个为E,可以得到E的根结点为E,通过后序中的CDB可以得到CBD的根节点为B。 

List<Node> in,post;    //in中序遍历结果,post后序遍历的结果
public void Ipinit(List<Node> in,List<Node> post){
    this.in = in;
    this.post = post;
    Collections.reverse(post);    //把post反转一下,便于操作
    root = initByIp(0,pre.size(),root);
}
private Node initByIp(int l,int r,Node p){
    int vis=-1;
    for(int i=l;i<r;i++){
        if(post.get(0) == in.get(i)){
            vis = i;break;
        }
    }
    p = new Node(e);
    post.remove(0);
    //后序遍历数组反转后,从数组的第一个开始刚好是根右左的顺序
    if(vis+1<r)    //说明右子树存在
        p.right = initByIp(vis+1,r,p.right);
    if(l<vis)    //说明左子树存在
        p.left = initByIp(l,vis,p.left);
    return p;
}
    

最后贴一组测试数据,上面的代码不保证一定正确。。。。。。

树是这样的

Integer[] a = new Integer[] {1,2,3,0,0,4,0,0,5,6,7,0,0,8,0,0,0};
Integer[] b = new Integer[] {1,2,3,4,5,6,7,8};    //先序
Integer[] c = new Integer[] {3,2,4,1,7,6,8,5};    //中序
Integer[] d = new Integer[] {3,4,2,7,8,6,5,1};    //后序
List<Integer> t = new ArrayList<>(Arrays.asList(a));
List<Integer> pre = new ArrayList<>(Arrays.asList(b));
List<Integer> in = new ArrayList<>(Arrays.asList(c)); 
List<Integer> post = new ArrayList<>(Arrays.asList(d)); 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值