选择排序
基本思想:
- 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
- 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
- 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
动图演示:
动图演示的是每次选出集合中最小的数,我们可以进行优化,每次从集合中选出最小和最大的数,分别与第一个数和最后一个数交换,这样可以每趟排序可以排好两个数,效率提高一倍。
参考代码:
// 选择排序
void SelectSort(int* a, int n)
{
// 一次选两个数,最大和最小插入到一前一后
int left = 0, right = n - 1;
while (left < right)
{
int mini = left;
int maxi = left;
for (int i = left; i <= right; ++i)
{
if (a[i] > a[maxi])
maxi = i;
if (a[i] < a[mini])
mini = i;
}
Swap(&a[mini], &a[left]);
if (left == maxi)
maxi = mini;
Swap(&a[maxi], &a[right]);
left++;
right--;
}
}
直接选择排序的特性总结:
- 直接选择排序思考非常好理解,但是效率不是很好。 实际中很少使用
- 时间复杂度: O(N^2)
- 空间复杂度: O(1)
- 稳定性:不稳定
堆排序
堆的概念及结构
堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
- 堆中某个结点的值总是不大于或不小于其父结点的值;
- 堆总是一棵完全二叉树。
将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。
堆向下调整算法
堆排序的第一步是建堆,建堆的方法有向上调整算法和向下调整算法,我们这里以向下调整算法建小堆为例:
- 向下调整算法的前提是两边子树必须全是大堆或者小堆
- 根据要调整的结点下标算出左孩子和右孩子的下标
- 比较左孩子和右孩子,取较小的值与要调整的结点值进行比较
- 如果较小的孩子结点的值比父亲结点的值要小,那就需要交换较小孩子的结点和父亲结点的值,并将较小孩子的值作为新的要向下调整的结点,继续向下调整知道叶子结点
- 如果较小的孩子结点的值比父亲结点的值要大,那就无需向下调整
建小堆参考图:
参考代码:
void AdjustDown(int* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
// 右孩子存在且右孩子比孩子小
if (child + 1 < n && a[child + 1] < a[child])
{
child++;
}
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
堆排序的实现
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。 需要注意的是排升序要建大堆,排降序建小堆。
建堆参考图:
堆排序的思想:
- 将需要排序的数组建成大根堆或者小根堆
- 利用堆删除的思想来进行排序,循环已经建成堆的数组,交换堆顶和最后一个数,类似于删除堆顶元素,将交换后的堆顶向下调整,形成新的堆,依次循环,就可以达到排序的目的
堆排序参考图:
参考代码:
void AdjustDown(int* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
// 右孩子存在且右孩子比孩子小
if (child + 1 < n && a[child + 1] > a[child])
{
child++;
}
if (a[parent] < a[child])
{
Swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// 堆排序
void HeapSort(int* a, int n)
{
// 建堆
for (int i = (n - 2) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
// 选堆顶和最后的数交换,类似于删除堆顶
// 新的堆顶再向下调整
for (int i = n - 1; i > 0; --i)
{
Swap(&a[0], &a[i]);
AdjustDown(a, i, 0);
}
}
直接选择排序的特性总结:
- 堆排序使用堆来选数,效率就高了很多。
- 时间复杂度: O(N*logN)
- 空间复杂度: O(1)
- 稳定性:不稳定
性能测试
测试程序:
// 测试排序的性能对比
void TestOP()
{
srand(time(0));
const int N = 100000;
int* a1 = (int*)malloc(sizeof(int) * N);
int* a2 = (int*)malloc(sizeof(int) * N);
int* a3 = (int*)malloc(sizeof(int) * N);
int* a4 = (int*)malloc(sizeof(int) * N);
for (int i = 0; i < N; ++i)
{
a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
free(a1);
free(a2);
free(a3);
free(a4);
}
测试结果: