您的位置:首页 > 编程语言 > Java开发

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

2017-11-17 17:18 513 查看
首先来看看什么是二叉树:二叉树一种所有结点的度都小于等于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{

d526
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);
}
}
虽然代码还有很大的改进空间...但是运行结果还算一切正常。欢迎在评论下面拍砖。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: