JAVA实现二叉树

树是编程中一种常用的数据结构。以前在学习数据结构时,总想着如何实际地实现出一颗二叉树出来,现在参考了《数据结构与算法分析 JAVA语言描述 第二版》之后,照着书中的例子实现了一颗二叉树,个人感觉书上面的二叉树实现操作比较复杂。下面将我学到的一些知识记录下来:

1,定义树的操作的基本接口,其中不包括插入或删除操作,因为这二种操作与树的结构相关,不同的树的实现有着不同的插入与删除方式,故不应该将这二种操作放在接口中让所有的类来实现。同时,接口中也不包括遍历操作,因为并不是每个应用都会用到遍历。我们可以定义返回一个迭代器的方法,由于树中有多种不同的遍历,树的类可以含有几个方法,每个方法返回一种迭代器。

基本接口中定义了树的常用操作,代码如下:

public interface TreeInterface<T> {
    public T getRootData();
    public int getHeight();
    public int getNumberOfNodes();
    public boolean isEmpty();
    public void clear();
}

 

2,由于对树的许多操作都少不了遍历,因此需要构造一个能够对树中的元素进行遍历的迭代器。本例中采用内部类的方式来实现迭代器,下面的定义的接口 TreeIteratorInterface 包含了生成不同迭代器的方法。让实现树的具体的JAVA类 implements TreeIteratorInterface<T>,然后该JAVA类再定义一个内部类 implements Iterator<T>,即可构造一个能够对树中元素进行遍历的迭代器。

为什么要让实现树的具体的JAVA类 implements TreeIteratorInterface<T>?

因为,TreeIteratorInterface<T>接口中定义了返回各种各样迭代器的方法,实现树的具体的JAVA类 的对象就可以调用这些方法获得迭代器了。在后面的例子中,我们定义了一个具体实现树数据结构的类 BinaryTree<T>,该类 implements BinaryTreeInterface<T>,而 BinaryTreeInterface<T> 又extends TreeIteratorInterface<T>,因而BinaryTree<T>的对象可以调用 TreeIteratorInterface<T>接口中的方法来获得迭代器了。

关于如何为自定义的数据结构实现迭代器,一个更好的参考例子:http://www.cnblogs.com/hapjin/p/4454594.html

TreeIteratorInterface<T> 的具体代码如下:

public interface TreeIteratorInterface<T> {
    public Iterator<T> getPreorderIterator();
    public Iterator<T> getPostorderIterator();
    public Iterator<T> getInorderIterator();
    public Iterator<T> getLevelOrderIterator();
}

 

3,定义了树的接口之后,由于我们大部分情况还是使用二叉树,因此下面定义二叉树的接口。首先,二叉树也是树,因此继承了TreeInterface<T>;其次,二叉树的对象要能够获得迭代器,因此又继承了TreeIteratorInterface<T>。(JAVA中接口允许多重继承)

该二叉树接口没有什么特别的操作(大部分基本操作已经从TreeInterface<T>中继承下来了),它只定义了两个方法,这两个方法用来生成二叉树。

//JAVA接口可以多继承
public interface BinaryTreeInterface<T> extends TreeInterface<T> ,TreeIteratorInterface<T>{
    //以下这两个方法定义了如何构造二叉树
    public void setTree(T rootData);//构造一颗以rootData为根的二叉树
    //构造一颗以rootData为根,leftTree为左子树,rightTree为右子树的二叉树
    public void setTree(T rootData, BinaryTreeInterface<T> leftTree, BinaryTreeInterface<T> rightTree);
}

 

4,与单链表一样,链表中有链表结点,二叉树中也需要定义表示结点的类。而这里定义的表示结点的类采用独立的外部类实现,而不是采用内部类来实现。

在考虑表示结点的类时,我们先定义了一个抽象的接口来规定对结点的一些基本操作。(这也是为什么我觉得书中二叉树实现比较复杂的原因)

该接口为 BinaryNodeInterface<T>,具体代码如下:

//二叉树结点的接口
public interface BinaryNodeInterface<T> {
    public T getData();//返回结点的数据部分
    public void setData(T newData);//设置结点的数据域的值
    
    public BinaryNodeInterface<T> getLeftChild();//获取结点的左孩子
    public BinaryNodeInterface<T> getRightChild();//获取结点的右孩子
    
    public void setLeftChild(BinaryNodeInterface<T> leftChild);//设置结点的左孩子为指定结点
    public void setRightChild(BinaryNodeInterface<T> rightChild);//设置结点的右孩子为指定结点
    
    public boolean hasLeftChild();//判断结点是否有左孩子
    public boolean hasRightChild();//判断结点是否有右孩子
    
    public boolean isLeaf();//检查结点是否是叶子结点
    
    public int getNumberOfNodes();//计算以该结点为根的子树的结点数目
    public int getHeight();//计算以该结点为根的子树的高度
    public BinaryNodeInterface<T> copy();//复制以该结点为根的子树
}

 

