十大经典排序算法及其C语言实现--带动图展示

排序算法大概算起来有以下十种

640

一、冒泡排序(Bubble Sort)

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

1.1、动画演示

请添加图片描述

1.2、代码

void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}void Bubble_sort(int *nums, int length) {for (int i=1; i<length; i++) {for (int j=0; j<length-i; j++) {if (nums[j] > nums[j+1]) {swap(&nums[j],&nums[j+1]);}}}
}

1.3、评价

最快就是当输入的数据是正序时。

最慢就是当输入的数据是反序时。

由于复杂度很高,所以数据量大时几乎不可以使用,但是算法简单,且为原地排序算法。

二、选择排序(Selection Sort)

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。

2.1、动画演示

请添加图片描述

2.2、代码

void Selection_sort(int *nums, int length) {for (int i=0; i<length-1; i++) {int flag = i;for (int j=i+1; j<length; j++) {if (nums[j] < nums[flag]) flag = j;}swap(&nums[i],&nums[flag]);}
}

2.3、评价

优势是简单直观,选一个最小的放在前面

劣势是时间复杂度高,不适合大规模数据,不适合部分排序

三、插入排序(Insertion sort)

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

3.1、动画演示

图片

3.2、代码

/* 写法1 */
void Insert_sort(int *nums, int length) {for (int i=1; i<length; i++) {for (int j=i; j>=1; j--) {if (nums[j] < nums[j-1]) {swap(&nums[j],&nums[j-1]);} else {break;}}}
}/* 写法2 */
void Insert_sort2(int *nums, int length) {for (int i=1; i<length; i++) {int temp = nums[i];int j;for (j=i; j>=1 && nums[j-1] > temp; j--) {nums[j] = nums[j-1];}nums[j] = temp;}
}

3.3、评价

插入排序是稳定的排序算法,即相同元素的相对位置在排序后不会改变。

时间复杂度高,不适合大规模数据,且每次插入都需要大量移动数据。

四、希尔排序(Shell sort)

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本思想是:先选定一个整数gap,把待排序文件中所有元素分成gap组,即所有距离为gap的分在同一组内,并对每一组内的元素进行直接插入排序(本质是进行预排序,使数组更接近有序,目的是使最后一次直接插入排序提高效率)。然后取gap=gap/2或者gap=gap/3+1重复上述分组和排序的工作。当gap到达1时,数组是已经接近有序的,而对于直接插入排序在顺序有序或者接近顺序有序的数组内的时间复杂度为O(N),效率很高,当gap=1,即以一个元素为一组,就相当于对整个数组进行直接插入排序了。得到的就是有序数组了。也就排好序了。

4.1、动画演示

在这里插入图片描述

  1. 选择增量序列: 首先确定一个增量序列,这个序列决定了每次分割的步长。常见的增量序列有 Hibbard 增量序列、Sedgewick 增量序列等。增量序列的选择对算法的效率有很大影响。
  2. 分割成子序列: 将原始序列分割成若干个子序列,每个子序列包含距离为增量序列中某个值的元素。通常是从第一个元素开始,每隔增量序列中的一个值取一个元素,这样依次形成子序列。
  3. 对各个子序列进行插入排序: 对每个子序列分别进行插入排序。即对每个子序列内部的元素进行排序,使得子序列内部基本有序。这一步通常采用的是简单插入排序。
  4. 逐步缩小增量: 重复上述步骤,但是每次使用的增量都会逐步缩小,直到增量为 1。
  5. 最后一次插入排序: 当增量为 1 时,整个序列会被分割成一个个相邻的子序列,此时进行最后一次插入排序。这时候,相邻元素之间的距离变为 1,即实现了对整个序列的最终排序。

4.2、代码

void Shell_sort(int *nums, int length) {for (int gap = length / 2; gap > 0; gap /= 2) {for (int i = gap; i < length; i++) {int temp = nums[i];int j;for (j = i; j >= gap && nums[j - gap] > temp; j -= gap) {nums[j] = nums[j - gap];}nums[j] = temp;}}
}

