目录
1.常见排序算法

前面一讲已经讲解了插入排序,这一讲我将讲解选择排序,但是有些算法的实现将依靠之前的数据结构知识,所以如果想要看懂这些算法的实现需要打牢数据结构的基础。每一个算法都需要一定的测试函数,所以我们需要给定一些测试用例来测试该算法是否适用于每一个算法,我们现在给定一些测试用例:1 2 3 4 5 6 7 8 9;9 8 4 2 4 1 3 7 5 ;2 3 4 3 4 2 7 9 2 4;100 101 195 124 125 129;以上四种测试用例对于不同的排序算法每一个测试用例可能会表现出不同的时间复杂度,每一个测试用例也有每一个测试用例对应的时间复杂度最好的一种或几种算法,我们在实现排序时我们需要一些预定的函数,如:交换函数,测试运行时间的函数等。
2.排序算法的预定函数
2.1交换函数
我们需要两个整型类型的指针(之后我们排序排列的都是整型),我们暂时不用typedef了,否则这样比较难理解。
cs
//交换函数
void Swap(int* a, int* b)
{
int p = *a;
*a = *b;
*b = p;
}
2.2测试算法运行时间的函数
这个函数我们权当只是用来测试每一个排序算法最终在实际运用时的表现,我们先进行自己的测试用例的测试,代码无误了再进行实际运用的测试。我们每一次学完这个算法就把这个算法的注释删除即可。
cs
// 测试排序的性能对⽐
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);
int* a5 = (int*)malloc(sizeof(int) * N);
int* a6 = (int*)malloc(sizeof(int) * N);
int* a7 = (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];
a5[i] = a1[i];
a6[i] = a1[i];
a7[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();
int begin5 = clock();
QuickSort(a5, 0, N - 1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
int begin7 = clock();
BubbleSort(a7, N);
int end7 = 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);
printf("QuickSort:%d\n", end5 - begin5);
printf("MergeSort:%d\n", end6 - begin6);
printf("BubbleSort:%d\n", end7 - begin7);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
free(a6);
free(a7);
}
上面的排序算法除了冒泡排序外我们之前基本上没学过,所以每一次我们都和之前学过的排序算法进行比较。
2.3已经实现过的排序算法
这个是为了测试之前的算法和现在实现的算法的比较,我们已经讲解过了插入排序,所以下面代码列出了插入排序的两个代码:
cs
//直接插入排序(升序)
void InsertSort1(int* arr, int n)
{
for (int i = 0; i < n - 1; i++)
{
//我们把需要比较的数放入tmp中,然后先与第i个数据比较,依次往前进行比较插入
int end = i;
int tmp = arr[end + 1];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;
}
}
//如果end=-1我们不能进行数组越界,所以要进行+1操作
arr[end + 1] = tmp;
}
}
//希尔排序
void ShellSort1(int* arr, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
}
}
3.选择排序算法的实现
算法思想:(1)在元素集合arr[i]--arr[n-1]中选择最大(小)的数据元素;(2)若它不是这组元素中最后一个元素(第一个元素)则交换;(3)重复上述操作知道集合中剩余1个元素
这个讲解可能听不懂,但是毕竟是思想,我等下会讲解如何实现的。
3.1直接选择排序
我们开始定义第一个元素的下标为min和max,然后让begin从下标为0位置进行遍历,如果遍历到后面,后面的数据比min指向的数据小,则min指向该数据的下标,如果比max指向的数据大,则max指向该数据的下标,最终i到达end(没有排好序的最后一个元素的下标)则停止,然后将min位置的数据和begin位置的数据交换,然后再把max下标的数据和end位置的数据交换,然后begin++,end--这是这样写出的代码:
cs
//直接选择排序
void SelectSort1(int* arr, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int max, min;
max = min = begin;
for (int i = begin + 1; i <= end; i++)
{
//我们从开始位置后面的数据进行遍历,原因不用解释
if (arr[i] < arr[min])
{
min = i;
}
if (arr[i] > arr[max])
{
max = i;
}
}
Swap(&arr[min], &arr[begin]);
Swap(&arr[max], &arr[end]);
begin++;
end--;
}
}
运行结果分别为:



