二叉树的存储结构

二叉树的存储结构有两种,顺序存储结构和链式存储结构。

1.顺序存储结构

按照顺序存储结构的定义,我们可以使用一组地址连续的存储单元依次自上而下,自左至右存储完全二叉树上的结点元素,即将完全二叉树上编号为i的结点元素存储在一维数组中下标为i-1的分量中。如下图所示:

这里写图片描述

这种存储方式对于满二叉树和完全二叉树是非常合适也是很高效方便的。因为满二叉树和完全二叉树采用顺序存储结构既不浪费空间,也可以根据公式很快地确定结点之间的关系。

但是对于一般的二叉树而言,必须用“虚结点”将一颗二叉树补成一棵完全二叉树来存储,否则无法确定结点之间的关系,这样的话就会造成存储空间的浪费。一种极端的情况是,对于单支二叉树,为了存储k个结点,需要2k-1个存储单元,下图说明了这种情况:

这里写图片描述

2.链式存储结构

设计不同的结点结构可以构成不同形式的链式存储结构。由二叉树的定义可知,二叉树的结点由一个数据元素和分别指向其左、右子树的两个分支构成,则表示二叉树的链表中的结点至少包含3个域:数据域和左、右指针域。利用这种结点结构所得的二叉树的存储结构称之为二叉链表,见下图。容易证明,在具有n个结点的二叉链表中有n+1个空链域。

这里写图片描述

下述代码是最简单的二叉链表的结点结构:

public class DoubleLinkedNode<T> {
    private T data;// 数据域
    private DoubleLinkedNode<T> left;
    private DoubleLinkedNode<T> right;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public DoubleLinkedNode<T> getLeft() {
        return left;
    }

    public void setLeft(DoubleLinkedNode<T> left) {
        this.left = left;
    }

    public DoubleLinkedNode<T> getRight() {
        return right;
    }

    public void setRight(DoubleLinkedNode<T> right) {
        this.right = right;
    }
}

有时,为了便于找到结点的双亲,则还可以在结点结构中增加一个指向其双亲结点的指针域,如下图所示,由这种结点结构所得的二叉树的存储结构称之为三叉链表。

这里写图片描述

不同的存储结构实现二叉树操作的方法也不同。例如要找某个结点的父结点,在三叉链表中很容易实现;在二叉链表中则需要从根结点出发一一查找。在实际应用中,要根据二叉树的实际用途来选择存储结构。

下述代码,我们以三叉链表作为二叉树的存储结构,如下:

public class TripleLinkedNode<T> {
    private T data;// 数据域
    private TripleLinkedNode<T> parent;// 父结点
    private TripleLinkedNode<T> left;// 左孩子
    private TripleLinkedNode<T> right;// 右孩子

    private int height;// 以该结点为根的子树的高度
    private int size;// 该结点的子孙数(包括结点本身)

    private boolean isVisited; // 访问标记

    public TripleLinkedNode() {
        this(null);
    }

