【算法】常见排序算法(插入排序、选择排序、交换排序和归并排序)

文章目录

  • 前言
  • 一、排序概念及常见排序算法框图
    • 1.排序概念
    • 2.常见排序算法框图
  • 二、实现比较排序算法
    • 1.插入排序
      • 1.1 直接插入排序
      • 1.2 希尔排序
    • 2.选择排序
      • 2.1 直接选择排序
      • 2.2 堆排序
    • 3.交换排序
      • 3.1 冒泡排序
      • 3.2 快速排序
        • 3.2.1 hoare版本
        • 3.2.2 挖坑法
        • 3.2.3 lomuto前后指针
      • 3.3 快速排序的复杂度讨论及其优化
        • 3.3.1 快速排序的复杂度
        • 3.3.2 随机选取基准值 和 三数取中选取基准值
        • 3.3.3 三路划分
    • 4.归并排序
      • 4.1 归并排序的思想及实现
  • 三、比较排序算法总结
    • 1.复杂度及稳定性分析
    • 2.测试代码:比较排序性能对比
  • 四、实现非比较排序算法
    • 1.计数排序


前言

1.插入排序(直接插入排序 和 希尔排序)、选择排序(直接选择排序 和 堆排序)、交换排序(冒泡排序 和 快速排序)和归并排序
2.重点内容:快速排序(三个版本的算法思路及代码实现 以及 快速排序的复杂度讨论及其优化方法:随机选取基准值 、三数取中选取基准值 和 三路划分)


一、排序概念及常见排序算法框图

1.排序概念

所谓排序,就是使⼀串记录,按照其中的某个或某些关键数据的大小,递增或递减的排列起来的操作。

2.常见排序算法框图

在这里插入图片描述
非比较排序中暂只介绍计数排序

二、实现比较排序算法

1.插入排序

1.1 直接插入排序

直接插入排序是⼀种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到⼀个已经排好序的有序序列中,直到所有的记录插入完为止,得到⼀个新的有序序列 。
在这里插入图片描述
在这里插入图片描述

void direct_insert_sort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];end--;}elsebreak;}arr[end + 1] = tmp;}
}

在这里插入图片描述
直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度(按最坏情况):O(N^2)
  3. 空间复杂度:O(1)

1.2 希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap(通常 gap = n/2 或 n/3+1,n为数组元素个数),把待排序数据分成gap组,所有的距离相等的数据分在同⼀组内,并对每⼀组内的数据进行插入排序,然后 gap = n/2(或 n/3+1)得到下一个整数,再将数组分成gap组,对每⼀组内的数据排序,当gap=1时,就相当于直接插入排序。
直接插入排序算法的缺陷是它的时间复杂度在最优情况和最差情况下相差极大。希尔排序在直接插入排序算法的基础上进行改进,通过几轮分组排序(轮次越后面的排序,数组会越有序,效率会更高)使数组在最后一轮直接插入排序时接近有序,最后一轮直接插入排序效率会接近O(N)。
综合来说,希尔排序的效率肯定是要高于直接插入排序算法的。

在这里插入图片描述
在这里插入图片描述

void shell_sort(int* arr, int n)
{int gap = n;while(gap>1){ gap = gap / 2;for (int i = 0; i < n - gap; i++){int end = i;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}elsebreak;}arr[end + gap] = tmp;}}
}

希尔排序的时间复杂度取决于增量序列的选择(gap划分方法的选择),不同增量序列会导致不同的性能表现:

(1)原始希尔序列 n / 2 , n / 4 , … , 1 n/2, n/4, \dots, 1 n/2,n/4,,1

  • 最坏情况 O ( n 2 ) O(n^2) O(n2)(与直接插入排序相同)
  • 平均情况:实际应用中约为 O ( n 1.5 ) O(n^{1.5}) O(n1.5),但缺乏严格证明。

(2)Hibbard 序列 1 , 3 , 7 , 15 , … , 2 k − 1 1, 3, 7, 15, \dots, 2^k-1 1,3,7,15,,2k1

  • 最坏情况 O ( n 3 / 2 ) O(n^{3/2}) O(n3/2)
  • 平均情况:接近 O ( n 5 / 4 ) ) O(n^{5/4})) O(n5/4))