为什么会出现排序错误的问题?
我们用第二副图来推测其过程:开始begin=0,end=8,最终min=5,max=0,然后先进行第一次交换后得到1 8 4 2 4 9 3 7 5发现我们把最大的换到了min位置,所以我们会导致之后的结果出现有问题。所以我们应该怎么解决?
我们可以在第一次交换前把max置为min然后就可以了,因为我们最大的数据已经交换到min位置了。所以最终代码如下:
cs
//直接选择排序
void SelectSort1(int* arr, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int max, min;
max = min = begin;
for (int i = begin + 1; i <= end; i++)
{
//我们从开始位置后面的数据进行遍历,原因不用解释
if (arr[i] < arr[min])
{
min = i;
}
if (arr[i] > arr[max])
{
max = i;
}
}
if (max == begin)
{
max = min;
}
Swap(&arr[min], &arr[begin]);
Swap(&arr[max], &arr[end]);
begin++;
end--;
}
}
所以之后的运行结果为:



我们再测试其在实际的应用的表现:
cs
// 测试排序的性能对⽐
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);
//int* a5 = (int*)malloc(sizeof(int) * N);
//int* a6 = (int*)malloc(sizeof(int) * N);
//int* a7 = (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];
//a5[i] = a1[i];
//a6[i] = a1[i];
//a7[i] = a1[i];
}
int begin1 = clock();
InsertSort1(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort1(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort1(a3, N);
int end3 = clock();
int begin4 = clock();
/*HeapSort(a4, N);
int end4 = clock();
int begin5 = clock();
QuickSort(a5, 0, N - 1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
int begin7 = clock();
BubbleSort(a7, N);
int end7 = 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);
printf("QuickSort:%d\n", end5 - begin5);
printf("MergeSort:%d\n", end6 - begin6);
printf("BubbleSort:%d\n", end7 - begin7);*/
free(a1);
free(a2);
free(a3);
/*free(a4);
free(a5);
free(a6);
free(a7);*/
}

我们发现直接选择排序的时间和直接插入排序的时间差不多,也就是时间复杂度接近于O(n^2)。主要是不管这个数组是否已经升序,这个排序仍然要遍历整个数组,这是改变不了的。
3.2堆排序
堆排序的实现请进入以下链接:数据结构初阶-堆的代码实现-CSDN博客,其中包含了堆排序的实现思想,所以很建议去看一下,堆排序的时间复杂度也有分析,不用赘述了,我们主要是看其运行的时间,代码如下:
cs
// 测试排序的性能对⽐
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);
//int* a5 = (int*)malloc(sizeof(int) * N);
//int* a6 = (int*)malloc(sizeof(int) * N);
//int* a7 = (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];
//a5[i] = a1[i];
//a6[i] = a1[i];
//a7[i] = a1[i];
}
int begin1 = clock();
InsertSort1(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort1(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort1(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort01(a4, N);
int end4 = clock();
/*int begin5 = clock();
QuickSort(a5, 0, N - 1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
int begin7 = clock();
BubbleSort(a7, N);
int end7 = 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);
//printf("QuickSort:%d\n", end5 - begin5);
//printf("MergeSort:%d\n", end6 - begin6);
//printf("BubbleSort:%d\n", end7 - begin7);
free(a1);
free(a2);
free(a3);
free(a4);
/*free(a5);
free(a6);
free(a7);*/
}
最终结果如下:
我们发现:堆排序相对于其他排序的时间要短得多,主要是因为其时间复杂度为O(n*logn)所以我们运行时间很短,这也是算另外一种快速排序了,到之后我们会发现堆排序和快速排序的时间都差不多。这也是一种很好的算法。
4.下讲预告
下一讲将讲解四种快速排序的方法,其中每一种方法都有其适用的地方,快速排序才是最难的,种类最多的,也是时间运行最快的(一定情况下),快速排序实现方法太多了,之后也会有很多快排,所以下节最重要,而且需要借助数据结构-栈和二叉树。下讲再见!