目次
1. ヒープの概念
ヒープは、優先度キューなどのアプリケーションを実装するためによく使用され、優先度が最も高い (または最も低い) 要素をすぐに見つけて削除できます。ヒープ操作には、新しい要素の挿入、ヒープ内の最上位要素(つまり、最高値) の削除、およびヒープ順序プロパティを満たすための既存のヒープの調整が含まれます。一般的なヒープ調整操作は、「フロート」 (アップ フィルター) と「シンク」 (ダウン フィルター)です。
ヒープには次の 2 つの主な特徴があります。
- ヒープは完全なバイナリ ツリーです
- ヒープ内の各ノードの値は、その子ノードの値以上 (または以下) である必要があります。
2. ヒープストレージ構造
ヒープの概念から、ヒープはその論理構造において完全なバイナリ ツリーであることがわかり、層の順序のルールに従って順番に効率的に格納できます。シーケンス テーブル。理解するために絵を描きます。
要素を配列に保存した後、完全なバイナリ ツリーのプロパティに従ってツリーを復元できます。i が配列内のノードの添字であると仮定すると、次のようになります。
- i = 0 の場合、i で表されるノードはルート ノードです。それ以外の場合、ノード i の親ノードは (i - 1)/2 です。
- 2 * i + 1 がノード数より小さい場合、ノード i の左の子の添字は 2 * i + 1 になります。それ以外の場合、左の子はありません。
- 2 * i + 2 がノード数より小さい場合、ノード i の右側の子のインデックスは 2 * i + 2 になります。それ以外の場合、右側の子は存在しません。
3. ヒープの実装
ここで実装されているのは、大規模なルート ヒープです。
public class Heap {
private int[] elem;
private int usedSize;
public Heap(int[] arr){
elem = new int[arr.length];
createHeap(arr);
}
//建堆
public void createHeap(int[] arr){
for (int i = 0; i < arr.length; i++) {
elem[i] = arr[i];
usedSize++;
}
for (int parent = (usedSize-2)/2; parent >= 0; parent--) {
shiftDown(parent,usedSize);
}
}
//向下调整
public void shiftDown(int parent, int len){}
public void swap(int i, int j){
int tmp = elem[i];
elem[i] = elem[j];
elem[j] = tmp;
}
//入堆
public void push(int val){}
public boolean isFull(){
return usedSize == elem.length;
}
//向上调整
public void shiftUp(int child){}
//出堆顶元素
public int poll(){
if(isEmpty()){
System.out.println("堆中没有元素");
return -1;
}
swap(0,usedSize-1);//将头尾交换
usedSize--;//去掉堆顶元素
shiftDown(0,usedSize);//重新排序
return elem[usedSize];//返回堆顶元素
}
public boolean isEmpty(){
return usedSize == 0;
}
//得到堆顶元素
public int peek(){
if(!isEmpty()){
return elem[0];
}
System.out.println("堆中没有元素");
return -1;
}
}
ヒープ内のコア コードは、shiftDown() メソッドとShiftUp() メソッドの実装です。他のメソッドについては、それらを見れば理解できます。これら 2 つのメソッドの実装について詳しく説明します。
3.1 シフトダウン()
たとえば、配列 {27, 15, 19, 18, 28, 34, 65, 49, 25, 37} をヒープにしたいのですが、どのように実装すればよいでしょうか? アイデア: 最初に最後のサブツリーを見つけて、パイルを形成した後、最後のサブツリーのルート ノードがツリー全体のルート ノードになるまで、他のサブツリーを順番に走査します。以下の図を参照してください。
コードは以下のように表示されます:
public void shiftDown(int parent, int len){
int child = 2*parent + 1;
while(child < len){
if(child+1 < len && elem[child] < elem[child+1]){
child++;
}
if(elem[parent] < elem[child]){
swap(parent,child);
parent = child;
child = 2*parent + 1;
}else{
break;
}
}
}
3.2 シフトアップ()
このメソッドは、要素を挿入するときにヒープの形式で要素を挿入します。基本的に、shiftDown とShiftUp の考え方は似ています。
public void shiftUp(int child){
int parent = (child-1)/2;
while(parent >= 0){
if(elem[child] > elem[parent]){
swap(child,parent);
child = parent;
parent = (child-1)/2;
}else {
break;
}
}
}
3.3 シフトダウンとシフトアップの時間計算量
4. ヒープソート
たとえば、昇順にソートしたい場合は、最初に大きなルート ヒープを作成し、次にルート ノードを最後のノードと交換する必要があります。このとき、最後のノードが最大である必要があるため、shiftDown を実行し、次に、ルート ノードを最後から 2 番目のノードと交換します。コードは以下のように表示されます:
/**
* 堆排序
* 时间复杂度:O(N*logN)
* 空间复杂度:O(1)
* 不稳定
*/
public void heapSort(int[] arr){
createHeap(arr);
int end = arr.length-1;
while(end > 0){
swap(arr,0,end);
shiftDown(arr,0,end);
end--;
}
}
private void shiftDown(int[] arr, int parent,int len) {
int child = 2*parent+1;
while(child < len){
if(child+1 < len && arr[child] < arr[child+1]){
child++;
}
if(arr[child] > arr[parent]){
swap(arr,child,parent);
parent = child;
child = 2*parent+1;
}else{
break;
}
}
}
public void swap(int[] arr, int i, int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public void createHeap(int[] array) {
for (int parent = (array.length-2)/2; parent >= 0; parent--) {
shiftDown(array,parent,array.length);
}
}