讲堆排序之前,先讲一下简单选择排序!
1,简单选择排序
基本思想:就是通过n-i次关键字间的比较,从n-i+1个数据中选出关键字最小的数据,并与第i个数据进行交换。
2,简单选择排序的代码实现
void Swap(int *a,int *b) //交换
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void SelectSort(int *arr, int n)
{
assert(arr != NULL && n > 1);
int i,j,min;
for(i = 0; i < n; ++i)
{
min = i; //将当前位置设定为最小值
for(j = i + 1; j < n; ++j)
{
if(arr[j] < arr[min]) //有小于当前最小值的数据
min = j;//将其下标赋给min
}
if(min != i) //如果不相等,说明找到最小值
Swap(&arr[min],&arr[i]);//将最小值交换到i位置
}
}
3,简单选择排序的时间复杂度
简单选择排序最大的特点就是交换移动数据的次数少,节省了时间,并且无论数据如何,其比较次数都是一样的多,n(n-1)/2次,交换次数,最好为0次,最差为n-1次,时间复杂度为O(n^2),空间复杂度为O(1),排序稳定。
4,简单选择排序和堆排序的异同
简单选择排序是将待排序的n个数据中选择一个最小的数据需要比较n-1次,这样的操作并没有把每一趟的比较结果保留下来,在后一趟的比较中,有许多比较在前一趟都已经做过了,但由于前一趟未保存这些比较结果,所有以后的每一趟排序又得重复执行了这些比较操作,因此数据的比较次数就比较多。
如果可以做到每次在选择到最小数据的同时,并根据比较结果对其他数据做出相应的调整,那样排序的总体效率就高了,而堆排序就是对简单选择排序进行的优化。
5,堆排序
何为堆排序?
堆排序 : 利用大根堆进行排序的方法。
基本思想:将待排序序列构造成一个大根堆,此时,整个序列的最大值就是堆顶的根节点,将它与堆数组末尾的元素进行交换,此时末尾就是最大值,然后将剩余的n-1个序列重新构造成一个堆,这样就可以得到次大值,如此执行下去,便可以得到一个有序序列。
何为堆?
堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大根堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小根堆。
如何建堆?
将堆排序的序列构建成一个大根堆,其实就是从下往上,从右往左,将每个非叶子结点当成根节点,将其子树调整成大根堆的过程。
(1),让我用图为大家模拟一下建堆的过程
1,我们的原始数据 23 14 35 24 68 54 32 29.
2,根据从下往上,从右往左,将每一个非叶子结点当成根节点,将其子树构造为大根堆的原理,我们先讲以3下标为根节点的这棵树构建成大根堆,它只有左子树,只需比较它与左孩子的大小,大的为根。
3,接下来将2下标为根结点的二叉树,构建成大根堆,将2下标与左右子树中较大的数进行交换。
4,接下来构建1下标为根结点的这棵树,让它与4下标的值进行交换即可。
5,最后构建下标为0为根结点的这棵二叉树,此时构建完成要满足整棵树都是大根堆,即根大于左右孩子的值,所以当0下标与1下标交换之后,还需要和1下标的孩子进行比较,直到它大于左右孩子或左右孩子为NULL,才能满足整棵树是一个大根堆。
6,此时这个堆就构建完成,是一个大根堆,堆建好,我们就可以进行排序了。
6,堆排序代码实现
//建堆
void HeapAdjust(int *arr,int i,int n)
{
if(arr == NULL || i < 0 || n < 1)
return;
int tmp = arr[i]; //把根结点的值保留下来
int j = 2 * i + 1; //j是i的左孩子
for(; j < n; j = 2 * j + 1)
{
if((j + 1) < n && arr[j] < arr[j+1] ) //右孩子的值大于左孩子
++j; //j是左右孩子中较大值的下标
if(tmp > arr[j]) //根结点大于左右孩子结点,,此时就是大根堆
break;
arr[i] = arr[j]; //左右结点较大的值给跟结点
i = j;
}
arr[i] = tmp;//以前的根结点的值给较大孩子结点,相当于做了交换
}
//堆排序
void HeapSort(int *arr,int n)
{
assert(arr != NULL && n > 1);
int i = (n - 1) / 2;//i为要调整的第一个非叶子结点
for(; i >= 0; --i)
{
HeapAdjust(arr,i,n); //把arr构建成一个大根堆
}
for(int j = n; j > 0; --j)
{
Swap(&arr[0],&arr[j-1]);//将堆顶元素与最后一个位置的元素进行交换
HeapAdjust(arr,0,j-2);//将arr[0...j-2]重新调整成大根堆
}
}
//测试一下
int main()
{
int arr[8] = { 23, 14, 35, 24, 68, 54, 32, 29 };
int len = sizeof(arr) / sizeof(arr[0]);
HeapSort(arr,len);
for(int i = 0; i < len; ++i)
{
printf("%d ",arr[i]);
}
printf("\n");
return 0;
}
7,堆排序的时间复杂度
时间复杂度:O(nlogn), 由于堆排序对原始数据的顺序并不敏感,所以平均复杂度还为O(nlogn)。
堆排序的运行时间主要消耗在初始构建堆和重建堆时的反复筛选上,构建堆的时间复杂度为O(n),重构堆的时间复杂度为O(logn),空间复杂度为O(1),所以堆排序适合于数据量较大的排序,由于原始数据的比较和跳跃是交换式的,所以堆排序不是一个稳定的排序算法。