(3)Sedgewick 序列 1 , 5 , 19 , 41 , … 1, 5, 19, 41, \dots 1,5,19,41,

  • 最坏情况 O ( n 4 / 3 ) O(n^{4/3}) O(n4/3)
  • 平均情况:接近 O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n),是已知最优增量序列之一。
增量序列最坏时间复杂度平均时间复杂度空间复杂度
原始序列 O ( n 2 ) O(n^2) O(n2) O ( n 1.5 ) O(n^{1.5}) O(n1.5)(经验) O ( 1 ) O(1) O(1)
Hibbard 序列 O ( n 3 / 2 ) O(n^{3/2}) O(n3/2) O ( n 5 / 4 ) O(n^{5/4}) O(n5/4) O ( 1 ) O(1) O(1)
Sedgewick 序列 O ( n 4 / 3 ) O(n^{4/3}) O(n4/3) O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n) O ( 1 ) O(1) O(1)

实际应用中,希尔排序通常比 O ( n 2 ) O(n^2) O(n2) 的算法(如冒泡排序)快得多,但不如 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的算法(如快速排序、归并排序)。

2.选择排序

2.1 直接选择排序

每⼀次从待排序的数据元素中选出最小(或最大)的⼀个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
在这里插入图片描述
优化版本:每⼀次从待排序的数据元素中选出一组最小值和最大值,分别存放在序列的起始位置和末尾位置,一次可以确定两个元素的位置,直到全部待排序的数据元素排完 。
在这里插入图片描述
在这里插入图片描述

void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void direct_select_sort(int* arr, int n)
{int left = 0;int right = n - 1;while (left < right){int min = left;int max = left;int cur = left + 1;while (cur <= right){if (arr[cur] > arr[max])max = cur;if (arr[cur] < arr[min])min = cur;cur++;}swap(&arr[left], &arr[min]);if (left == max)max = min;swap(&arr[right], &arr[max]);left++;right--;}
}

时间复杂度计算:
第一次遍历n个元素的数组,确认一组最大最小值;
第二次遍历n-2个元素的数组,确认一组最大最小值;
……
最后一次遍历2(或3)个元素的数组。
所以计算式子为:2+4+……+(n-2)+ n = n ^ 2 + n
最终得到时间复杂度为:O(N^2)

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度: O(N^2)
  3. 空间复杂度: O(1)

2.2 堆排序

堆是一棵完全二叉树,使用顺序结构的数组来存储数据。堆中某个结点的值总是不大于(或不小于)其父结点的值,将根结点最大的堆叫做大堆或大根堆,根结点最小的堆叫做小堆或小根堆。
在这里插入图片描述
在这里插入图片描述

核心思想总结
利用堆的性质高效地找到最大值(或最小值)并逐步完成排序。 其核心在于:

  1. 构建堆:将无序数组转化为堆结构。
  2. 反复提取极值:每次提取堆顶元素后调整堆,最终得到有序序列。
#include <queue>
#include <vector>
using namespace std;void heap_sort(int* arr, int n)
{priority_queue<int,vector<int>,greater<int>> hp; // priority_queue是C++的STL库中自带的堆结构,直接调用priority_queue生成一个小堆。// C语言要使用堆排序还需要自己先实现一个堆结构for (int i = 0; i < n; i++){hp.push(arr[i]); // 先把数组中所有元素插入小堆中(将无序数组转化为堆结构)} int i = 0;while (!hp.empty()){arr[i++] = hp.top(); // 每次提取堆顶元素后调整堆,最终得到有序序列hp.pop();} 
}

堆排序的特性总结:

  1. 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)
  2. 空间复杂度: O ( 1 ) O(1) O(1)

3.交换排序

3.1 冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较,满足条件进行交换;一趟冒泡排序确定一个数的位置。
在这里插入图片描述
在这里插入图片描述

冒泡排序改良:
(1)设置一个变量,让它可以判断一趟冒泡排序中有无进行数组元素交换。
(2)当一趟冒泡排序中无任何一次数组元素交换,说明数组中元素已经有序,这时可提前结束循环。

