数据结构排序——详解快排及其优化和冒泡排序(c语言实现、附有图片与动图示意)

news2025/1/10 12:19:48

上次讲了选择排序和堆排序:数据结构排序——选择排序与堆排序

今天就来快排和冒泡


文章目录

  • 1.快排
    • 1.1基本介绍
    • 1.2不同的分区方法及代码实现
      • 1.2.1Hoare版
      • 1.2.2挖坑版
      • 1.2.3 前后指针版
    • 1.3快排的优化
      • 1.3.1三数取中选key
      • 1.3.2递归到小的子区间时,可以考虑使用插入排序
      • 1.3.3大量重复数据采用三路划分
    • 1.4快排非递归
    • 2.冒泡排序


1.快排

1.1基本介绍

快速排序(Quick Sort)是一种常用的排序算法,它是由英国计算机科学家Tony Hoare于1959年发明的。快速排序的基本思想是通过分治的策略将一个数组分成两个子数组,然后分别对这两个子数组进行排序。具体步骤如下:

  1. 选择一个基准元素(通常是数组的第一个元素,右边先行)。
  2. 将数组分割成两部分,使得左边的元素都小于等于基准元素,右边的元素都大于基准元素。这个过程叫做分区(Partition)
  3. 对分割后的两个子数组分别重复步骤1和2(利用递归),直到子数组的大小为1或0,此时数组已经有序

==优化:==如果本身就很接近有序,那效率就慢了(一个逆序变升序,keyi就一直在左边,递归也只有右侧,所以选择三个数来找中间大小,能让keyi尽量向数组中间靠近),所以设计了Getmid函数来取中间大小的数

1.2不同的分区方法及代码实现

1.2.1Hoare版

使用两个索引,一个从数组的左边开始向右移动,另一个从数组的右边开始向左移动,直到它们相遇。在这个过程中,如果左指针指向的元素大于基准元素且右指针指向的元素小于基准元素,则交换这两个元素。当两个指针相遇时,将基准元素(keyi指向的)与相遇位置的元素交换,这样基准元素就归位了

请添加图片描述

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

int GetMid(int* a,int left, int right)//找中间的
{
	// a[left]      a[mid]           a[right]
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])  // mid是最大值
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[left] < a[right])
		{
			return left;
		}
		else if (a[mid] < a[right])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
}
//一次排序
int OneSort1(int* a, int left, int right)//使keyi位置的元素处于正确的位置上
{
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);//现在left处是三者的中间值了
	//左边第一个为key,右边先走才能保证相遇处比啊a[keyi]小
	int keyi = left;
	while (left < right)
	{
		while (a[right] >= a[keyi] && left < right)//右边先走
		{
			right--;
		}
		while (a[left] <= a[keyi] && left < right)//左侧找大的
		{
			left++;
		}
		Swap(&a[left], &a[right]);//找到一个大和一个小的就交换
	}
	Swap(&a[keyi], &a[left]);//把keyi放相遇位置
	return left;//返回相遇的索引
}

void QuickSort(int* a, int begin, int end)//升序
{
	if (begin >= end)
	{
		return;
	}
	// [begin, keyi-1] keyi [keyi+1, end]
	int keyi = OneSort1(a, begin, end);//找到keyi索引,才能分左右
	QuickSort(a, begin, keyi - 1);//左侧
	QuickSort(a, keyi + 1, end);//右侧
}

int main()
{
	int a[] = { 6,1,2,7,9,3,4,5,10,8 };
	printf("排序前:");
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	QuickSort(a, 0,sizeof(a) / sizeof(int)-1);
	printf("排序后:");
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

请添加图片描述

1.2.2挖坑版

选择基准元素后,先将基准元素保存到临时变量中,然后使用左右索引的方式找到需要交换的元素,将这些元素填入基准元素的位置,最后将基准元素填入最后一个空出来的位置

请添加图片描述

int OneSort2(int* a, int left, int right)//挖坑
{
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);//现在left处是三者的中间值了

	int key = a[left];//保存基准元素
	int hole = left;//储存坑下标,不能直接赋值为0
	while (left < right)
	{
		while (a[right] >= key && left < right)//右边先走,没有等号两侧出现相同值会死循环
		{
			right--;
		}//找到了就去赋值到第一个“坑”
		a[hole] = a[right];
		hole = right;//更新坑
		while (a[left] <= key && left < right)//左侧找大的
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	Swap(&key, &a[left]);//把keyi放相遇位置
	return left;//返回相遇的索引
}

1.2.3 前后指针版

pre指向第一个,cur指向下一个。cur找小后,pre++,然后交换(做到大的向后推),最后cur出数组结束

prev的情况有两种:

  1. 在cur还没遇到比key大的值时候,prev紧跟着cur
  2. 在cur还遇到比key大的值时候,prev在比key大的一组值的前面

本质是把一段大于key的区间,往后推,同时小的甩到左边去

请添加图片描述
请添加图片描述

int OneSort3(int* a, int left, int right)//挖坑
{
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);

	int keyi = left;
	int pre = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			pre++;
			Swap(&a[cur], &a[pre]);
		}
		cur++;
	}
	Swap(&a[pre], &a[keyi]);
	return pre;
}