5,接下来便是二叉树的节点的实现类了,该类 implements BinaryNodeInterface<T> 从而实现了接口中定义的各种对节点的操作。

class BinaryNode<T> implements BinaryNodeInterface<T>, java.io.Serializable{
    private T data;//结点的数据域
    private BinaryNode<T> left;//左孩子
    private BinaryNode<T> right;//右孩子
    
    public BinaryNode(){
        this(null);
    }
    //构造一个值为dataPortaion的结点
    public BinaryNode(T dataPortion){
        this(dataPortion, null, null);
    }
    
    public BinaryNode(T dataPortion, BinaryNode<T> leftChild, BinaryNode<T> rightChild){
        data = dataPortion;
        left = leftChild;
        right = rightChild;
    }
    
    
    @Override
    //返回结点的数据域的值
    public T getData() {
        return data;
    }

    @Override
    //更改结点数据域的值
    public void setData(T newData) {
        data = newData;
        
    }

    @Override
    //获得结点的左孩子
    public BinaryNodeInterface<T> getLeftChild() {
        return left;
    }

    @Override
    //获得结点的右孩子
    public BinaryNodeInterface<T> getRightChild() {
        return right;
    }

    @Override
    //更改结点的左孩子
    public void setLeftChild(BinaryNodeInterface<T> leftChild) {
        left = (BinaryNode<T>)leftChild;
    }

    @Override
    //更改结点的右孩子
    public void setRightChild(BinaryNodeInterface<T> rightChild) {
        right = (BinaryNode<T>)rightChild;
    }

    @Override
    public boolean hasLeftChild() {
        return left != null;
    }

    @Override
    public boolean hasRightChild() {
        return right != null;
    }

    @Override
    public boolean isLeaf() {
        return (left == null) && (right == null);
    }

    @Override
    //返回以该结点为根的子树中的结点的个数(包括根结点)
    public int getNumberOfNodes() {
        int leftNumber = 0;
        int rightNumber = 0;
        if(left != null)
            leftNumber = left.getNumberOfNodes();
        if(right != null)
            rightNumber = right.getNumberOfNodes();
        return 1 + leftNumber + rightNumber;
    }

    @Override
    //返回以此结点为根的子树的高度
    public int getHeight() {
        return getHeight(this);
    }
    
    private int getHeight(BinaryNode<T> node){
        int height = 0;
        if(node != null)
            height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
        return height;
    }

    @Override
    //该方法被构造二叉树的setTree()方法调用
    public BinaryNodeInterface<T> copy() {
        BinaryNode<T> newRoot = new BinaryNode<T>(data);
        if(left != null)
            newRoot.left = (BinaryNode<T>)left.copy();
        if(right != null)
            newRoot.right = (BinaryNode<T>)right.copy();
        return newRoot;
    }
}

 

6,二叉树的节点也定义好了,是时候实现二叉树了(好麻烦啊啊啊)。上面已经谈到,BinaryTree<T>实现了一颗具体的二叉树,并且通过定义了一个内部类来生成迭代器。这里就简要介绍下这个实现迭代器的内部类:该内部类名为 private class InorderIterator<T> implements Iterator<T>

这里,进行了一些偷懒,即下面的代码中只实现了能够对树进行中序遍历的迭代器即InorderIterator。当然了,你还可以再定义一个内部类PreorderIterator,然后按照先序遍历采用迭代的方法来实现先序遍历的迭代器。

下面正式介绍私有内部类InorderIterator<T>,由于它 implements Iterator<T>,因此需要实现Iterator<T>中定义的三个抽象方法,这三个方法就是用来完成迭代的(遍历的)。我们按照中序遍历(非递归方式)的逻辑来实现这三个方法。中序遍历(非递归方式)需要栈来辅助存储结构,因此,代码中定义了一个顺序栈来保存遍历过程中遇到的结点,而该顺序栈的实现请参考另一篇博文:http://www.cnblogs.com/hapjin/p/4442729.html

实现二叉树的BinaryTree<T>代码如下:

import java.util.Iterator;
import java.util.NoSuchElementException;

import list.SequenceStack;//SequenceStack在list包中
import list.Stack;//Stack接口在list包中

public class BinaryTree<T> implements BinaryTreeInterface<T>, java.io.Serializable{

    private BinaryNodeInterface<T> root;//树根结点

    private class InorderIterator<T> implements Iterator<T>{

        //定义一个顺序栈nodeStack来存放遍历过程中遇到的结点
        private Stack<BinaryNodeInterface<T>>nodeStack;//list包中有顺序栈的实现
        private BinaryNodeInterface<T> currentNode;
        public InorderIterator(){
            nodeStack = new SequenceStack<BinaryNodeInterface<T>>();
            currentNode = (BinaryNodeInterface<T>) root;//此处为什么需要强制转换呢?
        }
        
        /*
         * 按照中序遍历的逻辑进行实现Iterator接口中的方法,从而实现一个迭代器
         */
        @Override
        public boolean hasNext() {
            return (!nodeStack.empty()) || (currentNode != null);
        }

