基本的五大排序算法

news2024/12/23 3:44:32

目录

一,直接插入算法

二,希尔排序算法

三,选择排序

四,堆排序

五,冒泡排序算法


简介:

        排序算法目前是我们最常用的算法之一,据研究表明,目前排序占用计算机CPU的时间已高达百分之30到百分之50。可见,高效率的排序算法是我们必须掌握的基本算法之一,本篇博客就先跟大家介绍五种常用的排序算法:直接插入算法,希尔算法,选择算法,堆排序算法,冒泡算法。

一,直接插入算法

        直接插入算法的原理与我们打扑克牌时,进行不摸牌排序的效应一样。平常我们在打扑克牌时会不断的摸牌,每当我们摸到一个牌时就会往里面插入,使得我们手中的排有序。直接插入算法与之同理,其基本思想是把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列种,直到所有的记录插入完为止,得到一个新的有序序列,即摸牌效应,如下图,是插入排序的示意图:cbbddbcf00724df98c8b5eba624928ea.gif


        明白以上原理后,接下来要思考的是此算法的效率如何,不难发现,当为最好情况时(即有序排列),将一次性直接遍历整个数组,时间复杂度为O(N);当为最坏情况是(即要跟排列的顺序相反),需要一直往首元素插入,此时的时间复杂度为O(N^2)。具体代码如下:

#include <stdio.h>
//直接插入算法
void InsertSort(int* a, int n) {
    for (int i = 0; i < n - 1; i++) {
 // [0,end]有序,把end+1位置的插入到前序序列,控制[0,end+1]有序
        int end = i;
        int next = a[i + 1];
 // 升序排列
        while (end >= 0) {
            if (a[end] > next) {
                a[end + 1] = a[end];
            }
            else {
                break;
            }
            end--;
        }
        a[end + 1] = next;
    }
}
int main() {
	int a[] = { 5,9,7,3,0,1,4,2,6,8 };
	InsertSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++) {
		fprintf(stdout, "%d ", a[i]);
	}
	puts("");
	return 0;
}

运行图:

8c03d4451dab45979beda78308fd8322.png


二,希尔排序算法

        希尔排序是以插入排序为基础的排序。首先,该算法要设置一个分组,然后再用直接插入算法的思想进行预排序,预排序是将数据分组排序,每组数据之间有着一定的间距。如下图:

c00f8c71fda44afab1b44f5fdaa8d8f3.png

        上图中,第一组数据:9,5,8,5。第二组数据:1,7,6。第三组数据:2,4,3。三组数据先分别用直接插入算法进行预排序,排序后第一组数据为5,5,8,9。第二组数据为1,6,7。第三组数据为2,3,4。经过这些预排序后,整体的数据为5  1  2  5  6  3  8  7  4  9(不同颜色代表不同组中的数据,可直观的看出)。

        其中,将数据进行预排序的目的是大的数据更快的到后面去,小的数据更快的到前面去,其中,间距越大跳得越快,越不接近有序;间距越小跳得越慢,越接近有序,当间距为1时,直接为有序,因为,当间距为1时就是直接插入排序。

        希尔算法运用得原理就是给定一个间距值进行预排序,当间距值大于1时进行的排序为预排序,当间距值等于1时的排序就是直接插入排序,即排序已完成。本算法的效率很高,但时间复杂度很难计算出,暂时先不研究这个,代码实现具体如下:

#include <stdio.h>
//希尔排序算法
void ShellSort(int* a, int n) {
	int grab = n;//开始时,设置间距grab = n;
	//当grab == 1时为直接插入算法,当grab > 1时为预排序
	while (grab != 1) {
		//grab /= 2;//不断的缩减grab的大小,直到grab == 1排序完成
		//由于grab一次性的缩减比较小,导致算法效率较低,以下的grab为改进算法,提高算法效率
		grab = grab / 3 + 1;//不断缩减间距grab的大小,直到grab == 1排序完成
		//以下是根据间距grab的大小来进行的插入排序
		for (int j = 0; j < grab; j++) {
			for (int i = j; i < n - grab; i += grab) {
				int end = i;
				int x = a[i + grab];
				while (end >= 0) {
					if (a[end] > x) {
						a[end + grab] = a[end];
					}
					else {
						break;
					}
					end -= grab;
				}
				a[end + grab] = x;
			}
		}
	}
}
int main() {
	int a[] = { 5,9,7,3,0,1,4,2,6,8 };
	ShellSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++) {
		fprintf(stdout, "%d ", a[i]);
	}
	puts("");
	return 0;
}

