最大ヒープと最小ヒープのように、特定の整数セットの中央値を追跡するために中央値ヒープを実装したいと思います。 APIには次の3つの機能が必要です。
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)および<現在の中央値(lcm)の数を追跡するための2つの整数があります。
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;
}
}
しかし、ソリューション全体を思いつくことはできません。
最小ヒープと最大ヒープの2つのヒープが必要です。各ヒープには、データの約半分が含まれています。最小ヒープのすべての要素は中央値以上であり、最大ヒープのすべての要素は中央値以下です。
Min-heapにmax-heapよりも要素が1つ多い場合、中央値はmin-heapの上部にあります。また、max-heapにmin-heapよりも多くの要素が含まれている場合、中央値はmax-heapの最上部にあります。
両方のヒープに同じ数の要素が含まれる場合、要素の合計数は偶数になります。この場合、中央値の定義に従って選択する必要があります。a)2つの中間要素の平均。 b)2つの大きい方。 c)少ない。 d)2つのいずれかをランダムに選択します...
挿入するたびに、新しい要素をヒープの上部にある要素と比較して、挿入する場所を決定します。新しい要素が現在の中央値よりも大きい場合、最小ヒープに移動します。現在の中央値よりも小さい場合、最大ヒープになります。次に、バランスを取り直す必要があります。ヒープのサイズが複数の要素で異なる場合は、より多くの要素があるヒープから最小/最大を抽出し、他のヒープに挿入します。
要素のリストの中央値ヒープを構築するには、最初に線形時間アルゴリズムを使用して中央値を見つける必要があります。中央値がわかれば、中央値に基づいて最小ヒープと最大ヒープに要素を追加するだけです。中央値は要素の入力リストを均等に分割するため、ヒープのバランスを取る必要はありません。
要素を抽出する場合、ある要素をあるヒープから別のヒープに移動することにより、サイズの変更を補正する必要があります。この方法により、常に、両方のヒープが同じサイズになるか、1つの要素だけ異なるようになります。
上記のcomocomo comocomoの説明を使用して開発されたMedianHeapのJava実装です。
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);
}
}
}
完全にバランスの取れたバイナリ検索ツリー(BST)は中央値ヒープではありませんか?赤黒のBSTでさえ、必ずしも完全にバランスが取れているわけではありませんが、目的には十分近いかもしれません。そして、log(n)のパフォーマンスが保証されています!
AVLツリー は、赤黒BSTよりもバランスが取れているため、真の中央値ヒープにさらに近づきます。
ここで、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);
}
}
}
上記のcomocomocomocomoのアイデアに従ったScalaの実装です。
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
}
}
Max-heapとmin-heapを使用せずにこれを行う別の方法は、median-heapをすぐに使用することです。
最大ヒープでは、親は子よりも大きくなります。親が子の「中間」にある新しいタイプのヒープを持つことができます-左の子は親より小さく、右の子は親より大きくなります。すべての偶数エントリは左の子であり、すべての奇数エントリは右の子です。
Max-heapで実行できる同じswimおよびsink操作は、このmedian-heapでも実行できます-わずかな修正が必要です。 max-heapでの典型的なスイム操作では、挿入されたエントリは親エントリよりも小さくなるまで泳ぎます。ここでは、median-heapで、親よりも小さくなるまで泳ぎます(奇数エントリの場合) )または親より大きい(偶数エントリの場合)。
この中央ヒープの実装は次のとおりです。簡単にするために整数の配列を使用しました。
package priorityQueues;
import edu.princeton.cs.algs4.StdOut;
パブリッククラス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];
}
//エントリが上に泳ぎ、その左の子が小さくなり、右が大きくなる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;
}
}
//左の子が大きい場合、または右の子が小さい場合、エントリは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());
}
}