当前位置: 首页 > news >正文

常见排序算法(C语言实现)

文章目录

    • 排序介绍
    • 插入排序
      • 直接插入排序
      • 希尔排序
    • 选择排序
      • 选择排序
      • 堆排序
    • 交换排序
      • 冒泡排序
      • 快速排序
        • 递归实现
          • Hoare版本
          • 挖坑法
          • 前后指针版本
        • 非递归实现
          • Hoare版本
          • 挖坑法
          • 前后指针版本
        • 快排的优化
          • 三值取中
          • 小区间优化
    • 归并排序
      • 递归实现
      • 非递归实现
    • 计数排序
    • 排序算法复杂度及稳定性分析
      • 不同算法的运行效率

排序介绍

  排序就是使一串记录按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
  在待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的。比如A = B,在原序列中A在B前面,排序后A仍旧在B前面,则是稳定的。
  数据元素全部放在内存中的排序称为内部排序。
  数据元素过多而不能同时存放在内存中,需要根据排序过程的要求不断在内外存之间移动数据的排序称为外部排序。
在这里插入图片描述

插入排序

  基本思想:把待排序的记录按其关键码值的代销逐个插入到一个已经排好的有序序列中,知道所有的记录插入完为止,得到一个新的有序序列。

直接插入排序

  1. 基本介绍:
      在待排序的数组中,我们假设前n-1个元素已经是有序的了,然后将第n各元素逐一进行比较,然后将第n个元素放入合适的位置。
      一个元素集合越接近有序,直接插入算法的时间效率就越高。
  2. 代码:
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;}}
  1. 时间复杂度与空间复杂度
      插入排序的平均时间复杂度也是 O(n2),空间复杂度为常数阶 O(1),具体时间复杂度和数组的有序性也是有关联的。
      插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较N-1次,时间复杂度为 O(N)。最坏的情况是待排序数组是逆序的,此时需要比较次数最多,最坏的情况是 O(n2)。
  2. 动图演示:在这里插入图片描述

希尔排序

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

选择排序

  基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,放在序列的起始位置,直到全部待排序的数据元素排完。

选择排序

  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次,得到一个按排序码从小到大排列的有序序列。
      一个简单的优化就是,反正都要遍历一次,那就可以找出最大最小的值,分别放到结尾和开头,这样能够提高一些效率。
  2. 代码:
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--;}
}
  1. 时间复杂度与空间复杂度:
      直接选择排序的时间复杂度为 O(n2) ,所以当记录占用字节数较多时,通常比直接插入排序的执行速度快些;
      对于空间复杂度来说,简单选择排序仅需一个存储空间用于记录交换的暂存单元,即空间复杂度为 O(1);
      由于在直接选择排序中存在着不相邻元素之间的互换,因此,直接选择排序是一种不稳定的排序方法。
  2. 图示:
    在这里插入图片描述

堆排序

  1. 基本介绍:
      堆排序是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。它通过堆来进行数据选择。排升序要建大堆,降序要建小堆。
  2. 代码:
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;}
}
  1. 时间复杂度与空间复杂度:
      使用堆排序最坏的情况就是一直需要交换结点,则需要循环h - 1次(h为树的高度)。而h = log(N+1)(N为树的总结点数),则排序的时间复杂度为O(logN)。但是在进行堆排序之前需要先建堆,建堆的时间复杂度为O(N)。
      对于空间复杂度来说,堆排序仅需几个存储空间用于记录一些下标位置,所以空间复杂度为 O(1);
  2. 图示:
    在这里插入图片描述

交换排序

  所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排
序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

冒泡排序

  1. 基本介绍:
      冒泡排序是一个很形象的形容,因为它排序就像冒泡一样,元素是一个一个冒出来的。从第一个元素开始,两两比较,根据升序还是降序,将大的或小的元素向后移。
      如果一次遍历完了,都没有发生交换,就说明遍历的序列是有序的,可以直接跳出。这样可以提高一点效率,但是效率还是比不上其他排序算法。
  2. 代码:
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;}
}
  1. 时间复杂度与空间复杂度:

  2. 图示:
    在这里插入图片描述

快速排序

  快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想是:任取待排元素序列中的某元素作为其基准值(往往是取第一个元素或者最后一个元素),按照该排序码将待排序集合分为左右两个子序列,左子序列中的所有元素均小于基准值,右子序列中的所有元素均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

递归实现

