【算法基础】常见排序算法(持续更新中)

news2024/11/27 20:59:20

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:【C/C++】算法
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵
希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


在这里插入图片描述

目录

  • 一、直接插入排序
    • 1.1 基本思想
    • 1.2 算法思路
    • 1.3 代码实现
    • 1.4 特性总结
  • 二、希尔排序
    • 2.1 希尔排序的由来
    • 2.2 算法思路
    • 2.3 如何选择gap
    • 2.4 希尔排序效率问题
    • 2.5 代码实现
    • 2.6 希尔排序的特性总结
  • 三、直接选择排序
    • 3.1 基本思想
    • 3.2 算法思路
    • 3.3 代码实现
    • 3.4 优化版本
    • 3.5 特性总结
  • 四、堆排序
  • 五、冒泡排序
    • 5.1 思想
    • 5.2 算法分析
    • 5.3 代码实现(未优化版)
    • 5.4 代码实现(优化版)
  • 六、快速排序

一、直接插入排序

1.1 基本思想

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

实际中,玩扑克牌时,整理一副牌从小到大或者从大到小就用到了插入排序的思想

在这里插入图片描述

1.2 算法思路

在这里插入图片描述

首先数据内的第一个元素(下标为0)是不需要排序的。因此,当插入第i(i ≥ 1)个元素时,可以拿a[i]与当前数组内的最后一个元素进行比较,若a[i] < 数组最后一个元素,则将原来的位置上的元素向后移;若满足条件,则直接插入。

1.3 代码实现