4.3、评价

希尔排序的时间复杂度取决于增量序列的选择,一般情况下为 O(n^2)O(n^(3/2)) 之间,其中 n 是数组的长度。尽管希尔排序的时间复杂度不稳定,但在实际应用中,其性能通常比简单插入排序要好很多,特别是对于中等大小的数组。

五、归并排序(Merge sort)

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法)
  • 自下而上的迭代

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。

5.1、动画演示

请添加图片描述

5.2、代码

递归实现

void Merge(int *nums, int l, int r, int *tmp) {if (l >= r) return;int mid = (l + r) / 2;// 先分Merge(nums,l,mid,tmp);Merge(nums,mid+1,r,tmp);// 再合int l1 = l;int r1 = mid;int l2 = mid + 1;int r2 = r;int pos = 0;while (l1 <= r1 && l2 <= r2) {if (nums[l1] < nums[l2]) {tmp[pos++] = nums[l1++];} else {tmp[pos++] = nums[l2++];}}while (l1 <= r1) {tmp[pos++] = nums[l1++];}while (l2 <= r2) {tmp[pos++] = nums[l2++];}memcpy(nums+l,tmp,sizeof(int) * (pos));
}void Merge_sort(int *nums, int length) {int* tmp = (int*)malloc(sizeof(int) * length);Merge(nums,0,length-1, tmp);
}

非递归实现

void MergeSortNonR(int *nums, int length) {int* tmp = (int*)malloc(sizeof(int) * length);// gep为每次需要归并的元素个数for (int gap = 1; gap < length; gap *= 2) {for (int i=0; i<length; i += 2*gap) {int l1 = i;int r1 = i + gap - 1;int l2 = i + gap;int r2 = i + 2 * gap - 1;r2 = r2 < length-1 ? r2 : length - 1;int pos = 0;while (l1 <= r1 && l2 <= r2) {if (nums[l1] < nums[l2]) {tmp[pos++] = nums[l1++];} else {tmp[pos++] = nums[l2++];}}while (l1 <= r1) {tmp[pos++] = nums[l1++];}while (l2 <= r2) {tmp[pos++] = nums[l2++];}memcpy(nums + i, tmp, sizeof(int) * (pos));}}
}

5.3、评价

归并排序是一种非常有效的排序算法。稳定的时间复杂度:归并排序的时间复杂度为 O(n log n),其中 n 是待排序序列的长度。这意味着在最坏、平均和最好情况下,归并排序的时间复杂度都保持在这个级别,这使得它在各种情况下都能表现出良好的性能。稳定性:归并排序是一种稳定的排序算法,即相同元素的相对位置不会改变。这意味着如果原始序列中存在相同的元素,经过归并排序后,它们的相对顺序将保持不变。适合大规模数据:归并排序对于大规模数据的排序效率较高,因为其时间复杂度相对较低且稳定,不会受到数据分布的影响。

六、快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

6.1、动画演示

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

图片

6.2、代码

6.2.1、hoare法

123123

void Quick(int *nums, int l, int r) {if (l >= r) return;int begin = l;int end = r;while (l < r) {while (l < r && nums[r] >= nums[begin]) r--;while (l < r && nums[l] <= nums[begin]) l++;swap(&nums[l], &nums[r]);}swap(&nums[begin],&nums[l]);Quick(nums,begin,l-1);Quick(nums,l+1,end);
}void Quick_sort(int *nums, int length) {Quick(nums,0,length-1);
}

这种方法容易出bug

  1. right找小和left找大的过程中,要保证left < right,否则可能出现数组越界,比如1,9,6,4,2,7,8,2 ;右边的值都比key大,会导致越界。
  2. a[right] >= a[keyI]或者a[left] <= a[keyI]时,才能–right或者++left;如果是a[right] > a[keyI]或者a[left] < a[keyI]可能出现死循环,比如a[left] == a[right] == key时,交换完后不进入内部while,外部while陷入死循环。