Hoare版本
  1. 基本介绍:
      选出一个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值应该取的是最后一个元素。
  2. 代码:
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);
}
  1. 时间复杂度与空间复杂度:
      对于快速排序来说,比较的次数是固定的,不会超过O(n),那么划分的次数就非常重要了。如果初始序列是有序的,那么此时的排序过程就非常的像冒泡排序,时间复杂度为O(n),则最差的情况时间复杂度为O(n2)。
      如果每次key值都在中间,那么就有点像是二分法,则时间复杂度为O(logn),此时的时间复杂度就是O(n*logn)了。
      因为使用了递归,所以在执行过程中需要在栈中保存相关的信息,需要的空间和递归次数有关,递归与划分的次数有关系,也就是最好是O(logn),最差是O(n)。
  2. 图示:
    在这里插入图片描述
挖坑法
  1. 基本介绍:
      挖坑法是基于Hoare的,他通过挖坑的方法避免了R和L相遇时的值一定小于key值的问题,因为不是所有人都能理解为什么R和L相遇时的值是小于key值的。但是像Hoare一样,它也需要根据key值的选定来决定是R先走还是L先走。
      挖坑法一开始会取出key值,然后R移动找到比key值小的值,把这里的值填到L的位置上,随后L开始移动,找到比key值大的值填到R的位置上,然后R又开始移动,重复上述步骤,直到R和L相遇,最后把key值填入。
  2. 代码:
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);
}
  1. 时间复杂度与空间复杂度:
      核心思想并没有什么变化,换汤不换药,所以时间复杂度与Hoare版本是一样的。
  2. 图示:
    在这里插入图片描述
前后指针版本
  1. 基本介绍:
      这个是Hoare的一种变形,还是取一个key值,然后取prev和cur分别指向第一个元素和第二个元素,然后cur向后移动,遇到比key小的值,cur的值就和prev的值交换,遇到比key大的值cur就继续走。这样prev就两种情况与cur在同一位置,或者是停留在值比key值大的位置上,最后cur走到end后,就把prev与key交换,这样也就完成了左右子序列区分的任务。
      这是一种Hoare的变形,过程不容易理解,但是代码容易实现。
  2. 代码:
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);
}
  1. 时间复杂度与空间复杂度:
      时间复杂度与空间复杂度仍旧与Hoare版本的一样。
  2. 图示:
    在这里插入图片描述

非递归实现

  首先我们要知道一个点,每次递归都会开辟一次栈帧空间,而栈帧空间有一个特点就是先开辟的空间最后销毁,但是这也造成了一个问题,如果递归层度过深就会栈溢出。但是快排又依赖于这一先入栈后销毁的特性完成排序。所以我们要实现非递归的快排就需要实现这一特性,而在我们的数据结构中恰好有一个栈的数据结构是具备这种特性的,所以如果我们要非递归实现快排,就要使用栈这个数据结构。

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);}
}

归并排序

递归实现

  1. 基本介绍:
      归并排序是建立在归并操作上的一种有效的排序算法,它采用了分治的思想。将已经有序的子序列合并,得到完全有序的序列。即先使每个子序列有序,然后合并成一个有序的序列。将两个有序表合并成一个有序表叫做二路归并。归并排序需要先分解,在合并。
    在这里插入图片描述

  2. 代码:

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;
}
  1. 时间复杂度与空间复杂度:
      归并排序有点类似二叉树结构,高度O(logn),每层循环n次,所以时间复杂度O(n*logn);
      归并排序额外开辟了n个空间加上递归的logn,所以空间复杂度为O(n+logn),但是logn是可以忽略掉的,最后的复杂度为O(n)。
  2. 图示:
    在这里插入图片描述

非递归实现

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

  2. 代码:

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;
}

计数排序

  1. 基本介绍:
      计数排序又称为鸽巢排序,是对哈希直接定址法的变形应用,是一种非比较排序。它先统计相同元素出现的次数,然后根据统计的结果将序列回收到原来的序列中。
      计数排序适用于数据范围集中的序列,此时效率很高,但是适用的范围及场景受到限制。
  2. 代码:
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);
}
  1. 时间复杂度与空间复杂度:
      它的时间复杂度和空间复杂度都是由它自身元素的区间跨度决定的,时间复杂度是O(MAX(n,范围)),空间复杂度是O(范围)。
  2. 图示:
    在这里插入图片描述

排序算法复杂度及稳定性分析

