Java最小ヒープTopK問題の解決
3653 ワード
TopK問題とは,大量のデータ(ソースデータ)から最大(または最小)のK個のデータを取得することである.
TopK問題はよくある問題です.例えば、学校は全校生徒の中で最も成績の高い500人の学生を見つけ、ある検索エンジンは毎日100件の検索回数が最も多いキーワードを統計します.
この問題に対して、解決方法はたくさんあります.
方法1:ソースデータ中のすべてのデータをソートし、前のK個のデータを取り出すのがTopKである.
しかし,データ量が大きい場合,k個の最大数しか必要とせず,全体的なソートに時間がかかり,効率は高くない.
方法2:K長の配列a[]を維持し、ソースデータの前K個を読み出して配列を入れ、その配列を昇順に並べ替え、ソースデータのK個目以降のデータを順次読み出し、配列中の最小の要素(a[0])と比較して、a[0]より小さい直接pass、より大きい場合は、最小の要素a[0]を破棄し、二分法によりその位置を見つけ、その位置前の配列要素はソースデータの読み取りが終了するまで全体的に前方にシフトする.
これは方法よりも効率が大きく向上するが,Kの値が大きい場合,長さKのデータ全体がシフトするのも非常に時間がかかる.
この問題に対して,比較的効率の高い解決法は最小スタックを用いることである.
最小ヒープ(小根ヒープ)は、まず完全なツリーであり、すべての親ノードの値が2つのサブノードの値以下であるデータ構造です.
最小スタックのストレージ構造(物理構造)は、実際には配列です.次の図に示します.
スタックにはいくつかの重要な操作があります.
BuildHeap:通常の配列をスタックに変換し、変換が完了すると、配列はスタックの特性に合致します.すべての親ノードの値は2つのサブノードの値以下です.
Heapify(int i):元素iの左右のサブツリーが小根スタックである場合、Heapifyによりi元素をスタックの性質に適合するように適切な位置に降下させる.
上のTopK問題に戻り,最小スタックで解決する方法は,まずソースデータ中のK個の要素をK長の配列に配置し,配列を最小スタックに変換することである.さらに、ソースデータのK個以降のデータを順に取り、スタックのルートノード(配列の最初の要素)と比較し、最小スタックの性質によってルートノードは必ずスタックの中で最小の要素であり、それより小さい場合は直接pass、より大きい場合は要素を置き換え、ソースデータの遍歴が終わるまでルート要素をHeapifyする.
最小ヒープの実装:
最小ヒープによるTopKの取得:
作者:叉叉哥转载请注明出典:http://blog.csdn.net/xiao__gui/article/details/8687982
TopK問題はよくある問題です.例えば、学校は全校生徒の中で最も成績の高い500人の学生を見つけ、ある検索エンジンは毎日100件の検索回数が最も多いキーワードを統計します.
この問題に対して、解決方法はたくさんあります.
方法1:ソースデータ中のすべてのデータをソートし、前のK個のデータを取り出すのがTopKである.
しかし,データ量が大きい場合,k個の最大数しか必要とせず,全体的なソートに時間がかかり,効率は高くない.
方法2:K長の配列a[]を維持し、ソースデータの前K個を読み出して配列を入れ、その配列を昇順に並べ替え、ソースデータのK個目以降のデータを順次読み出し、配列中の最小の要素(a[0])と比較して、a[0]より小さい直接pass、より大きい場合は、最小の要素a[0]を破棄し、二分法によりその位置を見つけ、その位置前の配列要素はソースデータの読み取りが終了するまで全体的に前方にシフトする.
これは方法よりも効率が大きく向上するが,Kの値が大きい場合,長さKのデータ全体がシフトするのも非常に時間がかかる.
この問題に対して,比較的効率の高い解決法は最小スタックを用いることである.
最小ヒープ(小根ヒープ)は、まず完全なツリーであり、すべての親ノードの値が2つのサブノードの値以下であるデータ構造です.
最小スタックのストレージ構造(物理構造)は、実際には配列です.次の図に示します.
スタックにはいくつかの重要な操作があります.
BuildHeap:通常の配列をスタックに変換し、変換が完了すると、配列はスタックの特性に合致します.すべての親ノードの値は2つのサブノードの値以下です.
Heapify(int i):元素iの左右のサブツリーが小根スタックである場合、Heapifyによりi元素をスタックの性質に適合するように適切な位置に降下させる.
上のTopK問題に戻り,最小スタックで解決する方法は,まずソースデータ中のK個の要素をK長の配列に配置し,配列を最小スタックに変換することである.さらに、ソースデータのK個以降のデータを順に取り、スタックのルートノード(配列の最初の要素)と比較し、最小スタックの性質によってルートノードは必ずスタックの中で最小の要素であり、それより小さい場合は直接pass、より大きい場合は要素を置き換え、ソースデータの遍歴が終わるまでルート要素をHeapifyする.
最小ヒープの実装:
public class MinHeap
{
// -
private int[] data;
// ,
public MinHeap(int[] data)
{
this.data = data;
buildHeap();
}
//
private void buildHeap()
{
// (data.length) / 2 - 1 , 。
// * , 10 , (data.length) / 2 - 1 4,a[4] , a[5] *
for (int i = (data.length) / 2 - 1; i >= 0; i--)
{
// heapify
heapify(i);
}
}
private void heapify(int i)
{
//
int l = left(i);
int r = right(i);
// , 、 、
int smallest = i;
// ,
if (l < data.length && data[l] < data[i])
smallest = l;
// ,
if (r < data.length && data[r] < data[smallest])
smallest = r;
// , return,
if (i == smallest)
return;
// ,
swap(i, smallest);
// , heapify
heapify(smallest);
}
//
private int right(int i)
{
return (i + 1) << 1;
}
//
private int left(int i)
{
return ((i + 1) << 1) - 1;
}
//
private void swap(int i, int j)
{
int tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
// ,
public int getRoot()
{
return data[0];
}
// , heapify
public void setRoot(int root)
{
data[0] = root;
heapify(0);
}
}
最小ヒープによるTopKの取得:
public class TopK
{
public static void main(String[] args)
{
//
int[] data = {56,275,12,6,45,478,41,1236,456,12,546,45};
// Top5
int[] top5 = topK(data, 5);
for(int i=0;i<5;i++)
{
System.out.println(top5[i]);
}
}
// data k
private static int[] topK(int[] data,int k)
{
// K topk
int[] topk = new int[k];
for(int i = 0;i< k;i++)
{
topk[i] = data[i];
}
//
MinHeap heap = new MinHeap(topk);
// k , data
for(int i= k;i root)
{
heap.setRoot(data[i]);
}
}
return topk;
}
}
作者:叉叉哥转载请注明出典:http://blog.csdn.net/xiao__gui/article/details/8687982