6.2.2、挖坑法

img

void Quick(int *nums, int l, int r) {if (l >= r) return;int hole = l;int key = nums[l];while (l < r) {while (l < r && nums[r] >= key) r--;nums[hole] = nums[r];hole = r;while (l < r && nums[l] <= key) l++;nums[hole] = nums[l];hole = l;}nums[hole] = key;Quick(nums,l,hole-1);Quick(nums,hole+1,r);
}void Quick_sort(int *nums, int length) {Quick(nums,0,length-1);
}
6.2.3、前后指针法

img

void Quick(int *nums, int l, int r) {if (l >= r) return;int prev = l;int cur = l+1;int key = l;while (cur <= r) {if (nums[cur] < nums[key] && ++prev != cur) {swap(&nums[prev],&nums[cur]);}++cur;}Quick(nums,l,prev-1);Quick(nums,prev+1,r);
}void Quick_sort(int *nums, int length) {Quick(nums,0,length-1);
}

6.3、评价

快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序。实际上确实非常快,使用场景也非常广泛

七、堆排序(Heap sort)

利用堆这种数据结构进行排序,堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  • 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;

  • 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 Ο(nlogn)。

7.1、动画演示

图片

7.2、代码

// 调整堆,将以root为根的子树调整为最大堆
void heapify(int arr[], int n, int root) {int largest = root; // 初始化根节点为最大值int left = 2 * root + 1; // 左子节点int right = 2 * root + 2; // 右子节点// 如果左子节点比根节点大,则更新最大值if (left < n && arr[left] > arr[largest]) {largest = left;}// 如果右子节点比根节点大,则更新最大值if (right < n && arr[right] > arr[largest]) {largest = right;}// 如果最大值不是根节点,则交换根节点和最大值节点,并递归调整子树if (largest != root) {swap(&arr[root], &arr[largest]);heapify(arr, n, largest);}
}// 堆排序函数
void heapSort(int arr[], int n) {// 构建最大堆for (int i = n / 2 - 1; i >= 0; i--) {heapify(arr, n, i);}// 依次取出堆顶元素,调整堆for (int i = n - 1; i > 0; i--) {swap(&arr[0], &arr[i]); // 将当前堆顶元素(最大值)与数组末尾元素交换heapify(arr, i, 0); // 调整堆}
}

7.3、评价

堆排序不稳定,但是时间复杂度稳定,适用于大规模数据。

  • 堆排序通常需要随机访问数组元素,对于链式结构来说不太适用,因此在链表实现中可能效率较低。

  • 如果待排序的数据分布不均匀,堆排序的性能可能会下降,因为堆排序是一种基于比较的排序算法,而数据分布不均可能导致比较次数增多。

  • 相对于其他简单的排序算法(如插入排序、冒泡排序),堆排序的实现相对复杂,需要对堆的概念和操作有一定的了解。

八、计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

8.1、动画演示

图片

8.2、代码

void Counting_sort(int *nums, int length) {int *cache = (int *)malloc(sizeof(int) * 20);memset(cache,0,sizeof(int) * 20);for(int i=0;i<length;i++) {cache[nums[i]] ++;}int pos = 0;for(int i=0;i<20;i++) {while (cache[i]) {nums[pos++] = i;cache[i]--;}}
}

8.3、评价

计数排序具有线性复杂度,简单易实现,对于范围有限的数据非常友好。

九、桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。

  • 在额外空间充足的情况下,尽量增大桶的数量
  • 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

其主要是先把序列中的数据划分为一个个的范围,每个范围相当一个桶,将数据放入桶中,每个桶分别进行排序,然后取出拼接即可。计数排序不适用于序列中max-min范围较广或者序列中的数据不是整数的情况,但是桶排序没有这些限制,其主要步骤是将数据放入桶中,桶中的数据自己排序。

桶排序是一种针对大数据的排序方式,数据量越大,桶排序的优势越大

