【问题标题】:How to implement a Median-heap如何实现中值堆
【发布时间】:2013-02-25 12:09:29
【问题描述】:

像最大堆和最小堆一样,我想实现一个中值堆来跟踪给定整数集的中值。 API 应具备以下三个功能:

insert(int)  // should take O(logN)
int median() // will be the topmost element of the heap. O(1)
int delmedian() // should take O(logN)

我想使用数组 (a) 实现来实现堆,其中数组索引 k 的子项存储在数组索引 2*k 和 2*k + 1 中。为方便起见,数组从索引 1 开始填充元素. 这是我到目前为止所拥有的: 中值堆将有两个整数来跟踪到目前为止插入的整数数量,它们是 > 当前中值 (gcm) 和

if abs(gcm-lcm) >= 2 and gcm > lcm we need to swap a[1] with one of its children. 
The child chosen should be greater than a[1]. If both are greater, 
choose the smaller of two.

对于另一种情况也是如此。我想不出一个如何让元素下沉和游泳的算法。我认为应该考虑数字与中位数的接近程度,例如:

private void swim(int k) {
    while (k > 1 && absless(k, k/2)) {   
        exch(k, k/2);
        k = k/2;
    }
}

不过,我想不出完整的解决方案。

【问题讨论】:

  • 如果不限制任何给定值的多重性,这将变得很困难。

标签: java python algorithm data-structures


【解决方案1】:

另一种不使用最大堆和最小堆的方法是立即使用中值堆。

在最大堆中,父级大于子级。 我们可以有一种新类型的堆,其中父项位于子项的“中间”——左子项小于父项,右子项大于父项。所有偶数条目都是左孩子,所有奇数条目都是右孩子。

可以在最大堆中执行的相同游泳和下沉操作也可以在此中值堆中执行 - 稍作修改。在最大堆中的典型游泳操作中,插入的条目向上游泳直到小于父条目,在中值堆中,它将向上游泳直到小于父条目(如果它是奇数条目) 或大于父项(如果它是偶数项)。

这是我对这个中值堆的实现。为了简单起见,我使用了一个整数数组。

package priorityQueues;
import edu.princeton.cs.algs4.StdOut;

public class MedianInsertDelete {

    private Integer[] a;
    private int N;

    public MedianInsertDelete(int capacity){

        // accounts for '0' not being used
        this.a = new Integer[capacity+1]; 
        this.N = 0;
    }

    public void insert(int k){

        a[++N] = k;
        swim(N);
    }

    public int delMedian(){

        int median = findMedian();
        exch(1, N--);
        sink(1);
        a[N+1] = null;
        return median;

    }

    public int findMedian(){

        return a[1];


    }

    // entry swims up so that its left child is smaller and right is greater

    private void swim(int k){


        while(even(k) && k>1 && less(k/2,k)){

            exch(k, k/2);

            if ((N > k) && less (k+1, k/2)) exch(k+1, k/2);
            k = k/2;
        }

        while(!even(k) && (k>1 && !less(k/2,k))){

            exch(k, k/2);
            if (!less (k-1, k/2)) exch(k-1, k/2);
            k = k/2;
        }

    }

// if the left child is larger or if the right child is smaller, the entry sinks down
    private void sink (int k){

        while(2*k <= N){
            int j = 2*k;
            if (j < N && less (j, k)) j++;
            if (less(k,j)) break;
            exch(k, j);
            k = j;
        }

    }

    private boolean even(int i){

        if ((i%2) == 0) return true;
        else return false;
    }

    private void exch(int i, int j){

        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    private boolean less(int i, int j){

        if (a[i] <= a[j]) return true;
        else return false;
    }


    public static void main(String[] args) {

        MedianInsertDelete medianInsertDelete = new MedianInsertDelete(10);

        for(int i = 1; i <=10; i++){

            medianInsertDelete.insert(i);
        }

        StdOut.println("The median is: " + medianInsertDelete.findMedian());

        medianInsertDelete.delMedian();


        StdOut.println("Original median deleted. The new median is " + medianInsertDelete.findMedian());




    }
}


【讨论】:

