使用java实现顺序存储的二叉树

首先来看看什么是二叉树:二叉树一种所有结点的度都小于等于2的树。

需要写出二叉树就需要知道二叉树的一些性质:

1、二叉树的第i层最多有2^i个结点(最高层为第0层)

2、高度为h的二叉树最多有2^h-1个结点

3、对于一个给定的完全二叉树进行编号(从0开始),对于任意一个结点,其编号是i,则其左孩子标号是2i+1,右孩子的编号是2(i+1)(如果有的话);如果(i%2==0),则它的父结点的编号是i/2-1,如果(i%2!=0),则它的父节点的编号是(i-1)/2。

4、对于一个给定的编号i,它的层数计算方式为log2(i+1)向下取整(2为底,(i+1)的对数,其中编号从0开始,最高层为第0层),根据数学公式log2(i+1) = ln(i+1)/ln(2),因此可以使用Java中的Math.floor(Math.log(i+1)/Math.log(2))进行计算

5、遍历方式:

      广度优先遍历:一层一层从左到右进行遍历(借助队列)

      深度优先遍历:①先序遍历、每个结点的遍历都满足根结点、左结点、右结点(均借助栈)

                               ②中序遍历、每个结点的遍历都满足左结点、根结点、右结点

                               ③后序遍历、每个结点的遍历都满足左结点、右结点、根结点


二叉树常见的存储方式有两种:顺序存储和链式存储,对于非完全二叉树,顺序存储容易造成空间上的浪费,但是访问效率和修改效率比较高,链式存储则相反。

本次用Java实现顺序存储的二叉树(代码很冗长,可读性也比较糟糕...这点我承认):



首先创建一个接口Tree,规范二叉树的方法:

package com.arrayTree;

import com.arrayTree.TreeNode;

public interface Tree<E> {
    boolean isEmpty();
    E getRoot();
    E getParent(int nodeNum);
    E getLeftSibling(int nodeNum);
    E getRightSibling(int nodeNum);
    TreeNode<E> createNode(int headNum,E l, E r);
    TreeNode<E> createHead(E item,E l, E r);
    void breadFirstOrder();//广度优先
    void preOrder();//先序遍历
    void inOrder();//中序遍历
    void postOrder();//后序遍历
    void clear();//删除整个树
}

然后创建树的结点类TreeNode<E>:

package com.arrayTree;

public class TreeNode<E> {
    private E item;
    private E leftSibling;
    private E rightSibling;
    TreeNode(E item,E leftSibling, E rightSibling){//只有同包才能调用其构造方法
        this.setItem(item);
        this.setLeftSibling(leftSibling);
        this.setRightSibling(rightSibling);
    }
    public E getItem() {
        return item;
    }
    public void setItem(E item) {
        this.item = item;
    }
    public E getLeftSibling() {
        return leftSibling;
    }
    public void setLeftSibling(E leftSibling) {
        this.leftSibling = leftSibling;
    }
    public E getRightSibling() {
        return rightSibling;
    }
    public void setRightSibling(E rightSibling) {
        this.rightSibling = rightSibling;
    }
}

创建ArrayTree<E>类,实现Tree<E>接口:

package com.arrayTree;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

public class ArrayTree<E> implements Tree<E> {
    
