基础概念
- 堆逻辑上是一棵完全二叉树,将二叉树用层序遍历保存在数组中。
- 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
- 满足任意结点的值都小于其子树中结点的值,则是小堆,或者小根堆,或者最小堆
- 堆的基本作用是,快速找集合中的最值
下标关系
已知双亲(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小的数,建大堆。