排序算法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序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的小区物业管理系统【设计+论文+源码】

摘 要随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于小区物业管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了小区物业管理系统&#xff0c;它彻底改变了过去…...

Elasticsearch 学习+SpringBoot实战教程(三)

需要学习基础的可参照这两文章 Elasticsearch 学习SpringBoot实战教程&#xff08;一&#xff09; Elasticsearch 学习SpringBoot实战教程&#xff08;一&#xff09;_桂亭亭的博客-CSDN博客 Elasticsearch 学习SpringBoot实战教程&#xff08;二&#xff09; Elasticsearch …...

try-with-resource

try-with-resource是Java 7中引入的新特性&#xff0c;它可以方便地管理资源&#xff0c;自动关闭资源&#xff0c;从而避免了资源泄漏的问题。 作用 使用try-with-resource语句可以简化代码&#xff0c;避免了手动关闭资源的繁琐操作&#xff0c;同时还可以保证资源的正确关闭…...

leetcode148_排序链表的3种解法

1. 题目2. 解答 2.1. 解法12.2. 解法22.3. 解法3 1. 题目 给你链表的头结点head&#xff0c;请将其按升序排列并返回排序后的链表。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullp…...

使用stm32实现电机的PID控制

使用stm32实现电机的PID控制 PID控制应该算是非常古老而且应用非常广泛的控制算法了&#xff0c;小到热水壶温度控制&#xff0c;大到控制无人机的飞行姿态和飞行速度等等。在电机控制中&#xff0c;PID算法用的尤为常见。 文章目录使用stm32实现电机的PID控制一、位置式PID1.计…...

数学原理—嵌入矩阵

目录 1.嵌入矩阵的基本作用 2.嵌入矩阵的数学解释 3.嵌入矩阵在联合分布适应中的数学推导主要包括以下几个步骤 4.在JDA中&#xff0c;怎么得到嵌入矩阵 5.联合分布自适应中如何得到嵌入矩阵 &#xff08;另一种解释&#xff09; 1.嵌入矩阵的基本作用 在机器学习中&a…...

English Learning - L2 语音作业打卡 辅音翘舌音 [ʃ] [ʒ] 空气摩擦音 [h] Day31 2023.3.23 周四

English Learning - L2 语音作业打卡 辅音翘舌音 [ʃ] [ʒ] 空气摩擦音 [h] Day31 2023.3.23 周四&#x1f48c;发音小贴士&#xff1a;&#x1f48c;当日目标音发音规则/技巧:翘舌音 [ʃ] [ʒ]空气摩擦音 [h]&#x1f36d; Part 1【热身练习】&#x1f36d; Part2【练习内容】…...

记录springboot+vue+fastdfs实现简易的文件(上传、下载、删除、预览)操作

前言说明&#xff1a;springboot vue FastDFS实现文件上传&#xff08;支持预览&#xff09;升级版 FASTDFS部分 FASTDFS安装过程&#xff1a;基于centos 7安装FastDFS文件服务器 SpringBoot部分 springboot源码实现 package com.core.doc.controller;import com.baomid…...

Java中循环使用Stream应用场景

在JAVA中&#xff0c;涉及到对数组、Collection等集合类中的元素进行操作的时候&#xff0c;通常会通过循环的方式进行逐个处理&#xff0c;或者使用Stream的方式进行处理。例如&#xff0c;现在有这么一个需求&#xff1a;从给定句子中返回单词长度大于5的单词列表&#xff0c…...

中国蚁剑AntSword实战

中国蚁剑AntSword实战1.基本使用方法2.绕过安全狗连接3.请求包修改UA特征伪造RSA流量加密4.插件使用1.基本使用方法 打开蚂蚁宝剑&#xff0c;右键添加数据&#xff1a; 输入已经上传马的路径和连接密码&#xff1a; 测试连接&#xff0c;连接成功&#xff01; GetShell了&…...

C++ 直接初始化和拷贝初始化

首先我们介绍直接初始化&#xff1a;编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。文字描述可能会让你们云里雾里&#xff0c;那我们直接看代码&#xff1a; //先设计这样的一个类 class A{ public:A(){ cout << "A()" << endl; }A…...

数据迁移工具

1.Kettle Kettle是一款国外开源的ETL工具,纯Java编写,绿色无需安装,数据抽取高效稳定 (数据迁移工具)。 Kettle 中有两种脚本文件,transformation 和 job,transformation 完成针对数据的基础转换,job 则完成整个工作流的控制。 Kettle 中文名称叫水壶,该项目的主程序…...