  • 不幸的是,中位数并不总是以这种方式到达堆顶。我认为你只能保证堆的顶部在排序数组中位数的 log2(n) 跳范围内。考虑以下保持此不变性但顶部没有中值的堆:``` 70 17 87 11 93 25 92 2 67 85 95 8 52 0 94 1 6 39 78 3 ```
【解决方案2】:

这里是我的代码,基于 comocomocomocomo 提供的答案:

import java.util.PriorityQueue;

public class Median {
private  PriorityQueue<Integer> minHeap = 
    new PriorityQueue<Integer>();
private  PriorityQueue<Integer> maxHeap = 
    new PriorityQueue<Integer>((o1,o2)-> o2-o1);

public float median() {
    int minSize = minHeap.size();
    int maxSize = maxHeap.size();
    if (minSize == 0 && maxSize == 0) {
        return 0;
    }
    if (minSize > maxSize) {
        return minHeap.peek();
    }if (minSize < maxSize) {
        return maxHeap.peek();
    }
    return (minHeap.peek()+maxHeap.peek())/2F;
}

public void insert(int element) {
    float median = median();
    if (element > median) {
        minHeap.offer(element);
    } else {
        maxHeap.offer(element);
    }
    balanceHeap();
}

private void balanceHeap() {
    int minSize = minHeap.size();
    int maxSize = maxHeap.size();
    int tmp = 0;
    if (minSize > maxSize + 1) {
        tmp = minHeap.poll();
        maxHeap.offer(tmp);
    }
    if (maxSize > minSize + 1) {
        tmp = maxHeap.poll();
        minHeap.offer(tmp);
    }
  }
}

【讨论】:

    【解决方案3】:

    这是一个 Scala 实现,遵循上面 comocomocomocomo 的想法。

    class MedianHeap(val capacity:Int) {
        private val minHeap = new PriorityQueue[Int](capacity / 2)
        private val maxHeap = new PriorityQueue[Int](capacity / 2, new Comparator[Int] {
          override def compare(o1: Int, o2: Int): Int = Integer.compare(o2, o1)
        })
    
        def add(x: Int): Unit = {
          if (x > median) {
            minHeap.add(x)
          } else {
            maxHeap.add(x)
          }
    
          // Re-balance the heaps.
          if (minHeap.size - maxHeap.size > 1) {
            maxHeap.add(minHeap.poll())
          }
          if (maxHeap.size - minHeap.size > 1) {
            minHeap.add(maxHeap.poll)
          }
        }
    
        def median: Double = {
          if (minHeap.isEmpty && maxHeap.isEmpty)
            return Int.MinValue
          if (minHeap.size == maxHeap.size) {
            return (minHeap.peek+ maxHeap.peek) / 2.0
          }
          if (minHeap.size > maxHeap.size) {
            return minHeap.peek()
          }
          maxHeap.peek
        }
      }
    

    【讨论】:

      【解决方案4】:

      这是一个 MedianHeap 的 java 实现,是在上面 comocomocomocomo 的解释的帮助下开发的。

      import java.util.Arrays;
      import java.util.Comparator;
      import java.util.PriorityQueue;
      import java.util.Scanner;
      
      /**
       *
       * @author BatmanLost
       */
      public class MedianHeap {
      
          //stores all the numbers less than the current median in a maxheap, i.e median is the maximum, at the root
          private PriorityQueue<Integer> maxheap;
          //stores all the numbers greater than the current median in a minheap, i.e median is the minimum, at the root
          private PriorityQueue<Integer> minheap;
      
          //comparators for PriorityQueue
          private static final maxHeapComparator myMaxHeapComparator = new maxHeapComparator();
          private static final minHeapComparator myMinHeapComparator = new minHeapComparator();
      
          /**
           * Comparator for the minHeap, smallest number has the highest priority, natural ordering
           */
          private static class minHeapComparator implements Comparator<Integer>{
              @Override
              public int compare(Integer i, Integer j) {
                  return i>j ? 1 : i==j ? 0 : -1 ;
              }
          }
      
          /**
           * Comparator for the maxHeap, largest number has the highest priority
           */
          private static  class maxHeapComparator implements Comparator<Integer>{
              // opposite to minHeapComparator, invert the return values
              @Override
              public int compare(Integer i, Integer j) {
                  return i>j ? -1 : i==j ? 0 : 1 ;
              }
          }
      
