数据结构详解——最大(小)左倾树

数据结构详解——最大(小)左倾树

最大(小)左倾树的定义及用途

最大(小)左倾树实际上是对优先级队列的一种实现。所谓优先级队列,与先入先出(FIFO)的一般队列不同,优先级队列是按照元素的优先级确定出队顺序的。优先级队列的一种实现是数据结构详解——堆)。虽然堆结构具有很好的空间和时间利用率,但它并不能适用于所有的优先级队列的应用。特别是希望合并多个优先级队列时,以及涉及多个大小不同的优先级队列时,左倾树的结构更为适用。

在介绍左倾树的定义之前,首先要先介绍另外几个概念:

  • 扩展二叉树:对于一棵二叉树,所有的空子树用一个特殊的节点代替,该节点称为外部节点,其余节点称为内部节点。带有外部节点的二叉树就称为扩展二叉树。
  • s值:对于一个节点x,x到外部节点的最短路径的长度称为x的s值。若x为外部节点,则s值为0;如果x是内部节点,则x的s值为 m i n { s ( L ) , s ( R ) } + 1 ( L , R 为 x 的 左 右 孩 子 ) min\{s(L),s(R)\}+1(L,R为x的左右孩子) min{s(L),s(R)}+1L,Rx

下面的图给出了一个扩展二叉树的例子:
在这里插入图片描述
其中蓝色圆形代表内部节点,红色矩形代表外部节点,并用abcdef作了标注。再根据s值的定义,我们把各个内部节点的s值标在图中:
在这里插入图片描述
下面给出左倾树最大(小)左倾树的定义:

  • 一棵二叉树是基于高度的左倾树(HBLT),对于每个内部节点满足:左孩子的s值大于等于右孩子的s值。
  • 最大(小)左倾树既是HBLT,也是最大(小)树。(最大树的定义见 数据结构详解——堆

最大左倾树和最小左倾树本质上是相同的,下面的讨论中将只讨论最大左倾树。前面给出的扩展二叉树中,a的父亲节点不满足HBLT的条件,因此它不是HBLT。下面给出了最大左倾树的一个例子:
在这里插入图片描述
另外还有一种基于权重的左倾树(WBLT),与HBLT非常类似,定义如下:

  • w值(权重):对于二叉树中的一个节点来说,以该节点为根节点的子树中的内部节点个数称为该节点的w值。
  • 如果一个二叉树中,每个内部节点的左孩子的w值都大于等于右孩子的w值,则称该二叉树为基于权重的左倾树(WBLT)。最大(最小)WBLT既是一棵WBLT,也是一棵最大(最小)树。

WBLT和HBLT对于查找、插入、删除、合并等操作是类似的,下面就只介绍对HBLT的操作。利用HBLT,我们也可以实现优先级队列。

对于HBLT,有以下的性质:

  • x x x为根的子树中,节点的数量至少为 2 s ( x ) − 1 2^{s(x)}-1 2s(x)1个。
  • 如果以 x x x为根的子树中有 m m m个节点,则 s ( x ) s(x) s(x)最多为 l o g 2 ( m + 1 ) log_2(m+1) log2(m+1).
  • x x x沿最右侧路径到某个外部节点(即从 x x x开始,沿右孩子移动构成的路径)的长度为 s ( x ) s(x) s(x).

操作最大HBLT

下面我们来讨论如何操作最大HBLT,主要需要实现以下的操作:

  • 插入到HBLT
  • 删除HBLT中的最大元素
  • 合并两个HBLT
  • 初始化HBLT

合并操作

合并策略可以用递归的方式很好地描述。假设A和B是我们要合并的两棵最大HBLT,并且假设A和B都不为空(如果一棵树为空,则另一棵就是结果)。那么合并过程可以用如下方法描述:

  • 首先比较A和B的根节点较大的作为合并后的树的根节点
  • 不妨设A的根节点较大,那么A的左子树L不变A的右子树和B合并成一个新的最大左倾树C比较L和C的s值较大的作为左子树

举个例子说明,假设我们要合并如下图所示的两棵最大HBLT:
在这里插入图片描述
首先我们要比较根节点的值,确定哪一个节点作为合并后的根节点,然后将右子树和另一棵树进行合并。这实际上是一个拆分的过程,直到拆分出一棵空树为止,不断拼接成HBLT,并检查左右孩子的s值大小,并在必要时进行交换即可。下图展示了这个流程:
在这里插入图片描述
总结一下,具体编程时,所写的函数的参数是要合并的两棵树的根节点x和y,具体的流程如下:

  • 首先检查两棵树是否有一棵为空树,有空树则返回另一棵。
  • 通过检查后的两棵树都不为空,那么就比较它们的根节点的值,选取最大的那个。为了方便起见,我们可以约定经过这个操作后,x的值始终大于等于y的值。
  • 以x的右子树和y进行递归。
  • 检查x的左右孩子的s值,如果左孩子的s值较小则交换左右孩子。并且对于根节点来说,根节点的s值等于右孩子的s值+1.
  • 返回x。

(具体的实现代码在最后)

插入操作和删除操作

最大HBLT的插入操作和删除操作实际上都可以统一成两个HBLT的合并问题

假设要将元素x插入到最大HBLT中,我们可以把元素x视为一个含有一个内部节点的HBLT,就可以将插入操作转化成两棵树的合并操作。

HBLT和堆一样,最大元素位于根节点。如果删除根节点,我们就可以得到左右子树两个HBLT,再将这两棵树合并起来,就可以完成删除操作。

初始化操作

假设我们要初始化一个含有n个元素的最大HBLT,如果我们使用n次插入操作来进行初始化,那么总的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),这并不是最优的初始化方法。事实上,更优的操作如下:

  • 首先把n个元素都创建成一个含一个节点的最大HBLT。
  • 将它们放入一个普通的先入先出的队列中,然后每次让两棵最大HBLT出队列,进行合并,然后再将合并后的树放入队列,直到这个队列中最后只剩下一个最大HBLT为止。

