【数据结构】八大排序(二)

目录

前言:

冒泡排序

冒泡排序代码实现

冒泡排序特性总结

快速排序

单趟排序hoare版本

单趟排序挖坑法

单趟排序快慢指针法

 快速排序整体概览

快排的优化

三数取中法选key

小区间优化


前言:

上文介绍了直接插入排序,希尔排序,选择排序,堆排序并对四种排序进行了详尽的探讨,本文将以排序的基本思想,代码实现和各种排序的特性总结三个角度继续分析冒泡排序,快速排序

冒泡排序

冒泡排序的基本思想:

将待排序的序列从前向后依次比较相邻元素的值,如果逆序则交换

升序:将数组中相邻元素从前往后依次进行比较,如果前一个元素比后一个元素大,则交换,一趟下来后最大元素就在数组的末尾;

降序:将数组中相邻元素从前往后依次进行比较,如果前一个元素比后一个元素小,则交换,一趟下来后最小元素就在数组的末尾;

具体步骤如下

  1. 比较待排序序列中相邻的两个元素,如果发现左边的元素比右边的元素大,则交换这两个元素
  2. 每一对相邻的两个元素重复第一步的动作,从左至右依次执行,最后的元素一定是最大的元素
  3. 由于最大的元素位于数组尾元素的位置,下一趟两两比较的范围为待排序序列的首元素倒数第二个元素所处的位置,这段范围成为新的待排序序列,重复步骤1,步骤2;
  4. 持续以上步骤 1, 步骤 2, 步骤 3 的工作,每重复一次需要排序的序列中少一个最右边的元素,直到没有任何一对数字需要比较排序;

......

冒泡排序代码实现