          /**
           * Constructor for a MedianHeap, to dynamically generate median.
           */
          public MedianHeap(){
              // initialize maxheap and minheap with appropriate comparators
              maxheap = new PriorityQueue<Integer>(11,myMaxHeapComparator);
              minheap = new PriorityQueue<Integer>(11,myMinHeapComparator);
          }
      
          /**
           * Returns empty if no median i.e, no input
           * @return
           */
          private boolean isEmpty(){
              return maxheap.size() == 0 && minheap.size() == 0 ;
          }
      
          /**
           * Inserts into MedianHeap to update the median accordingly
           * @param n
           */
          public void insert(int n){
              // initialize if empty
              if(isEmpty()){ minheap.add(n);}
              else{
                  //add to the appropriate heap
                  // if n is less than or equal to current median, add to maxheap
                  if(Double.compare(n, median()) <= 0){maxheap.add(n);}
                  // if n is greater than current median, add to min heap
                  else{minheap.add(n);}
              }
              // fix the chaos, if any imbalance occurs in the heap sizes
              //i.e, absolute difference of sizes is greater than one.
              fixChaos();
          }
      
          /**
           * Re-balances the heap sizes
           */
          private void fixChaos(){
              //if sizes of heaps differ by 2, then it's a chaos, since median must be the middle element
              if( Math.abs( maxheap.size() - minheap.size()) > 1){
                  //check which one is the culprit and take action by kicking out the root from culprit into victim
                  if(maxheap.size() > minheap.size()){
                      minheap.add(maxheap.poll());
                  }
                  else{ maxheap.add(minheap.poll());}
              }
          }
          /**
           * returns the median of the numbers encountered so far
           * @return
           */
          public double median(){
              //if total size(no. of elements entered) is even, then median iss the average of the 2 middle elements
              //i.e, average of the root's of the heaps.
              if( maxheap.size() == minheap.size()) {
                  return ((double)maxheap.peek() + (double)minheap.peek())/2 ;
              }
              //else median is middle element, i.e, root of the heap with one element more
              else if (maxheap.size() > minheap.size()){ return (double)maxheap.peek();}
              else{ return (double)minheap.peek();}
      
          }
          /**
           * String representation of the numbers and median
           * @return 
           */
          public String toString(){
              StringBuilder sb = new StringBuilder();
              sb.append("\n Median for the numbers : " );
              for(int i: maxheap){sb.append(" "+i); }
              for(int i: minheap){sb.append(" "+i); }
              sb.append(" is " + median()+"\n");
              return sb.toString();
          }
      
          /**
           * Adds all the array elements and returns the median.
           * @param array
           * @return
           */
          public double addArray(int[] array){
              for(int i=0; i<array.length ;i++){
                  insert(array[i]);
              }
              return median();
          }
      
          /**
           * Just a test
           * @param N
           */
          public void test(int N){
              int[] array = InputGenerator.randomArray(N);
              System.out.println("Input array: \n"+Arrays.toString(array));
              addArray(array);
              System.out.println("Computed Median is :" + median());
              Arrays.sort(array);
              System.out.println("Sorted array: \n"+Arrays.toString(array));
              if(N%2==0){ System.out.println("Calculated Median is :" + (array[N/2] + array[(N/2)-1])/2.0);}
              else{System.out.println("Calculated Median is :" + array[N/2] +"\n");}
          }
      
          /**
           * Another testing utility
           */
          public void printInternal(){
              System.out.println("Less than median, max heap:" + maxheap);
              System.out.println("Greater than median, min heap:" + minheap);
          }
      
          //Inner class to generate input for basic testing
          private static class InputGenerator {
      
              public static int[] orderedArray(int N){
                  int[] array = new int[N];
                  for(int i=0; i<N; i++){
                      array[i] = i;
                  }
                  return array;
              }
      
              public static int[] randomArray(int N){
                  int[] array = new int[N];
                  for(int i=0; i<N; i++){
                      array[i] = (int)(Math.random()*N*N);
                  }
                  return array;
              }
      
              public static int readInt(String s){
                  System.out.println(s);
                  Scanner sc = new Scanner(System.in);
                  return sc.nextInt();
              }
          }
      