#include <stdio.h>
// n - 数组元素个数
void InsertSort(int* a, int n)
{
	//数组第一个元素不需要参与排序
	for (int i = 1; i < n; i++)
	{
		// end - 每一次数组最后一个元素的下标
		int end = i - 1; 
		//InsertNum - 要插入的元素
		int InsertNum = a[i];
		while (end >= 0)
		{
			if (a[end] > InsertNum)
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		//当循环来到此处有两种可能性
		//1.break跳出来的
		//2.头插
		a[end + 1] = InsertNum;
	}
}

int main()
{
	int a[] = { 9,4,2,3,10,8,6,1,7,5 };
	int ArraySize = sizeof(a) / sizeof(a[0]);

	InsertSort(a, ArraySize);

	for (int i = 0; i < ArraySize; i++)
		printf("%d ", a[i]);

	return 0;
}

【运行结果】

在这里插入图片描述

1.4 特性总结

  1. 时间复杂度
    最好情况:原数组已经是按需求有序了,每趟只需与前面的有序元素序列的最后一个元素进行比较,总比较次数为n - 1,时间复杂度为 O(N);
    最坏情况:假设要排升序,原数组是逆序的情况下,O(N2)
    综上,直接插入排序的时间复杂度为O(N2)
  2. 空间复杂度:O(1)
  3. 稳定性:插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。如果碰见一个和插入元素相等的,那么将会把待插入元素放在相等元素的后面。所以,相等元素的相对的前后顺序没有改变,所以插入排序是稳定的。

二、希尔排序

2.1 希尔排序的由来

从直接插入排序中,(目标数组是升序)我们总结了:当原数组是降序的时候,时间复杂度为O(N2),效率极低;当原数组是接近升序的,那么时间复杂度就是O(N),效率最高。因此,又一位名叫希尔的大佬发现,如果一开始就让数组内的元素接近有序的话,插入排序的效率不就大大提升了吗?所以,希尔排序是直接插入排序的优化

2.2 算法思路

  • 首先,预排序,目的:让数组内的元素接近有序

如何使数组接近有序?
先选定一个间隔gap的整数,然后以距离为gap数进行分组排序,其分组排序还是运用到直接插入排序

  • 最后对有序数组进行插入排序即可

当间隔gap == 1时,也就是说,距离为1的分为一组,那不就是插入排序

【演示】

(数据来源于后面的代码)

在这里插入图片描述

通过以上图片,我们还可以总结一个规律:gap为几,就代表有几组

2.3 如何选择gap

gap的取法有多种。最初希尔大佬提出取gap = n / 2gap =gap / 2,直到gap =1,后来 Knuth提出取gap = gap / 3 + 1,还有人提出取奇数好,也有人提出gap互质好。但无论哪一种主张都没有得到证明。但是在算法思路中,图中的gap取值是根据希尔大佬来的,而我在代码实现中给定的是Knuth的方法

2.4 希尔排序效率问题

有人想:希尔排序在预排序的时候不是运用到很多的插入排序,为什么其效率还是比插入排序高?
原因是:其实gap的取值决定数组内的元素是否接近有序,gap越大,排的也越快,但越不接近有序;gap越小,排的也就越慢,但越接近有序。所以一开始gap的值可以设为数组元素个数(gap一定不可能超过数组元素个数),每次进行/2,不断缩小gap,其实最后发现,希尔排序的插入排序的次数其实是小于直接排序的插入次数的

2.5 代码实现

#include <stdio.h>
//n - 数组元素个数
void ShellSort(int* a, int n)
{
	//gap最大也只能取到数组元素个数
	int gap = n;
	while (gap > 1)
	{
		//gap的取值无论是多少,最后结果一定是1
		//那么当gap=1时,就是最后的插入排序
		gap = gap / 3 + 1;
		//gap为几,就代表有几组
		for (int i = 0; i < gap; i++)
		{
		    //以下代码和插入排序差不多
			for (int j = i; j < n - gap; j += gap)
			{
				int end = j;
				int InsertNum = a[end + gap];
				while (end >= 0)
				{
					if (a[end] > InsertNum)
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				a[end + gap] = InsertNum;
			}
		}
	}
}

int main()
{
	int a[] = { 9,4,2,3,10,8,6,1,7,5 };
	int ArraySize = sizeof(a) / sizeof(a[0]);

	ShellSort(a, ArraySize);

	for (int i = 0; i < ArraySize; i++)
		printf("%d ", a[i]);

	printf("\n");
	return 0;
}

【结果展示】

在这里插入图片描述

2.6 希尔排序的特性总结

1. 希尔排序是对直接插入排序的优化
2. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算。Knuth进行了大量的试验统计,计算出希尔排序的时间复杂度大约为O(N1.3)

但是我们可以用代码进行性能测试的对比
在这里插入图片描述
如图,当数据个数由10w时,性能结构如下图所示
在这里插入图片描述

三、直接选择排序

3.1 基本思想

每一次从原数组的元素中选出最大(降序)/ 最小(升序)的一个元素,放在序列的起始位置,直至全部待排序的数据全部排完。

3.2 算法思路

在这里插入图片描述

【未优化版(升序)】
如上动图所示,定义一个left表示数组的首元素,假设它是数组中最小的,然后以首元素的大小为基准值再后遍历数组,找到其最小的放在左边,重复上述步骤,直到集合剩余一个元素为止

3.3 代码实现

//【升序为例】

#include <stdio.h>

void Swap(int* p1, int* p2)
{
	int t = *p1;
	*p1 = *p2;
	*p2 = t;
}

void select_sort(int* a, int n)
{
	int left = 0;
	//LastIdex - 数组最后一个元素的下标
	int LastIdex = n - 1;
	while (left < LastIdex)
	{
		//假设数组开头的元素是最小的
		int Min = left;
		//往后遍历每一个元素
		for (int i = left + 1; i <= LastIdex; i++)
		{
			if (a[i] < a[Min])
				Min = i;
		}
		//循环结束后,Min一定是整个数组中最小元素的下标
		//交换到开头
		Swap(&a[left], &a[Min]);
		left++;
	}
}

int main()
{
	int a[] = { 10,1,6,9,4,7,2,3,8,5 };
	int aSize = sizeof(a) / sizeof(a[0]);

	select_sort(a, aSize);

	for (int i = 0; i < aSize; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

【结果展示】

在这里插入图片描述

3.4 优化版本

【算法思路】

  1. 定义leftright分别表示数组的头和尾
  2. 每次遍历left~right区间里的元素,找到最小的数与left为下标的元素进行交换;同理,找到最大的元素与以right为下标的元素进行交换,完成交换后分别缩小leftright之间的范围
  3. 重复上述步骤,直到集合剩余一个元素为止

【代码实现】

#include <stdio.h>

void Swap(int* p1, int* p2)
{
	int t = *p1;
	*p1 = *p2;
	*p2 = t;
}

void select_sort2(int* a, int n)
{
	int left = 0, right = n - 1;
	while (left < right)
	{
		int Max = left, Min = left;
		for (int i = left + 1; i <= right; i++)
		{
			//更新
			if (a[i] > a[Max])
			{
				Max = i;
			}
			if (a[i] < a[Min])
			{
				Min = i;
			}
		}
		//交换
		Swap(&a[left], &a[Min]);
		//可能存在left和Max重叠
		if (left == Max)
		{
			Max = Min;
		}
		Swap(&a[right], &a[Max]);
		//缩小区间
		left++;
		right--;
	}
}

int main()
{
	int a[] = { 10,1,6,9,4,7,2,3,8,5 };
	int aSize = sizeof(a) / sizeof(a[0]);

	select_sort2(a, aSize);

	for (int i = 0; i < aSize; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

【结果展示】

在这里插入图片描述

分析leftMax重叠的情况
在这里插入图片描述若没有修正的那一步,交换完a[left]a[Min]后,Max就不再指向最大的元素了。因此,当leftMax重叠时,就要修正Max的位置

【优化版与未优化版性能比较】

在这里插入图片描述

【结果展示】

在这里插入图片描述

3.5 特性总结

1. 选择复杂度无论好坏都是O(N2),效率极低,实际中很少使用
2. 空间复杂度:O(1)
3. 稳定性:不稳定

四、堆排序

//堆排序
#include <stdio.h>

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)
	{
		if (child + 1 < n && a[child + 1] > a[child])
		{
			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)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	
	int end = n - 1;//最后一个元素
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

int main()
{
	int a[] = { 4,7,10,8,1,5,2,3 };
	int Asize = sizeof(a) / sizeof(a[0]); 

	HeapSort(a, Asize);
	
	for (int i = 0; i < Asize; i++)
		printf("%d ", a[i]);

	printf("\n");

	return 0;
}

堆排序在上篇博客中详细分析过了,这里就不再赘述了 —> [点击跳转]

五、冒泡排序

5.1 思想

两两相邻的元素进行比较,有可能需要交换

5.2 算法分析

在这里插入图片描述

N个数字要排序,总共进行N - 1趟排序,每x趟的排序次数为(N-x)

5.3 代码实现(未优化版)

#include <stdio.h>

void Swap(int* p1, int* p2)
{
	int t = *p1;
	*p1 = *p2;
	*p2 = t;
}

void bubble_sort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = 0; j < n - 1 - i; j++)
		{
			// 排升序
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
			}
		}
	}
}

int main()
{
	int a[] = { 10,1,6,9,4,7,2,3,8,5 };
	int aSize = sizeof(a) / sizeof(a[0]);

	bubble_sort(a, aSize);

	for (int i = 0; i < aSize; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

【程序结果】

在这里插入图片描述

【程序特性分析】

  1. 未优化版的时间复杂度最好(升序)和最坏(降序)的情况都需要遍历一遍数组,因此时间复杂度为:O(N2
  2. 空间复杂度:O(1)
  3. 稳定性:稳定

5.4 代码实现(优化版)

优化版是对有序序列进行了特判,如果第一次遍历一遍数组发现内部根本没有进行交换,就代表其有序

#include <stdio.h>
#include <stdbool.h>

void Swap(int* p1, int* p2)
{
	int t = *p1;
	*p1 = *p2;
	*p2 = t;
}

void bubble_sort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		bool exchage = false;
		for (int j = 0; j < n - 1 - i; j++)
		{
			// 排升序
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
				exchage = true;
			}
		}
		//若没发生交换,则有序
		if (exchage == false)
		{
			break;
		}
	}
}

int main()
{
	int a[] = { 10,1,6,9,4,7,2,3,8,5 };
	int aSize = sizeof(a) / sizeof(a[0]);

	bubble_sort(a, aSize);

	for (int i = 0; i < aSize; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

【程序结果】

在这里插入图片描述

【程序特性分析】

对于时间复杂度的优化,假设数组原本就有序(最好),因此只用遍历一遍数组,最好的时间复杂度为O(N),但最坏的时间复杂度还是O(N2

六、快速排序

博主还在更新中~,4_28上线hh

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

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

相关文章

【KVM虚拟化】· 图形化KVM安装linux

目录 &#x1f341;虚拟化技术 &#x1f342;KVM的整体结构 &#x1f342;KVM 的功能列表 &#x1f342;KVM 工具集合 &#x1f341;安装kvm虚拟化功能 &#x1f341;创建虚拟机 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &#x1f990;专栏地址&#xff1a;KVM虚拟化…

FreeRTOS 信号量(二) ------ 计数型信号量

文章目录 一、计数型信号量1. 计数型信号量简介2. 创建计数型信号量①函数 xSemaphoreCreateCounting()②函数 xSemaphoreCreateCountingStatic() 3. 计数型信号量创建过程分析4. 释放和获取计数信号量 二、计数型信号量操作实验 一、计数型信号量 1. 计数型信号量简介 有些资…

数据结构与算法(小议递归)

文章目录 前言一、递归是什么&#xff1f;二、在什么时候适用递归1.测试一下 总结 前言 递归是一种常用的算法设计&#xff0c;递归就是一种循环推理。简单来说就是调用原算法本身的算法。 这里主要探讨递归的使用&#xff0c; 一、递归是什么&#xff1f; 用一个简单的例子来…

嵌入式代码查看分析利器---Understand

平时在开发嵌入式程序的时候大多数使用的都是keil软件&#xff0c;一般小的工程使用keil没感觉到有什么问题&#xff0c;但是当工程比较大的时候&#xff0c;比如移植了FreeRTOS系统或者LWIP网络系统时&#xff0c;代码全部编译一次就要花费很长世间&#xff0c;特别是开启了点…

CIKM论文解读 | 淘宝内容化推荐场景下对多场景全域表征的思考与应用

我们结合逛逛推荐场景中的具体问题&#xff0c;从多场景全域表征的视角进行了一系列的探索与内容推荐场景的应用&#xff0c;从全域表征的范围、信息迁移方式以及模型框架的应用等维度展开我们的优化工作&#xff0c;取得了阶段性的优化经验和业务效果。 背景介绍 淘宝逛逛自20…

鸿蒙混合打包。在现有安卓应用的基础上扩展鸿蒙的特性,以最快的速度布局鸿蒙生态!

鸿蒙混合打包 介绍 鸿蒙混合打包。在现有安卓应用的基础上扩展鸿蒙的特性&#xff0c;以最快的速度布局鸿蒙生态&#xff01; 参考&#xff1a;京东APP鸿蒙版上架实践。本项目只是这篇文章的一个验证&#xff0c;让更多的兄弟们可以少走弯路。 我尽可能把每一步的改动作为一…

【HarmonyOS】【JS】Tabs如何设置区分TabBar和TabContent的分割线不显示

【关键字】 Tabs&#xff0c;分割线 【问题描述】 使用JS开发HarmonyOS应用时&#xff0c;使用Tabs组件&#xff0c;默认自带TabBar和TabContent的蓝色分割线&#xff0c;由于蓝色分割线样式不可设置&#xff0c;若不想要此蓝色分割线&#xff0c;如何去除蓝色分割线&#xf…

搭建外网minecraft服务器方案

很多minecraft服务器主都想自己搭建一个外网可以访问的minecraft服务器&#xff0c;在没有外网IP的情况下&#xff0c;一般都是使用Logmein Hamachi方案。这种方案有它的弊端&#xff0c;需要客户机安装Hamachi&#xff0c;十分不方便。另外&#xff0c;免费版只支持5人&#x…

C++三大特性—继承 “基类与派生类之间的类型转换与赋值转换”

理解基类与派生类之间的类型转换是理解C语言面向对象编程的关键所在 继承 通过继承联系在一起的类构成一种层次关系&#xff0c;层次关系的根部有一个基类&#xff0c;其他直接或间接从基类继承而来&#xff0c;称为派生类。    继承(inheritance)机制是面向对象程序设计使代…

Spring RabbitMQ 实现消息队列延迟

1.概述 要实现RabbitMQ的消息队列延迟功能&#xff0c;一般采用官方提供的 rabbitmq_delayed_message_exchange插件。但RabbitMQ版本必须是3.5.8以上才支持该插件&#xff0c;否则得用其死信队列功能。 2.安装RabbitMQ延迟插件 检查插件 使用rabbitmq-plugins list命令用于查看…

C++引用进阶篇:让你的程序更加高效、安全、简洁

文章目录 前言1. 引用和临时数据&#x1f351; 什么样的临时数据会放到寄存器中&#x1f351; 关于常量表达式&#x1f351; 引用也不能指代临时数据&#x1f351; 引用作为函数参数 2. 为const引用创建临时变量3. const引用与转换类型&#x1f351; 引用类型的函数形参请尽可能…

SPFA 算法:实现原理及其应用

文章目录 一、前言二、SPFA 算法1、SPFA算法的基本流程2、代码详解 三、SPFA 算法已死 &#xff1f; 一、前言 SPFA算法&#xff0c;全称为Shortest Path Faster Algorithm&#xff0c;是求解单源最短路径问题的一种常用算法&#xff0c;它可以处理有向图或者无向图&#xff0…

PySide2 QWebEngine与Web js交互

文章目录 单向交互双向传值案例 单向交互 QWebEngineView加载web页面&#xff0c;web页面中点击按钮&#xff0c;执行js代码&#xff0c;js的返回值传给QWebEnginePage&#xff0c;使用python进行保存结果。 单向&#xff0c;js向python(PySide2)端传输数据。 前端实现 <…

前端web3入门脚本三:一键完成与dex的交互,羊毛党必备

前言 该脚本用途&#xff1a;一键可以完成与dex的所有交互&#xff0c;包括2次swap&#xff0c;添加/移除流动性&#xff0c;以及farm和提取LP。一次运行可以有6条交易记录。 无论是个人单刷还是羊毛党批量地址刷交互都完美适配。当然反女巫方案不在这次文章的讨论范围内。 一、…

javascript中find(), filter(), some(), every(), map()等方法介绍

1、find() find() 用于找出第一个符合条件的数组成员。它的参数是一个回调函数&#xff0c;所有数组成员依次执行该回调函数&#xff0c;直到找出第一个返回值为true的成员&#xff0c;然后返回该成员。如果没有符合条件的成员&#xff0c;则返回undefined。 find()方法的回调…

利用Matab进行覆盖计算----战术计算

在 contour函数中添加如下代码 %------- 计算畅通区面积和占比例 --------% Spi*maxrange*maxrange/1e6; S0 nnz(isInRange)*reslons*reslats/1e6;isnn ~isnan(cdata); cdata0 cdata(isnn); S1numel(cdata0)*reslons*reslats/1e6;AS1/S0; % 畅通区所占比例; fprintf("…

CLion开发工具 | 06 - 使用CLion开发STM32(无需Cmake)

专栏介绍 文章目录 专栏介绍一、准备工作1. 工具准备2. 裸机工程准备二、使用CLion打开工程三、基于CLion写代码1. LED blink代码2. printf重定位代码四、编译工程1. 编译配置2. 选择编译目标3. 编译五、烧录1. OpenOCD基础知识(了解)2. 设置CLion路径3. 新建CLion配置文件4.…

面试总结,4年经验

小伙伴你好&#xff0c;我是田哥。 本文内容是一位星球朋友昨天面试遇到的问题&#xff0c;我把核心的问题整理出来了。 1&#xff1a;Java 层面的锁有用过吗&#xff1f;除了分布式锁以外 是的&#xff0c;Java中提供了多种锁机制来保证并发访问数据的安全性和一致性。常见的J…

分析GC日志解读

目录 GC分类 GC日志分类 GC日志结构剖析 透过日志看垃圾收集器 透过日志看GC原因 GC日志分析工具 GC分类 针对HotSpot VM的实现&#xff0c;它里面的GC按照回收区域又分为两大种类型&#xff1a;一种是部分收集&#xff08;Partial GC&#xff09;&#xff0c;一种是整堆…

VPN 虚拟专用网络隧道

1 什么是VPN VPN(全称&#xff1a;Virtual Private Network)虚拟专用网络&#xff0c;是依靠ISP和其他的NSP&#xff0c;在公共网络中建立专用的数据通信的网络技术&#xff0c;可以为企业之间或者个人与企业之间提供安全的数据传输隧道服务。在VPN中任意两点之间的链接并没有…