void bubble_sort(int* arr, int n)
{for (int i = 0; i < n; i++){int exchange = 0; // 设置变量exchange,用来判断一趟冒泡排序中有无进行数组元素交换for (int j = 0; j < n - i - 1; j++){if (arr[j] > arr[j + 1]){exchange = 1;swap(&arr[j], &arr[j + 1]);}} if(exchange == 0){break;}}
}

时间复杂度计算(按最坏情况):
每趟冒泡排序确定一个元素的位置,所以n个元素要进行n-1次冒泡排序。
第一趟冒泡排序要进行n-1次比较,确认一个元素的位置;
第二趟冒泡排序要进行n-2次比较(比上一趟少进行一次比较,因为确定了位置的元素无需再进行比较),再确认一个元素的位置;
……
依此类推,第n-1趟冒泡排序只需要进行1次比较
所以计算式子为:1+2+……+(n-2)+(n-1)=(n ^ 2)/ 2 - n/2
最终得到时间复杂度为:O(N^2)

冒泡排序的特性总结:

  1. 时间复杂度: O(N^2)
  2. 空间复杂度: O(1)

3.2 快速排序

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

3.2.1 hoare版本

算法思路 :

  1. 确定基准值(此处直接选择最左元素为基准值),用key标记其下标;创建left和right标记,left起始指向key+1位置,right起始指向末尾位置
  2. 从右向左找出比基准值小的数据,用下标right标记;从左向右找出比基准值大的数据,用下标left标记;然后right和left指向的数据互换,进入下次循环;期间一旦left>right,循环直接终止
  3. 结束循环后,right所在的位置就是基准值的正确位置,把基准值交换到right位置
  4. 以基准值位置划分数组,左边的数组<=基准值,右边的数组>=基准值
  5. 接着对左边和右边数组进行以上同样的操作,不断划分数组,直到无法再进行划分
    在这里插入图片描述
void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void hoare_quicksort(int* arr, int begin, int end)
{int key = begin;int left = begin + 1;int right = end;while (left <= right){while (left <= right && arr[right] >= arr[key])right--;while (left <= right && arr[left] <= arr[key])left++;if (left <= right){swap(&arr[left], &arr[right]);right--;left++;}}swap(&arr[key], &arr[right]);key = right; // [begin,key-1] key [key+1,end]if (begin < key - 1)hoare_quicksort(arr, begin, key - 1);if (key + 1 < end)hoare_quicksort(arr, key + 1, end);
}
3.2.2 挖坑法

算法思路 :

  1. 确定基准值(此处直接选择最左元素为基准值),创建变量key保存基准值;创建left和right标记,left起始指向开头位置(起始以left指向位置为"坑"位),right起始指向末尾位置
  2. 首先移动right标记从右向左找出比基准值小的数据,找到后立即放入左边"坑"位中,当前位置变为新的"坑"位,然后移动left从左向右找出比基准值大的数据,找到后立即放入右边"坑"位中,当前位置变为新的"坑",循环进行此过程,过程中一旦left和right相遇,循环立即终止。
  3. 结束循环后,当前的"坑"位就是基准值的正确位置,将最开始用变量key存储的基准值放入当前的"坑"位中
  4. 以基准值位置划分数组,左边的数组<=基准值,右边的数组>=基准值
  5. 接着对左边和右边数组进行以上同样的操作,不断划分数组,直到无法再进行划分
    在这里插入图片描述
    在这里插入图片描述
void digholes_quicksort(int* arr, int begin, int end)
{int key = arr[begin];int left = begin;int right = end;while (left < right){while (left < right && arr[right] >= key)right--;if (left == right)break;elsearr[left] = arr[right];while (left < right && arr[left] <= key)left++;if (left == right)break;elsearr[right] = arr[left];}arr[right] = key; // [begin,right-1] right [right+1,end]if (begin < right - 1)digholes_quicksort(arr, begin, right - 1);if (right + 1 < end)digholes_quicksort(arr, right + 1, end);
}
3.2.3 lomuto前后指针

算法思路 :

  1. 确定基准值(此处直接选择最左元素为基准值),用key标记其下标;创建prev和cur标记,prev起始指向开头位置,cur起始指向prev+1位置
  2. 首先移动cur标记从左向右找出比基准值小的数据,找到之后,prev先后移一位,然后将cur和prev指向的数据交换,紧接着cur后移一位,继续循环进行此过程,直到cur越界,循环才终止。
  3. 结束循环后,prev所在的位置就是基准值的正确位置,把基准值交换到prev位置
  4. 以基准值位置划分数组,左边的数组<=基准值,右边的数组>=基准值
  5. 接着对左边和右边数组进行以上同样的操作,不断划分数组,直到无法再进行划分
    在这里插入图片描述
    在这里插入图片描述
void lomuto_quicksort(int* arr, int begin, int end)
{int key = begin;int prev = begin;int cur = prev + 1;while (cur <= end){while (cur <= end && arr[cur] >= arr[key])cur++;if (cur <= end){prev++;swap(&arr[cur], &arr[prev]);cur++;}}swap(&arr[key], &arr[prev]);key = prev; // [begin,key-1] key [key+1,end]if (begin < key - 1)lomuto_quicksort(arr, begin, key - 1);if (key + 1 < end)lomuto_quicksort(arr, key + 1, end);
}

3.3 快速排序的复杂度讨论及其优化

3.3.1 快速排序的复杂度

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

时间复杂度分析:

  1. 最好情况

    • 每次划分都能将数组均匀分成两部分(每次选的基准值都接近中位数)。
    • 递归深度为 log ⁡ n \log n logn,每层需遍历 n n n 个元素(实际就第一层需遍历n个元素,每层递归都会确定一些元素的位置,递归层数越深,需遍历的元素越少,但统一按每层遍历n个元素计算比较方便)。
    • 时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)
  2. 平均情况

    • 假设基准值通过随机选择或三数取中法选出,划分的均衡性服从概率分布。
    • 数学期望计算后仍为 O ( n log ⁡ n ) O(n \log n) O(nlogn)
    • 时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)
  3. 最坏情况

    • 每次划分极不均衡(如数组已有序且固定选第一个元素为基准值)。
    • 递归深度为 n n n,总操作次数为 n + ( n − 1 ) + ⋯ + 1 = n ( n + 1 ) 2 n + (n-1) + \dots + 1 = \frac{n(n+1)}{2} n+(n1)++1=2n(n+1)
    • 时间复杂度 O ( n 2 ) O(n^2) O(n2)