【C/C++】程序的内存开辟

在C/C语言中&#xff0c;不同的类型开辟的空间区域都是不一样的. 这节我们就简单了解下开辟不同的类型内存所存放的区域在哪里. 文章目录栈区&#xff08;stack&#xff09;堆区&#xff08;heap&#xff09;数据段&#xff08;静态区&#xff09;常量存储区内存开辟布局图栈区…...

全网最完整,接口测试总结彻底打通接口自动化大门,看这篇就够了......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 所谓接口&#xff0…...

28-flume和kafka为什么要结合使用

一&#xff1a;flume和kafka为什么要结合使用 首先&#xff1a;Flume 和 Kafka 都是用于处理大量数据的工具&#xff0c;但它们的设计目的不同。Flume 是一个可靠地收集、聚合和移动大量日志和事件数据的工具&#xff0c;而Kafka则是一个高吞吐量的分布式消息队列&#xff0c;…...

STM32外设-定时器详解

0. 概述 本文针对STM32F1系列&#xff0c;主要讲解了其中的8个定时器的原理和功能 1. 定时器分类 STM32F1 系列中&#xff0c;除了互联型的产品&#xff0c;共有 8 个定时器&#xff0c;分为基本定时器&#xff0c;通用定时器和高级定时器基本定时器 TIM6 和 TIM7 是一个 16 位…...

史上最详细的改良顺序表讲解,看完不会你打我

目录 0.什么是顺序表 1.顺序表里结构体的定义 2.顺序表的初始化 3.顺序表的输入 4.增加顺序表的长度 5.1顺序表的元素查找&#xff08;按位查找&#xff09; 5.2顺序表的元素查找&#xff08;按值查找&#xff09;在顺序表进行按值查找&#xff0c;大概只能通过遍历的方…...

【Unity入门】资源包导入和导出

【Unity入门】资源包导入和导出 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;1&#xff09;资源目录 Unity的资源&#xff08;模型&#xff0c;场景&#xff0c;脚本&#xff09;等都保存在Assert目录下&…...

python条件语句与循环语句

目录 一、条件语句 1.1if 二、循环语句 2.1while 2.2for循环 2.3break和continue 三、test和总结 一、条件语句 1.1if Python条件语句是通过一条或多条语句的执行结果&#xff08;True或者False&#xff09;来决定执行的代码块。 Python程序语言指定&#xff1a; 任…...

【leetcode】链表(2)

目录 1. 环形链表 解题思路 2. 环形链表 II 解题思路 3. 删除排序链表中的重复元素 解题思路 4. 删除排序链表中的重复元素 II 解题思路 5. 移除链表元素 解题思路 6. 链表的中间结点 解题思路 1. 环形链表 OJ&#xff1a;环形链表 给你一个链表的头节点 head &am…...

使用Vue+vue-router+路由守卫实现路由鉴权功能实战

目录 一、本节介绍和上节回顾 1. 上节介绍 2. Vue SpringBoot前后端分离项目实战的目录 3. 本小节介绍 二、Vue-router改造以及路由鉴权 1. 路由数据的拆分 2. 路由守卫 三、404错误页的实现 1. 创建全局css样式 2. 全局样式引入 3. 404页面的开发 4. el-button的…...

多线程(三):Thread 类的基本属性

上一个篇章浅浅了解了一下 线程的概念&#xff0c;进程与线程的区别&#xff0c;如何实现多线程编程。 而且上一章提到一个重要的面试点&#xff1a; start 方法和 run 方法的区别。 start 方法是从系统那里创建一个新的线程&#xff0c;这个线程会自动调用内部的run 方法&…...

蓝桥杯嵌入式第六课--串口收发

前言串口作为一个考试中考察频率较高的考点&#xff0c;其套路比较固定&#xff0c;因此值得我们仔细把握。本节课主要着眼于快速配置实现 串口收发与串口的中断。CubeMX配置选择串口2配置异步收发模式基本参数设置&#xff08;波特率、校验位等等&#xff09;开启串口收发中断…...

蓝桥杯冲刺 - Lastweek - 你离省一仅剩一步之遥!!!(掌握【DP】冲刺国赛)