这样进行初始化的时间复杂度为 O ( n ) O(n) O(n),是更优的算法。

Java语言实现的最大HBLT

实现代码如下:

import java.util.ArrayList;

public class MaxHblt implements MaxPriorityQueue {
    private class HbltNode implements BinaryTreeNode{
        private Comparable element;
        private HbltNode leftChild;
        private HbltNode rightChild;
        public int s;//s值

        //constructor
        public HbltNode(){

        }

        public HbltNode(Comparable element){
            this.element = element;
            s = 0;
        }

        public HbltNode(Comparable element,
                        HbltNode leftChild,
                        HbltNode rightChild){
            this.element = element;
            this.leftChild = leftChild;
            this.rightChild = rightChild;
            s = 0;
        }

        public HbltNode(Comparable element,
                        HbltNode leftChild,
                        HbltNode rightChild,
                        int s){
            this.element = element;
            this.leftChild = leftChild;
            this.rightChild = rightChild;
            this.s = s;
        }

        //set method
        @Override
        public void setElement(Object element) {
            this.element = (Comparable) element;
        }

        @Override
        public void setLeftChild(BinaryTreeNode leftChild) {
            this.leftChild = (HbltNode) leftChild;
        }

        @Override
        public void setRightChild(BinaryTreeNode rightChild) {
            this.rightChild = (HbltNode) rightChild;
        }

        //get method
        @Override
        public Comparable getElement() {
            return element;
        }

        public HbltNode getLeftChild() {
            return leftChild;
        }

        @Override
        public HbltNode getRightChild() {
            return rightChild;
        }

        @Override
        public String toString() {
            return "HbltNode{" +
                    "element=" + element +
                    '}';
        }
    }

    private HbltNode root;
    private int queueSize;

    MaxHblt(){
        root = null;
        queueSize = 0;
    }

    @Override
    public Comparable getMax() {
        if (size() == 0) return null;
        return root.getElement();
    }

    @Override
    public Comparable removeMax() {
        if (size() == 0) return null;
        Comparable maxElement = root.getElement();
        root = meld(root.leftChild,root.rightChild);
        queueSize--;
        return maxElement;
    }

    @Override
    public boolean isEmpty() {
        if (root == null) return true;
        else return false;
    }

    @Override
    public int size() {
        return this.queueSize;
    }

    @Override
    public void put(Comparable theObject) {
        root = meld(root,new HbltNode(theObject));
        queueSize++;
    }

    //合并两棵子树
    public void meld(MaxHblt x) {
        root = meld(root,x.root);
        queueSize += x.size();
    }

    public static HbltNode meld(HbltNode x,HbltNode y) {
        //x或y为空,直接返回
        if (x == null) return y;
        if (y == null) return x;

        //比较根节点的值,将根节点较大的作为x
        if (x.getElement().compareTo(y.getElement()) < 0){
            HbltNode temp = x;
            x = y;
            y = temp;
        }

        //x的右子树和y进行合并,作为x的新右子树
        x.setRightChild(meld(x.rightChild,y));

        //比较左右孩子的s值,在必要时进行交换
        if (x.leftChild == null) {
            x.setLeftChild(x.rightChild);
            x.setRightChild(null);
            x.s = 1;
        }
        else {
            if (x.leftChild.s < x.rightChild.s){
                HbltNode temp = x.rightChild;
                x.setRightChild(x.leftChild);
                x.setLeftChild(temp);
            }
            //更新s值
            x.s = x.rightChild.s + 1;
        }
        return x;
    }

    //初始化
    public void initialize(Comparable[] element,int size) {
        ArrayList<HbltNode> hbltNodeArrayList = new ArrayList<>();
        root = null;
        queueSize = size;
        for (int i = 0;i < size;i++) {
            hbltNodeArrayList.add(new HbltNode(element[i]));
        }
        while (hbltNodeArrayList.size() >= 2){
            HbltNode x = hbltNodeArrayList.remove(0);
            HbltNode y = hbltNodeArrayList.remove(0);
            hbltNodeArrayList.add(meld(x,y));
        }
        if (size > 0) root = hbltNodeArrayList.remove(0);
    }
}

相关联的三个接口定义如下:

/**
 * 二叉树节点
 */ 
public interface BinaryTreeNode {
    void setElement(Object element);
    void setLeftChild(BinaryTreeNode leftChild);
    void setRightChild(BinaryTreeNode rightChild);
    Object getElement();
    BinaryTreeNode getLeftChild();
    BinaryTreeNode getRightChild();
}
/**
 * 优先级队列
 */
public interface PriorityQueue {
    boolean isEmpty();//队列为空返回True
    int size();//返回队列长度
    void put(Comparable theObject);//插入元素
}
/**
 * 最大优先级队列
 */
public interface MaxPriorityQueue extends PriorityQueue{
    Comparable getMax();//返回最大元素
    Comparable removeMax();//去除优先级最大的元素
}

可以看到,左倾树实现的优先级队列虽然在空间利用率上小于堆,但它在合并两个优先级队列上的便捷性是无可比拟的,在具体开发过程中,应当根据实际需求确定使用何种数据结构。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值