空间复杂度分析:

  1. 最好情况

    • 递归深度为 log ⁡ n \log n logn,栈空间占用与递归深度成正比。
    • 空间复杂度 O ( log ⁡ n ) O(\log n) O(logn)
  2. 平均情况

    • 递归深度仍为 log ⁡ n \log n logn(通过随机化选择基准值减少最坏概率)。
    • 空间复杂度 O ( log ⁡ n ) O(\log n) O(logn)
  3. 最坏情况

    • 递归深度为 n n n(如每次仅处理一个元素)。
    • 空间复杂度 O ( n ) O(n) O(n)
  • 优化策略随机选择或三数取中法选择基准值,可有效降低与最坏情况1类似场景(数组有序或部分有序)的时间和空间复杂度 ;三路划分法 可有效降低与最坏情况2类似场景(数组存在大量重复数据)的时间和空间复杂度。
  • 空间占用虽为原地排序,但需注意递归栈的隐式空间消耗。
3.3.2 随机选取基准值 和 三数取中选取基准值

决定快排性能的关键点是每次单趟排序后,基准值对数组的分割,如果每次选的基准值都接近数组中的中位数,那么快排的递归树就是颗均匀的满二叉树,性能最佳。但是实践中虽然不可能每次都是二分居中,但是性能也还是可控的。但是如果出现每次选到最小值/最大值,划分为0个和N-1的子问题时,时间复杂度为O(N^2),数组有序并且每次固定选第一个元素为基准值前不进行任何干预,就会出现这样的问题。
我们仍然可以采用第一个元素作为基准值的方法,不过在那之前进行随机选取 或 三数取中法选出基准值,再把选出的基准值换到第一个元素,这样可以有效减少因数组有序或部分有序而降低快排效率的情况。

  1. 随机选取基准值
    每次排序时,从当前数组中随机选择一个元素作为基准值,再通过交换操作将其移动到数组的起始位置,最后进行常规的划分操作。
