八大排序详解(默认升序)

news2024/11/17 7:33:48

一、直接插入排序

直接插入排序:直接插入排序就是像打扑克牌一样,每张牌依次与前面的牌比较,遇到比自己大的就将大的牌挪到后面,遇到比自己小的就把自己放在它后面(如果自己最小就放在第一位),所有牌排一遍后就完成了排序。

代码如下: 

// 插入排序
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		int tmp = a[i];
		int end = i - 1;
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

总结:直接插入排序的时间复杂度为O(n^2)空间复杂度为O(1)具有稳定性(相对位置不变),在接近升序的情况下效果最好,接近O(n)。

二、希尔排序

希尔排序:直接插入排序在接近有序的情况下很快,所以就想到在直接插入排序之前先预排序,让数据接近有序,再用直接插入排序,效果就会提升,这就是希尔排序。

预排序: 先将数据分成 gap 组,每组里面使用直接插入排序。 

gap为1时,就是直接插入排序。

 代码如下: 

// 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;//控制gap,最后一次是1
		for (int i = 0; i < n; i++)
		{
			int tmp = a[i];
			int end = i - gap;
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

总结:希尔排序时间复杂度不好计算,根据教科书,为O(n^1.3)空间复杂度为O(1),因为预排序,希尔排序不稳定,也因为预排序,希尔排序在接近有序(无论升序或降序)时,速度最快。

三、选择排序 

选择排序:类似打牌,选择排序就是在 所有牌中选出最小的放在最左边,选出最大的放在最右边,然后再从剩余的牌中重复此操作。

需要注意的是当最大的牌在最左边时,若先与最小的牌交换位置,则会造成错误,只要加个判断调整即可。

 代码如下:

// 选择排序
void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int min = begin, max = begin;
		for (int i = begin; i <= end; ++i)
		{
			if (a[min] > a[i])
				min = i;
			if (a[max] < a[i])
				max = i;
		}
		swap(&a[min], &a[begin]);
		if (a[begin] == a[max])
			max = min;
		swap(&a[max], &a[end]);
		begin++;
		end--;
	}
}

总结时间复杂度为O(n^2),空间复杂度为O(n),不具有稳定性(因为与第一张牌交换时可能会改变相对位置)。

四、冒泡排序 

冒泡排序 遇到比自己小的就交换,每趟冒泡都可以把最大的值放到后面,次大的值放到倒数第二位.....然后结束。

代码如下: 

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		int flag = 0;
		for (int j = 0; j < n - i - 1; ++j)
		{
			if (a[j] > a[j + 1])
			{
				swap(&a[j], &a[j + 1]);
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}

 总结时间复杂度为O(n^2),空间复杂度为O(n),具有稳定性。

五、堆排序

请看这里(づ ̄ 3 ̄)づ:堆排序与TopK问题-CSDN博客 

六、快速排序 

 快速排序:选一个key,将小于key的排在左边,大于key的排在右边,排完后key就在有序时的对应位置。

然后递归左区间和右区间,每个数都在对应位置就能使整体有序。

让 key 在有序时的对应位置,有三种方法。(小指的是比key小,大就是比key大)

hoare版本:最老的版本,右边找小,左边找大,找到交换,重复操作到 left >= right 再将 left 与 key 位置的值交换

 挖坑法key看作坑,右边找小,找到交换,右边看作坑,左边找大,找到交换,左边再看作坑,依次循环,最后坑就在相应位置。

双指针法:一个cur指针,一个prev指针,cur遇到小就与++prev位置交换,遇到大就cur++

使prev位置是小的最后一个

代码如下:

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	int key = left;
	while (left < right)
	{
		//必须先右再左,若先左再右则要交换left+1的位置
		//前面加一个left<right防止越界,后面不加等号可能会导致死循环
		while (left < right && a[right] >= a[key])
			right--;

		while (left < right && a[left] <= a[key])
			left++;
		swap(&a[left], &a[right]);
	}
	swap(&a[key], &a[left]);
	return left;
}

/ 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
	int hole = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[hole])
			right--;
		swap(&a[hole], &a[right]);
		hole = right;

		while (left < right && a[left] <= a[hole])
			left++;
		swap(&a[hole], &a[left]);
		hole = left;
	}
	return hole;
}
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
	int key = left;
	int prev = left, cur = prev + 1;
	while (cur <= right)
	{
		if (a[cur] < a[key] && ++prev != cur)
		{
			swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	swap(&a[key], &a[prev]);
	return prev;
}

排好一个位置,然后递归左右区间排剩余位置,最终使所有数据有序。

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

	//int key = PartSort1(a, left, right);
	//int key = PartSort2(a, left, right);
	int key = PartSort3(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);
}

 快排在数据接近有序时会出现剩余数据全在右区间的情况,此时最慢,时间复杂度为O(n^2)