运行图:

becf500f9ba94dafb29bbf8610299968.png


三,选择排序

1,选择排序的基本思想:

        每一次从待排序的数据元素中选出最小或最大的一个元素,存放在系列的起始位置,即进行一趟选择,直到全部待排序的数据元素排完,其中时间复杂度:O(N^2)。空间复杂度:O(1)。如下图:

1b799e0b19b44a5bad38455dd945756e.gif


        以上就是选择排序的基本套路,但我们仔细思考下来,其实这种方法还有优化空间,当数据进行一趟选择时,我们可直接选出最大数和最小数,将其放在开头或末尾。然后再次进行遍历,直到头尾遍历相遇为止。代码如下:

#include <stdio.h>
//选择算法
void Swap(int* x1, int* x2) {
	int t = *x1;
	*x1 = *x2;
	*x2 = t;
}
void SelectSort(int* a, int n) {
	int begin = 0, end = n - 1;
	while (begin < end) {
		int max = begin, min = begin;
		//进行一趟遍历,确定最小值和最大值的下标,当前面遍历等于end时排序就已排好
		for (size_t i = begin + 1; i < end; i++) {
			if (a[max] < a[i]) {
				max = i;
			}
			if (a[min] > a[i]) {
				min = i;
			}
		}
		Swap(a + max, a + end);
		//当min在最后时,max与原先的end交换了位置,所以这时的end的下标已经变了
		if (min == end) {
			min = max;
		}
		Swap(a + min, a + begin);
		//前面排好,往后前进
		begin++;
		//后面排好,往前前进
		end--;
	}
}
int main() {
	int a[] = { 5,9,7,3,0,1,4,2,6,8 };
	SelectSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++) {
		fprintf(stdout, "%d ", a[i]);
	}
	puts("");
	return 0;
}

运行图:

bb38e77485f7424887864e60aafa71a5.png


四,堆排序

        堆排序跟二叉堆排序一模一样,即先建立堆结构,然后进行堆的调整,使整体有序。要注意的是,升序排列首选大堆,降序排列首选小堆建立。(这一原理运用的是二叉树的知识,如若感觉有困难,就必须先把二叉树的堆结构再学习下。不建议直接上来搞这一块知识)原理如下图所示。

b6d9d60472e044dd8c1ee03a47e1d055.gif

        其中结构上是选择堆结构上的选择排序,结构图如下:

7ce2be480a244150a4d25854eb4de07c.png

        由于原理与之前的二插堆一样,在这里就不做过多结构上的说明,下面是代码的实现和讲解:

#include <stdio.h>
void Swap(int* x1, int* x2) {
	int t = *x1;
	*x1 = *x2;
	*x2 = t;
}
//归并算法
void Adjustdown(int* a, int n, int parent) {
	int child = 2 * parent + 1;
	while (child < n) {
		if (child + 1 < n && a[child] < a[child + 1]) {
			child++;
		}
		if (a[child] > a[parent]) {
			Swap(a + child, a + parent);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			break;
		}
	}
}
//以升序为例,升序用建大堆思想
void HeapSort(int* a, int n) {
	//从最后一层走起,因为要保证导入的parent往下都是有序的,即大堆建立
	for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
		Adjustdown(a, n, i);
	}
	//大堆建立后根为最大数,然后进行排序
	int end = n - 1;
	while (end > 0) {
		Swap(a, a + end);//因为根为最大数,要升序就要把最大数放入最后
		Adjustdown(a, end, 0);
		end--;
	}
}
int main() {
	//归并算法
	int a[] = { 5,9,7,3,0,1,4,2,6,8 };
	HeapSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++) {
		fprintf(stdout, "%d ", a[i]);
	}
	puts("");
	return 0;
}

运行图:

733610906033452682b0570acb3e9784.png


五,冒泡排序算法

        冒泡排序是一种交换排序,所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,此种交换的特点是将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动,其中时间复杂度:O(N^2);空间复杂度:O(1)。

冒泡排序的原理图:

c28419fdc0f441048b337a84ca87b598.gif

此种排序非常简单,具体代码如下:

#include <stdio.h>
void Swap(int* x1, int* x2) {
	int t = *x1;
	*x1 = *x2;
	*x2 = t;
}
//冒泡排序算法
void BubbleSort(int* a, int n)
{
	for (size_t j = 0; j < n; j++)
	{
		//用x进行控制,可提升算法的效率
		int x = 0;
		for (size_t i = 1; i < n - j; i++)
		{
			//进行排序,当没有进入此步时,说明是有序的
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				x = 1;
			}
		}
		//当x == 0时,说明序列是有序的,可直接跳出,提升算法的效率
		if (x == 0)
		{
			break;
		}
	}
}
int main() {
	//冒泡算法
	int a[] = { 5,9,7,3,0,1,4,2,6,8 };
	BubbleSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++) {
		fprintf(stdout, "%d ", a[i]);
	}
	puts("");
	return 0;
}

运行图:

6060a2e144b746dc96dd61380f7eb6e4.png


总:在五种排序算法中,效率最高的是希尔排序,效率最低的是冒泡排序,堆排序与希尔不相上下,但堆排序实现起来比较麻烦,因此个人建议首选希尔,直接插入算法比选择排序算法和冒泡略高一筹,但在进行大量数据排序时,效率都不高,因此,在五大基本排序中,希尔排序为最佳选择。

 

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

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

相关文章

1003 我要通过!

一.问题&#xff1a; “答案正确”是自动判题系统给出的最令人欢喜的回复。本题属于 PAT 的“答案正确”大派送 —— 只要读入的字符串满足下列条件&#xff0c;系统就输出“答案正确”&#xff0c;否则输出“答案错误”。得到“答案正确”的条件是&#xff1a; 字符串中必须仅…

数组和切⽚ - Go语言从入门到实战

