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

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

今天就来快排和冒泡


文章目录

  • 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]);
			}
		}
	}
}

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

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

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

相关文章

09、Kafka ------ 通过修改保存时间来删除消息(retention.ms 配置)

目录 通过修改保存时间来删除消息★ 删除指定主题的消息演示1、修改kafka检查过期消息的时间间隔2、修改主题下消息的过期时间3、查看修改是否生效4、先查看下主题下有没有消息5、添加几条消息看效果6、查看消息是否被删除 ★ 恢复主题的retention.ms配置1、先查看没修改前的te…

NLP(十八):LLM 的推理优化技术纵览

原文&#xff1a;NLP&#xff08;十八&#xff09;&#xff1a;LLM 的推理优化技术纵览 - 知乎 目录 收起 一、子图融合&#xff08;subgraph fusion&#xff09; 1.1 FasterTransformer by NVIDIA 1.2 DeepSpeed Inference by Microsoft 1.3 MLC LLM by TVM 二、模型压…

可视可交互!在全志H618上用OpenCV读取图像显示到PyQt5窗口上

OpenCV能够处理图像、视频、深度图像等各种类型的视觉数据&#xff0c;在某些情况下&#xff0c;尽管OpenCV可以显示窗口&#xff0c;但PyQt5可能更适合用于创建复杂的交互式应用程序&#xff0c;而自带GPU的H618就成为了这些图像显示的最佳载体。 这里分享一个代码&#xff0…

实战(CVE-2023-42442)JumpServer未授权访问漏洞

声明&#xff1a; 该文章仅供网络安全领域的学习使用&#xff0c;请勿利用文章内的相关技术从事任何非法行为。 测试资产为日本IP&#xff0c;因此未做任何打码处理&#xff0c;我们只进行poc&#xff08;漏洞验证&#xff09;&#xff0c;不进行exp&#xff08;漏洞利用&#…

使用numpy处理图片——模糊处理

大纲 高斯模糊方框模糊其他算法median_filtermaximum_filterminimum_filterpercentile_filterrank_filtergaussian_laplacecorrelatemorphological_laplacewhite_tophatmorphological_gradientblack_tophat 在《使用numpy处理图片——滤镜》一文中&#xff0c;我们尝试了去掉一…

Python文件自动化处理

os模块 Python标准库和操作系统有关的操作创建、移动、复制文件和文件夹文件路径和名称处理 路径的操作 获取当前Python程序运行路径不同操作系统之间路径的表示方式 windows中采用反斜杠(\)作为文件夹之间的分隔符 Mac和Linux中采用斜杠(/)作为文件夹之间的分隔符 把文件…

cuda12.0 安装 pytorch

前两天买的y7000p到了&#xff0c;然后就要重新配下环境。 流程如下 首先下载miniconda &#xff0c;我下的是python3.8的创建自己的自定义环境检查自己的cuda版本&#xff0c;我的是cuda:12.0然后再pytorch上找到对应cuda版本的进行下载&#xff0c;pip install或者conda in…

Fluids —— Fluid sourcing

目录 FLIP Boundary: None FLIP Boundary: Velocity FLIP Boundary: Pressure Other methods SOP FLIP流体为生成粒子提供三种Boundary方式&#xff08;None、Velocity、Pressure&#xff09;&#xff1b; 注&#xff0c;源对象必须是封闭且实体3D或体积对象&#xff0c;开…

(超详细)2-YOLOV5改进-添加SimAM注意力机制

1、在yolov5/models下面新建一个SimAM.py文件&#xff0c;在里面放入下面的代码 代码如下&#xff1a; import torch import torch.nn as nnclass SimAM(torch.nn.Module):def __init__(self, e_lambda1e-4):super(SimAM, self).__init__()self.activaton nn.Sigmoid()self…

【局域网window10系统搭建共享文件夹或与手机共享】

