目录
3 选择排序
3.1 简单选择排序
基本思想
算法实现
算法分析
3.2 堆排序
基本思想
算法实现
算法分析
算法应用——TOP K问题
选择排序基本思想:每一趟在n - i + 1(i = 1,2,…n - 1)个记录中选取关键字最小的记录作为有序序列中第i个记录。
选择排序引申出简单选择排序和堆排序。
第1趟,在待排序记录r[1]~r[n]中选出最小的记录,将它与r[1]交换;第2趟,在待排序记录r[2]~r[n]中选出最小的记录,将它与r[2]交换;以此类推,第i趟在待排序记录r[i]~r[n]中选出最小的记录,将它与r[i]交换,使有序序列不断增长直到全部排序完毕。
以下为简单选择排序的存储状态,其中大括号内为无序区,大括号外为有序序列:
初始序列:{ 49 27 65 97 76 12 38 }
第1趟:12与49交换:12{ 27 65 97 76 49 38 }
第2趟:27不动 :12 27{65 97 76 49 38}
第3趟:65与38交换:12 27 38{97 76 49 65}
第4趟:97与49交换:12 27 38 49{76 97 65}
第5趟:76与65交换:12 27 38 49 65{97 76}
第6趟:97与76交换:12 27 38 49 65 76 97 完成
#include
#include
using namespace std;
void SelectSort(int *a, int n)
{
int i,j,min;
for (i = 0; i < n - 1; i++)
{
min = i; //每次将min置成无序组起始位置元素下标
for (j = i +1; j < n; j++) //遍历无序组,找到最小元素
{
if (a[min]>a[j])
min = j;
}
if (min != i) //如果最小元素不是无序组起始位置元素,则与起始元素交换位置
swap(a[i], a[min]);
}
}
int main()
{
int a[6] = { 5, 4, 3, 2, 9, 1 };
int i = 0;
SelectSort(a, 6); //这里需要将数列元素个数传入。可用sizeof在函数内求得元素个数。
for (i = 0; i < 6; i++)
{
printf("%d ", a[i]);
}
system("pause");
return 0;
}
由于要选出最小值,故无序区中的每个元素都要参与比较,所以无论初始数据序列的状态如何,总的比较次数为:
C = n - 1 + n - 2 + n - 3 + … + 2 + 1 = n(n - 1) / 2
故任何时候直接选择排序的时间复杂度为O(N ^ 2),空间复杂度为O(1)。
直接选择排序是一个不稳定的算法。例如,排序序列为{ 5,3,2,5,4,1 },第一趟排序后得到{ 1,3,4,5,4,5 },两个5的相对位置发生了变化。
参考博客
堆排序就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值。如此反复执行,便能得到一个有序序列了。
#include
using namespace std;
// 函数AdjustDown将元素k向下进行调整,堆主要的两个函数之一
/*
A为序列,start为初始下标,end为终止下标
*/
void HeapAdjust(int *A, int start, int end)
{
int i = 0;
int temp = A[start]; //A[start]暂存
for (i = 2 * start+1; i < end; i =i*2+1){
if (i < end && A[i] < A[i + 1]){
i++;
}
if (temp > A[i])
{
break;
}
else{
A[start] = A[i];
start = i;
}
}
A[start] = temp; //将筛选结点的值放入最终位置
}
//堆排序算法
void HeapSort(int *A, int len){
int i = 0;
for(i = len / 2 - 1; i >= 0; i--)
{
HeapAdjust(A, i, len -1);
}
for (i = len-1; i > 0; i--)
{
swap(A[i], A[0]); //输出堆顶元素(和堆底元素交换)
HeapAdjust(A, 0, i-1); //整理,把剩余的i个元素整理成堆
}
}
时间复杂度 O(nlog2n),在最好、最坏和平均情况下,堆排序的时间复杂度为O(nlog2n)
空间复杂度 O(1),仅使用了常数个辅助单元
稳定性:不稳定
另外,由于初始构建堆所需的比较次数比较多,因此,它并不适合待排序序列个数较少的情况。
如果要选出前K大,则将数据中的前K个元素建立成一个小根堆,从第K + 1个元素开始往后依次比较,如果元素大于小根堆的堆顶,那么就和堆顶交换,交换后重新调整为小根堆。这样变量一遍所有数据,最后得到的大小为K的小根堆就是前K大的树。
如果要选出前K小,则将数据中的前K个元素建立成一个大根堆,从第K + 1个元素开始往后依次比较,如果元素小于小根堆的堆顶,那么就和堆顶交换,交换后重新调整为大根堆。这样变量一遍所有数据,最后得到的大小为K的大根堆就是前K小的树。
以选出前k小的数为例,代码:
#include
using namespace std;
int main()
{
int data[] = { 1, 3, 5, 6, 2, 4, 8, 7 }; //存储输入的n个数
int len = sizeof(data) / sizeof(int);
int k;
cin >> k;
if (k>len || k <= 0)
return;
int *b = new int[k];
for (int i = 0; ib[0])
continue;
else
{
b[0] = data[i];
AdjustDown(b, 0, k);
}
}
for (int i = 0; i
4 排序总结
参考博客1
参考博客2
关于排序你应该知道的
PS:更正下表错误:简单选择排序不稳定
1)稳定性
所有简单排序(时间复杂度为O(n^2))都是稳定排序,简单选择排序除外;
所有改进排序都是不稳定排序,归并排序除外。
2)比较次数
比较次数与初始排列无关的是选择排序。
在初始序列基本有序的情况下,最优的是直接插入排序,此时时间复杂度O(n),其次是冒泡排序,时间复杂度也为O(n),快速排序在此时性能最差,时间复杂度O(n^2)。
同时,快速排序在初始序列逆序的时候,性能也最差,此时时间复杂度也为O(n^2)。
堆排序对初始数据集的排列顺序不敏感,在最好、最坏和平均情况下,堆排序的时间复杂度均为O(nlog2n)。
3)空间复杂度
基于比较的排序算法中,归并排序的空间复杂度最高,为O(n),其次为快速排序,为O(logn),其余为O(1)。
4)时间复杂度
基于比较的排序算法时间复杂度下界为O(nlog2n)。