所以我们可以加一个优化:三数取中。就可以极大地规避这种可能性。

三数取中:加一个三数取中的函数,在left,right,(left+right)/2 这三个位置中选出中间那个位置做key.

以hoare版本为例

int GetMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] > a[right])
	{
		if (a[right] > a[mid])
			return right;
		else
		{
			if (a[left] > a[mid])
				return mid;
			else
				return left;
		}
	}
	else
	{
		if (a[left] > a[mid])
			return left;
		else
		{
			if (a[mid] > a[right])
				return right;
			else
				return mid;
		}
	}
}

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	int mid = GetMid(a, left, right);
	swap(&a[mid], &a[left]);
	int key = left;
	while (left < right)
	{
		//必须先右再左,若先左再右则要交换left+1的位置
		//前面加一个left<right防止越界,后面不加等号可能会导致死循环
		while (left < right && a[right] >= a[key])
			right--;

		while (left < right && a[left] <= a[key])
			left++;
		swap(&a[left], &a[right]);
	}
	swap(&a[key], &a[left]);
	return left;
}

在递归左右区间时,根据我们学过的满二叉树知识,最后一层的结点几乎占全部的 50% ,倒数第二层大概占 25% 依次类推,而且最后的小区间是接近有序的,所以我们可以判断区间里的数据个数,若小于10,则用直接插入排序直接排完,可以减少80%多栈的调用。 

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	if (right - left + 1 <= 10)
	{
		InsertSort(a + left, right - left + 1);
		return;
	}
	//int key = PartSort1(a, left, right);
	//int key = PartSort2(a, left, right);
	int key = PartSort3(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);
}

非递归版 :用栈模拟函数栈帧的调用过程

// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	stack<int> st;
	st.push(right);
	st.push(left);
	while (!st.empty())
	{
		int begin = st.top();
		st.pop();
		int end = st.top();
		st.pop();
		int key = PartSort3(a, begin, end);

		if (key - 1 < begin)//区间有效入栈
		{
			st.push(key - 1);
			st.push(begin);
		}
		
		if (key + 1 < end)//区间有效入栈
		{
			st.push(end);
			st.push(key + 1);
		}
	}
}

 总结时间复杂度O(nlogn),空间复杂度O(logn),不稳定(找到key合适的位置后要交换)

七、归并排序

 归并排序:大家都做过两个有序数组归并到一起然后整体有序的题目吧,这就是归并排序的核心思路,它先将整个数组递归成一个一个的,然后再不断归并,最后整体有序。就相当于后序遍历的思路,左边有序,右边有序,归并自己,然后自己就有序

 

void _MergeSort(int* a, int* tmp, int left, int right)
{
    //只有一个数就返回
	if (left >= right)
		return;
	int mid = (left + right) / 2;
	_MergeSort(a, tmp, left, mid);
	_MergeSort(a, tmp, mid+1, right);
    //两个数组有序,归并到整体有序
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int j = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[j++] = a[begin1];
			begin1++;
		}
		else
		{
			tmp[j++] = a[begin2];
			begin2++;
		}
	}
	while (begin1 <= end1)
	{
		tmp[j++] = a[begin1];
		begin1++;
	}
	while (begin2 <= end2)
	{
		tmp[j++] = a[begin2];
		begin2++;
	}
    //拷贝回对应位置
	memcpy(a+left, tmp+left, sizeof(int) * (right - left + 1));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a, tmp, 0, n - 1);
}

非递归版本: 这个思路就是两两归并,四四归并,八八归并....最后整体有序,所以就可以设置一个gap从1到n,让数据模拟上述过程,循环归并。注意的是要考虑数据不足越界情况

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(n * sizeof(int));
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2*gap)
		{
            //利用gap设置区间
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//考虑越界情况
			if (begin2 >= n)//第二组越界就没必要归并了
				break;
			if (end2 >= n)//begin2没越,end2越界,修正end2
				end2 = n - 1;
            //归并
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1];
					begin1++;
				}
				else
				{
					tmp[j++] = a[begin2];
					begin2++;
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1];
				begin1++;
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2];
				begin2++;
			}
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
}