#include <time.h>
#include <stdlib.h>
void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void random_select(int* arr, int begin, int end)
{srand((unsigned)time(NULL));// 随机选择基准值的位置,选择区间在begin和end之间int key = rand() % (end - begin + 1) + begin;//把基准值位置的元素与begin位置元素互换,后续依然可以选第一个元素为基准值划分函数  swap(&arr[key], &arr[begin]);
}
  1. 三数取中选取基准值
    从当前子数组的首、中、尾三个位置取元素,选择三者的中位数作为基准值,通过交换操作将其移动到数组的起始位置,最后进行常规的划分操作。
void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void median_select(int* arr, int begin, int end)
{int mid = begin + ((end - begin) >> 1);//计算数组中间的元素的下标  // 使用三数取中法选择三者的中位数作为基准值并将其换到begin位置 if (arr[mid] < arr[end])//目标: arr[mid] >= arr[end]  {swap(&arr[mid], &arr[end]);}if (arr[begin] > arr[mid])//目标: arr[begin] <= arr[mid]  {swap(&arr[begin], &arr[mid]);}if (arr[begin] < arr[end]) //目标: arr[begin] >= arr[end]  {swap(&arr[begin], &arr[end]);}//此时,arr[mid] >= arr[begin] >= arr[end]  //begin位置上保存这三个位置中间的值,后续依然可以选第一个元素为基准值划分函数  
}
3.3.3 三路划分

快速排序的三路划分是一种优化策略,用于处理数组中存在大量重复元素的情况。 它将数组划分为三个区域:

  • 小于基准值的部分(左段)
  • 等于基准值的部分(中段)
  • 大于基准值的部分(右段)

通过将重复元素集中到中间段,后续递归时只需处理左右两段,避免对重复元素重复操作,从而提升效率。

算法思路(类似hoare的左右指针和lomuto的前后指针的结合) :

  1. 确定基准值(直接选择最左元素为基准值),创建变量key保存基准值;创建 left、right 和 cur 标记,left起始指向开头位置,right起始指向末尾位置,cur起始指向left+1位置
  2. cur标记从左向右移动会遇到三种不同的情形,每种情形都会有对应的处理方式:
  • 情形一:cur遇到比基准值小的值后跟left位置交换,换到左边,left++,cur++;
  • 情形二:cur遇到比基准值大的值后跟right位置交换,换到右边,right–;
  • 情形三:cur遇到跟基准值相等的值后,cur++。
  1. 直到cur > right才结束以上过程
  2. 以 left 和 right位置划分数组,左边的数组<基准值,右边的数组>基准值
  3. 接着对左边和右边数组进行以上同样的操作,不断划分数组,直到无法再进行划分
    在这里插入图片描述

4.归并排序

4.1 归并排序的思想及实现

归并排序算法思想:
归并排序(MERGE-SORT)是建立在归并操作上的⼀种有效的排序算法,该算法是采用分治法的⼀个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成⼀个有序表,称为二路归并。

在这里插入图片描述

void merge_sort(int* arr, int begin, int end, int* tmp)
{int mid = begin + (end - begin) / 2;if (begin < mid)merge_sort(arr, begin, mid, tmp);if (mid + 1 < end)merge_sort(arr, mid + 1, end, tmp);int left1 = begin;int right1 = mid;int left2 = mid + 1;int right2 = end;int n = begin;while (left1 <= right1 && left2 <= right2){if (arr[left1] <= arr[left2])tmp[n++] = arr[left1++];elsetmp[n++] = arr[left2++];}while (left1 <= right1){tmp[n++] = arr[left1++];}while (left2 <= right2){tmp[n++] = arr[left2++];}while (begin <= end){arr[begin] = tmp[begin];begin++;}
}

三、比较排序算法总结

1.复杂度及稳定性分析

