- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
什么是堆排序
将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次最大值。如此反复执行,就能得到一个有序序列了。这个过程其实就是先构建一个最大/最小二叉堆,然后不停的取出最大/最小元素(头结点),插入到新的队列中,以此达到排序的目的。
时间,空间
时间:o(n*logn)
空间:O(1)
代码实现
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
//将一个数组变成堆
heapInsert(arr, i);
}
int size = arr.length;
swap(arr, 0, --size);
while (size > 0) {
//将已经变成堆的数组的0位置保证是最大的
heapIfy(arr, 0, size);
//交换位置,并将最大值放到数组的后面,最大的值交换后会跟堆断开连接,
// 每次堆的节点会减少
swap(arr, 0, --size);
}
}
public static void heapInsert(int[] arr, int index) {
//当堆中,子节点大于父节点就进行交换,
while (arr[index] > arr[(index - 1) / 2]) {
//进行交换
swap(arr, index, (index - 1) / 2);
//当前位置就到了父节点 继续循环
index = (index - 1) / 2;
}
}
public static void heapIfy(int[] arr, int index, int size) {
//找到左节点
int left = index * 2 + 1;
while (left < size) {
//找到两个子节点中最大的节点的角标
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
//最大的子节点跟父节点进行比较得出最大值
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
//经过比较之后已经得到了谁最大,就返回
break;
}
//子节点比父节点大,位置交换
swap(arr, largest, index);
//记录index位置
index = largest;
//继续找下一个左节点
left = index * 2 + 1;
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
执行过程
是否稳定
不稳定
我们知道堆的结构是节点i的孩子为2 * i和2 * i + 1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, … 1这些个父节点选择元素时,就会破坏稳定性。有可能第n / 2个父节点交换把后面一个元素交换过去了,而第n / 2 - 1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。