总结 时间复杂度O(nlogn),空间复杂度O(n),稳定。缺点是空间复杂度过高,优势是可以在磁盘中排序(外排序)

 八、计数排序

计数排序:计数排序是类似哈希表先开一个数组,让每个值都有一个对应位置,遍历数据,遇到就在对应位置++,最后遍历数组输出

 代码如下:

void CountSort(int* a, int n)
{
	//assert(n>0);
    //找到最大值与最小值,为映射做准备
	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* Count = (int*)calloc(range, sizeof(int));
	if (Count == NULL)
	{
		perror("calloc fail");
		exit(-1);
	}
    
    //对应位置++
	for (int i = 0; i < n; ++i)
	{
		Count[a[i] - min]++;
	}
	int j = 0;
	for (int i = 0; i < range; ++i)
	{
        //把对应位置写回原数组
		while (Count[i]--)
		{
			a[j++] = i + min;
		}
	}
}

总结 :计数排序的时间复杂度为O(max(range, n)),空间复杂度为O(range),稳定适用于数据集中在某一范围的时候

感谢大家观看,欢迎指出错误(๑•̀ㅂ•́)و✧ 

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

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

相关文章

PX4仿真添加world模型文件,并使用yolov8进行跟踪

前言 目的:我们是为了在无人机仿真中使用一个汽车模型,然后让仿真的无人机能够识别到这个汽车模型。所以我们需要在无人机仿真的环境中添加汽车模型。 无人机仿真中我们默认使用的empty.world文件,所以只需要将我们需要的模型添加到一起写进这个empty.world文件中去就可以…

电脑多开微信教程,可以多开n个

下载地址 链接&#xff1a;https://pan.baidu.com/s/1uWXIhfTZ-aD0A4RBxrI8bg?pwdy2s5 提取码&#xff1a;y2s5 效果如图&#xff1a;

地下水数值模拟软件如何选择?GMS、Visual MODFLOW Flex、FEFLOW、MODFLOW

强调模块化教学&#xff0c;分为前期数据收集与处理&#xff1b;三维地质结构建模&#xff1b;地下水流动模型构建&#xff1b;地下水溶质运移模型构建和反应性溶质运移构建5个模块&#xff1b;采用全流程模式将地下水数值模拟软件GMS的操作进行详细剖析和案例联系。不仅使学员…

Android中的RxJava入门及常用操作符

文章目录 1.定义2.作用3.特点4.使用4.1创建被观察者&#xff08;Observable&#xff09;4.2创建观察者&#xff08;Observer&#xff09;4.3订阅&#xff08;Subscribe&#xff09;4.4Dispose 5.操作符5.1操作符类型5.2just操作符5.2链式调用5.3 fromArray操作符5.4 fromIterab…

服务器文件备份

服务器上&#xff0c;做好跟应用程序有关的文件备份&#xff08;一般备份到远程的盘符&#xff09;&#xff0c;有助于当服务器发生硬件等故障时&#xff0c;可以对系统进行进行快速恢复。 下面以Windows服务器为例&#xff0c;记录如何做文件的备份操作。 具体操作如下&#…

贷款行业,教你如何直接沟通客户

信贷行业拓展业务的人力与时间成本非常高。如是做小微贷款业务的公司可能在寻找贷款客户、筛选客户资质这两项初始工作上花掉超过50%的精力。 并且由于行业特殊性&#xff0c;金融信贷受政策的影响比较大&#xff0c;没法形成固定的推广渠道&#xff0c;线上营销不好做&#x…

什么是站内搜索引擎?如何在网站中加入站内搜索功能?

在当今数字时代&#xff0c;用户体验对于网站的成功起着至关重要的作用。提升用户体验和改善整体网站性能的一种方法是引入站内搜索引擎。站内搜索引擎是一种强大的工具&#xff0c;它的功能类似于Google或Bing等流行搜索引擎&#xff0c;但它专注于实施自己网站上的内容。用户…

工业路由器项目应用(4g+5g两种工业路由器项目介绍)

引言&#xff1a; 随着工业智能化的不断发展&#xff0c;工业路由器在各个领域的应用越来越广泛。本文将介绍两个工业路由器项目的应用案例&#xff0c;一个是使用SR500 4g工业路由器&#xff0c;另一个是使用SR800 5g工业路由器。 详情&#xff1a;https://www.key-iot.com/i…