文章目录&#x1f4ac;前言&#x1f3af;week3&#x1f332;day10-1背包完全背包多重背包多重背包 II分组背包&#x1f332;day2数字三角形 - 线性DP1015. 摘花生 - 数字三角形&#x1f332;day3最长上升子序列 - 线性DP1017. 怪盗基德的滑翔翼 - LIS1014.登山 - LIS最长公共子…...

C++ map与set的学习

1. 关联式容器在初阶阶段&#xff0c;我们已经接触过STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、forward_list(C11)等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面存储的是元素本身。关联式容器也…...

【C语言初阶】函数

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;函数是什么&#xff1f;&#x1f337;函数的分类&#x1f33a;库函数&#x1f33a;自定义函数&#x1f337;函数的参数&#x1f337;函数的调用&#x1f337;函数的嵌套调用和链式访问&#x1f33a;嵌套调用&a…...

CentOS 7安装redis6.2.6(包括服务开机自启和开放端口)

CentOS 7安装redis6.2.61. 官网下载redis文件2. 校验安装依赖2.1 安装系统默认版本gcc2.2 升级gcc版本3. 解压编译安装4. 修改配置redis.conf4.2 设置密码4.3 绑定ip&#xff08;可选&#xff09;5. 启动redis服务并测试5.2 测试安装是否成功5.3 redis开机自启配置6.开放防火墙…...

基于注解的自动装配~

Autowired&#xff1a;实现自动装配功能的注解 Autowired注解能够标识的位置&#xff1a; 标识在成员变量上&#xff0c;此时不需要设置成员变量的set方法标识在成员变量对应的set方法上标识在为当前成员变量赋值的有参构造上使用注解进行自动装配&#xff0c;只要在其成员变量…...

【深度学习】【分布式训练】Collective通信操作及Pytorch示例

相关博客 【深度学习】【分布式训练】Collective通信操作及Pytorch示例 【自然语言处理】【大模型】大语言模型BLOOM推理工具测试 【自然语言处理】【大模型】GLM-130B&#xff1a;一个开源双语预训练语言模型 【自然语言处理】【大模型】用于大型Transformer的8-bit矩阵乘法介…...

独立创建网站/公司官网搭建

问题&#xff1a;安装了Git-1.9.4-preview和TortoiseGit等工具后&#xff0c;Git服务器开通了账号和密码并配置了邮箱。克隆了服务器代码到本地&#xff0c;按需求进行代码开发。提交本地代码到服务器时出现错误。具体如下&#xff1a; git push 提交代码到远程服务器是出现错误…...

桂林北站疫情防控最新消息/seo排名优化表格工具

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2022高压电工复审题库为高压电工模拟考试题库考前必练习题目&#xff01;2022高压电工理论题库及在线模拟考试根据高压电工新版教材大纲编写。高压电工模拟考试试题随时根据安全生产模拟考试一点通上查找答案。 1、【…...

wordpress网购插件/阿里域名注册官网

概述 JSON是一种轻量化的数据传输格式&#xff0c;在各种场景都有运用。比如在ajax中&#xff0c;服务端的数据一般通过JSON字符串的格式传输给前端&#xff0c;前端ajax引擎自动将JSON字符串转化为JS对象&#xff08;需要将ajax的返回内容格式设置为"json"&#xff…...

网站后台管理开发/qianhu微建站

1.请阅读并运行AboutException.java示例&#xff0c;然后通过后面的几页PPT了解Java中实现异常处理的基础知识。 &#xff08;1&#xff09;import javax.swing.*; class AboutException { public static void main(String[] a) { int i1, j0, k; ki/j; try { …...

公众号开发主要做什么/搜索引擎优化行业

0.目录 1.前言 2.使用方向键来实现光标左右移动 3.按两下ESC键退出程序 4.移动光标到行首 5.移动光标到行尾 6.总代码 1.前言 之前已经写过一篇文章了&#xff1a;实现一个简单的行编辑器 实现的功能有&#xff1a;1.按下大小写字母或者数字的时候&#xff0c;显示在屏幕上 2.可…...

dede网站建设的个人总结/搜索引擎排名大全

nana 安全牛 漏洞管理依然是大多数安全计划的重要组成部分&#xff0c;全球绝大多数公司企业都认同这一点。 在安全公司Tripwire最近的一份网络健康状态调查中&#xff0c;80%的受访者称自家企业有漏洞扫描计划。约60%的受访者每天或每周进行一次扫描&#xff0c;40%的受访者…...