十、基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

10.1、动画演示

图片

10.2、评价

基数排序适用于待排序元素为非负整数,并且数值范围不是很大的情况。它将整数按位数划分,并依次进行排序,适用于范围较小的整数排序。

基数排序可扩展到外部排序,即处理大规模数据的排序问题。例如,可以将数据分块读入内存,然后使用基数排序对每个数字位进行排序,再将结果写回磁盘。

基数排序的这些优势使得它在某些特定的排序需求中具有较好的性能和适用性。然而,基数排序的缺点是需要额外的存储空间来存储中间结果,且对待排序元素的数据类型有一定要求(通常是非负整数)。

基数排序不需要比较

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

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

相关文章

python统计分析——一般线性回归模型

参考资料&#xff1a;python统计分析【托马斯】 当我想用一个或多个其他的变量预测一个变量的时候&#xff0c;我们可以用线性回归的方法。 例如&#xff0c;当我们寻找给定数据集的最佳拟合线的时候&#xff0c;我们是在寻找让下式的残差平方和最小的参数(k,d)&#xff1a; 其…

基于51单片机的秒表设计—0.01精度99.99s

基于51单片机的秒表设计 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.数码管显示秒表&#xff0c;精度为0.01&#xff1b; 2.记时最大为99.99s,过后清零记数。 3.一个按键实现开始…

突破传统,创新营销:淘宝天猫详情接口API的无限可能

随着互联网的迅猛发展&#xff0c;电子商务平台成为了现代商业营销的重要组成部分。淘宝和天猫作为中国最大的电子商务平台之一&#xff0c;拥有庞大的商品数据库和海量的买家资源。为了更好地实现个性化、精准化的营销策略&#xff0c;淘宝天猫推出了详情接口API&#xff0c;联…

Kibana管理ES生命周期

希望通过Kibana界面管理ES索引的生命周期 版本&#xff1a;7.15.2 创建索引模板 创建索引模板方便匹配索引&#xff0c;对匹配到的一批索引采用同一套生命周期管理策略&#xff0c;例如开发环境的所有索引以dev-开头&#xff0c;可以创建样式为dev-*的索引模板以匹配开发环境…

uni-starter和uni-admin初始化

1. 初始化uni-starter项目之后需要在uniCloud–>cloudfunctions–>common–>uni-config-center下面新增uni-id文件夹&#xff0c;下面新增config.json文件。 1.1配置好tokenSecret和微信的appId&#xff0c;appSecret等,如下&#xff1a; {"passwordSecret"…

pygame旋转角度发射射线

self.x self.x math.cos(math.radians(self.xuanzhuanjiao)) * 70 self.y self.y - math.sin(math.radians(self.xuanzhuanjiao)) * 70 旋转角度&#xff0c;70是间隔 间隔太小会卡 import pygame from pygame.locals import * import sys import mathpygame.init()width, …

03-JAVA设计模式-装饰模式

装饰模式 什么装饰模式 装饰器模式&#xff08;Decorator Pattern&#xff09;也叫包装器模式&#xff0c;是一种结构型设计模式&#xff0c;允许用户在不改变对象的情况下&#xff0c;动态地给对象增加一些额外的职责&#xff08;功能&#xff09;。装饰器模式相比生成子类更…

echarts 多环形图

环形图效果&#xff1a; option {"angleAxis": {"max": 1,"show": false,"splitLine": {"show": false},"axisLabel": {"show": false},"axisTick": {"show": false}},"ra…

美国N4433A安捷伦电子校准件

181/2461/8938产品概述&#xff1a; 安捷伦N4433A微波电子校准&#xff08;ECal&#xff09;模块使矢量网络分析仪的校准变得快速、简单和准确。ECal是一种用于矢量网络分析仪的精密单连接校准技术。与机械校准套件相比&#xff0c;使用ECal进行完整的三端口或四端口校准所需的…

基于FPGA的HDMI设计导航页面