        @Override
        public T next() {
            BinaryNodeInterface<T> nextNode = null;
            while(currentNode != null){
                nodeStack.push(currentNode);
                currentNode = currentNode.getLeftChild();
            }
            if(!nodeStack.empty()){
                nextNode = nodeStack.pop();
                assert nextNode != null;
                currentNode = nextNode.getRightChild();
            }
            else
                throw new NoSuchElementException();
            return nextNode.getData();
        }

        @Override
        //仅仅是完成遍历的功能,删除功能是不需要有的。
        public void remove() {
            throw new UnsupportedOperationException();
        }
        
    }
    
    public BinaryTree(){
        root = null;
    }
    public BinaryTree(T rootData){
        root = new BinaryNode<T>(rootData);
    }
    public BinaryTree(T rootData, BinaryTree<T> leftTree, BinaryTree<T> rightTree){
        privateSetTree(rootData, leftTree, rightTree);
    }
    
    @Override
    public void setTree(T rootData) {
        root = new BinaryNode<T>(rootData);
    }

    @Override
    /*
     *以rootData为根,leftTree为左子树,rightTree为右子树
     *生成一颗新的二叉树,setTree()实际调用了privateSetTree()来构造二叉树
     */
    public void setTree(T rootData, BinaryTreeInterface<T> leftTree,
            BinaryTreeInterface<T> rightTree) {
            privateSetTree(rootData, (BinaryTree)leftTree, (BinaryTree)rightTree);
    }
    private void privateSetTree(T rootData, BinaryTree<T>leftTree, BinaryTree<T> rightTree){
        root = new BinaryNode<T>(rootData);
        if((leftTree != null) && (!leftTree.isEmpty()))
            root.setLeftChild(leftTree.root);
        if((rightTree != null) && (!rightTree.isEmpty())){
            if(rightTree != leftTree)
                root.setRightChild(rightTree.root);
            else
                root.setRightChild(rightTree.root.copy());
        }
        if((leftTree != null) && (leftTree != this))
            leftTree.clear();
        if((rightTree != null) && (rightTree != this))
            rightTree.clear();    
    }
    
    //更改根结点的数据域
    protected void setRootData(T rootData){
        root.setData(rootData);
    }
    
    //更改根结点
    protected void setRootNode(BinaryNodeInterface<T> rootNode){
        root = rootNode;
    }
    protected BinaryNodeInterface<T> getRootNode(){
        return root;
    }
    
    @Override
    //返回树的根节点的数据域
    public T getRootData() {
        T rootData = null;
        if(root != null)
            rootData = root.getData();//调用节点的getData(),返回该节点的数据域
        return rootData;
    }

    @Override
    //返回二叉树的高度
    public int getHeight() {
        return root.getHeight();//二叉树的高度即为以根结点为根的子树的高度
    }

    @Override
    //返回二叉树中结点的个数
    public int getNumberOfNodes() {
        return root.getNumberOfNodes();
    }

    @Override
    public boolean isEmpty() {
        return root == null;
    }

    @Override
    public void clear() {
        root = null;
    }

    //中序遍历二叉树
    public void inorderTraverse(){
        inorderTraverse(root);
    }
    
    private void inorderTraverse(BinaryNodeInterface<T> node){
        if(node != null){
            inorderTraverse((BinaryNode)node.getLeftChild());
            System.out.println(node.getData());//若使用迭代器,可以在测试程序中输出,而不是在这里使用输出语句
            inorderTraverse((BinaryNode)node.getRightChild());
        }
    }
    
    public void preorderTraverse(){
        preorderTraverse(root);
    }
    
    private void preorderTraverse(BinaryNodeInterface<T> node){
        if(node != null){
            System.out.println(node.getData());
            preorderTraverse((BinaryNode)node.getLeftChild());
            preorderTraverse((BinaryNode)node.getRightChild());
        }
    }
    
    public void postorderTraverse(){
        postorderTraverse(root);
    }
    private void postorderTraverse(BinaryNodeInterface<T> node){
        if(node != null){
            postorderTraverse((BinaryNode)node.getLeftChild());
            postorderTraverse((BinaryNode)node.getRightChild());
            System.out.println(node.getData());
        }
    }
    @Override
    //获得先序遍历器的方法,由于在该类中没有定义生成先序迭代器的私有内部类,因此该方法为空实现
    public Iterator<T> getPreorderIterator() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    //获得后序遍历器的方法,由于在该类中没有定义生成后序迭代器的私有内部类,因此该方法为空实现
    public Iterator<T> getPostorderIterator() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Iterator<T> getInorderIterator() {
        return new InorderIterator();
    }

    @Override
    //获得层次遍历器的方法,由于在该类中没有定义生成层次迭代器的私有内部类,因此该方法为空实现
    public Iterator<T> getLevelOrderIterator() {
        // TODO Auto-generated method stub
        return null;
    }
}

 

posted @ 2015-04-25 21:47  大熊猫同学  阅读(6527)  评论(0编辑  收藏  举报