算法 最好 最坏 平均时间复杂度 空间 稳定 冒泡排序 O ( n ) O ( n 2 ) O ( n 2 ) O ( 1 ) ✓ 直接插入排序 O ( n ) O ( n 2 ) O ( n 2 ) O ( 1 ) ✓ 归并排序 O ( n log ⁡ n ) O ( n log ⁡ n ) O ( n log ⁡ n ) O ( n ) ✓ 直接选择排序 O ( n 2 ) O ( n 2 ) O ( n 2 ) O ( 1 ) ✗ 堆排序 O ( n log ⁡ n ) O ( n log ⁡ n ) O ( n log ⁡ n ) O ( 1 ) ✗ 希尔排序 O ( n log ⁡ n ) O ( n 2 ) O ( n 1.3 ) O ( 1 ) ✗ 快速排序 O ( n log ⁡ n ) O ( n 2 ) O ( n log ⁡ n ) O ( log ⁡ n ) ✗ \begin{array}{|l|c|c|c|c|c|} \hline \text{算法} & \text{最好} & \text{最坏} & \text{平均时间复杂度} & \text{空间} & \text{稳定} \\ \hline 冒泡排序 & O(n) & O(n^2) & O(n^2) & O(1) & ✓ \\ 直接插入排序 & O(n) & O(n^2) & O(n^2) & O(1) & ✓ \\ 归并排序 & O(n \log n) & O(n \log n) & O(n \log n) & O(n) & ✓ \\ 直接选择排序 & O(n^2) & O(n^2) & O(n^2) & O(1) & ✗ \\ 堆排序 & O(n \log n) & O(n \log n) & O(n \log n) & O(1) & ✗ \\ 希尔排序 & O(n \log n) & O(n^2) & O(n^{1.3}) & O(1) & ✗ \\ 快速排序 & O(n \log n) & O(n^2) & O(n \log n) & O(\log n) & ✗ \\ \hline \end{array} 算法冒泡排序直接插入排序归并排序直接选择排序堆排序希尔排序快速排序最好O(n)O(n)O(nlogn)O(n2)O(nlogn)O(nlogn)O(nlogn)最坏O(n2)O(n2)O(nlogn)O(n2)O(nlogn)O(n2)O(n2)平均时间复杂度O(n2)O(n2)O(nlogn)O(n2)O(nlogn)O(n1.3)O(nlogn)空间O(1)O(1)O(n)O(1)O(1)O(1)O(logn)稳定

2.测试代码:比较排序性能对比

用以上各种比较排序算法排序同一组随机生成的数据,测试时间(单位:毫秒)消耗,要在Release版本运行这段代码(含递归的算法会在Debug版本生成更多调试数据,影响算法效率):

#include "sort.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>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* tmp = (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();direct_insert_sort(a1, N);int end1 = clock();int begin2 = clock();shell_sort(a2, N);int end2 = clock();int begin3 = clock();direct_select_sort(a3, N);int end3 = clock();int begin4 = clock();heap_sort(a4, N);int end4 = clock();int begin5 = clock();hoare_quicksort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();merge_sort(a6, 0, N - 1, tmp);int end6 = clock();int begin7 = clock();bubble_sort(a7, N);int end7 = clock();printf("direct_insert_sort:%d\n", end1 - begin1);printf("shell_sort:%d\n", end2 - begin2);printf("direct_select_sort:%d\n", end3 - begin3);printf("heap_sort:%d\n", end4 - begin4);printf("hoare_quicksort:%d\n", end5 - begin5);printf("merge_sort:%d\n", end6 - begin6);printf("bubble_sort:%d\n", end7 - begin7);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);free(tmp);
}int main()
{TestOP();return 0;
}

在这里插入图片描述

四、实现非比较排序算法

1.计数排序

计数排序是对哈希直接定址法的变形应用。 操作步骤:
1)统计相同元素出现次数
2)根据统计的结果将序列回收到原来的序列中

在这里插入图片描述
"找出最大值k,创建长度为k+1的计数数组"的方式有些时候会造成很大空间浪费,可以找出待排序数组中的最大最小值,按范围开空间:
在这里插入图片描述