FPGA使用HDMI更多时候用于传输图像数据&#xff0c;并不会传输音频数据&#xff0c;因此以下文章均采用DVI接口协议&#xff0c;HDMI与DVI的视频传输协议基本一致&#xff0c;区别也很小。 首先需要了解HDMI的来源&#xff0c;以及物理接口类型以及引脚信号&#xff0c;最后对几…

【Java】Java使用Swing实现一个模拟计算器(有源码)

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 今天翻了翻之前写的代码&#xff0c;发现自己之前还写了一个计算器&#xff0c;今天把我之前写的代码分享出来。 我记得那会儿刚学不会写&#xff0c;写的乱七八糟&#xff0c;但拿来当期末作业还是不错的哈哈。 直接上…

力扣404,左叶子之和

404. 左叶子之和 已解答 简单 相关标签 相关企业 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3,9,20,null,null,15,7] 输出: 24 解释: 在这个二叉树中&#xff0c;有两个左叶子&#xff0c;分别是 9 和 15&#xff0c;…

C:数据结构之链栈(不带头)

目录 前序 准备工作 函数声明 函数接口 1.初始化 2.创造节点 3. 判断栈空 4.入栈 5.出栈 6.取栈顶元素 7.销毁栈 8. 获取栈的元素个数 总结 前序 链栈是基于单链表实现的,其实栈更加适合使用顺序表来实现的,这篇文章我们来探讨一下链栈的实现。 准备工作 老规…

透视 Insilico 英矽智能:AI 制药明星企业的飞跃、困境与破局

衰老&#xff0c;从古至今困扰了无数仁人志士。无论是千古一帝秦始皇&#xff0c;还是雄才大略汉武帝&#xff0c;亦或者挥斥方遒唐太宗&#xff0c;这些伟大的帝王无一例外的都留下了许多追求长生的故事。当时光的指针落在了 21 世纪的第二个十年&#xff0c;随着全球老龄化问…

【c++】string类常见接口函数

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好啊&#xff0c;本节我们来到STL内容的第一部分&#xff1a;string类接口函数的介绍 目录 1.string类的认识2.常见接口讲解2.1 string类对象的常见构造2.2 对string对象的遍历和…

Jackson解析Json数据

1.概述 Jackson 和 FastJson 一样&#xff0c;是一个 Java 语言编写的&#xff0c;可以进行 JSON 处理的开源工具库&#xff0c;Jackson 的使用非常广泛&#xff0c;Spring 框架默认使用 Jackson 进行 JSON 处理。 Jackson 有三个核包&#xff0c;分别是 Streaming、Databid、…

【实战JVM】类的生命周期

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

百度OCR身份证识别C++离线SDKV3.0 C#对接

百度OCR身份证识别C离线SDKV3.0 C#对接 目录 说明 效果 问题 项目 代码 下载 说明 自己根据SDK封装了动态库&#xff0c;然后C#调用。 SDK 简介 本 SDK 适应于于 Windows 平台下的⾝份证识别系统,⽀持 C接⼜开发的 SDK,开发者可在VS2015 下⾯进⾏开发&#xff08;推荐…

推荐学习什么编程语言?

选择编程语言学习时&#xff0c;除了就业因素外&#xff0c;还可以考虑以下几个方面来决定学习哪些编程语言&#xff1a; 个人兴趣与目标&#xff1a;如果你对某个特定领域感兴趣&#xff0c;比如游戏开发、数据分析、人工智能等&#xff0c;可以选择与该领域紧密相关的编程语言…

Altair® Access™ 面向研究人员和工程师的 HPC 作业提交门户

Altair Access™ 面向研究人员和工程师的 HPC 作业提交门户 Access 具有一个简单、强大且统一的界面&#xff0c;可以从中提交和监控远程集群、云或其他资源的相关作业&#xff0c;使工程师和研究人员能够专注于主要活动&#xff0c;减少在应用程序运行及数据移动上投入的时间。…