IPO观察丨重新启动上市,“小而美”能让科迪乳业再次出圈吗?

如今&#xff0c;乳制品市场俨然是一片红海&#xff0c;尽管市场竞争激烈&#xff0c;但对于一些企业而言&#xff0c;发展机会仍然相当可观。 近日举办的2023年中工作会议上&#xff0c;科迪乳业母公司科迪集团对外表示&#xff0c;要部署好下个阶段的重点工作&#xff0c;为…

Jenkins 添加节点Node报错JNI error has occurred UnsupportedClassVersionError

节点日志 报错信息如下 Error: A JNI error has occurred, please check your installation and try again Exception in thread “main” java.lang.UnsupportedClassVersionError: hudson/remoting/Launcher has been compiled by a more recent version of the Java Runtime…

阿里云服务器通用算力型、经济型、七代云服务器实例、倚天云服务器实例区别参考

目前阿里云服务器的实例规格中&#xff0c;既有五代六代实例规格&#xff0c;也有七代和八代倚天云服务器&#xff0c;同时还有通用算力型及经济型这些刚推出不久的新品云服务器实例&#xff0c;其中第五代实例规格已经不是主推的实例规格了&#xff0c;现在主售的实例规格是七…

windows server 2012 R2的C盘空间满了,但是找不到大文件的两种原因

目录 一、第一种原因&#xff1a;windows server backup备份导致C盘空间耗尽 二、第二种原因&#xff1a;超级桌管软件生成的文件放在C盘被隐藏 最近经历了两次C盘满了&#xff0c;但是又找不到大文件的问题&#xff0c;定位了许久&#xff0c;以下是两种原因。 一、第一种原…

python实现UI自动化配置谷歌浏览器驱动

web端UI自动化执行在哪个浏览器&#xff0c;需要对应哪个浏览器的驱动。以谷歌浏览器为例&#xff0c;进行配置。一、查看谷歌浏览器版本 如下截图&#xff1a;我的谷歌浏览器版本是&#xff1a; 117.0.5938.150 二、下载对应版本谷歌浏览器驱动 首先可以从其他版本驱动地址中…

超详细 | 鲸鱼优化算法原理及其实现(Matlab/Python)

鲸鱼优化算法(whale optimization algorithm,WOA)是由Mirjalili和Lewis[1]于2016年提出的一种新型群体智能优化搜索方法,它源于对自然界中座头鲸群体狩猎行为的模拟&#xff0c;该算法整个过程包含搜索觅食、收缩包围和螺旋更新位置三个阶段。 鲸鱼优化算法的三个种群更新机制…

为什么亚马逊速卖通等跨境卖家都选择自养号测评,有哪些优势

自养号测评是一种对于跨境电商卖家来说非常重要的运营手段。通过自养号测评&#xff0c;卖家可以快速增加产品的销量、评论数量&#xff0c;并提升在平台中的排名&#xff0c;从而促进产品的流量转化和订单增长。自养号测评与传统的机刷方式不同&#xff0c;它通过伪装设备参数…

Linux TCP 通信并发

多进程 客户端 #include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() {//创建套接字int lfd socket(AF_INET, SOCK_STREAM, 0);if(lfd -1) {perror("socket");exi…

突发,美国再将42家中企列入实体名单 | 百能云芯

2023年10月6日&#xff0c;美国商务部工业和安全局&#xff08;BIS&#xff09;宣布对实体名单进行了更新&#xff0c;涉及到新增了49个实体。这些实体来自七个不同的国家&#xff0c;其中42家位于中国&#xff0c;其余七家分别位于爱沙尼亚、芬兰、德国、印度、土耳其、阿拉伯…

知识图谱系列Paper 1:Open-CyKG: An Open Cyber Threat Intelligence Knowledge Graph

向前进&#xff01; 一、摘要 Instant analysis of cybersecurity reports is a fundamental challenge for security experts as an immeasurable amount of cyber information is generated on a daily basis, which necessitates automated information extraction tools t…

英文论文实例赏析——如何写前言?

写作与实验、统计一样重要 研究生的学习往往会遵循这样的过程&#xff1a;实验——数据分析——写作。虽然写作是最后进行的&#xff0c;但写作的学习这应该和实验的学习、数据分析的学习保持同步&#xff0c;因为写作与统计和实验技能一样&#xff0c;是科研工具箱的必…