基礎概念
- 堆邏輯上是一棵完全二叉樹,將二叉樹用層序遍歷保存在數組中。
- 滿足任意結點的值都大於其子樹中結點的值,叫做大堆,或者大根堆,或者最大堆
- 滿足任意結點的值都小於其子樹中結點的值,則是小堆,或者小根堆,或者最小堆
- 堆的基本作用是,快速找集合中的最值
下標關係
已知雙親(parent)的下標,則:
左孩子(left)下標 = 2 * parent + 1;
右孩子(right)下標 = 2 * parent + 2;
已知孩子(不區分左右)(child)下標,則:
雙親(parent)下標 = (child - 1) / 2;
向下調整成小堆
根據下標找到index的左孩子,堆如果沒有左孩子一定沒有右孩子,右孩子等於左孩子下標+1,如果超過size,說明沒有右孩子。
找出左右孩子中的最小的一個,然後和array[index]比較,比array[index]小就交換,index就變爲了min,左孩子下標=2*index+1也發生了改變,然後繼續while循環,直到左孩子不存在也就是left>=size的時候。如果比array[index]大,則不用交換
private static void swap(int[] array, int index, int min) {
int k=array[index];
array[index]=array[min];
array[min]=k;
}
private static void shiftDownSmall(int[]array, int index, int size) {
int left=2*index+1;
while(left<size){
int right=left+1;
int min=left;
if(right<size){
if(array[right]<array[left])
min=right;
}
if(array[index]>array[min]){
swap(array,index,min);
index=min;
left=2*index+1;
}else{
break;
}
}
}
向下調整成大堆
和向下調整成小堆是同理的。只不過比array[index]大才交換。
private static void shiftDownBig(int[] array, int index, int size) {
int left=2*index+1;
while(left<size){
int right=left+1;
int max=left;
if(right<size){
if(array[right]>array[left])
max=right;
}
if(array[index]<array[max]){
swap(array,index,max);
index=max;
left=2*index+1;
}else{
break;
}
}
}
建小堆
建堆首先要從倒數第一個非葉子結點開始向下調整,最後一個葉子結點下標爲size-1,它的父母結點爲(size-2)/2(即倒數第一個非葉子結點)。
public static void createHeapSmall(int[] a, int s) {
for(int i=(s-2)/2;i>=0;i--){
shiftDownSmall(a,i,s);
}
}
建大堆
和建小堆同理哦。
public static void createHeapBig(int[] a, int s) {
for(int i=(s-2)/2;i>=0;i--){
shiftDownBig(a, i, s);
}
}
入隊列
- 首先按尾插方式放入數組2. 比較其和其雙親的值的大小,如果雙親的值小,則滿足堆的性質,插入結束。3.否則,交換其和雙親位置的值,重新進行 2、3 步驟4. 直到根結點
public class MyPriorityQueue {
// 不做擴容考慮
private int[] array;
private int size;
MyPriorityQueue() {
array = new int[16];
size = 0;
}
public void offer(int element) {
array[size++] = element;
shiftUpSmall(array, size - 1);
}
}
public static void shiftUpSmall(int[] array, int i){
// 直到 i == 0 之前,一直
// 先找到 i 的雙親的下標
// 比較 array[parent] 和 array[i]
// 如果滿足條件,調整結束
// 否則,交換,然後 讓 i = parent 繼續
while(i!=0){
int p=(i-1)/2;
if(array[p]<=array[i]){
break;
}
swap(array,p,i);
i=p;
}
}
出隊列
爲了防止破壞堆的結構,刪除時並不是直接將堆頂元素刪除,而是用數組的最後一個元素替換堆頂元素,然後通過向下調整方式重新調整成堆。
public int poll() {
int element = array[0];
array[0] = array[--size];
Heap.shiftDownSmall(array, 0, size);
return element;
}
//返回隊首元素
public int peek() {
// 不做錯誤處理
return array[0];
}
堆排序
升序建大堆,將第一個結點(max)與堆中最後一個葉子結點交換,然後將此葉子結點之前的結點重新進行向下調整成大堆,直到i=size-1。
/堆排序(升序用大堆,降序用小堆)
public static void heapSort(int[] array){
createHeapBig(array,array.length);
for(int i=0;i<array.length-1;i++){
// 無序 [0, array.length - i)
// 有序 [array.length - i, array.length)
swap(array, 0, array.length - i - 1);
// 無序 [0, array.length - i - 1)
// 長度 array.length - i - 1
shiftDownBig(array,0,array.length-i-1);
}
}
降序建小堆,同理噠。
import java.util.Arrays;
public class Solution1 {
//堆排序
public static void HeapSort(int array[]){
creatDownSmall(array,array.length);
for(int i=0;i<array.length-1;i++){
swap(array,0,array.length-i-1);
shiftDownSmall(array,0,array.length-i-1);
}
}
private static void shiftDownSmall(int[] array, int index, int size) {
int left=2*index+1;
while(left<size){
int right=left+1;
int min=left;
if(right<size){
if(array[right]<array[left])
min=right;
}
if(array[index]>array[min]){
swap(array,index,min);
index=min;
left=2*index+1;
}else{
break;
}
}
}
private static void swap(int[] array, int i, int i1) {
int k=array[i];
array[i]=array[i1];
array[i1]=k;
}
private static void creatDownSmall(int[] array, int size) {
for(int i=(size-2)/2;i>=0;i--){
shiftDownSmall(array,i,size);
}
}
public static void main(String[] args) {
int[] a = { 9,5,2,7,3,4,5,2,6,8 };
HeapSort(a);
System.out.println(Arrays.toString(a));
}
}
TOP k問題
在海量數據中,尋找前k大的數。考慮到快排,但是不能解決海量問題,所以只能用堆。建含有k個元素的小堆,用於存儲當前最大的k個元素,接着,從k+1個元素開始掃描,和堆頂元素進行比較,如果被掃描的元素大於堆頂,則替換堆頂元素,並調整堆。時間複雜度爲O(n*log k),空間複雜度爲O (k)。
尋找前k小的數,建大堆。