    public Object[] elementData;//存储元素的数组
    public int level;//层数 最高层为第0层
    public TreeNode<E> root;//根
    private final static int DEFAULT_LEVEL = 10;//默认层数为10层
    public ArrayTree(int level){
        elementData = new Object[(2<<level)-1];//使用位运算代替乘法
        this.level = level;
    }
    public ArrayTree(){
        this(DEFAULT_LEVEL);
    }
    @Override
    public boolean isEmpty() {
        return elementData[0] == null;
    }
    @SuppressWarnings("unchecked")
    @Override
    public E getRoot() {
        return (E) elementData[0];
    }
    @SuppressWarnings("unchecked")
    @Override
    public E getParent(int nodeNum) {
        if(nodeNum%2 == 0){
            return checkIndex((nodeNum>>1)-1)?(E)elementData[(nodeNum>>1)-1]:null;
        }
        else{
            return checkIndex((nodeNum-1)>>1)?(E)elementData[(nodeNum-1)>>1]:null;
        }
    }
    @SuppressWarnings("unchecked")
    @Override
    public E getLeftSibling(int nodeNum) {
        return (checkIndex(nodeNum)&&checkIndex((nodeNum<<1)+1))?(E)elementData[(nodeNum<<1)+1]:null;
    }
    @SuppressWarnings("unchecked")
    @Override
    public E getRightSibling(int nodeNum) {
        return (checkIndex(nodeNum)&&checkIndex((nodeNum+1)<<1))?(E)elementData[(nodeNum+1)<<1]:null;
    }
    @Override
    public void breadFirstOrder() {
        /*
         * 广度优先遍历
        * 直接顺序访问存储数组
         * */
        String str = "";
        for(int i = 0 ; i<elementData.length ; i++){
            if(elementData[i] != null)
                str +=elementData[i]+" ";
        }
        System.out.println(str);
    }
    @Override
    public void preOrder() {
        /*
         * 先序遍历:
         * 遇到一个结点,先访问该结点,在遍历左子树,将右子树压入栈中,直到左子树遍历完,然后开始弹栈,对弹栈出来的结点继续像之前一样遍历,直到栈为空
         * */
        Stack<Integer> dataStack = new Stack<Integer>();//用栈来存放节点编号
        int currentNum = 0;
        String str = "";
        while(!dataStack.isEmpty()||elementData[currentNum]!=null){//栈不为空或者当前节点不为空
            if(checkIndex(currentNum)&&elementData[currentNum]!=null){//当前节点不为空
                str += (elementData[currentNum]+" ");//输出当前节点
                if(checkIndex((currentNum+1)<<1)&&elementData[(currentNum+1)<<1]!=null)//如果有右孩子
                    dataStack.push((currentNum+1)<<1);//右孩子索引放入栈中
                currentNum = (currentNum<<1)+1;//移向左孩子
            }
            else{
                currentNum = dataStack.pop();//出栈 访问存入的右孩子
            }
        }
        System.out.println(str);
    }
    @Override
    public void inOrder() {
        /*
         * 中序遍历:
         * 从根结点开始向左搜索,每遇到一个结点就把他压入栈中,然后取遍历这个结点的左子树,遍历完左子树后,开始弹栈,遍历弹出结点的右子树。
         * */
        Stack<Integer> dataStack = new Stack<Integer>();//用栈来存放节点编号
        int currentNum = 0;
        String str ="";
        while(!dataStack.isEmpty()||elementData[currentNum]!=null){//栈不为空或者当前节点不为空
            if(checkIndex(currentNum)&&elementData[currentNum]!=null){//当前节点不为空
                dataStack.push(currentNum);//当前结点作为根节点入栈
                currentNum = (currentNum<<1)+1;//转向左孩子
            }
            else{//节点为空了 当前路径访问到头了
                currentNum = dataStack.pop();//出栈 访问存入的根结点
                str += (elementData[currentNum]+" ");//访问根结点
                currentNum = (currentNum+1)<<1;//当前结点切换为栈中根结点的右孩子
            }
        }
        System.out.println(str);
    }
    @Override
    public void postOrder() {
        /*
         * 后序遍历:
         * 从根结点开始 向左搜索,每搜索到一个结点就将其压入栈中,直到栈中的结点不再有左子树为止。读取栈顶元素,如果该节点有右子树且未被访问,就访问其右子树,否则访问该节点并且弹栈
         * */
        Set<Integer> visitedSet = new HashSet<Integer>();//存放访问过的结点
        int currentNum = 0;
        Stack<Integer> dataStack = new Stack<Integer>();//用栈来存放编号
        String str = "";
        while(checkIndex(currentNum)&&elementData[currentNum]!=null){
            while(checkIndex((currentNum<<1)+1)&&elementData[(currentNum<<1)+1]!=null){
                dataStack.push(currentNum);//持续向左搜索 一旦遇到左结点为空 就停止搜索
                currentNum = (currentNum<<1)+1;
            }
            //(当前结点不为空)且(没有右孩子或者右孩子被访问过了) 则访问该节点
            while(checkIndex(currentNum) && elementData[currentNum]!=null && 
                    (!checkIndex(((currentNum+1)>>1)) || elementData[(currentNum+1)<<1]==null||
                    visitedSet.contains(elementData[(currentNum+1)<<1]))){
                str += elementData[currentNum];
                str +=" ";
                visitedSet.add(currentNum);//添加进被访问过的集合
                if(dataStack.isEmpty()){//栈空直接结束
                    System.out.println(str);
                    return;
                }
                currentNum = dataStack.pop();
            }
            dataStack.push(currentNum);
            currentNum = (currentNum+1)<<1;//转向右子树
        }
    }
    @Override
    public void clear() {
        root = null;
        elementData = null;
        level = 0;
    }
    public boolean checkIndex( int index ){//检查index这个索引会不会导致数组越界
        return index>=0&&index<=((1<<(level+1))-2);//共level层时提供的编号的上下限
    }
    
