摘要
作为选择排序的改进版,堆排序可以把每一趟元素的比较结果保存下来,以便我们在选择最小/大元素时对已经比较过的元素做出相应的调整。
堆排序是一种树形选择排序,在排序过程中可以把元素看成是一颗完全二叉树,每个节点都大(小)于它的两个子节点,当每个节点都大于等于它的两个子节点时,就称为大顶堆,也叫堆有序; 当每个节点都小于等于它的两个子节点时,就称为小顶堆。
(大顶堆(有序堆)) (小顶堆)
算法思想(以大顶堆为例)
1.将长度为n的待排序的数组进行堆有序化构造成一个大顶堆
2.将根节点与尾节点交换并输出此时的尾节点
3.将剩余的n -1个节点重新进行堆有序化
4.重复步骤2,步骤3直至构造成一个有序序列
假设待排序数组为[20,50,10,30,70,20,80]
构造堆
在构造有序堆时,我们开始只需要扫描一半的元素(n/2-1 ~ 0)即可,为什么?
因为(n/2-1)~0的节点才有子节点,如图1,n=8,(n/2-1) = 3 即3 2 1 0这个四个节点才有子节点
(图1:初始状态)
所以代码4~6行for循环的作用就是将3 2 1 0这四个节点从下到上,从右到左的与它自己的子节点比较并调整最终形成大顶堆,过程如下:
第一次for循环将节点3和它的子节点7 8的元素进行比较,最大者作为父节点(即元素60作为父节点)
【红色表示交换后的状态】
第二次for循环将节点2和它的子节点5 6的元素进行比较,最大者为父节点(元素80作为父节点)
第三次for循环将节点1和它的子节点3 4的元素进行比较,最大者为父节点(元素70作为父节点)
第四次for循环将节点0和它的子节点1 2的元素进行比较,最大者为父节点(元素80作为父节点)
(注意这里,元素20和元素80交换后,20所在的节点还有子节点,所以还要再和它的子节点5 6的元素进行比较,这就是28行代码 i = j 的原因)
至此有序堆已经构造好了!如下图:
调整堆
下面进行while循环
(1)堆顶元素80和尾40交换后-->调整堆
(2)堆顶元素70和尾30交换后-->调整堆
(3)堆顶元素60尾元素20交换后-->调整堆
(4)其他依次类推,最终已排好序的元素如下:
(5) 我们可以用一个动态图来理解堆排序
(6) 排序算法总结
堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。
比如序列:{ 9, 5, 7, 5 },堆顶元素是9,堆排序下一步将9和第二个5进行交换,得到序列 { 5, 5, 7, 9 },再进行堆调整得到{ 7, 5, 5, 9 },重复之前的操作最后得到{ 5, 5, 7, 9 }从而改变了两个5的相对次序。
C++(STL代码)
#include<iostream>
#include<vector>
using namespace std;
/***************************************************************
本程序实现堆排序,堆排序是一种时间复杂度为
O(nlogn)的不稳定排序算法,最好和最坏情况都
是O(nlogn),常用排序算法链接:
https://www.cnblogs.com/eniac12/p/5329396.html
anthor:李金泽,BioInformation Lab,HIT 2018.11.01
*****************************************************************/
template<typename T>
void Swap(vector<T>& a, int i, int j) {
T temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
template<typename T>
void HeapSort(vector<T>& a) {
int len = a.size() - 1;
for (int i = len / 2 - 1; i >= 0; i--) {//构造一个大顶堆,这时的a[0]为最大值
AdjustHeap(a, i, len);
}
while (len >= 0) {
Swap(a, 0, len--);
AdjustHeap(a, 0, len);//调整堆,使其满足堆的定义(父节点比它的俩子节点都要大)
}
}
template<typename T>
void AdjustHeap(vector<T>& a, int i, int len)
{
int left, right;//存放左右节点的下标
int MaxPoint;//存放较大子节点的下标
while ((left = 2 * i + 1) < len) {
right = left + 1;
MaxPoint = left;
if (left < len && a[right] > a[left]) {//当left==len时,i节点没有右子节点,MaxPoint指针指向左节点
MaxPoint++;
}
if (a[i] < a[MaxPoint]) {
Swap(a, i, MaxPoint);
}
else { break; }
i = MaxPoint;//将父节点设置为子节点,开始向子树调整堆结构
}
}
int main()
{
vector<int> a = { 20,50,20,40,70,10,80,30,60 };
cout << "排序之前:" << endl;
for (int i = 0; i < a.size(); i++) {
cout << a[i]<<" ";
}
HeapSort<int>(a);
cout << "排序之后:" << endl;
for (int i = 0; i < a.size(); i++) {
cout << a[i]<<" ";
}
getchar();
return 0;
}
Java代码:
public class HeapSort {
private static void heapSort(int[] arr) {
int len = arr.length -1;
for(int i = len/2 - 1; i >=0; i --){ //堆构造
heapAdjust(arr,i,len);
}
while (len >=0){
swap(arr,0,len--); //将堆顶元素与尾节点交换后,长度减1,尾元素最大
heapAdjust(arr,0,len); //再次对堆进行调整
}
}
public static void heapAdjust(int[] arr,int i,int len){
int left,right,j ;
while((left = 2*i+1) <= len){ //判断当前父节点有无左节点(即有无孩子节点,left为左节点)
right = left + 1; //右节点
j = left; //j"指针指向左节点"
if(j < len && arr[left] < arr[right]) //右节点大于左节点
j ++; //当前把"指针"指向右节点
if(arr[i] < arr[j]) //将父节点与孩子节点交换(如果上面if为真,则arr[j]为右节点,如果为假arr[j]则为左节点)
swap(arr,i,j);
else //说明比孩子节点都大,直接跳出循环语句
break;
i = j;
}
}
public static void swap(int[] arr,int i,int len){
int temp = arr[i];
arr[i] = arr[len];
arr[len] = temp;
}
public static void main(String[] args) {
int array[] = {20,50,20,40,70,10,80,30,60};
System.out.println("排序之前:");
for(int element : array){
System.out.print(element+" ");
}
heapSort(array);
System.out.println("\n排序之后:");
for(int element : array){
System.out.print(element+" ");
}
}
}
运行结果: