1.什么是堆
堆是一种数据结构,需要满足以下两个特点
a. 是一个完全二叉树
b. 对于堆上的每一个结点, 都必须满足: 父结点的值大于子结点的值
2.什么是完全二叉树
下面七棵树都是完全二叉树
完全二叉树最大的特点是: 生成节点顺序是 从上到下,从左到右 依次生成
从树的第h-1层(倒数第二层)开始进行向下调整
建大堆 进行堆排序之后 是升序
建小堆 进行堆排序之后 是降序
向下调整有两种方式: 一种是"父节点大于所有的子节点",另一种是"父节点小于所有的子节点"
针对经典topK问题, 也就是找出很多个数中最大的前K个数, 是建大堆还是建小堆?
答: 建小堆. 举例:要挑选出武林中前五名最厉害武林高手, 现在随机给出五个武林高手, 让这五个武林高手内部做一次向下调整, 挑出最菜的那个人跟武林中其他人进行比武. 如果没有人打过这个最菜的武林高手, 那么这五位武林高手就是武林中最厉害的五位; 而如果来比武的那个人打赢了最菜的, 那么最菜的那个人就出去, 这个来比武的人就是这五分之一了, 然后这五个人内部再进行一次向下调整, 再挑选出最菜的那个人, 跟武林中其他人进行比武....以此类推, 就可以找出武林中最厉害的前五名高手
记住一个公式:
对于一个下标为i的结点:
它的父结点是: parent = (i - 1) / 2
它的左孩子是: 2i + 1
它的右孩子是: 2i + 2
3.代码实现(建大堆)
public class heapSort {
public static void heapSort(int[] array){//进行堆排序
buildHeap(array); //先建堆
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)
// 交换后: 无序区间: [0, array.length - i - 1);有序区间: [array.length - i - 1, array.length)
// 无序区间长度: array.length - i - 1
shiftDown(array, array.length - i - 1, 0); // -i是代表每交换完之后,就会砍断一个结点,
} //砍断一个结点这一步不用写出来,只用在heapify这一步时默认减少了一个结点即可
}
private static void swap(int[] array, int a, int b){
int temp = array[a];
array[a] = array[b];
array[b] = temp;
}
private static void buildHeap(int[] array){ //建堆
for(int i = (array.length - 1)/2; i>= 0; i--){ //从array.length的父结点i到根结点开始向下调整.
shiftDown(array, array.length, i);//表示调整array.length的父节点i那一块小三角区域
//每次只调整一个小三角区域的内容(即只调整父结点以及它的左右子树)
}
}
public static void shiftDown(int[] array, int size, int index){
//size表示这棵树一共有size个结点
//index表示对下标为某某的结点做向下调整的操作
//如果要对一个结点进行向下调整操作, 这个结点必须是父结点
int left = 2 * index + 1;
while(left < size) { //表示"如果左子树存在"
// 是为了保证是在树节点总个数范围内,因为是从倒数第二层开始向下调整,可能倒数第二层有的结点没有子节点
int max = left;//先让最大值等于左子树
int right = 2 * index + 2;
if(right < size){ //表示"如果右子树存在"
if(array[right] > array[left]){
max = right;
}
}
if(array[index] >= array[max]){
break;//若要调整的下标是最大的, 则break, 表示树的这一块区域已变成堆,停止对这一块区域的调整
}
//否则交换
swap(array, index, max);
index = max;//index的下标是max
left = 2 * index + 1; //更新一下新index的左子树
}
}
public static void main(String[] args){
int[] array = {4,2,1,9,3,7,2,55};
heapSort(array);
for(int i = 0; i < array.length; i++){
System.out.print(array[i] + " ");
}
}
}
4.性能分析
时间复杂度: O(nlog(n))
空间复杂度: O(1)
不稳定