    public void ensureCapacity(int level){//确保存储的数组有足够的容量
        if(level>this.level){
            if(elementData == null){
                elementData = new Object[1];//为null就开辟一个
            }
            elementData  = Arrays.copyOf(elementData, (2<<level)-1);//用copyOf来扩容
            this.level = level;
        }
    }
    
    @Override
    public TreeNode<E> createNode( int headNum,E l, E r) {
        if(!checkIndex(headNum)||elementData[headNum]==null){//headNum没有
            throw new IllegalArgumentException("头编号不存在");
        }
        if(l != null||r != null){//左右节点都有
            if(checkIndex((headNum+1)<<1)&&level<10){//检查左右节点有没有足够level
                ensureCapacity(10);//不到10层直接加到10层
            }
            else if(checkIndex((headNum+1)<<1)&&level>=10){//检查左右节点有没有足够level
                ensureCapacity((headNum+1)<<1);//到了10层还不够则每次不够加一层
            }
        }
        @SuppressWarnings("unchecked")
        TreeNode<E> tn = new TreeNode<E>((E) elementData[headNum],l,r);//如果后面是null 直接创建
        elementData[(headNum<<1)+1] = l;
        elementData[(headNum+1)<<1]=r;
        return tn;
    }
    @Override
    public TreeNode<E> createHead(E item, E l, E r) {
        if(root!=null){
            throw new IllegalArgumentException("已经有头了");
        }
        if(level<1){
            ensureCapacity(10);
        }
        root = new TreeNode<E>(item, l, r);
        elementData[0] = item;
        elementData[1] = l;
        elementData[2] = r;
        return root;
    }
}

最后写一个测试类:

public class Test{
    public static void main(String[] args) {
        System.out.println("Tree test:");
        ArrayTree<Integer> at = new ArrayTree<Integer>();
        at.createHead(0, 1, 2);
        at.createNode(1, 3, 4);
        at.createNode(2, 5, 6);
        at.createNode(3, 7, 8);
        at.breadFirstOrder();//0 1 2 3 4 5 6 7 8
        at.preOrder();//0 1 3 7 8 4 2 5 6 
        at.inOrder();//7 3 8 1 4 0 5 2 6
        at.postOrder();//7 8 3 4 1 5 6 2 0
        System.out.println(at.level);
        System.out.println(at.getParent(3));
        System.out.println(at.getLeftSibling(0));
        System.out.println(at.getRightSibling(-1));
        at.clear();
        at.createHead(1, 2, 3);
    }
}
虽然代码还有很大的改进空间...但是运行结果还算一切正常。欢迎在评论下面拍砖。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值