八大排序 详解(中)
接上篇博文——《八大排序 详解(上)》
在这篇博文中本人就不过多解释排序的重要性了,接着是本人上篇博文——《八大排序 详解(上)》来讲,在这篇博文中,本人要进行讲解的是剩下的四大排序——插入排序、希尔排序(插入排序的进阶版)、桶排序以及归并排序
五、插入排序:
插入排序的原理是:
将所需排列的数组分为两半——已排和未排
每次将未排的第一个数取出,然后从已排数组的第一个开始比较,遇到比它大的数,就插到那个数的前面。
那么,我们现在用一张动态图来展示一下这个排序算法:
那么,用代码来实现的结果如下:
void insertSort(int *array, int count) {
int i;
int j;
int t;
int tmp;
for (i = 1; i < count; i++) {
tmp = array[i];
for (j = 0; j < i && tmp > array[j]; j++)
;
for (t = i; t > j; t--) {
array[t] = array[t-1];
}
array[j] = tmp;
}
}
从这里我们能够发现,插入排序的时间复杂度是O(n ^2)。
六、希尔排序(插入排序的进阶版):
希尔排序和别的几大排序相比,算是比较中庸那种,因为它的最差情况和最优情况基本没多大差别,它的基本思想如下:
首先,我们将每隔step个数的每个数放在一起,这样分好类之后,再用新的step分组,再进行如上操作(每个step都是数组长度每次除以2所得),直到step为0时结束
现在,我们用一个动态图来实现这个排序算法:
而作为插入排序的进阶版,这个排序算法和插入排序算法的代码都基本一样。
相关代码如下:
static void insertSortOnce(int *array, int count, int start, int step) {
int i;
int j;
int t;
int tmp;
for (i = start + step; i < count; i += step) {
tmp = array[i];
for (j = start; j < i && tmp > array[j]; j += step)
;
for (t = i; t > j; t -= step) {
array[t] = array[t-step];
}
array[j] = tmp;
}
}
void ShellSort(int *array, int count) {
int step;
int start;
printf("\nShell sort\n");
for (step = count/2; step; step /= 2) {
for (start = 0; start < step; start++) {
insertSortOnce(array, count, start, step);
}
}
}
这里对上面的代码做下解释:
第一个函数的类型前都有个static,意思是这个函数仅在该文件中使用,我们所编写的函数在接下来的代码中能够发现,提供给用户使用的只是这八大排序,至于构成这八大函数的子函数,不会提供给用户,这也是我们在数据结构与算法专题中要提到的,编写的.h的.c函数能够提供给外部使用的函数,在.h文件中声明;至于不能给外部使用的函数,就在.c文件中声明,我们在未来编程的过程中会了解到我们所做的工具函数和这里一样,并不是把所有的函数都提供给用户使用。
从这里我们能够发现,希尔排序的时间复杂度是O(n ^(1.3 ~ 2))。
七、归并排序:
归并排序的基本思想是:
分而治之,先两个两个比较,凑齐两组两两排好的组后,再进行四个四个比较,之后就是八个八个...直至比较的范围是整个数组的长度,待排完这轮后就结束排序。
现在,我们通过一张动态图来展示下这个排序算法:
那么,相关代码如下:
static void merge(int* array, int left, int right, int mid, int* temp) {
int i = left;
int j = mid + 1;
int tempLeft = left;
int t = 0;
while (i <= mid && j <= right) {
temp[t] = array[i] <= array[j] ? array[i++] : array[j++];
t++;
}
while (i <= mid) {
temp[t++] = array[i++];
}
while (j <= right) {
temp[t++] = array[j++];
} //以上三个循环的目的是将整个数组排好序再存入temp数组中
t = 0;
while (tempLeft <= right) { //这个循环的目的是将排好序的数组内的值赋值给原数组
array[tempLeft++] = temp[t++];
}
}
static void mSort(int* array, int left, int right, int* temp) {
int mid;
if (left >= right) {
return;
}
mid = (left + right) / 2;
mSort(array, left, mid, temp);
mSort(array, mid + 1, right, temp);
merge(array, left, right, mid, temp);
}
void mergeSort(int* array, int count) {
int *temp;
temp = (int *)calloc(sizeof(int), count);
mSort(array, 0, count-1, temp);
}
从这里我们能够发现,归并排序的时间复杂度是O(n * log n)。
八、桶排序:
桶排序作为我们八大排序的最后一个来讲,是因为这个排序算法在我看来比较偏激,因为它变相地运用了“用空间换时间”的思想,它的大致原理如下:
首先,我们放置10个桶,分别代表某一位为0~9哪一位的数都有谁,最后将桶中所有数取出放入原数组中,这样就完成了桶排序。
现在,我们通过一张动态图来展示下这个排序算法:
那么,代码如下:
//寻找原数组中的最大值
static int findMaxNum(const int *array, int count) {
int max = array[0];
int i;
for (i = 1; i < count; i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
}
//寻找原数组中的最小值
static int findMinNum(const int *array, int count) {
int min = array[0];
int i;
for (i = 1; i < count; i++) {
if (array[i] < min) {
min = array[i];
}
}
return min;
}
void bucketSort(int *array, int count){
int i = 0;
int j = 0;
int max = findMaxNum(array, count);
int min = findMinNum(array, count);
int length = max - min; //通过最大值和最小值 来获取结果“桶”的容量
int *res = (int *)calloc(sizeof(int), length+1);
//用 结果数组中的适当下标的单元中的数,来记录垓下标在原数组中的出现次数
for (i = 0; i < count; i++) {
res[array[i] - min]++;
}
//为原数组赋排序后的值
for(i = 0; i <= length; ++i) {
while(res[i]--) {
array[j++] = i + min;
}
}
free(res);
}
我们可以在这里发现,桶排序的其实就是先放入桶中,再从桶中取出,放回原数组,这样下来,时间复杂度为O(n),但是空间复杂度比较高,所以它也算是很偏激那种排序算法,如果对时间需求高,这个排序算法无疑是最好的选择。
那么,到目前为止,我们的八大排序就讲解完成了,希望同学们在看完本人的讲解之后能够有所收获!
关于本人有关排序的最后一篇博文——《八大排序 详解(下)》,将会使用《八大排序 详解(上)》和本篇博文所编写的所有函数,并以一种十分凶悍的方法进行调用函数,也会用到本人之前的博文《指向函数的指针——消费未来》那篇所运用的知识,希望看到这里的同学能够耐心将下篇看完,谢谢!!!