void bubblesort(int arr[], int n)
{
	// 外层循环控制冒泡排序的趟数
	int i = 0;
	for (i = 0; i < n - 1; i++)
	{
		//内层循环控制要比较元素的个数
		int j = 0;
		for (j = 0; j < n - i - 1; j++)
		{
			int temp = 0;
			if (arr[j]>arr[j + 1])
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

冒泡排序特性总结

1.时间复杂度

冒泡排序的平均 时间复杂度是O(n^2),最好时间复杂度是O(n),最坏时间复杂度是O(n^2);

2. 空间复杂度

额外开辟的空间为常数个,所以空间复杂度为O(1)

3.算法稳定性

冒泡排序是一种稳定的排序算法,即相等元素的相对位置在排序前后不会发生改变;

快速排序

快速排序的基本思想

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值右子序列中所有元素均大于基准值,然后对左右子序列分别递归地进行快速排序,直到所有元素都排列在相应位置上为止;

单趟排序hoare版本

思路如下:

1. 选取待排序序列最左边元素作为基准值key,定义两个指针left和right分别指向待排序序列的最左侧和最右侧,right从右向左走,left 从左向右走;

2.right先走,right指针找比基准值key小的数;left后走, left找比基准值key大的数;找到后将两个数交换位置;

3. 当left指针与right指针相遇时,将相遇位置的值与基准值key进行交换;

void swap(int* p, int* q)
{
	int tmp = *p;
	*p = *q;
	*q = tmp;
}
//单趟排序hoare版本
int PartSort(int* a, int left, int right)
{
	int keyi = left;//选取左边作为基准值,则右边先走
	while (left < right)//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;
}

思考: 为什么left与right相遇位置处的数值比key要小

left与right相遇有如下俩种情形:

相遇的第一种情形:right动left不动,相遇位置为left位置,left位置与前一个right位置进行了交换,交换之后left位置比key小;

相遇的第二种情形:right不动left动,相遇位置为right位置,此时left找大没有找到与right相遇,而right位置一定比key小;

思考:为什么左边取基准值key,右边先走?

保证当left与right相遇时,相遇位置小于基准值

  1. right 停下时,left 与 right 相遇,由于right 找比 key小的值,所以此时 right 的位置一定比key小;
  2. left 停下时,right 与 left 进行交换,交换后 left 指向的值比 key 小,此时 right 遇到 left 的位置一定比key小;

单趟排序挖坑法

思路如下:

1. 选择数组第一个数作为基准数,基准数的位置形成一个坑位
2. 定义两个指针left与right分别指向待排序序列的第一个数和最后一个数;
3. 从right开始向前遍历,找到第一个小于基准数的数a[right],将其赋值给a[left],a[right]成为一个新的坑位;
4. 从left开始向后遍历,找到第一个大于基准数的数a[left],将其赋值给a[right],a[left]成为一个新的坑位;
5. 重复步骤3和4,直到left > right
6. 将基准数填入最后一个坑中,a[left]=key;

//单趟排序挖坑法
int PartSort(int* a, int left, int right)
{
	int key = a[left];
	int hole = left;
	while (left < right)
	{
		//右边先走
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole]=a[right];
		hole = right;
		//左边再走
		while (left < right&&a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}

	a[hole] = key;
	return hole;
}

单趟排序快慢指针法

思路如下:

1. 选取待排序序列的第一个元素作为基准值key;
2. 定义两个指针prev和cur,prev指针指向序列开头,cur指针指向prev指针的后一个位置
3. 从cur开始向前遍历,如果遇到比key小的元素,就将prev指针向后移动一位,并交换prev和cur指向的元素;
4. 遍历结束后,将key与prev指向的元素交换位置,此时prev指向的位置就是key的最终位置;

//单趟排序前后指针法
int PartSort(int* a, int left, int right)
{
	int keyi = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		//(++prev)!=cur为了防止++prev与cur指向相同数值,此时交换毫无意义;
		if (a[cur] < a[keyi]&&(++prev)!=cur)
		{
			swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	swap(&a[keyi], &a[prev]);
	return prev;
}

 快速排序整体概览

快速排序首先选取基准值key,将待排序序列分为两个子序列,左子序列放比基准值小的数,右子序列放比基准值大的数,然后再将子序列以以上方式同样分割,直到数组有序,下图单趟排序采取hoare版本

  • 经过一趟hoare排序,区间被划分为[begin, keyi-1] U keyi U [keyi+1, end] ,单趟排序一次就会唯一确定一个元素来到最终排序所确定的位置

  • 先递归排序左子序列[begin, keyi-1] , 再递归排序右子序列[keyi+1, end],类似二叉树前序遍历

  • 根据上图可得出递归终止条件为区间不存在(begin>end)或者只有一个元素(begin=end)
void QuickSort(int*a, int begin, int end)
{
	//递归终止的条件
	//1.区间不存在(begin>end)或者只有一个元素(begin=end)
	if (begin >= end)
		return;
	int keyi = PartSort3(a, begin, end);
	// 一趟排序原排序区间划分为如下三部分
	//[begin keyi-1]U[keyi]U[keyi+1,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

快排的优化

三数取中法选key

若选取的基准值key是序列中最大或最小的数值,则快速排序的递归深度会非常深,排序效率会很低;若是一个有序数组使用快速排序,则递归深度为n,单趟排序也为n,时间复杂度为O(n^2);

假定待排序序列的左下标left , 右下标right,左右下标所对应的中间下标 midi=(left+right)/2,取三个下标所对应的值的中间值为基准值key,可以防止快排出现最差情形;

//三数取中法选keyi keyi为中间值的下标
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;
		}
		//mid为最小值
		else if (a[left] > a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		//mid为最大值
		else if (a[left]>a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

小区间优化

由于快速排序是递归实现的,而每一次函数调用,均需要开辟函数栈帧,当递归到最后几层时,此时数组中的值其实已经接近有序,最后几层的函数调用会极大占用函数栈帧所开辟的空间;

假设数组大小N=10,即二叉树节点个数N=10;

10个数值,递归了3~4层,而最后三层占据比例为87.5%,调用函数的次数就越多,开辟函数栈帧的消耗越大,导致效率下降,代价太大,在递归分割过程中,当区间较小时,不再递归分割排序,序列在递归分割过程中,子序列已经接近有序插入排序每一次单趟排序都只向前遍历到最大值之后,一共执行n次,若数组有序,则总共只执行n次,最差情况下每次都只是从i遍历到0下标,综合考虑是执行次数最优,故选择直接插入排序进行优化

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	// 小区间优化,小区间不再递归分割排序,降低递归次数
	if ((end - begin + 1) > 10)
	{
		int keyi = PartSort(a, begin, end);

		// [begin, keyi-1] keyi [keyi+1, end]
		QuickSort1(a, begin, keyi - 1);
		QuickSort1(a, keyi + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C# ReadOnlyRef Out

C# ReadOnly ReadOnly先看两种情况1.值类型2.引用类型 结论 Ref Out ReadOnly官方文档 ReadOnly 先看两种情况 1.值类型 当数据是值类型时&#xff0c;标记为Readonly时&#xff0c;如果再次设置值&#xff0c;会提示报错&#xff0c;无法分配到只读字段 public class A {pri…

基于LNMP快速搭建WordPress平台

目录 1 LNMP简介 2 WordPress简介 3 安装MySQL环境 3.1 安装MySQL 3.1.1 下载wget工具 3.1.2 下载MySQL官方yum源安装包 3.1.3 安装MySQL官方yum源 3.1.4 mysql安装 3.2 启动MySQL 3.3 获取默认密码 3.4 登录MySQL ​ 3.5 修改密码 3.6 创建WordPress数据库并授权 3.6.1 创…

WPF创建进度条

使用wpf做一个原生的进度条&#xff0c;进度条上面有值&#xff0c;先看效果。 功能就是点击按钮&#xff0c;后台处理数据&#xff0c;前台显示处理数据的变化&#xff0c;当然还可以对进度条进行美化和关闭的操作&#xff0c;等待后台处理完毕数据&#xff0c;然后自动关闭。…

设计师福利!2024在线图标设计网站推荐,不容错过的宝藏!

在当今竞争激烈的商业环境中&#xff0c;公司或个人品牌的视觉识别元素已经成为区分你和竞争对手的关键因素之一。一个独特而引人注目的标志可以深深扎根于人们的心中&#xff0c;并在消费者心中建立一个强烈的品牌印象。如果你正在寻找合适的工具来创建或改进你的标志&#xf…

js数组map()的用法

JavaScript Array map() 方法 先说说这个方法浏览器的支持&#xff1a; 支持五大主流的浏览器&#xff0c; 特别注意&#xff1a;IE 9 以下的浏览器不支持&#xff0c;只支持IE 9以上的版本的浏览器 特别注意&#xff1a;IE 9 以下的浏览器不支持&#xff0c;只支持IE 9以上的…

基于mvc电影院售票预订选座系统php+vue+elementui

本影院售票系统主要包括二大功能模块&#xff0c;管理员功能模块和用户功能模块。 &#xff08;1&#xff09;管理员模块&#xff1a;系统中的核心用户管理员登录后&#xff0c;通过管理员功能来管理后台系统。主要功能有&#xff1a;首页、个人中心、电影类型管理、场次时间管…

Corel产品注册机Corel Products KeyGen 2023 – XFORCE解决会声会影2023试用30天

CorelDRAW注册机2023支持全系列产品_Corel Products KeyGen 2023 X-FORCE v8 CorelDRAW注册机2023支持全系列产品_Corel Products KeyGen 2023 X-FORCE v8&#xff0c;Corel产品注册机&#xff08;Corel Products KeyGen 2023 – XFORCE&#xff09;&#xff0c;支持Corel旗下所…

jQuery_07 函数的使用

在jQuery中&#xff0c;如何使用函数呢&#xff1f; 1.基本函数 函数(常用的) 其实有很多函数&#xff0c;但是我们只需要掌握常用的函数即可 1.val 操作dom对象的value val() 没有参数 获取dom数组中第一个dom对象的value值 val(value) 有参数 设置dom数组中所有dom对象的…

Zotero | 取消翻译后自动添加笔记

目录 Step1&#xff1a;点击 “编辑” << “首选项” Step2&#xff1a;“翻译” << 取消勾选 “自动翻译批注” 在 Zetoro 中&#xff0c;选择颜色标记勾画的内容&#xff0c;将会自动生成一条笔记&#xff0c;如下图所示&#xff1a; 本人觉得很鸡肋&#xff0…

记录本地与服务器之间数据传输方法(上传、下载文件)

文章目录 一、使用scp命令实现参数说明示例说明 二、使用工具实现windows系统苹果系统如有启发&#xff0c;可点赞收藏哟~ 一、使用scp命令实现 scp 是 secure copy &#xff08;安全复制&#xff09;的缩写, scp 是基于 ssh 登陆进行安全的远程文件拷贝命令。相当于 cp 命令 …

渗透测试考核--两层内网 cs windows socks5

这里考核为渗透 这里是网络拓扑图 这里记录一下 两台外网 两台内网 首先拿到C段 nmap进行扫描 外网1 nmap -p 80 172.16.17.2/24 主机存活 一般都是web服务入手 所以我们指定80端口 然后去查找开放的 最后获取到2个ip Nmap scan report for 172.16.17.177 Host is u…

哈希函数:保护数据完整性的关键

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

WebSocket快速入门

WebSocket 借鉴&#xff1a; https://blog.csdn.net/weixin_45747080/article/details/117477006 https://cloud.tencent.com/developer/article/1887095 简介 WebSocket 是一种网络传输协议&#xff0c;可在单个 TCP 连接上进行全双工通信&#xff0c;位于 OSI 模型的应用…

Android Bitmap保存成至手机图片文件,Kotlin

Android Bitmap保存成至手机图片文件&#xff0c;Kotlin fun saveBitmap(name: String?, bm: Bitmap) {val savePath Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString()if (!Files.exists(Paths.get(savePath))) {Log.d("保存文…

DHCP协议及实验omnipeek抓包工具分析 IPv4协议

一 抓包命令 adb shell tcpdump -i wlan0 -w /data/tcpdump.pcap 抓包后截图如下 二 DHCP是什么 2.1 DHCP定义 DHCP( Dynamic Host Configuration Protocol, 动态主机配置协议)定义: 存在于应用层(OSI) 前身是BOOTP(Bootstrap Protocol)协议 是一个使用UDP(User …

【Unity实战】按物品掉落率,随机掉落战利品物品系统(附项目源码)

文章目录 前言开始参考源码完结 前言 当开发游戏时&#xff0c;一个常见的需求是实现一个物品随机掉落系统。这个系统可以让玩家在击败敌人或完成任务后获得随机的物品奖励&#xff0c;增加游戏的可玩性和乐趣。 在Unity中&#xff0c;我们可以通过编写代码来实现这样的战利品…

Open Feign 源码解析(一) --- FactoryBean的妙用

什么是Open Feign? OpenFeign 是 Spring Cloud 全家桶的组件之一&#xff0c; 其核心的作用是为 Rest API 提供高效简洁的 RPC 调用方式 搭建测试项目 服务接口和实体 项目名称 cloud-feign-api 实体类 public class Order implements Serializable {private Long id;p…

windows中打开psql命令行

一、第一种方式 1.点击下方的psql&#xff0c;打开命令行窗口 2.中括号中的是默认值&#xff0c;直接回车就行 3.成功 二、第二种方式 双击安装目录中的执行文件 “D:\soft\postgresql\catalogue\scripts\runpsql.bat” 三、第三种方式 1.加到环境变量 把“D:\soft\postg…

ubuntu vmware开启3d加速画面异常

在ubuntu上开启vmware&#xff0c;进入全屏就会出现左上角和右下角两个不同的画面&#xff0c;并来回闪&#xff0c;不使用3d加速&#xff0c;一切正常&#xff0c;但是画面模糊。在ubuntu18 20 22上测试&#xff0c;vmware 15 16 17问题依旧。 原因 经过测试&#xff0c;原…

【Java】认识异常

文章目录 一、异常的概念和体系结构1.异常的概念2.异常的体系结构3.异常的分类 二、异常的处理1.防御式异常2.异常的抛出3.异常的捕捉 三、异常的处理流程四、自定义异常类 一、异常的概念和体系结构 1.异常的概念 在Java中&#xff0c;将程序执行过程中发生的不正常行为称为…