堆是一种完全二叉树,且具有以下性质:
大顶堆:每个节点的值都大于或者等于其左右孩子节点的值;
小顶堆:每个节点的值都小于或者等于其左右孩子节点的值。
堆排序是利用堆来设计的一种排序算法,其最好、最坏和平均时间复杂度均为。该排序算法是一种选择排序算法,不稳定。
堆排序的基本思路:
1)首先建堆,升序排序选择大顶堆,降序排序选择小顶堆;
2)将堆顶元素与当前堆的最后一个元素进行交换,将最大(最小)元素沉到数组末尾,相当于从堆中删除当前最大(最小元素)。
3)重新调整当前堆(删除最大或者最小元素后)的结构。
4)反复执行交换+调整的步骤,直到整个序列有序。
public class HeapSort {
public static void main(String[] args) {
int[ ] nums = new int[ ]{
-2, 1, -3, 4, -1, 2, 1, -5, 4
};
//创建最大堆
int length = nums.length;
for( int i = length / 2 - 1; i >= 0; i-- ){
//从最后一个非叶子节点开始,将以i为根节点的完全二叉树调整为最大堆
adjHeap( nums, i, length );
}
//交换+调整最大堆
for( int i = length - 1; i > 0; i-- ){
//交换
swap( nums, 0, i );
//调整
adjHeap( nums, 0, i );
}
//输出
System.out.println( Arrays.toString( nums ) );
}
//将以i为根节点的完全二叉树调整为最大堆
public static void adjHeap( int[ ] nums, int i, int length ){
//将根节点的值暂时存储起来
int temp = nums[ i ];
int parent, child;
for( parent = i; parent * 2 + 1 < length; parent = child ){
child = parent * 2 + 1; //左孩子
//如果存在右孩子,且右孩子的值更大,child指向右孩子
if( child + 1 < length && nums[ child + 1 ] > nums[ child ] ){
child++;
}
//如果较大的孩子节点比父节点更大,需要交换节点的值,否则,就找到根节点的位置
if( temp < nums[ child ] ){
nums[ parent ] = nums[ child ];
}
else{
break;
}
}
//找到根节点i的位置
nums[ parent ] = temp;
}
//交换数组中两个数的值
public static void swap( int[ ] nums, int a, int b ){
int temp = nums[ a ];
nums[ a ] = nums[ b ];
nums[ b ] = temp;
}
}
在实际中,可以利用优先队列来设计最大堆和最小堆。优先队列默认从小到大排序,即最小堆。
最大堆:
PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>( ( a, b ) -> b - a );
最小堆:
PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>( ( a, b ) -> a - b );