数组和切⽚ - Go语言从入门到实战 数组的声明 package main import "fmt" func main() { var a [3]int //声明并初始化为默认零值 a[0] 1 fmt.Println("a:", a) // 输出: a: [1 0 0] b : [3]int{1, 2, 3} //声明同时初始化 fmt.Println("b:…

番外6:下载+安装+配置Linux

#########配置Linux---后续 step08: 点击编辑虚拟机设置&#xff0c;选择下载好的映像文件.iso进行挂载&#xff1b; step09: 点击编辑虚拟机选项&#xff0c;选择UEFI启动模式并点击确定&#xff1b; step10: 点击开启虚拟机&#xff0c;选择Install rhel &#xff1b; 备注&…

架构的未来:微前端与微服务的融合

文章目录 微服务架构简介微前端架构简介微前端与微服务的融合1. 共享服务2. 基于事件的通信3. 统一的身份和认证4. 交付管道的集成 示例&#xff1a;使用微服务和微前端的电子商务平台微服务架构微前端架构融合微服务和微前端 结论 &#x1f389;欢迎来到架构设计专栏~架构的未…

【Linux系统编程】僵尸进程与孤儿进程

文章目录 1. 僵尸进程2. 僵尸进程的危害3. 孤儿进程 1. 僵尸进程 上一篇文章进程的状态中最后我们提出了僵尸状态&#xff1a; 为了方便子进程退出后父进程或操作系统获取该进程的退出结果&#xff0c;Linux进程退出时&#xff0c;进程一般不会立即死亡&#xff0c;而是要维持…

【Spring底层原理】BeanFactory的实现

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 容器实现 一、BeanFactory实现的特点1.1 Be…

2023年中国半导体IP行业发展概况及趋势分析:半导体IP的市场空间广阔[图]

半导体指IP指芯片设计中预先没计、验证好的功能模块&#xff0c;处于半导体产业链最上游&#xff0c;为芯片设计厂商提供设计模块。半导体IP按交付方式可分为软核、硬核和固核&#xff1b;按产品类型可分为处理器IP、接口IP、其他物理IP及其他数字IP。 半导体IP分类 资料来源&…

K-Means(上):数据分析 | 数据挖掘 | 十大算法之一

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

ubuntu 18.04 LTS安装opencv 3.4.16 + opencv_contrib 3.4.16

1.下载 opencv 3.4.16 opencv_contrib 3.4.16 其中&#xff0c;opencv_contrib解压后的多个文件夹复制到opencv内、合并 2.安装 参考博文&#xff1a; https://zhuanlan.zhihu.com/p/650792342 https://zhuanlan.zhihu.com/p/87197806 其中 &#xff08;1&#xff09;cmake前…

【设计模式】五、原型模式

文章目录 概述示例传统的方式的优缺点原型模式原理结构图-uml 类图 原型模式解决克隆羊问题的应用实例Sheep类实现clone()运行原型模式在 Spring 框架中源码分析 深入讨论-浅拷贝和深拷贝浅拷贝的介绍 小结 概述 示例 克隆羊问题 现在有一只羊 tom&#xff0c;姓名为: tom, 年…

nginx隐藏版本号和标识

1.隐藏版本号:nginx-服务器banner泄漏风险_banner信息泄露_javachen__的博客-CSDN博客 2.隐藏nginx标识 cd /usr/local/nginx-1.24.0/src/corevi nginx.h在第14行 cd /usr/local/nginx-1.24.0/src/httpvi ngx_http_special_response.c在第22,29,36行 cd /usr/local/nginx-1.2…

踩坑日记 uniapp 底部 tabber遮挡住购物车结算

tabbar 被购物车结算遮挡 在小程序上tabbar没有将固定栏遮挡&#xff0c;如果直接调高&#xff0c;浏览器H5页面是对了&#xff0c;但在小程序上面离底部的定位就太高了 原代码 // 底部结算样式.shop-foot {border-top: 2rpx solid #F7F7F7;background-color: #FFF;position: …

法国心理健康平台【Teale】完成1000万欧元A轮融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;位于法国巴黎的心理健康平台【Teale】今日宣布已完成1000万欧元的A轮融资。 本轮融资由Alter Equity和Bpifrance的Digital Venture基金领投&#xff0c;目前的支持者ISAI和Evolem也参与其中。 该公…

ChatGPT的截图识别功能测评:开启图像中的文字与信息的新纪元

文章目录 根据截图&#xff0c;识别菜品根据截图&#xff0c;识别数学公式根据截图生成前端UI代码可视化图像复现案例一案例二 更多可以使用的方向 制作人&#xff1a;川川 辛苦测评&#xff0c;如果对你有帮助支持一下书籍&#xff1a;https://item.jd.com/14049708.html 根据…

spring boot整合常用redis客户端(Jedis、Lettuce、RedisTemplate、Redisson)常见场景解决方案

Java操作redis有三种客户端供选择&#xff1a;Jedis、Lettuce、Redisson。 在实际项目中运用最多的客户端还是Redisson、RedisTemplate&#xff1b;其中RedisTemplate并非是一个新的redis客户端实现&#xff0c;RedisTemplate是Spring Data Redis中提供的封装好的redis操作模板…

闪击笔试题

选择题 ping命令不涉及什么协议? A&#xff1a;DNS B: TCP C: ARP D: ICMP B&#xff0c;ping基于ICMP协议&#xff0c;解析路由会用到ARP和DNS a、b、c三人参加学科竞赛&#xff0c;每个学科按一二三名次给x、y、z分&#xff0c;已知a得22分&#xff0c;b和c得9分&#xf…

进程控制以及相关原语的使用(创建,终止,阻塞,唤醒,切换)

1.基本概念 1.进程控制 进程控制的主要功能是对系统中的所有进程实施有效的管理&#xff0c;它具有创建新进程、撤销已有进程、实现进程状态转换等功能。 进程控制就是要实现进程状态转换。 2.实现进程控制(原语) 1.原语 原语是一种特殊的程序,它的执行具有原子性。也就是…

初阶数据结构(四)带头双向链表

&#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;数据结构 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &#x1f339;&#x1f339;&#x1f339;关注我带你学习编程知识 带头双向链表 链表的相关介绍初始化链表销毁链…

一文带你搞懂Redis持久化

Redis持久化 Redis的数据是存储在内存的&#xff0c;当程序崩溃或者服务器宕机&#xff0c;那么内存里的数据就会丢失。所以避免数据丢失的情况&#xff0c;需要将数据保存到其他的存储设备中。 Redis提供两种方式来持久化&#xff0c;分别是 RDB(Redis Database)&#xff1a…

格拉姆角场GAF将时序数据转换为图像并应用于凯斯西储大学轴承故障诊断(Python代码,CNN模型)

1.运行效果&#xff1a; 格拉姆角场GAF将时序数据转换为图像并应用于凯斯西储大学轴承故障诊断&#xff08;Python代码&#xff09;_哔哩哔哩_bilibili 环境库 只要tensorflow版本大于等于2.4.0即可运行 同样的模型应用于东南大学轴承数据集&#xff1a;格拉姆角场GAF将时序…