1.3快排的优化

1.3.1三数取中选key

从待排序数组的首、尾和中间位置各选取一个元素,然后取它们的中间值作为基准元素,确保选择的基准元素相对中间位置的元素更为接近

代码在Hoare版已经展示过了

int GetMid(int* a,int left, int right)//找中间的
{
	// a[left]      a[mid]           a[right]
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])  // mid是最大值
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[left] < a[right])
		{
			return left;
		}
		else if (a[mid] < a[right])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
}

1.3.2递归到小的子区间时,可以考虑使用插入排序

当递归到小的子区间时,可以考虑使用插入排序来优化快速排序。因为插入排序在小规模数据上的排序效率通常比快速排序更高==(当数量比较少时,也没必要在递归好几层了)==

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

int OneSort3(int* a, int left, int right)//挖坑
{
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);

	int keyi = left;
	int pre = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			pre++;
			Swap(&a[cur], &a[pre]);
		}
		cur++;
	}
	Swap(&a[pre], &a[keyi]);
	return pre;
}

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

	if ((end - begin + 1) > 10)
	{
		// [begin, keyi-1] keyi [keyi+1, end]
		int keyi = OneSort3(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
	else
	{
		//用插入排序
		InsertSort(a + begin, end - begin + 1);
	}
}

1.3.3大量重复数据采用三路划分

快速排序的三路划分通过将数组分为小于等于=和大于基准元素的三个部分==,有效地处理了重复元素,提高了算法的效率

快速排序的三路划分算法的基本思想是使用三个指针/下标来维护三个区域:小于基准元素的区域、等于基准元素的区域和大于基准元素的区域。在每一次遍历数组的过程中,将数组分为这三个区域,并将指针移动到合适的位置。最终,数组会被划分成小于基准元素、等于基准元素和大于基准元素的三个部分

基本步骤:

请添加图片描述

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

	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);
	int cur = left + 1;
	int key = a[left];//储存一下,后面比较来用,用a[left]会被替代
	while (cur <= right)
	{
		if (a[cur] < key)
		{
			Swap(&a[cur], &a[left]);
			cur++;
			left++;
		}
		else if (a[cur] == key)
		{
			cur++;
		}
		else
		{
			Swap(&a[cur], &a[right]);
			right--;
		}
	}
	QuickSort(a, begin, left - 1);
	QuickSort(a, right + 1, end);
}

1.4快排非递归

快速排序的非递归实现通常使用栈来模拟递归调用的过程。在快速排序的递归实现中,每次递归调用都将函数参数压入栈中,然后在递归返回时再从栈中弹出参数(二者性质相同)。

非递归实现则需要手动维护一个栈,将需要处理的子序列的起始和结束位置压入栈中,然后循环处理栈中的元素,直到栈为空(递归一次就用一次)

void QuickSortNonR(int* a, int begin, int end)//利用栈,先想好先排左侧再排右侧
{
	ST st;
	STInit(&st);
	STPush(&st,end);//把各个区间的两侧的索引(整形)插入进Stack中
	STPush(&st,begin);//栈(后进先出),先排左侧所以后入左
	while (!STEmpty(&st))
	{
		int left = STTop(&st);
		STPop(&st);
		int right = STTop(&st);
		STPop(&st);

		int keyi = OneSort1(a, left, right);//得到基准元素下标
		// [begin, keyi-1] keyi [keyi+1, end]
		if (keyi + 1 < right)//等于说明就一个,没必要
		{
			STPush(&st, right);
			STPush(&st, keyi);
		}
		if (left < keyi-1)
		{
			STPush(&st, keyi-1);
			STPush(&st, left);
		}
	}
	STDestroy(&st);
}