void count_sort(int* arr, int n)
{int min = arr[0], max = arr[0];for (int i = 1; i < n; i++){if (arr[i] > max)max = arr[i];if (arr[i] < min)min = arr[i];} int range = max - min + 1;int* count = (int*)calloc(range, sizeof(int));if (count == NULL){perror("calloc fail");return;}// 统计次数for (int i = 0; i < n; i++){count[arr[i] - min]++; } // 排序int j = 0;for (int i = 0; i < range; i++){while (count[i]--){arr[j++] = i + min;}}free(count);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/74297.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go语言分布式锁实战:dlock助力构建高并发稳定系统

在构建分布式系统时&#xff0c;一个常见且棘手的问题便是资源竞争和数据一致性问题。分布式锁作为一种常用的解决方案&#xff0c;在多个进程或节点之间协调访问共享资源时显得尤为重要。今天&#xff0c;我们将介绍一款分布式锁库——dlock&#xff0c;并通过详细的使用示例带…

算法方法快速回顾

&#xff08;待修改&#xff09; 目录 1. 双指针2. 滑动窗口理论基础 3. 二分查找3. 二分查找理论基础 4. KMP5. 回溯算法6. 贪心算法7. 动态规划7.1. 01背包7.2. 完全背包7.3. 多重背包 8. 单调栈9. 并查集10. 图论10.1. 广度优先搜索&#xff08;BFS&#xff09;10.2. 深度优…

深度学习:让机器学会“思考”的魔法

文章目录 引言&#xff1a;从“鹦鹉学舌”到“举一反三”一、深度学习是什么&#xff1f;1. 定义&#xff1a;机器的“大脑”2. 核心思想&#xff1a;从数据中“悟”出规律 二、深度学习的“大脑”结构&#xff1a;神经网络1. 神经元&#xff1a;深度学习的基本单元2. 神经网络…

电动自行车/电动工具锂电池PCM方案--SH367003、SH367004、SH79F329

在消费电子系统中&#xff0c;如手机电池包&#xff0c;笔记本电脑电池包等&#xff0c;带有控制IC、功率MOSFETFE管以及其他电子元件的电路系统称为电池充放电保护板Protection Circuit Module &#xff08;PCM&#xff09;&#xff0c;而对于动力电池的电池管理系统&#xff…

补码详细分析

补码引入 举一个生活化的例子 假设由一个挂钟&#xff0c;它只能顺时钟调时间&#xff0c;那么它调时间就分成了一下两种情况 正好顺时针调就能调好 如&#xff1a;时针从5调到9需要逆时针调才能调好 如&#xff1a;时针从10调到7 在上面的情况中1是不用处理的&#xff0c;2…

计算机网络入门:物理层与数据链路层详解

&#x1f310; &#xff08;专业解析 中学生也能懂&#xff01;&#xff09; &#x1f4d6; 前言 计算机网络就像数字世界的“高速公路系统”&#xff0c;而物理层和数据链路层是这条公路的基石。本文用 专业视角 和 生活化比喻 &#xff0c;带你轻松理解这两层的核心原理&a…

哪些视频格式在webview2中播放可以设置成透明的?

在WebView2中&#xff0c;能够播放并设置成透明背景的视频格式主要取决于其支持的编解码器以及视频是否包含alpha通道&#xff08;透明度信息&#xff09;。以下是支持透明背景的视频格式&#xff1a; 支持透明背景的视频格式 1. WebM&#xff08;使用VP9编解码器&#xff09; …

【基于ROS的A*算法实现路径规划】A* | ROS | 路径规划 | Python

### 记录一下使用Python实现ROS平台A*算法路径规划 ### 代码可自取 &#xff1a;Xz/little_projecthttps://gitee.com/Xz_zh/little_project.git 目录 一、思路分析 二、算法实现 三、路径规划实现 一、思路分析 要求使用A*算法实现路径规划&#xff0c;可以将该任务分为三…

2025-03-23 吴恩达机器学习3——多维特征

文章目录 1 多元引入2 矢量化2.1 示例2.2 非矢量化实现2.3 矢量化实现2.4 应用 3 特征缩放3.1 举例3.2 必要性3.3 方法3.3.1 最大最小值缩放&#xff08;Min-Max Scaling&#xff09;3.3.2 均值归一化&#xff08;Mean Normalization&#xff09;3.3.3 Z 分数归一化&#xff08…

正点原子内存管理学习和修改

由于项目需要用到内存管理进行动态申请和释放&#xff0c;今天又重新学习了一下正点原子的内存管理实验&#xff0c;温习了一下内存管理的实质。首先先上正点原子内存管理的源代码&#xff1a; malloc.c文件&#xff1a; #include "./MALLOC/malloc.h"#if !(__ARMC…

时空观测者:俯身拾贝

目录 中华文明时空贝壳集&#xff08;按时间排序&#xff09;1. 良渚玉琮&#xff08;约公元前3300-2300年&#xff09;2. 三星堆青铜神树&#xff08;公元前1200年&#xff09;3. 殷墟甲骨文&#xff08;约公元前14世纪&#xff09;4. 京杭大运河&#xff08;公元前486年始建&…

护网期间监测工作全解析:内容与应对策略

护网期间监测工作全解析&#xff1a;内容与应对策略 一、引言 在数字化浪潮中&#xff0c;网络安全的重要性愈发凸显&#xff0c;护网行动作为保障关键信息基础设施安全的关键举措&#xff0c;备受瞩目。护网期间&#xff0c;监测工作是发现潜在威胁、防范攻击的重要防线。全…

【Centos7搭建Zabbix4.x监控HCL模拟网络设备:zabbix-server搭建及监控基础05

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 5.zabbix监控HCL模拟网络设备 在保证zabbix-server与HCL网络相通的情况下进行如下操作。 5.1创建主机群 配置-主机群-创建主机群 图 19 取名&#xff0c;添加。 图 20 5.2 创建监控…

趣味极简品牌海报艺术贴纸设计圆润边缘无衬线粗体装饰字体 Chunko Bold - Sans Serif Font

Chunko Bold 是一种功能强大的显示字体&#xff0c;体现了大胆极简主义的原则 – 当代设计的主流趋势。这种自信的字体将粗犷的几何形状与现代的趣味性相结合&#xff0c;具有圆润的边缘和强烈的存在感&#xff0c;与当今的极简主义设计方法完美契合。无论是用于鲜明的构图还是…

Spring Boot(十七):集成和使用Redis

Redis(Remote Dictionary Server,远程字典服务器)是一个开源的、基于内存的数据结构存储系统,它可以用作数据库、缓存和消息中间件。Spring Boot 中集成和使用Redis主要涉及以下几个步骤: 添加依赖 在项目的pom.xml文件中添加Redis的依赖。Spring Boot提供了对Redis的集…

2025-03-21 Unity 序列化 —— 自定义2进制序列化

文章目录 前言1 项目结构1.1 整体1.2 代码 2 实现2.1 Processor2.1.1 BaseType2.1.2 CollectionType2.1.3 CustomType 2.2 ByteFormatter2.3 ByteHelper 3 使用 前言 ​ BinaryFormatter 类可以将 C# 类对象快速转换为字节数组数据。 ​ 在网络开发时&#xff0c;不会使用 Bi…

为WordPress自定义一个留言板

要在WordPress中创建一个留言反馈表单&#xff0c;并实现后台管理功能&#xff0c;您可以按照以下步骤进行操作&#xff1a; 1. 创建留言反馈表单 首先&#xff0c;您需要使用一个表单插件来创建表单。推荐使用 Contact Form 7 或 WPForms。以下是使用 Contact Form 7 的示例…

嵌入式项目:利用心知天气获取天气数据实验方案

【实验目的】 1、利用心知天气服务器获取指定位置天气数据 2、将天气数据解析并可视化显示到OLED屏幕 【实验原理】 【实验步骤】 官网注册

go-zero学习笔记

内容不多&#xff0c;只有部分笔记&#xff0c;剩下的没有继续学下去&#xff0c;包括路由与处理器、日志中间件、请求上下文 文章目录 1、go-zero核心库1.1 路由与处理器1.2 日志中间件1.3 请求上下文 1、go-zero核心库 1.1 路由与处理器 package mainimport ("github…

【Go】Go语言继承-多态模拟

继承&#xff08;结构体嵌入&#xff09;多态&#xff08;接口实现和空接口&#xff09; 1. 继承&#xff08;结构体嵌入&#xff09; Go 语言没有传统的面向对象的继承机制&#xff0c;但可以通过“结构体嵌入”实现类似继承的效果。 结构体嵌入&#xff1a;在结构体中嵌入另…