【算法】排序——选择排序和交换排序(快速排序)

news2025/1/13 19:43:06

 =========================================================================

主页点击直达:个人主页

我的小仓库:代码仓库

C语言偷着笑:C语言专栏

数据结构挨打小记:初阶数据结构专栏

Linux被操作记:Linux专栏

LeetCode刷题掉发记:LeetCode刷题

算法头疼记:算法专栏 

=========================================================================

目录

前言

选择排序

直接选择排序

交换排序

冒泡排序

快速排序

hoare法

 挖坑法

双指针法

快速排序优化

优化一、三数取中

优化二、小区间优化

快速排序非递归


前言

上篇文章讲述了插入排序及插入排序的优化希尔排序,今天我们继续给大家带来排序中的选择排序和交换排序,选择排序包括直接选择排序、 其中还包括堆排序,因为之前讲过堆排序,这篇文章就不多讲解,点击直达堆排序。交换排序包括冒泡排序、快速排序。让我们开始今天的选择排序之旅吧!!!


选择排序

基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

直接选择排序

在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素
若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换

在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

 实现代码

//交换函数
void swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
//直接选择排序
void SelectSort(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		int mini = i;
		for (int j = i + 1; j < n; j++)
		{
			if (a[mini] > a[j])
			{
				mini = j;
			}
		}
		swap(&a[i], &a[mini]);
	}
}

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


交换排序

基本思想:

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。

交换排序的特点是:

将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

冒泡排序

实现代码

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

}

如果我们给一个有序数组的话,冒泡排序还是会两两相比较,会很麻烦我们不妨给一个变量,如果发生交换的话就改变这个变量的值每一趟冒泡排序后我们判断这个值是否改变,如果没改变则这个数组是有序的。

冒泡排序优化

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; j++)
	{
		int tmp = 1;
		for (int i = 0; i < n - 1-j; i++)
		{
			if (a[i + 1] < a[i])
			{
				Swap(&a[i + 1], &a[i]);
                tmp=0;
			}
		}
		if (tmp == 1)
		{
			return;
		}
	}

}

快速排序

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

这里我们能发现每一趟排序后的基准值都会被排序到正确的位置,因此我们以排好序的基准值为分界线,将数组分成左右两部分,左右两部分再根据排序的方法进行排序。每次排序都会产生一个排好序的基准值,每次都要根据这个基准值将数组分成两部分。因此我们考虑使用递归。

hoare法

实现代码 

//hoare法
int PartSort1(int* a, int left, int right)
{
	int keyi = left;
	while (left < right)
	{
		//右边找小
		//防止越界, 防止死循环
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		//左边找大
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[keyi], &a[left]);
	return left;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = PartSort1(a, begin, end);
	//[0 , keyi-1] keyi [keyi+1 , end]
	QuickSort(a, keyi + 1, end);
	QuickSort(a, begin, keyi - 1);
}

 挖坑法

实现代码

//挖坑法
int PartSort2(int* a, int left, int right)
{
	int key = a[left];
	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		a[keyi] = a[right];
		keyi = right;
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		a[keyi] = a[left];
		keyi = left;
	}
	a[keyi] = key;
	return keyi;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = PartSort2(a, begin, end);
	//[0 , keyi-1] keyi [keyi+1 , end]
	QuickSort(a, keyi + 1, end);
	QuickSort(a, begin, keyi - 1);
}

双指针法

 实现代码