    public TripleLinkedNode(T e) {
        data = e;
        height = 1;// 初始化高度为1,即自己的高度
        size = 1;// 初始化的子孙数为1,即自己
        parent = left = right = null;
        isVisited = false;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public boolean isVisited() {
        return isVisited;
    }

    public void setVisited(boolean isVisited) {
        this.isVisited = isVisited;
    }

    // 辅助方法,判断当前结点的位置情况
    /**
     * 是否有父亲
     * 
     * @return
     */
    public boolean hasParent() {
        return parent != null;
    }

    /**
     * 是否有左孩子
     * 
     * @return
     */
    public boolean hasLeft() {
        return left != null;
    }

    /**
     * 是否有右孩子
     * 
     * @return
     */
    public boolean hasRight() {
        return right != null;
    }

    /**
     * 是否是叶子结点
     * 
     * @return
     */
    public boolean isLeaf() {
        // 既没有左孩子也没有右孩子
        return !hasLeft() && !hasRight();
    }

    /**
     * 判断是否是父结点的左孩子
     * 
     * @return
     */
    public boolean isLeft() {
        return hasParent() && parent.left == this;
    }

    /**
     * 判断是否是父结点的右孩子
     * 
     * @return
     */
    public boolean isRight() {
        return hasParent() && parent.right == this;
    }

    // 与height有关的方法
    /**
     * 取结点的高度,即以该结点为根的树的高度
     * 
     * @return
     */
    public int getHeight() {
        return height;
    }

    /**
     * 更新当前结点及其祖先的高度
     */
    public void updateHeight() {
        // 新高度初始化为1,高度等于左右子树高度加1中的大者
        int newHeight = 1;
        if (hasLeft()) {
            newHeight = Math.max(newHeight, getLeft().getHeight() + 1);
        }
        if (hasRight()) {
            newHeight = Math.max(newHeight, getRight().getHeight() + 1);
        }
        if (newHeight == height) {
            return;// 高度没有发生变化直接返回
        }
        height = newHeight;// 否则更新高度
        if (hasParent()) {
            getParent().updateHeight();// 递归更新祖先的高度
        }
    }

    // 与size有关的方法
    /**
     * 获取以该结点为根的树的结点数
     * 
     * @return
     */
    public int getSize() {
        return size;
    }

    /**
     * 更新当前结点及其祖先的子孙数
     */
    public void updateSize() {
        size = 1;// 初始化为1,结点本身
        if (hasLeft()) {
            size += getLeft().getSize();// 加上左子树的规模
        }
        if (hasRight()) {
            size += getRight().getSize();// 加上右子树的规模
        }
        if (hasParent()) {
            getParent().updateSize();// 递归更新祖先的规模
        }
    }

    // 与parent有关的方法
    /**
     * 获取父结点
     * 
     * @return
     */
    public TripleLinkedNode<?> getParent() {
        return parent;
    }

    /**
     * 断绝与父结点的关系
     */
    public void sever() {
        if (!hasParent()) {
            return;
        }
        // 更新结点
        if (isLeft()) {
            parent.left = null;
        } else {
            parent.right = null;
        }
        // 更新parent的height和size
        parent.updateHeight();
        parent.updateSize();
        parent = null;
    }

    // 与left有关的方法
    /**
     * 获取左孩子
     * 
     * @return
     */
    public TripleLinkedNode<T> getLeft() {
        return left;
    }

    /**
     * 设置当前结点的左孩子,返回原左孩子
     * 
     * @param l
     * @return
     */
    public TripleLinkedNode<T> setLeft(TripleLinkedNode<T> l) {
        TripleLinkedNode<T> oldLeft = this.left;
        // 断开当前左孩子的结点关系
        if (hasLeft()) {
            left.sever();
        }
        if (l != null) {
            l.sever();// 断开l与其父结点的关系
            this.left = l;
            l.parent = this;// 不要忘记parent指针
            this.updateHeight();
            this.updateSize();
        }
        return oldLeft;
    }

    // 与right有关的方法
    /**
     * 获取右孩子
     * 
     * @return
     */
    public TripleLinkedNode<T> getRight() {
        return right;
    }

    /**
     * 设置当前结点右孩子,返回原右孩子
     * 
     * @param r
     * @return
     */
    public TripleLinkedNode<T> setRight(TripleLinkedNode<T> r) {
        TripleLinkedNode<T> oldRight = this.right;
        if (hasRight()) {
            right.sever();
        }
        if (r != null) {
            r.sever();
            this.right = r;
            r.parent = this;
            this.updateHeight();
            this.updateSize();
        }
        return oldRight;
    }
}

可以看到,我们为了方便使用,在三叉链表的结点结构加入了一些属性,其中,parent表示指向父结点的指针,height表示以该结点为根的子树的高度,size表示该结点的子孙数(包括自身),另外,还有isVisited访问标记,用于之后的遍历中。

并且,在结点中实现了各个需要用到的方法,其中最重要的是setLeft()setRight()设置左右孩子的两个方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值