用 C 语言描述几种排序算法
排序算法是最基本且重要的一类算法,本文基于 VS2017,使用 C 语言来实现一些基本的排序算法。
一、选择排序
选择排序,先找到数组中最小的元素,然后将这个元素与数组的第一个元素位置互换(如果第一个元素就是最小元素,则与自己互换位置)。然后在剩下的元素中寻找最小的元素,与第二个元素位置互换。以此循环,直到整个数组完成排序。
算法描述:
1)第一趟,从无序的数组中选出最小的元素,与第一个元素交换位置;
2)第二趟,除去第一个元素,剩下的元素组成一个无序数组,从中找出最小的元素然后与第二个元素交换位置;
3)重复 n - 1 次即可得到有序的数组。
算法分析:
选择排序的时间复杂度为 O(N2),空间复杂度为 O(1),是非稳定排序
#include <stdio.h> #include <stdlib.h> #include <string.h>
int *selectSort(int *numArray,int arrayLen); int main() { int numArray[] = {10,5,21,3,7,0,11,59,12,11,20}; int arrayLen = sizeof(numArray) / sizeof(int); int *newArray = selectSort(numArray,arrayLen); for (int k = 0; k < arrayLen; k++) { printf("%d ",newArray[k]); } return 0; } int *selectSort(int *numArray,int arrayLen) { int len = arrayLen; int temp = 0; for (int i = 0; i < len - 1; i++) { for (int j = i + 1; j < len; j++) { if (numArray[j] <= numArray[i]) { temp = numArray[i]; numArray[i] = numArray[j]; numArray[j] = temp; } } } return numArray; }
二、直接插入排序
直接插入排序将一个数组分为一个有序数组和一个无序数组。一开始,第一个元素单独作为一个有序数组,其他元素为无序数组;然后,每次从无序数组中取出第一个元素,与有序数组中的元素进行比较并插入到有序数组中去,与之组成一个新的有序数组,以此重复直到排序完成。
算法描述:
1)第一个元素自己作为一个有序数组;
2)从第二个元素开始,将它与其左侧第一个元素比较,若左侧第一个元素比它大,则继续与左侧第二个元素比较,直到遇到不大于(小于或等于)该元素的元素,将该元素插入到所找到的元素的右边,此时该元素及其左边的元素是已排序的;
3)选取第 3、4、...、n 个元素,重复步骤 2;
4)得到有序的数组。
算法分析:
直接插入排序的时间复杂度为 O(N2),空间复杂度为 O(1),是稳定排序
#include <stdio.h> #include <stdlib.h> #include <string.h> int *insertSort(int *numArray,int arrayLen); int main() { int len; int numArray[] = {10,23,1,6,4,20,45,5,3,7,19,100,22}; len = sizeof(numArray) / sizeof(int); int *newArray = insertSort(numArray,len); for (int k = 0; k < len; k++) { printf("%d ",newArray[k]); } return 0; } int *insertSort(int *numArray,int arrayLen) { int temp; for (int i = 1; i < arrayLen; i++) { for (int j = i; j > 0; j--) { if (numArray[j] < numArray[j - 1]) { temp = numArray[j]; numArray[j] = numArray[j - 1]; numArray[j - 1] = temp; } } } return numArray; }
三、冒泡排序
冒泡排序一次比较两个元素,如果前一个元素比后一个元素大,则交换两个元素的位置。从第一个元素开始,对每对相邻的元素都执行这个操作, 直到最后一个元素,则一次比较完成后,最大的元素会被放到数组最右边;去掉最右边的元素再对剩余的数组进行冒泡排序,直到所有元素都完成排序。
算法描述:
1)从第一个元素开始,比较两个相邻的元素(第1、2个元素),若后者比前者小,则交换两者的位置;
2)依次交换所有的元素,一趟交换完成后,最右边的元素为最大的元素;
3)然后再从第一个元素开始交换,除了最后一个元素;
4)重复,知道所有元素都被排序。
算法分析:
冒泡排序的时间复杂度为 O(N2),空间复杂度为 O(1),是稳定排序
#include <stdio.h> #include <stdlib.h> #include <string.h> int *bubbleSort(int *numArray,int arrayLen); int main() { int len; int numArray[] = {20,13,2,9,5,34,56,25,18,0,33,67}; len = sizeof(numArray) / sizeof(int); int *newArray = bubbleSort(numArray,len); for (int k = 0; k < len; k++) { printf("%d ", newArray[k]); } return 0; } int *bubbleSort(int *numArray, int arrayLen) { int temp; if (arrayLen < 2) { return numArray; } for (int i = 0; i < arrayLen; i++) { for (int j = 0; j < arrayLen - i - 1; j++) { if (numArray[j + 1] < numArray[j]) { temp = numArray[j + 1]; numArray[j + 1] = numArray[j]; numArray[j] = temp; } } } return numArray; }
四、希尔排序
希尔排序是第一批突破 O(n2) 时间复杂度的算法之一,它是插入排序的一种变种,与插入排序不同的是,它通过比较相距一定间隔的元素来工作;各趟比较所用的距离随着算法的进行而减小,直到最后一趟比较(只比较相邻元素)为止,这是距离为 1 。因此希尔排序又叫缩小增量排序。
算法描述:
1)选择一个增量 k(一般为数组长度的一半),以这个增量为步长将数组分为 k 个小数组,对每个小数组进行直接插入排序;
2)缩小增量为 k / 2,以这个增量为步长将数组分为 k / 2 个小数组,对每个小数组进行直接插入排序,由于这个小数组有一部分是已经排序了的,因此排序起来会比较容易;
3)继续缩小增量,并排序,直到增量为 1;
4)最终得到一个已排序的数组。
算法分析:
希尔排序的时间复杂度为 O(N log N),空间复杂度为 O(1),为非稳定排序。
#include <stdio.h> #include <stdlib.h> #include <string.h> int *shellSort(int *numArray, int arrayLen); int main() { int len; int numArray[] = {13,2,8,18,30,22,0,81,45,36,7,65,22}; len = sizeof(numArray) / sizeof(int); int *newArray = shellSort(numArray,len); for (int k = 0; k < len; k++) { printf("%d ", newArray[k]); }
return 0; } int *shellSort(int *numArray,int arrayLen) { int step; int temp; int j; if (arrayLen < 2) return numArray; for (step = arrayLen / 2; step > 0; step /= 2) { for (int i = step; i < arrayLen; i++) { temp = numArray[i]; for (j = i - step; j >= 0 && temp < numArray[j]; j -= step) { numArray[j + step] = numArray[j]; } numArray[j + step] = temp; } } return numArray; }
五、归并排序
归并排序的基本思想是先把一个大的无序数组拆分成两个小的无序数组,然后对这两个数组分别进行排序,之后再将两个有序的小数组合并成一个有序的大数组。
通过递归的方式,将数组一直分割,直到数组大小为 1 ,此时数组只有一个元素,为有序状态;然后将两个大小为 1 的数组合并成一个大小为 2 的数组;再把大小为 2 的两个数组合并成大小为 4 的数组...直到将所有的数组合并成一个大数组。
算法描述:
1)将一个无序的数组分成两个无序的小数组;
2)将两个无序的小数组分别分成两个更小的无序数组;
3)重复第二步,知道小数组的长度为1,此时每个小数组都是有序的;
4)将长度为 1 的两个小数组合并成一个长度为 2 的有序数组;
5)将长度为 2 的两个小数组合并成长度为 4 的有序数组;
6)最终合成一个有序的大数组。
很显然,用递归的思想实现归并排序会比较方便。
算法分析:
归并排序的时间复杂度为 O(N log N),空间复杂度为 O(N),为稳定排序。
#include <stdio.h> #include <stdlib.h> #include <string.h> void mergeSort(int *sourceArray,int arrayLen); void mSort(int *sourceArray, int *tmpArray, int left, int right); void merge(int *sourceArray, int *tmpArray, int lpos, int rpos, int rightEnd); int main() { int sourceArray[] = {27,23,98,10,44,26,4,9,2,0,11,56,34,67,10}; int len = sizeof(sourceArray) / sizeof(int); mergeSort(sourceArray,len); for (int k = 0; k < len; k++) { printf("%d ", sourceArray[k]); }
return 0; } // 归并排序 void mergeSort(int *sourceArray, int arrayLen) { int *tmpArray = (int *)malloc(sizeof(int) * arrayLen); // 新建一个临时数组 if (tmpArray != NULL) { mSort(sourceArray, tmpArray, 0, arrayLen - 1); free(tmpArray); } else { perror("malloc failed!"); exit(EXIT_FAILURE); } } // 排序 void mSort(int *sourceArray, int *tmpArray, int left, int right) { int mid = (left + right) / 2; if (left < right) { mSort(sourceArray, tmpArray, left, mid); mSort(sourceArray, tmpArray, mid + 1, right); merge(sourceArray, tmpArray, left, mid + 1, right); } } // 归并 void merge(int *sourceArray,int *tmpArray, int lpos, int rpos, int rightEnd) { int i, leftEnd, elementNum, tmpPos; tmpPos = lpos; // 临时指针 leftEnd = rpos - 1; elementNum = rightEnd - lpos + 1; // 归并比较 while (lpos <= leftEnd && rpos <= rightEnd) { if (sourceArray[lpos] <= sourceArray[rpos]) { tmpArray[tmpPos++] = sourceArray[lpos++]; } else { tmpArray[tmpPos++] = sourceArray[rpos++]; } } // 将左边剩余元素填入数组 while (lpos <= leftEnd) { tmpArray[tmpPos++] = sourceArray[lpos++]; } // 将右边剩余元素填入数组 while (rpos <= rightEnd) { tmpArray[tmpPos++] = sourceArray[rpos++]; } // 将临时数组中的元素拷贝到原数组中去 for (i = 0; i < elementNum; i++, rightEnd--) { sourceArray[rightEnd] = tmpArray[rightEnd]; } }
六、快速排序
快速排序是在实践中已知最快的一种排序算法。其基本思想是先从数组中选择一个元素作为基准元素,然后分别从数组的头部和尾部开始往数组中间遍历,并与基准元素比较,一次遍历的结果是将这个基准元素移到数组的中间(基准数的左边所有元素都小于或等于它,右边所有元素都大于或等于它);然后再从这个基准元素的左边和右边各选一个元素作为两边小数组各自的基准元素,重复之前的做法,将基准元素移到数组中间,直到所有元素都被排好序。
算法描述:
1)选择一个基准元素(比如说数组最左边的元素);
2)设置两个哨兵(指针) left 和 right,分别指向数组最左边的元素和数组最右边的元素;
3)先移动 right 哨兵,从数组右边往左边移动,当 right 哨兵所指位置的元素小于基准数时,停止移动 right 哨兵;
4)再移动 left 哨兵,从数组左边往右边移动,当 left 哨兵所指位置的元素大于基准数时,停止移动 left 哨兵;
5)交换 left 和 right 所指位置的元素;
6)重复步骤 3~5,直到 left >= right,停止移动,并将基准元素与 left 所指向位置的元素互换;
7)此时基准元素左边数组的元素全部小于它,右边数组的元素全部大于它;
8)对基准元素左边和右边的数组分别重复步骤 2~6;
9)得到被排序的数组。
算法分析:
快速排序的时间复杂度为 O(N log N),空间复杂度为 O(log N),为非稳定排序。
#include <stdio.h> #include <stdlib.h> #include <string.h> void quickSort(int *sourceArray, int leftPos, int rightPos); int main() { int sourceArray[] = { 29,13,4,23,10,54,27,87,33,0,32,5,90,76 }; int len = sizeof(sourceArray) / sizeof(int); quickSort(sourceArray, 0, len - 1); for (int k = 0; k < len; k++) { printf("%d ", sourceArray[k]); }
return 0; } void quickSort(int *sourceArray, int leftPos, int rightPos) { int baseNum = sourceArray[leftPos]; // 基准数 int tmpPosLeft = leftPos; // tmpPos 用于存放基准数在数组中的位置 int tmpPosRight = rightPos; int temp; if (leftPos >= rightPos) { return; } while (leftPos < rightPos) { // 先从右边找起,找到比基准数小的数为止 while (leftPos < rightPos && sourceArray[rightPos] >= baseNum) { rightPos--; } // 再从左边找,找到比基准数大的为止 while (leftPos < rightPos && sourceArray[leftPos] <= baseNum) { leftPos++; } // 交换两个哨兵所在处的值 if (leftPos < rightPos) { temp = sourceArray[leftPos]; sourceArray[leftPos] = sourceArray[rightPos]; sourceArray[rightPos] = temp; } } // 最后将基准数放到数组中央,此时基准数左边的数全部小于它,基准数右边的数全部大于它 sourceArray[tmpPosLeft] = sourceArray[leftPos]; sourceArray[leftPos] = baseNum; quickSort(sourceArray, tmpPosLeft, leftPos - 1); quickSort(sourceArray, leftPos + 1, tmpPosRight); }
参考资料:
《数据结构与算法分析 -- C语言描述》