2.冒泡排序

它重复地遍历要排序的列表,比较每对相邻的元素,并按照大小顺序交换它们。重复这个过程直到整个列表排序完成

步骤:

  1. 从列表的第一个元素开始,依次比较相邻的两个元素,如果它们的顺序不正确就交换它们。
  2. 继续遍历列表,重复上述比较和交换的过程,直到没有任何一对相邻元素需要交换为止。
  3. 列表排序完
void BobbleSort(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]);
			}
		}
	}
}

好啦,这次内容就到这里啦,下次带来归并排序,感谢大家支持!!!

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

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

相关文章

python查看安装包所依赖的包版本

python查看安装包所依赖的包版本 1. 找到包的位置 site-packages 文件夹的位置import gevent # ctrl 点进去就行了2. 返回包环境文件夹的上一层&#xff0c;会看到下面有一个 gevent-{版本号}.dist-info的文件夹3. 查看 METADATA 文件Requires-Dist: greenlet >2.0.0 ...#…

Vue-13、Vue深度监视

1、监视多级结构中某个属性的变化 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>watch深度监视</title><script type"text/javascript" src"https://cdn.jsdelivr.net/npm…

鸿蒙APP适配的设备类型

鸿蒙OS&#xff08;HarmonyOS&#xff09;是一款面向多设备的分布式操作系统&#xff0c;因此鸿蒙APP可以适配多种设备类型。以下是一些鸿蒙APP可能需要适配的设备类型&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c…

通过WebSocket实现异步导出

前言 本篇文章记录大批量数据导出时间过长,导致接口请求超时问题。 解决思路与流程 前端点击导出按钮时开启websocket连接逻辑处理异步执行文件处理好后&#xff0c;得到文件的绝对路径后台socket通知前端绝对路径的地址前端下载文件到浏览器 文章目录 本地环境 一、WebSock…

树形table 10w+数据每次点击要十几秒 懒加载解决点击卡顿

el-table 树形table 10w数据每次点击要十几秒 懒加载解决点击卡顿 //vue 表头要增加lazy :load"load" <el-tablelazystripeborderref"table"row-key"id":load"load":data"tableData":tree-props"{ children: child…

MySQL之数据的导入、导出远程备份

目录 一. navicat的导入、导出 1.1 导入 1.2 导出 二. mysqldump命令导入、导出 2.1 导出 2.2 导入 三. LOAD DATA INFILE 命令导入、导出 3.1 设置 3.2 导出 3.3 导入 3.4 查看secure_file_priv设置 四. 远程备份 4.1 导出 4.2 导入 五. 思维导图 一. navicat的导入、导…

ChatGPT4 助力 Python 数据分析与可视化、人工智能建模及论文高效撰写

2022年11月30日&#xff0c;可能将成为一个改变人类历史的日子——美国人工智能开发机构OpenAI推出了聊天机器人ChatGPT3.5&#xff0c;将人工智能的发展推向了一个新的高度。2023年4月&#xff0c;更强版本的ChatGPT4.0上线&#xff0c;文本、语音、图像等多模态交互方式使其在…

如何用GPT写代码?

详情点击链接&#xff1a;如何用GPT写代码&#xff1f; 一OpenAI 1.最新大模型GPT-4 Turbo 2.最新发布的高级数据分析&#xff0c;AI画图&#xff0c;图像识别&#xff0c;文档API 3.GPT Store 4.从0到1创建自己的GPT应用 5. 模型Gemini以及大模型Claude2二定制自己的GPTs…

12、JVM高频面试题

1、JVM的主要组成部分有哪些 JVM主要分为下面几部分 类加载器&#xff1a;负责将字节码文件加载到内存中 运行时数据区&#xff1a;用于保存java程序运行过程中需要用到的数据和相关信息 执行引擎&#xff1a;字节码文件并不能直接交给底层操作系统去执行&#xff0c;因此需要…

24年教资报名千万不要卡在照片上,看看照片有啥要求?

每年都有很多人教资报名卡在照片上&#xff0c;总是审核不通过&#xff0c;24年教资报名千万不要卡在照片上&#xff0c;快来看看照片有啥要求吧&#xff1f;如果还没有准备&#xff0c;可以支付宝搜索【亿鸣证件照】或者微信搜索【随时照】小程序&#xff0c;然后进入小程序的…

