左倾堆(一种可高效合并的优先队列)
左倾堆(Leftist Heap)是一个便于merge操作的数据结构,通过左倾树(Leftist Tree)实现。左倾树是一种特殊的二叉树,树中结点除了满足普通二叉堆的key大小规定外,还要求每一个结点X的左子树的Null Path Length(NPL)值不小于右子树的NPL值,因此这也是与普通二叉堆的区别:虽然普通二叉堆也满足左倾树的条件,左倾树往往不是一棵完全二叉树(而且通常不平衡),从而左倾树不能用数组表示了。上面提到的NPL指的是某个结点到NULL结点(总共有n+1个NULL结点)的最短路径长度,规定NULL结点本身的NPL等于-1,叶子结点的NPL等于0,非叶结点的NPL等于它的两个孩子结点的NPL最小值加1。按照左倾树的定义,它的左右子树都是左倾树, 而且有一个重要属性:一个具有n个结点的左倾堆最右边路径长度(即相当于root或树的NPL值)最多为floor(log(n+1)),利用这个特性可以在右路高效地实现下面的merge操作。
普通的二叉堆合并复杂度为O(n),而左倾堆的合并复杂度只有O(logn),(n是合并后的左倾堆的结点个数),因此左倾堆的最大优势是高效的合并操作。最小左倾堆的merge操作的算法是,输入两个左倾堆h1和h2,假设h1的根结点key值大于h2的根结点key值(如果不是这样,交换h1和h2),则将h2的根作为合并后的root结点,同时递归调用merge(h1.root,h2.root.right),返回的结果作为h2.root的右子树,由于merge操作保证了返回的树满足左倾树条件,因此返回的结果(现在是h2.root的右子树)也已经是一颗左倾树了。如果h2.root.right的NPL值大于h2.root.left的NPL值,需要交换两棵子树h2.root.left和h2.root.right,最后更新h2.root的NPL。
左倾堆的Extract-Min操作先删除root结点,然后调用merge操作将左右子树合并为一颗新的左倾树;Insert操作将要插入的元素看作一个只包含root结点的左倾堆,然后调用merge操作将两个左倾堆合并。
构造左倾堆:如果还是采用Insert操作,则复杂度为O(nlogn);如果使用普通二叉堆的构造方法,则复杂度为O(n),但是这并不是最好的左倾堆,原因是一方面构造的左倾堆是完全二叉树,但是另一方面又不能按照数组对它进行操作。采用FIFO队列结构可以达到O(n)复杂度,同时构造出来的左倾堆左倾效果最好,做法如下:将每个元素作为左倾堆(允许任何两个元素值相同)依次enqueue,然后每次从队列中dequeue两个左倾堆,用merge算法合并,并将合并的左倾堆enqueue到队尾,直到队列中只剩一个左倾堆,这个左倾堆就是最终结果。
实现:
import java.util.LinkedList; /** * * Leftist Heap * * Copyright (c) 2011 ljs (http://blog.csdn.net/ljsspace/) * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php) * * @author ljs * 2011-08-20 * */ public class LeftistMinHeap { static class LeftistHeapNode{ int key; LeftistHeapNode left; LeftistHeapNode right; int npl; //null-path-length public LeftistHeapNode(int key){ this.key = key; this.npl = 0; } public String toString(){ return this.key + "(npl=" + this.npl + ")"; } public LeftistHeapNode merge(LeftistHeapNode rhsRoot){ if(rhsRoot==this || rhsRoot == null) return this; LeftistHeapNode root1 = null; //the root of the merged tree LeftistHeapNode root2 = null; if(rhsRoot.key<this.key){ //merge this with rhsRoot's right child root1 = rhsRoot; root2 = this; }else{ //merge rhsRoot with this's right child root1 = this; root2 = rhsRoot; } LeftistHeapNode tmpRoot = root2.merge(root1.right); root1.right = tmpRoot; if(root1.left == null){ //left can not be null unless right is null root1.right = null; root1.left = tmpRoot; root1.npl = 0; }else{ if(root1.right.npl>root1.left.npl){ //swap left and right child root1.right = root1.left; root1.left = tmpRoot; } //at this time, the right child has the shortest null-path root1.npl = root1.right.npl + 1; } return root1; } } private LeftistHeapNode root; public LeftistMinHeap(LeftistHeapNode root){ this.root = root; } private static LeftistHeapNode merge(LeftistHeapNode root1,LeftistHeapNode root2){ if(root1 == null) return root2; if(root2 == null) return root1; return root1.merge(root2); } public static LeftistMinHeap merge(LeftistMinHeap h1,LeftistMinHeap h2){ LeftistHeapNode rootNode = merge(h1.root,h2.root); return new LeftistMinHeap(rootNode); } public static LeftistMinHeap buildHeap(int[] A){ LinkedList<LeftistHeapNode> queue = new LinkedList<LeftistHeapNode>(); int n = A.length; //init: queue all elements as a single-node tree for(int i=0;i<n;i++){ LeftistHeapNode node = new LeftistHeapNode(A[i]); queue.add(node); } //merge adjacent heaps and enqueue the merged heap afterward while(queue.size()>1){ LeftistHeapNode root1 = queue.remove(); //dequeue LeftistHeapNode root2 = queue.remove(); LeftistHeapNode rootNode = merge(root1,root2); queue.add(rootNode); } LeftistHeapNode rootNode = queue.remove(); return new LeftistMinHeap(rootNode); } public void insert(int x){ this.root = LeftistMinHeap.merge(new LeftistHeapNode(x), this.root); } public Integer extractMin(){ if(this.root == null) return null; int min = this.root.key; this.root = LeftistMinHeap.merge(this.root.left, this.root.right); return min; } public static void main(String[] args) { int[] A = new int[]{4,8,10,9,1,3,5,6,11}; LeftistMinHeap heap = LeftistMinHeap.buildHeap(A); heap.insert(7); Integer min = null; while((min = heap.extractMin()) != null){ System.out.format(" %d", min); } System.out.println(); System.out.println("********************"); A = new int[]{3,10,8,21,14,17,23,26}; LeftistHeapNode a0 = new LeftistHeapNode(A[0]); LeftistHeapNode a1 = new LeftistHeapNode(A[1]); LeftistHeapNode a2 = new LeftistHeapNode(A[2]); LeftistHeapNode a3 = new LeftistHeapNode(A[3]); LeftistHeapNode a4 = new LeftistHeapNode(A[4]); LeftistHeapNode a5 = new LeftistHeapNode(A[5]); LeftistHeapNode a6 = new LeftistHeapNode(A[6]); LeftistHeapNode a7 = new LeftistHeapNode(A[7]); a0.left = a1; a0.npl = 1; a0.right = a2; a1.left = a3; a1.npl = 1; a1.right = a4; a4.left = a6; a2.left = a5; a5.left = a7; LeftistMinHeap h1 = new LeftistMinHeap(a0); int[] B = new int[]{6,12,7,18,24,37,18,33}; LeftistHeapNode b0 = new LeftistHeapNode(B[0]); LeftistHeapNode b1 = new LeftistHeapNode(B[1]); LeftistHeapNode b2 = new LeftistHeapNode(B[2]); LeftistHeapNode b3 = new LeftistHeapNode(B[3]); LeftistHeapNode b4 = new LeftistHeapNode(B[4]); LeftistHeapNode b5 = new LeftistHeapNode(B[5]); LeftistHeapNode b6 = new LeftistHeapNode(B[6]); LeftistHeapNode b7 = new LeftistHeapNode(B[7]); b0.left = b1; b0.npl = 2; b0.right = b2; b1.left = b3; b1.npl = 1; b1.right = b4; b4.left = b7; b2.left = b5; b2.npl = 1; b2.right = b6; LeftistMinHeap h2 = new LeftistMinHeap(b0); heap = LeftistMinHeap.merge(h1,h2); while((min = heap.extractMin()) != null){ System.out.format(" %d", min); } System.out.println(); System.out.println("********************"); A = new int[]{1,5,7,10,15,20,25,50,99}; a0 = new LeftistHeapNode(A[0]); a1 = new LeftistHeapNode(A[1]); a2 = new LeftistHeapNode(A[2]); a3 = new LeftistHeapNode(A[3]); a4 = new LeftistHeapNode(A[4]); a5 = new LeftistHeapNode(A[5]); a6 = new LeftistHeapNode(A[6]); a7 = new LeftistHeapNode(A[7]); LeftistHeapNode a8 = new LeftistHeapNode(A[8]); a0.left = a1; a0.npl = 2; a0.right = a2; a1.left = a3; a1.npl = 1; a1.right = a4; a2.left = a5; a2.npl = 1; a2.right = a6; a5.left = a7; a5.npl = 1; a5.right = a8; h1 = new LeftistMinHeap(a0); B = new int[]{22,75}; b0 = new LeftistHeapNode(B[0]); b1 = new LeftistHeapNode(B[1]); b0.left = b1; h2 = new LeftistMinHeap(b0); heap = LeftistMinHeap.merge(h1,h2); while((min = heap.extractMin()) != null){ System.out.format(" %d", min); } System.out.println(); } }
测试输出:
1 3 4 5 6 7 8 9 10 11
********************
3 6 7 8 10 12 14 17 18 18 21 23 24 26 33 37
********************
1 5 7 10 15 20 22 25 50 75 99