常见排序算法(C语言实现)
文章目录
- 排序介绍
- 插入排序
- 直接插入排序
- 希尔排序
- 选择排序
- 选择排序
- 堆排序
- 交换排序
- 冒泡排序
- 快速排序
- 递归实现
- Hoare版本
- 挖坑法
- 前后指针版本
- 非递归实现
- Hoare版本
- 挖坑法
- 前后指针版本
- 快排的优化
- 三值取中
- 小区间优化
- 归并排序
- 递归实现
- 非递归实现
- 计数排序
- 排序算法复杂度及稳定性分析
- 不同算法的运行效率
排序介绍
排序就是使一串记录按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
在待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的。比如A = B,在原序列中A在B前面,排序后A仍旧在B前面,则是稳定的。
数据元素全部放在内存中的排序称为内部排序。
数据元素过多而不能同时存放在内存中,需要根据排序过程的要求不断在内外存之间移动数据的排序称为外部排序。

插入排序
基本思想:把待排序的记录按其关键码值的代销逐个插入到一个已经排好的有序序列中,知道所有的记录插入完为止,得到一个新的有序序列。
直接插入排序
- 基本介绍:
在待排序的数组中,我们假设前n-1个元素已经是有序的了,然后将第n各元素逐一进行比较,然后将第n个元素放入合适的位置。
一个元素集合越接近有序,直接插入算法的时间效率就越高。 - 代码:
void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}}
- 时间复杂度与空间复杂度
插入排序的平均时间复杂度也是 O(n2),空间复杂度为常数阶 O(1),具体时间复杂度和数组的有序性也是有关联的。
插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较N-1次,时间复杂度为 O(N)。最坏的情况是待排序数组是逆序的,此时需要比较次数最多,最坏的情况是 O(n2)。 - 动图演示:

希尔排序
- 基本介绍:
希尔排序是一种插入排序,又称为缩小增量排序。希尔排序在直接插入排序的基础上引入了分组,先分组进行预排序,然后在通过直接插入排序完成最后的排序。
先选定一个数gap(小于这个待排序数据的总数)作为第一增量,元素之间相差gap的元素作为一组,然后分组进行直接插入排序,排序完成后缩小增量,在重复上述操作,直到gap=1,实现最终的排序。 - 代码:
void ShellSort(int* a, 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 = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}
- 时间复杂度与空间复杂度:
希尔排序时间复杂度是 O(n1.3-2),空间复杂度为常数阶 O(1)。希尔排序没有时间复杂度为 O(n(logn)) 的快速排序算法快 ,因此对中等大小规模表现良好,但对规模非常大的数据排序不是最优选择,总之比一般 O(n2) 复杂度的算法快得多。
算法在执行过程中,只需要几个定义的临时变量,所以空间复杂度为常数级O(1)。 - 图示:

选择排序
基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,放在序列的起始位置,直到全部待排序的数据元素排完。
选择排序
- 基本介绍:
直接选择排序也是一种简单的排序方法,它的基本思想是:第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,第二次从R[1]~R[n-1]中选取最小值,与R[1]交换,…,第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,…,第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
一个简单的优化就是,反正都要遍历一次,那就可以找出最大最小的值,分别放到结尾和开头,这样能够提高一些效率。 - 代码:
void SelectSort(int* a, int n)
{int begin = 0, end = n - 1;while (begin < end){int minI = begin, maxI = begin;for (int i = begin + 1; i <= end; i++){if (a[i] < a[minI])minI = i;if (a[i] > a[maxI])maxI = i;}int tmp = a[begin];a[begin] = a[minI];a[minI] = tmp;if (maxI == begin)maxI = minI;tmp = a[end];a[end] = a[maxI];a[maxI] = tmp;begin++;end--;}
}
- 时间复杂度与空间复杂度:
直接选择排序的时间复杂度为 O(n2) ,所以当记录占用字节数较多时,通常比直接插入排序的执行速度快些;
对于空间复杂度来说,简单选择排序仅需一个存储空间用于记录交换的暂存单元,即空间复杂度为 O(1);
由于在直接选择排序中存在着不相邻元素之间的互换,因此,直接选择排序是一种不稳定的排序方法。 - 图示:

堆排序
- 基本介绍:
堆排序是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。它通过堆来进行数据选择。排升序要建大堆,降序要建小堆。 - 代码:
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 确认child指向大的那个孩子if (child + 1 < n && a[child + 1] > a[child]){++child;}// 1、孩子大于父亲,交换,继续向下调整// 2、孩子小于父亲,则调整结束if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapSort(int* a, int n)
{// 向下调整建堆 -- O(N)// 升序:建大堆for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(a, n, i);}// O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}
- 时间复杂度与空间复杂度:
使用堆排序最坏的情况就是一直需要交换结点,则需要循环h - 1次(h为树的高度)。而h = log(N+1)(N为树的总结点数),则排序的时间复杂度为O(logN)。但是在进行堆排序之前需要先建堆,建堆的时间复杂度为O(N)。
对于空间复杂度来说,堆排序仅需几个存储空间用于记录一些下标位置,所以空间复杂度为 O(1); - 图示:

交换排序
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排
序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
冒泡排序
- 基本介绍:
冒泡排序是一个很形象的形容,因为它排序就像冒泡一样,元素是一个一个冒出来的。从第一个元素开始,两两比较,根据升序还是降序,将大的或小的元素向后移。
如果一次遍历完了,都没有发生交换,就说明遍历的序列是有序的,可以直接跳出。这样可以提高一点效率,但是效率还是比不上其他排序算法。 - 代码:
void BubbleSort(int* a, int n)
{for (int i = 0; i < n; i++){int exchange = 0;for (int j = 1; j < n - i; j++){if (a[j - 1] > a[j]){int tmp = a[j - 1];a[j - 1] = a[j];a[j] = tmp;exchange = 1;}}if (exchange == 0)break;}
}
-
时间复杂度与空间复杂度:
-
图示:

快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想是:任取待排元素序列中的某元素作为其基准值(往往是取第一个元素或者最后一个元素),按照该排序码将待排序集合分为左右两个子序列,左子序列中的所有元素均小于基准值,右子序列中的所有元素均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
递归实现
Hoare版本
- 基本介绍:
选出一个key值,定义一个L和一个R,L向右走R向左走。(需要注意的是,如果key值是第一个元素,R先走。如果key值是最后一个元素,L先走)
R先移动,遇到比key值小的就停止,然后L开始移动,遇到比key值大的停止,然后将此时L与R的值交换,然后重复以上步骤。直到L和R相遇,此时的值一定是小于key值的,然后把它与key交换。而此时的key值就放在了它应该在的位置,同时将序列分成了左右两个子序列。
为什么R和L相遇时的值一定是小于key值的?R在遇到小于key值的值时会停止,等L移动,L移动有两个结果,一种是遇到比key值大的,两者交换,然后R继续移动;另一种是没有遇到必key值大的,直到相遇,这个是比key值小的值。L在停止前必然是R遇到一个比key小的值停止,两者会交换。所以造成R和L相遇的值一定小于key值的原因是R先走,这是非常巧妙的一步。如果是L先走,那必然相遇位置的值是大于key值的,此时的key值应该取的是最后一个元素。 - 代码:
void QuickSortHoare(int* a, int begin, int end)
{if (begin >= end)return;int left = begin, right = end;int keyI = left;while (left < right){// 右边先走,找小于key值的值while (left < right && a[right] >= a[keyI])right--;// 左边后走,找大于key值的值while (right < right && a[left] <= a[keyI])left++;int tmp = a[left];a[left] = a[right];a[right] = tmp;}int tmp = a[left];a[left] = a[keyI];a[keyI] = a[left];keyI = left;// 左子序列[begin,keyI),右子序列(keyI,end]QuickSortHoare(a, begin, keyI - 1);QuickSortHoare(a, keyI + 1, end);
}
- 时间复杂度与空间复杂度:
对于快速排序来说,比较的次数是固定的,不会超过O(n),那么划分的次数就非常重要了。如果初始序列是有序的,那么此时的排序过程就非常的像冒泡排序,时间复杂度为O(n),则最差的情况时间复杂度为O(n2)。
如果每次key值都在中间,那么就有点像是二分法,则时间复杂度为O(logn),此时的时间复杂度就是O(n*logn)了。
因为使用了递归,所以在执行过程中需要在栈中保存相关的信息,需要的空间和递归次数有关,递归与划分的次数有关系,也就是最好是O(logn),最差是O(n)。 - 图示:

挖坑法
- 基本介绍:
挖坑法是基于Hoare的,他通过挖坑的方法避免了R和L相遇时的值一定小于key值的问题,因为不是所有人都能理解为什么R和L相遇时的值是小于key值的。但是像Hoare一样,它也需要根据key值的选定来决定是R先走还是L先走。
挖坑法一开始会取出key值,然后R移动找到比key值小的值,把这里的值填到L的位置上,随后L开始移动,找到比key值大的值填到R的位置上,然后R又开始移动,重复上述步骤,直到R和L相遇,最后把key值填入。 - 代码:
void QuickSortPit(int* a, int begin, int end)
{// 当只有一个数据或数列不存在时if (begin >= end)return;int left = begin;int right = end;int key = a[left];int pit = left;while (left < right){// 右边先走,找比key值小的值while (left < right && a[right] >= key){right--;}a[pit] = a[right];pit = right;// 左边再走,找比key值大的值while (left < right && a[left] <= key){left++;}a[pit] = a[left];pit = left;}a[pit] = key;QuickSortPit(a, begin, pit - 1);QuickSortPit(a, pit + 1, end);
}
- 时间复杂度与空间复杂度:
核心思想并没有什么变化,换汤不换药,所以时间复杂度与Hoare版本是一样的。 - 图示:

前后指针版本
- 基本介绍:
这个是Hoare的一种变形,还是取一个key值,然后取prev和cur分别指向第一个元素和第二个元素,然后cur向后移动,遇到比key小的值,cur的值就和prev的值交换,遇到比key大的值cur就继续走。这样prev就两种情况与cur在同一位置,或者是停留在值比key值大的位置上,最后cur走到end后,就把prev与key交换,这样也就完成了左右子序列区分的任务。
这是一种Hoare的变形,过程不容易理解,但是代码容易实现。 - 代码:
void QuickSortPoint(int* a, int begin, int end)
{if (begin >= end)return;int keyI = begin;int prev = begin;int cur = begin + 1;while (cur <= end){// 找到比key小的值时,与prev++位置交换,小的向前移动,大的向后移动if (a[cur] < a[keyI] && ++prev != cur){int tmp = a[prev];a[prev] = a[cur];a[cur] = tmp;}cur++;}int tmp = a[prev];a[prev] = a[keyI];a[keyI] = tmp;keyI = prev;QuickSortPoint(a, begin, keyI - 1);QuickSortPoint(a, keyI + 1, end);
}
- 时间复杂度与空间复杂度:
时间复杂度与空间复杂度仍旧与Hoare版本的一样。 - 图示:

非递归实现
首先我们要知道一个点,每次递归都会开辟一次栈帧空间,而栈帧空间有一个特点就是先开辟的空间最后销毁,但是这也造成了一个问题,如果递归层度过深就会栈溢出。但是快排又依赖于这一先入栈后销毁的特性完成排序。所以我们要实现非递归的快排就需要实现这一特性,而在我们的数据结构中恰好有一个栈的数据结构是具备这种特性的,所以如果我们要非递归实现快排,就要使用栈这个数据结构。
Hoare版本
int Hoare(int* a, int begin, int end)
{int left = begin, right = end;int keyI = left;while (left < right){// 右边先走,找小于key值的值while (left < right && a[right] >= a[keyI])right--;// 左边后走,找大于key值的值while (right < right && a[left] <= a[keyI])left++;int tmp = a[left];a[left] = a[right];a[right] = tmp;}int tmp = a[left];a[left] = a[keyI];a[keyI] = a[left];keyI = left;return keyI;
}void QuickSortNonR(int* a, int begin, int end)
{// 创建、初始化栈,将begin、end插入栈中Stack st;StackInit(&st);StackPush(&st, begin);StackPush(&st, end);// 栈非空就循环while (!StackEmpty(&st)){int right = StackTop(&st);StackPop(&st);if (StackEmpty(&st))break;int left = StackTop(&st);StackPop(&st);if (StackEmpty(&st))break;int keyI = Hoare(a, left, right);if (keyI + 1 < right){StackPush(&st, keyI + 1);StackPush(&st, right);}if (left < keyI - 1){StackPush(&st, left);StackPush(&st, keyI - 1);}}StackDestroy(&st);
}
挖坑法
int Pit(int* a, int begin, int end)
{int left = begin;int right = end;int key = a[left];int pit = left;while (left < right){// 右边先走,找比key值小的值while (left < right && a[right] >= key){right--;}a[pit] = a[right];pit = right;// 左边再走,找比key值大的值while (left < right && a[left] <= key){left++;}a[pit] = a[left];pit = left;}a[pit] = key;return pit;
}
void QuickSortNonR(int* a, int begin, int end)
{// 创建、初始化栈,将begin、end插入栈中Stack st;StackInit(&st);StackPush(&st, begin);StackPush(&st, end);// 栈非空就循环while (!StackEmpty(&st)){int right = StackTop(&st);StackPop(&st);if (StackEmpty(&st))break;int left = StackTop(&st);StackPop(&st);if (StackEmpty(&st))break;int keyI = Pit(a, left, right);if (keyI + 1 < right){StackPush(&st, keyI + 1);StackPush(&st, right);}if (left < keyI - 1){StackPush(&st, left);StackPush(&st, keyI - 1);}}StackDestroy(&st);
}
前后指针版本
int Point(int* a, int begin, int end)
{int keyI = begin;int prev = begin;int cur = begin + 1;while (cur <= end){// 找到比key小的值时,与prev++位置交换,小的向前移动,大的向后移动if (a[cur] < a[keyI] && ++prev != cur){int tmp = a[prev];a[prev] = a[cur];a[cur] = tmp;}cur++;}int tmp = a[prev];a[prev] = a[keyI];a[keyI] = tmp;keyI = prev;return keyI;
}
void QuickSortNonR(int* a, int begin, int end)
{// 创建、初始化栈,将begin、end插入栈中Stack st;StackInit(&st);StackPush(&st, begin);StackPush(&st, end);// 栈非空就循环while (!StackEmpty(&st)){int right = StackTop(&st);StackPop(&st);if (StackEmpty(&st))break;int left = StackTop(&st);StackPop(&st);if (StackEmpty(&st))break;int keyI = Point(a, left, right);if (keyI + 1 < right){StackPush(&st, keyI + 1);StackPush(&st, right);}if (left < keyI - 1){StackPush(&st, left);StackPush(&st, keyI - 1);}}StackDestroy(&st);
}
快排的优化
三值取中
我们之前提到过,如果每次key值的位置恰好在最边上,那么快排的的时间效率就会变成O(n2),虽然这样的概率很小,但是还是有概率会出现。这时我们可以采用三值取中法来避免这种的情况发生。因为key值是影响划分次数的关键,三值取中,就是找到首、中、尾三个位置的值,比较大小,将中间大小的值与key值交换,这样就能保证key值的位置不会是在最边上。
int GetMidIndex(int* a, int begin, int end)
{int mid = (begin + end) / 2;if (a[begin] < a[mid]){if (a[mid] < a[end]){return mid;}else if (a[begin] > a[end]){return begin;}else{return end;}}else // a[begin] > a[mid]{if (a[mid] > a[end]){return mid;}else if (a[begin] < a[end]){return begin;}else{return end;}}
}
小区间优化
每一层的递归都会以2倍数进行增长,即1,2,4,8,16……,通过这个数列我们可以发现,在逻辑上只要我们减少一层递归,就能减少约一半的递归次数。所以我们可以结合其他排序,进行一个判断,在只有多少数的时候就采用其他排序,这样就能有效的避免递归过深。
void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}if ((end - begin + 1) < 15){// 小区间用直接插入替代,减少递归调用次数InsertSort(a + begin, end - begin + 1);}else{int keyi = PartSort3(a, begin, end);// [begin, keyi-1] keyi [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}
}
归并排序
递归实现
-
基本介绍:
归并排序是建立在归并操作上的一种有效的排序算法,它采用了分治的思想。将已经有序的子序列合并,得到完全有序的序列。即先使每个子序列有序,然后合并成一个有序的序列。将两个有序表合并成一个有序表叫做二路归并。归并排序需要先分解,在合并。

-
代码:
void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin >= end)return;int mid = (begin + end) / 2;// [begin, mid] [mid+1, end] 递归让子区间有序_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid+1, end, tmp);// 归并[begin, mid] [mid+1, end]int begin1 = begin, end1 = mid;int begin2 = mid+1, end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}memcpy(a + begin, tmp + begin, sizeof(int)*(end - begin + 1));
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int)*n);if (tmp == NULL){perror("malloc fail");exit(-1);}_MergeSort(a, 0, n - 1, tmp);free(tmp);tmp = NULL;
}
- 时间复杂度与空间复杂度:
归并排序有点类似二叉树结构,高度O(logn),每层循环n次,所以时间复杂度O(n*logn);
归并排序额外开辟了n个空间加上递归的logn,所以空间复杂度为O(n+logn),但是logn是可以忽略掉的,最后的复杂度为O(n)。 - 图示:

非递归实现
-
基本介绍:
归并排序的非递归算法并不需要借助栈这个数据结构来实现,如果使用了栈反而会非常的麻烦,我们只需要控制每次参与合并的元素个数即可,最终便能使序列变为有序。
但是我们需要考虑到一些特殊情况,因为归并是两两归并的,也就是它归并的元素个数是1,2,4,8,16……这样增长的,那么如果元素个数不是这样标准的倍数呢?这时就会有三种情况。
①:最后一个分组中,右侧区间元素个数不够,此时我们合并序列,就需要对这个区间的边界进行控制;
②:最后一个分组中,右侧区间没有元素,就是元素刚好只够左侧区间,那么我们就不需要对这个分组进行合并,因为它本身已经是有序的了;
③:最后一个分组中,左侧区间的元素个数不够,那么我们就不需要对该小组进行合并了。

-
代码:
void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int)*n);if (tmp == NULL){perror("malloc fail");exit(-1);}// 归并每组数据个数,从1开始,因为1个认为是有序的,可以直接归并int rangeN = 1;while (rangeN < n){for (int i = 0; i < n; i += 2 * rangeN){// [begin1,end1][begin2,end2] 归并int begin1 = i, end1 = i + rangeN - 1;int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1;int j = i;// end1 begin2 end2 越界// 修正区间 ->拷贝数据 归并完了整体拷贝 or 归并每组拷贝if (end1 >= n){end1 = n - 1;// 不存在区间begin2 = n;end2 = n - 1;}else if (begin2 >= n){// 不存在区间begin2 = n;end2 = n - 1;}else if (end2 >= n){end2 = n - 1;}while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}}// 也可以整体归并完了再拷贝memcpy(a, tmp, sizeof(int)*(n));rangeN *= 2;}free(tmp);tmp = NULL;
}
计数排序
- 基本介绍:
计数排序又称为鸽巢排序,是对哈希直接定址法的变形应用,是一种非比较排序。它先统计相同元素出现的次数,然后根据统计的结果将序列回收到原来的序列中。
计数排序适用于数据范围集中的序列,此时效率很高,但是适用的范围及场景受到限制。 - 代码:
void CountSort(int* a, int n)
{int max = a[0], min = a[0];for (int i = 1; i < n; i++){if (a[i] < min)min = a[i];if (a[i] > max)max = a[i];}int range = max - min + 1;int* countA = (int*)calloc(range, sizeof(int));if (NULL == countA){perror("calloc fail\n");exit(-1);}// 统计次数for (int i = 0; i < n; i++)countA[a[i] - min]++;// 排序int k = 0;for (int j = 0; j < range; j++){while (countA[j]--)a[k++] = j + min;}free(countA);
}
- 时间复杂度与空间复杂度:
它的时间复杂度和空间复杂度都是由它自身元素的区间跨度决定的,时间复杂度是O(MAX(n,范围)),空间复杂度是O(范围)。 - 图示:

排序算法复杂度及稳定性分析
| 排序算法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
| 简单选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
| 直接插入排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
| 希尔排序 | O(nlogn)~O(n2) | O(n1.3) | O(n2) | O(1) | 不稳定 |
| 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
| 归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
| 快速排序 | O(nlogn) | O(nlogn) | O(n2) | O(nlogn)~O(n) | 不稳定 |
不同算法的运行效率
数据过大可能会导致栈溢出,所以选择非递归的快排和归并排序来测试。
void TestOP()
{srand(time(0));const int N = 50000;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);int* a8 = (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];a8[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();BubbleSort(a5, N);int end5 = clock();int begin6 = clock();QuickSortNonR(a6, 0, N - 1);int end6 = clock();int begin7 = clock();MergeSortNonR(a7, N);int end7 = clock();int begin8 = clock();CountSort(a8, N);int end8 = 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("BubbleSort:%d\n", end5 - begin5);printf("QuickSort:%d\n", end6 - begin6);printf("MergeSort:%d\n", end7 - begin7);printf("CountSort:%d\n", end8 - begin8);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);free(a8);
}

相关文章:
常见排序算法(C语言实现)
文章目录排序介绍插入排序直接插入排序希尔排序选择排序选择排序堆排序交换排序冒泡排序快速排序递归实现Hoare版本挖坑法前后指针版本非递归实现Hoare版本挖坑法前后指针版本快排的优化三值取中小区间优化归并排序递归实现非递归实现计数排序排序算法复杂度及稳定性分析不同算…...
基于jsp+ssm+springboot的小区物业管理系统【设计+论文+源码】
摘 要随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势;对于小区物业管理系统当然也不能排除在外,随着网络技术的不断成熟,带动了小区物业管理系统,它彻底改变了过去…...
Elasticsearch 学习+SpringBoot实战教程(三)
需要学习基础的可参照这两文章 Elasticsearch 学习SpringBoot实战教程(一) Elasticsearch 学习SpringBoot实战教程(一)_桂亭亭的博客-CSDN博客 Elasticsearch 学习SpringBoot实战教程(二) Elasticsearch …...
try-with-resource
try-with-resource是Java 7中引入的新特性,它可以方便地管理资源,自动关闭资源,从而避免了资源泄漏的问题。 作用 使用try-with-resource语句可以简化代码,避免了手动关闭资源的繁琐操作,同时还可以保证资源的正确关闭…...
leetcode148_排序链表的3种解法
1. 题目2. 解答 2.1. 解法12.2. 解法22.3. 解法3 1. 题目 给你链表的头结点head,请将其按升序排列并返回排序后的链表。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullp…...
使用stm32实现电机的PID控制
使用stm32实现电机的PID控制 PID控制应该算是非常古老而且应用非常广泛的控制算法了,小到热水壶温度控制,大到控制无人机的飞行姿态和飞行速度等等。在电机控制中,PID算法用的尤为常见。 文章目录使用stm32实现电机的PID控制一、位置式PID1.计…...
数学原理—嵌入矩阵
目录 1.嵌入矩阵的基本作用 2.嵌入矩阵的数学解释 3.嵌入矩阵在联合分布适应中的数学推导主要包括以下几个步骤 4.在JDA中,怎么得到嵌入矩阵 5.联合分布自适应中如何得到嵌入矩阵 (另一种解释) 1.嵌入矩阵的基本作用 在机器学习中&a…...
English Learning - L2 语音作业打卡 辅音翘舌音 [ʃ] [ʒ] 空气摩擦音 [h] Day31 2023.3.23 周四
English Learning - L2 语音作业打卡 辅音翘舌音 [ʃ] [ʒ] 空气摩擦音 [h] Day31 2023.3.23 周四💌发音小贴士:💌当日目标音发音规则/技巧:翘舌音 [ʃ] [ʒ]空气摩擦音 [h]🍭 Part 1【热身练习】🍭 Part2【练习内容】…...
记录springboot+vue+fastdfs实现简易的文件(上传、下载、删除、预览)操作
前言说明:springboot vue FastDFS实现文件上传(支持预览)升级版 FASTDFS部分 FASTDFS安装过程:基于centos 7安装FastDFS文件服务器 SpringBoot部分 springboot源码实现 package com.core.doc.controller;import com.baomid…...
Java中循环使用Stream应用场景
在JAVA中,涉及到对数组、Collection等集合类中的元素进行操作的时候,通常会通过循环的方式进行逐个处理,或者使用Stream的方式进行处理。例如,现在有这么一个需求:从给定句子中返回单词长度大于5的单词列表,…...
中国蚁剑AntSword实战
中国蚁剑AntSword实战1.基本使用方法2.绕过安全狗连接3.请求包修改UA特征伪造RSA流量加密4.插件使用1.基本使用方法 打开蚂蚁宝剑,右键添加数据: 输入已经上传马的路径和连接密码: 测试连接,连接成功! GetShell了&…...
C++ 直接初始化和拷贝初始化
首先我们介绍直接初始化:编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。文字描述可能会让你们云里雾里,那我们直接看代码: //先设计这样的一个类 class A{ public:A(){ cout << "A()" << endl; }A…...
数据迁移工具
1.Kettle Kettle是一款国外开源的ETL工具,纯Java编写,绿色无需安装,数据抽取高效稳定 (数据迁移工具)。 Kettle 中有两种脚本文件,transformation 和 job,transformation 完成针对数据的基础转换,job 则完成整个工作流的控制。 Kettle 中文名称叫水壶,该项目的主程序…...
【C/C++】程序的内存开辟
在C/C语言中,不同的类型开辟的空间区域都是不一样的. 这节我们就简单了解下开辟不同的类型内存所存放的区域在哪里. 文章目录栈区(stack)堆区(heap)数据段(静态区)常量存储区内存开辟布局图栈区…...
全网最完整,接口测试总结彻底打通接口自动化大门,看这篇就够了......
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 所谓接口࿰…...
28-flume和kafka为什么要结合使用
一:flume和kafka为什么要结合使用 首先:Flume 和 Kafka 都是用于处理大量数据的工具,但它们的设计目的不同。Flume 是一个可靠地收集、聚合和移动大量日志和事件数据的工具,而Kafka则是一个高吞吐量的分布式消息队列,…...
STM32外设-定时器详解
0. 概述 本文针对STM32F1系列,主要讲解了其中的8个定时器的原理和功能 1. 定时器分类 STM32F1 系列中,除了互联型的产品,共有 8 个定时器,分为基本定时器,通用定时器和高级定时器基本定时器 TIM6 和 TIM7 是一个 16 位…...
史上最详细的改良顺序表讲解,看完不会你打我
目录 0.什么是顺序表 1.顺序表里结构体的定义 2.顺序表的初始化 3.顺序表的输入 4.增加顺序表的长度 5.1顺序表的元素查找(按位查找) 5.2顺序表的元素查找(按值查找)在顺序表进行按值查找,大概只能通过遍历的方…...
【Unity入门】资源包导入和导出
【Unity入门】资源包导入和导出 大家好,我是Lampard~~ 欢迎来到Unity入门系列博客,所学知识来自B站阿发老师~感谢 (1)资源目录 Unity的资源(模型,场景,脚本)等都保存在Assert目录下&…...
python条件语句与循环语句
目录 一、条件语句 1.1if 二、循环语句 2.1while 2.2for循环 2.3break和continue 三、test和总结 一、条件语句 1.1if Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块。 Python程序语言指定: 任…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