          public static void main(String[] args){
              System.out.println("You got to stop the program MANUALLY!!");        
              while(true){
                  MedianHeap testObj = new MedianHeap();
                  testObj.test(InputGenerator.readInt("Enter size of the array:"));
                  System.out.println(testObj);
              }
          }
      }
      

      【讨论】:

      • 您不需要将 Comparator 传递给 minHeap,因为它已经按整数自然顺序升序排序。
      【解决方案5】:

      您需要两个堆:一个最小堆和一个最大堆。每个堆包含大约一半的数据。最小堆中的每个元素都大于或等于中位数,最大堆中的每个元素都小于或等于中位数。

      当 min-heap 比 max-heap 多一个元素时,中位数在 min-heap 的顶部。而当最大堆比最小堆多一个元素时,中位数在最大堆的顶部。

      当两个堆包含相同数量的元素时,元素的总数是偶数。 在这种情况下,您必须根据您对中位数的定义进行选择: a) 两个中间元素的平均值; b) 两者中的较大者; c) 较小的; d)随机选择两者中的任何一个......

      每次插入时,将新元素与堆顶部的元素进行比较,以确定插入位置。如果新元素大于当前中值,则进入最小堆。如果它小于当前中值,则进入最大堆。然后你可能需要重新平衡。如果堆的大小相差不止一个元素,则从具有更多元素的堆中提取最小/最大值并将其插入另一个堆。

      为了构造元素列表的中值堆,我们应该首先使用线性时间算法并找到中值。一旦知道了中值,我们就可以根据中值简单地将元素添加到 Min-heap 和 Max-heap 中。不需要平衡堆,因为中位数会将输入的元素列表分成相等的两半。

      如果您提取一个元素,您可能需要通过将一个元素从一个堆移动到另一个堆来补偿大小变化。这样,您就可以确保两个堆始终具有相同的大小或仅相差一个元素。

      【讨论】:

      • 如果两个堆有相同数量的元素怎么办?
      • 那么元素总数是偶数。在这种情况下,根据您对中位数的定义采取行动: a) 始终选择较低的; b) 总是选择更高的; c) 随机选择; d) 中位数是这两个中间元素的平均值...
      • 当您考虑它时,d) 中值解决方案有点奇怪,因为当您在堆上调用 remove() 时,您希望它给您实际删除的元素。如果您通过平均计算中位数,那么remove() 将返回一个数字(您计算的中位数),但实际上会删除一个不同的数字。所以如果你把n元素添加到这种MedianHeap中,然后removeMedian并为每个元素放入另一个数据结构中,第二个数据结构中的元素将与进入MedianHeap的元素不一样。跨度>
      • 为了完整起见,我们还应该补充一点:为了构建元素列表的中值堆,我们应该首先使用线性时间算法并找到中值。一旦知道了中值,我们就可以根据中值简单地将元素添加到 Min-heap 和 Max-heap 中。不需要平衡堆,因为中位数会将输入的元素列表分成相等的两半。
      • 线性时间算法并求中位数:en.wikipedia.org/wiki/Median_of_medians
      【解决方案6】:

      完美平衡的二叉搜索树 (BST) 不是中值堆吗?的确,即使是红黑 BST 也并不总是完美平衡,但它可能已经足够接近您的目的了。并且 log(n) 性能有保证!

      AVL trees 比红黑 BST 更加平衡,因此它们更接近于真正的中值堆。

      【讨论】:

      • 那么每次操作集合时都需要保持一个中值。因为它需要O(logN) 来检索 BST 中任意等级的元素。还是够了……我知道……
      • 是的,但是中值堆会在恒定时间内给出中值。
      • @Bruce:这只是在 BST 的这个意义上是正确的:一旦你构建了结构,获得中位数(不删除它)是 O(0),但是,如果你确实删除它,然后你必须重建堆/树,这两者都需要 O(logn)。
      • @angelatlarge 我喜欢你的想法。但取决于您如何定义中位数,它可能比 O(0) 更昂贵。如果元素个数为偶数,并且您将中位数定义为两个中间元素的平均值,那么您必须在根的旁边再找到一个元素。
      猜你喜欢
      • 2010-09-22
      • 2012-06-13
      • 2020-04-11
      • 2011-05-28
      • 1970-01-01
      • 2019-01-13
      • 2016-11-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多