局域网window10系统搭建共享文件夹或与手机共享 1、Window 10之间搭建共享文件夹1.1 ping通两台window 10 电脑1.2 创建共享账号&#xff08;window 10专业版&#xff09;1.3 创建共享文件夹以及配置1.4访问共享文件夹 2、手机访问window10 共享文件夹&#xff08;结合步骤一&a…

vulhub中的Nginx 文件名逻辑漏洞(CVE-2013-4547)

目录 Nginx 文件名逻辑漏洞&#xff08;CVE-2013-4547&#xff09; 1.cd到CVE-2013-4547 2.执行docker-compose up -d 3.查看靶场是否开启成功 4.访问浏览器 5.上传含有一句话木马的图片 6.burp抓包 7.在shell.gif加空格 8.放包 9.访问路径 10.继续抓包 11.在aa后面…

【vitest 单元测试】如何蹭 ant-design-web3 的PR

这篇文章分享单测经验&#xff0c;希望你能收获到有用的单测知识或者pr思路&#xff0c;填补单测的过程可以深刻理解组件内部的每一个流程&#xff0c;相信一定有所收获。 ant-design-web3 前言查看单测覆盖情况运行命令&#xff0c;本地会生成一份临时目录通过live server打开…

如何用GPT制作PPT和写代码?

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

《路由与交换技术》---简答题

1、什么是STP&#xff1f;解决什么问题&#xff1f; STP代表生成树协议&#xff08;Spanning Tree Protocol&#xff09;。它是用于在计算机网络中解决环路问题的一种协议。 STP的主要目标是消除环路&#xff0c;保持网络的稳定性和可靠性&#xff0c;同时提供冗余路径以实现网…

Python爬虫-新能源汽车对应的“年份月份”销量榜

前言 本文是该专栏的第15篇,后面会持续分享python爬虫干货知识,记得关注。 在本专栏前面,笔者有单独详细介绍采集新能源汽车销量榜,感兴趣的同学,可以往前翻阅查看《Python爬虫-新能源汽车销量榜》。而之后,也有很多同学单独私信,那如果要单独采集某个年份,某个月份的…

【论文综述】一篇关于GAN在计算机视觉邻域的综述

前言 这是一篇关于GAN在计算机视觉领域的综述。 正文 生成对抗网络是一种基于博弈论的生成模型&#xff0c;其中神经网络用于模拟数据分布。应用领域&#xff1a;语言生成、图像生成、图像到图像翻译、图像生成文本描述、视频生成。GAN模型能够复制数据分布并生成合成数据&a…

用React给XXL-JOB开发一个新皮肤(二):目录规划和路由初始化

目录 一. 简述二. 目录规划三. Vite 配置 3.1. 配置路径别名3.2. 配置 less 四. 页面 4.1. 入口文件4.2. 骨架文件4.3. 普通页面 五. 路由配置六. 预览启动 一. 简述 上一篇文章我们介绍了项目初始化&#xff0c;此篇文章我们会先介绍下当前项目的目录规划&#xff0c;接着对…

了解统计分类中的贝叶斯理论误差限

一、介绍 统计分类和机器学习领域正在不断发展&#xff0c;努力提高预测模型的准确性和效率。这些进步的核心在于一个基本基准&#xff0c;即贝叶斯理论误差极限。这个概念深深植根于概率和统计学&#xff0c;是理解分类算法的局限性和潜力的基石。本文深入探讨了贝叶斯错误率的…

实现多级缓存(Redis+Caffeine)

文章目录 多级缓存的概述多级缓存的优势 多级缓存的概述 在高性能的服务架构设计中&#xff0c;缓存是一个不可或缺的环节。在实际的项目中&#xff0c;我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中&#xff0c;只有当缓存的访问没有命中时再查询数据库。在…

java基础之函数

函数 概念 是一段具有特定功能的代码, 特点为可以多次执行.通常情况下一个函数对应一个功能 语法 访问修饰符 static 返回值类型 函数名(形参列表){//操作语句 } public static void 函数名(){} 位置 类以内,其他函数以外,与主函数平级 调用 自定义函数必须经过调用才…