麒麟Linux安装新版微信的方法

麒麟Linux系统目前有v10和v10sp1&#xff0c;注意&#xff0c;恶趣味的是v10和v10sp1竟然不通用&#xff0c;这导致了一些国产程序出现运行bug,通过系统自带的麒麟商店无法图形界面安装&#xff0c;甚至搜索不到微信等等一系列问题&#xff0c;易用度确实很差。 解决办法也很简…

七人拼团模式:社交电商的新面目

随着社交电商的快速发展&#xff0c;七人拼团模式作为一种创新的商业模式&#xff0c;正在引领着一场消费革命。它不仅改变了消费者的购物习惯&#xff0c;还为电商平台带来了巨大的流量和收入。本文将深入探讨七人拼团模式的玩法、优势、发展趋势以及如何为电商平台带来可观收…

Netty-Netty基础应用与了解

前言 Netty 的优势 1、 API 使用简单&#xff0c;开发门槛低&#xff1b; 2、功能强大&#xff0c;预置了多种编解码功能&#xff0c;支持多种主流协议&#xff1b; 3、定制能力强&#xff0c;可以通过 ChannelHandler 对通信框架进行灵活地扩展&#xff1b; 4、性能高…

BGP协议概念与配置(HCIP完整版)

目录 一、BGP协议基础 1、路由的分类 2、为什么要使用BGP协议 3、BGP概述 4、AS号 二、BGP协议概述 1、场景 2、作用 3、优势 4、BGP邻居类型 5、BGP特征 6、BGP报文类型 7、BGP工作过程 8、BGP状态 9、BGP路由默认优先级为255 10、BGP邻居关系建立的完整流程 …

【深度学习:视觉基础模型】视觉基础模型 (VFM) 解释

【深度学习&#xff1a;视觉基础模型】视觉基础模型 VFM 解释 了解视觉基础模型从 CNN 到 Transformer 的演变自我监督和适应能力 流行的视觉基础模型DINO&#xff08;自蒸馏&#xff0c;无标签&#xff09;SAM&#xff08;分段任意模型&#xff09;SegGPTMicrosofts Visual Ch…

【uview2.0】Keyboard 键盘 与 CodeInput 验证码输入 结合使用 uview

https://www.uviewui.com/components/codeInput.html &#xff08;CodeInput 验证码输入&#xff09; https://www.uviewui.com/components/keyboard.html &#xff08;Keyboard 键盘&#xff09; <u-keyboard mode"number" :dotDisabled"true" :show&q…

分割、合并、转换、重组:强大的自部署 PDF 处理工具 | 开源日报 No.143

Stirling-Tools/Stirling-PDF Stars: 13.2k License: GPL-3.0 这个项目是 Stirling-PDF&#xff0c;它是一个功能强大的基于本地主机的 Web PDF 操作工具&#xff0c;使用 Docker 进行部署。其主要功能包括分割、合并、转换、重新组织 PDF 文件以及添加图片、旋转和压缩等多种…

供应链+低代码,实现数字化【共赢链】转型新策略

在深入探讨之前&#xff0c;让我们首先明确供应链的基本定义。供应链可以被理解为一个由采购、生产、物流配送等环节组成的网状系统&#xff0c;它始于原材料的采购&#xff0c;经过生产加工&#xff0c;最终通过分销和零售环节到达消费者手中。 而数字化供应链&#xff0c;则是…

STM32F103RCT6使用数据手册及应用示例程序分享

STM32F103RCT6是意法半导体&#xff08;STMicroelectronics&#xff09;推出的一款Cortex-M3内核的高性能微控制器。它具有丰富的外设功能和强大的处理能力&#xff0c;适用于多种应用场景。 要进行手册数据分析&#xff0c;首先需要下载并查阅STM32F103RCT6的技术参考手册。可…

三菱plc学习入门(三,FB模块)

小编很抱歉&#xff0c;因为小编是以基恩士&#xff0c;三菱的plc一起学习并找发现不同&#xff01;&#xff01;&#xff01;并结合工作的案例来进行学习&#xff0c;所以内容上与系统的学习还是存在差异。如果只是单独的学习此篇文章&#xff0c;如果对您有帮助&#xff0c;欢…