左式堆

Posted 读书使人进步

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了左式堆相关的知识,希望对你有一定的参考价值。

1.简介

  设计一种堆结构像二叉堆那样高效的支持合并操作而且只使用一个数组似乎很困难。原因在于,合并似乎需要把一个数组拷贝到另一个数组中去,对于相同大小的堆,这将花费O(N)。正因为如此,所有支持高效合并的高级数据结构都需要使用指针。像二叉堆那样,左式堆也有结构性和堆序性。不仅如此,左式堆也是二叉树,它和二叉堆之间的唯一区别在于:左式堆不是理想平衡的,而实际上是趋向于非常不平衡。

2.左式堆性质

  把任意节点X的零路径长(null path length, NPL) Npl(X)定义为从X到一个没有两个儿子的节点的最短路径长。因此,具有0个或1个儿子的节点的Npl值为0,而Npl(NULL)=-1。注意,任意节点的零路径长比它的各个儿子节点的最小值多1。

  左式堆的性质是:对于堆中的每一个节点X,左儿子的零路径长至少与右儿子的零路径长一样大。因此,下图1中,左边的二叉树是左式堆,而右边的二叉树则不是。这个性质使左式堆明显更偏重于使树向左增加深度,左式堆的名称也由此而来。

  图1:两棵树的零路径长,只有左边的树是左式堆

3.左式堆操作

  左式堆的基本操作是合并(Merge),注意,插入只是合并的特殊情形。因为可以把插入看成是单节点堆与一个大的堆的Merge。对于合并操作,主要采取的是递归解法。如图所示,两个左式堆H1,H2,注意,最小元在根处。除数据,左指针,右指针外,每个单元还需要有一个指示零路径长的项。

  如果这两个堆有一个是空的,那么可以直接返回。否则,为了合并两个堆,需要比较它们的根。首先,将具有大的根值的堆与具有较小的根值的堆的右子树堆合并。在本例中,我们递归的将H2与H1在8处的右子树堆合并,得到如图3中的堆。

  图3:将H2与H1的右子树堆合并的结果

  如果直接将图3中的堆作为H1的右儿子,如图4所示,那么新的H1虽然满足堆序性质,但是它不是左式堆,因为左子树的零路径长为1,而根的 右儿子的零路径长为2.因此,左式堆的性质在根处被破坏。不过,很容易看到,树的其余部分必然是左式堆,由于递归步骤,根的右子树也是左式堆。根的左子树没发生变化,必然也是左式堆。这样一来,只需要对根进行调整就可以了。使整个树是左式堆的做法如下:只要交换根的左儿子和右儿子,并更新零路径长,就完成 了Merge。如图5所示:

   图4:H1接上图3中的左式堆作为右儿子的结果


  图5:交换H1的根的儿子得到的结果

4.代码实现

    public class LeftistHeap<T extends Comparable<T>> {  
        private static class Node<T> {  
            T element;  
            Node<T> left;  
            Node<T> right;  
            int npl;  
            public Node(T element) {  
                this(element, null, null);  
            }  
            public Node(T element, Node<T> left, Node<T> right) {  
                this.element = element;  
                this.left = left;  
                this.right = right;  
                this.npl = 0;  
            }  
        }  
      
        private Node<T> root;  
      
        public LeftistHeap() {  
            this.root = null;  
        }  
      
        // 合并兩個左式堆  
        public void merge(LeftistHeap<T> rhs) {  
            if (rhs == null)  
                return;  
            root = merge(root, rhs.root);  
            rhs.root = null;  
        }  
      
        // 向左式堆中添加元素  
        public void insert(T element) {  
            root = merge(new Node<T>(element), root);  
        }  
      
        // 找寻左式堆中最小节点  
        public T findMin() {  
            if (isEmpty())  
                return null;  
            return root.element;  
        }  
      
        /** 
         * 删除堆中最小节点,由于堆的最小节点就在根上,所以可以直接删除,但是删除根后,需要在将左右子树合并 
         *  
         * @return 
         */  
        public T deleteMin() {  
            if (isEmpty())  
                return null;  
            T minItem = root.element;  
            root = merge(root.left, root.right);  
            return minItem;  
        }  
      
        // 判断左式堆是否为空  
        public boolean isEmpty() {  
            return root == null;  
        }  
      
        // 将左式堆设置为空堆  
        public void makeEmpty() {  
            this.root = null;  
        }  
      
        /** 
         * 1、若第一个根节点为空,则返回第二个根节点; 2、若第一个不为空第二个为空,则返回第一个根节点; 
         * 3、一、二节点都不为空时,判断那个是根较小的节点,将根较小的节点作为第一个参数传递给merge1方法 
         *  
         * @param h1 
         * @param h2 
         * @return 
         */  
        private Node<T> merge(Node<T> h1, Node<T> h2) {  
            if (h1 == null)  
                return h2;  
            if (h2 == null)  
                return h1;  
            if (h1.element.compareTo(h2.element) < 0) {  
                return merge1(h1, h2);  
            } else {  
                return merge1(h2, h1);  
            }  
        }  
      
        /** 
         * 将根节点较大的树合并到根节点较小的树上去: 1、若根节点较小的树无左子树,则将根节点较大的树作为其左子树 
         * 2、若根节点较小的树有左子树,则将根节点较大的树和根节点较小的树的右子树合并,作为根节点较小的树的右子树 
         * 3、若左子树的零路径长小于右子树的零路径长,则交换左右子树 4、根节点较小的树的零路径长修正为其右子树的零路径长度+1 
         *  
         * @param h1 
         * @param h2 
         * @return 
         */  
        private Node<T> merge1(Node<T> h1, Node<T> h2) {  
            if (h1.left == null)  
                h1.left = h2;  
            else {  
                h1.right = merge(h1.right, h2);  
                if (h1.left.npl < h1.right.npl)  
                    swapChildren(h1);  
                h1.npl = h1.right.npl + 1;  
            }  
            return h1;  
        }  
      
        // 交换两个子树  
        private void swapChildren(Node<T> t) {  
            Node<T> tmp = t.left;  
            t.left = t.right;  
            t.right = tmp;  
        }  
    }  
View Code

以上是关于左式堆的主要内容,如果未能解决你的问题,请参考以下文章

左式堆的实现与详解

堆之左式堆和斜堆

数据结构--左式堆的思想和代码

左式堆实现

左式堆的合并

左式堆