//双指针法
int PartSort3(int* a, int left, int right)
{
	int prev = left;
	int cur = left + 1;
	int keyi = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			Swap(&a[++prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	return  prev;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int keyi = PartSort3(a, begin, end);
	//[0 , keyi-1] keyi [keyi+1 , end]
	QuickSort(a, keyi + 1, end);
	QuickSort(a, begin, keyi - 1);
}

 上面的三中方法我们使用函数递归来完成的,如果给予一个很大的数组需要排序,递归的深度可能会很深,会导致栈溢出。


快速排序优化

我们能否发现快速排序的递归很像二叉树的遍历,但是还有一个问题我们每次选的基准值排好序后都不一定都处于数组中大致的中间位置,这是我们理想的情况,如果不理想呢,每次选好的基准值排好序后都位于开头,这样只能将一个数组元素分割出去,而不是将近分开一半一半。
最好情况:每次都将近分开一半。

时间复杂度:O(N*logN)

最坏的情况:每次都分开一小部分

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

优化一、三数取中

我们在传参数时会传数组的长度,我们不妨在在数组头,数组尾和数组中间这三个值之间挑出中间值放在数组的头部,作为基准值,在进行排序。

实现代码 

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

这样我们实现了三数取中,再将下面的代码放到三种方法排序之前就可以了

int mid = GetMidi(a, left, right);
	Swap(&a[left], &a[mid]);

 这样我们初步的优化就完成了。

优化二、小区间优化

我们一上面的10个元素的数组为例,递归深度只有三四层没必要使用递归向下深入,继续浪费栈空间,我们不妨当数组元素大于等于10的时候递归,当数组元素小于10时使用插入排序

实现代码

void QuickSort(int* a, int begin,int end)
{
	if (begin >= end)
		return;
	//小区间优化,小区间不在递归分割排序,减少递归次数
	if((end-begin+1)>10)
	{ 
		int keyi = PartSort3(a, begin, end);
		//[begin,keyi-1] keyi [keyi+1 ,end]zd
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}
}

快速排序非递归

这里我们使用栈数据结构配合数组下标来完成非递归的实现。

实现代码

void QuickSortNonR(int* a, int left, int right)
{
	ST st;
	STInit(&st);
	STPush(&st, right);
	STPush(&st, left);
	while (!STEmpty(&st))
	{
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);
		int keyi = PartSort1(a, begin, end);
		if (keyi+1<end)
		{
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}
		if (keyi - 1 > begin)
		{
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}
	STDer(&st);
}

快速排序的特性总结:
1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(logN)
4. 稳定性:不稳定 

选择排序和快速排序的基本内容及优化到这就讲完了,其中快速排序使用非递归来实现确实优点难理解,大家可以一边分析代码,一边画图来理解。希望能积极指出文章中的错误,下期带来排序的最后一篇文章归并排序,敬请期待!!! 

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

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

相关文章

React18入门(第一篇)——JSX、TSX语法详解

文章目录 一、JSX 语法简介二、和 HTML 标签的几点不同三、JSX 属性四、JSX 事件4.1 简单点击事件4.2 类型限制4.3 带参数&#xff0c;箭头函数 五、插入 JS 变量六、JSX 中使用条件判断七、循环 一、JSX 语法简介 JSX - 是 JS 的扩展&#xff0c;写在 JS 代码里面&#xff0c…

【Docker】Docker的应用包含Sandbox、PaaS、Open Solution以及IT运维概念的详细讲解

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 &#x1f4d5;作者简介&#xff1a;热…

Windows下Tensorflow docker python开发环境搭建

前置条件 windows10 更新到较新的版本&#xff0c;硬件支持Hyper-V。 参考&#xff1a;https://learn.microsoft.com/zh-cn/windows/wsl/install 启用WSL 在Powershell中输入如下指令&#xff1a; dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsys…

数据挖掘(2)数据预处理

一、数据预处理 1.1概述 数据预处理的重要性 杂乱性&#xff1a;如命名规则。重复性&#xff1a;同一客观事再不完整性&#xff1a;噪声数据&#xff1a;数据中存在错误或异常的现象。 数据预处理的常见方法 数据清洗&#xff1a;去掉数据中的噪声&#xff0c;纠正不一致。数…

【C语言】循环结构程序设计 (详细讲解)

前言&#xff1a;前面介绍了程序中常常用到的顺序结构和选择结构&#xff0c;但是只有这两种结构是不够的&#xff0c;还有用到循环结构(或者称为重复结构)。因为在日常生活中或是在程序所处理的问题中常常遇到需要重复处理的问题。 【卫卫卫的代码仓库】 【选择结构】 【专栏链…

C语言数组和指针笔试题(五)(一定要看)

这里写目录标题 指针运算笔试题解析题目1解析结果 题目2解析结果 题目3解析结果 题目4解析结果 题目5解析结果 题目6解析结果 题目7解析结果 题目8解析结果 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412;个人主页 &a…

【智能家居项目】裸机版本——设备子系统(LED Display 风扇)

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《智能家居项目》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 输入子系统中目前仅实现了按键输入&#xff0c;剩下的网络输入和标准输入在以后会逐步实现&am…

生鲜蔬果同城配送社区团购小程序商城的作用是什么

生鲜蔬果行业作为市场主要支撑之一&#xff0c;从业商家众多的同时消费者也从不缺&#xff0c;尤其对中高城市&#xff0c;生鲜蔬果除了传统线下超市、市场经营外&#xff0c;线上更是受到大量消费者信任&#xff0c;而很多商家也是自建了生鲜蔬果商城多场景生意经营。 那么通…

家具商家通过商城小程序发展的作用是什么

家具商品覆盖床具、桌椅茶几、沙发等多个细分种类&#xff0c;市场需求较高&#xff0c;而传统消费者也是通过线下方式购买配送&#xff0c;但随着线下经营痛点显现&#xff0c;如流量匮乏拓客难、无法满足同城外地客户随时购物需求、营销难、经营难等&#xff0c;因此不少商家…

第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和

第五十六天| 第九章 动态规划 part14 1143. 最长公共子序列 1035. 不相交的线 53. 最大子序和 一、1143. 最长公共子序列 题目链接&#xff1a; 题目介绍&#xff1a; 思路&#xff1a; 本题和“最长重复子数组”区别在于**这里不要求是连续的了&#xff0c;但要有相对顺序*…

安防监控用品经营商城小程序搭建

安防监控产品种类很多&#xff0c;如监控摄像头、烟感、机房系统、对讲机等&#xff0c;虽然不是每个家庭都需要&#xff0c;但却占据着市场不小份额&#xff0c;其应用度也非常广泛&#xff0c; 而在实际销售方面&#xff0c;除了门店、入驻第三方电商平台或朋友圈售卖外&…

Docker 容器监控 - Weave Scope

Author&#xff1a;rab 目录 前言一、环境二、部署三、监控3.1 容器监控 - 单 Host3.2 容器监控 - 多 Host 总结 前言 Docker 容器的监控方式有很多&#xff0c;如 cAdvisor、Prometheus 等。今天我们来看看其另一种监控方式 —— Weave Scope&#xff0c;此监控方法似乎用的人…

ctfshow—1024系列练习

1024 柏拉图 有点像rce远程执行&#xff0c;有四个按钮&#xff0c;分别对应四份php文件&#xff0c;开始搞一下。一开始&#xff0c;先要试探出 文件上传到哪里&#xff1f; 怎么读取上传的文件&#xff1f; 第一步&#xff1a;试探上传文件位置 直接用burp抓包&#xff0c;…

PowerPoint如何设置密码?

PowerPoint&#xff0c;也就是PPT&#xff0c;是很多人工作中经常用的办公软件&#xff0c;而PPT和Word、Excel等一样可以设置密码保护。 PPT可以设置两种密码&#xff0c;一种是“打开密码”&#xff0c;也就是需要密码才能打开PPT&#xff1b;还有一种是设置成有密码的“只读…

python实现http/https拦截

python实现http拦截 前言:为什么要使用http拦截一、技术调研二、技术选择三、使用方法前言:为什么要使用http拦截 大多数爬虫玩家会直接选择API请求数据,但是有的网站需要解决扫码登录、Cookie校验、数字签名等,这种方法实现时间长,难度高。需求里面不需要高并发,有没有…

Docker系列--在容器中安装JDK的方法(有示例)

原文网址&#xff1a;Docker系列--在容器中安装JDK的方法(有示例)_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍如何在容器中安装JDK。 为什么要装JDK&#xff1f; JDK里有很多工具&#xff0c;比如jps、jstack、jmap等&#xff0c;可以排查问题。 本文目标 给Ubuntu系…

【数据结构】海量数据处理

【数据结构】海量数据处理 前言 海量数据处理是指基于海量数据的存储和处理&#xff0c;正因为数据量太大&#xff0c;所以导致要么无法在短时间内迅速处理&#xff0c;要么无法一次性装入内存。 对于时间问题&#xff0c;就可以采用位图、布隆过滤器等数据结构来解决。对于…

摄影后期图像编辑软件Lightroom Classic 2023 mac中文特点介绍

Lightroom Classic 2023 mac是一款图像处理软件&#xff0c;是数字摄影后期制作的重要工具之一&#xff0c;lrc2023 mac适合数字摄影后期制作、摄影师、设计师等专业人士使用。 Lightroom Classic 2023 mac软件特点 高效的图像管理&#xff1a;Lightroom Classic提供了强大的图…

分布式应用程序协调服务 ZooKeeper 详解

目录 1、ZooKeeper简介 2、ZooKeeper的使用场景 3、ZooKeeper设计目的 4、ZooKeeper数据模型 5、ZooKeeper几个重要概念 5.1、ZooKeeper Session 5.2、ZooKeeper Watch 5.3、Consistency Guarantees 6、ZooKeeper的工作原理 6.1、Leader Election 6.2、Leader工作流…

Docker中MySql容器的数据挂载

1.查看是否有数据卷 docker inspect mysql 说明&#xff1a;Name的值是随机生成的不是命令的。因此没有数据卷。 2. 目录挂载 说明&#xff1a;本地目录不允许简写&#xff1b;在执行docker runi命令时&#xff0c;使用-v本地目录&#xff1a;容器内目录可以完成本地目录挂载…