【C语言】快速排序

news2024/11/18 9:24:01

文章目录

  • 一、hoare版本
  • 二、挖坑法
  • 三、前后指针法
  • 四、非递归快排
  • 五、快速排序优化
    • 1、三数取中选key值
    • 2、小区间优化
  • 六、代码测试

在这里插入图片描述

一、hoare版本

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

算法思想:

  1. 定义一个keyi存入随机一个数key的下标换到数组首元素,这里先直接默认key为数组首元素
  2. 定义一个left和一个right,分别存入数组首元素和尾元素的下标,用来移动交换
  3. 排升序我们让右边right先向左移动,找到比key的值小的元素则停下来换到left移动
  4. left向右移动,找到比key的值大的元素则停下
  5. 交换下标为left和right的元素
  6. 重复以上操作直到left与right相遇(相等)
  7. 交换key和下标为left的元素
  8. 此时key的左边都是比它小的数,右边都是比它大的数
  9. 再分别对左右序列进行以上的单趟排序,反复操作直到左右序列只有一个或者没有元素时停止操作,数列即可有序

hoare版本单趟排序图示:

在这里插入图片描述

hoare版本代码:

//交换
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//hoare版本
void QuickSort1(int* a, int begin, int end)
{
	//递归结束条件
	if (begin >= end)
	{
		return;
	}
	int keyi = begin;
	int left = begin;
	int right = end;
	//每趟排序直到左右相遇
	while (left < right)
	{
		//右边先走,找到比key值小的
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		//right找到比key值小的之后换到left走,找到比key值大的
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		//交换
		Swap(&a[left], &a[right]);
	}
	//将key值换到中间
	Swap(&a[keyi], &a[left]);
	//更新key
	keyi = left;
	//对左右序列继续排序
	QuickSort1(a, begin, keyi - 1);
	QuickSort1(a, keyi + 1, end);
}

整体流程图:

在这里插入图片描述

二、挖坑法

挖坑法思想:

  1. 先将第一个数据存在变量key中,将此处作为最开始的坑位,用下标hole记录
  2. 然后right开始向前走,找到比key值小的元素后停下,将此元素放进坑里(下标为hole处),然后此处变为坑,hole变为此时的right
  3. 然后left开始向后移动,找到比key值大的元素后停下,将此元素放进坑里(下标为hole处),然后此处变为坑,hole变为此时的left
  4. 然后又换回right移动,如此反复直到left与right相遇(left与right相遇的地方一定是坑)
  5. 然后将key放入left与right相遇的位置,也就是坑的位置,此时hole左边都是小于等于它的,右边都是大于等于它的
  6. 如此单趟排序便结束,然后继续对hole左右序列继续反复执行以上操作,直到左右序列只有一个或者没有元素时停止操作,数列即可有序

挖坑法单趟排序图示:

在这里插入图片描述

挖坑法代码:

//挖坑法
void QuickSort2(int* a, int begin, int end)
{
	//递归结束条件
	if (begin >= end)
	{
		return;
	}
	int left = begin;
	int right = end;
	int key = a[left];
	//坑最初与left一样在开始位置
	int hole = left;
	//每趟排序直到左右相遇
	while (left < right)
	{
		//右边先走,找到比key值小的
		while (left < right && a[right] >= key)
		{
			right--;
		}
		//将right找到的比key小的元素放进坑中
		a[hole] = a[right];
		//更新坑的位置
		hole = right;

		//然后左边走找到比key值大的元素停下来
		while (left < right && a[left] <= key)
		{
			left++;
		}
		//将left找到的比key大的元素放进坑中
		a[hole] = a[left];
		//更新坑的位置
		hole = left;
	}
	//将key放入坑中
	a[hole] = key;
	//对左右序列继续排序
	QuickSort2(a, begin, hole - 1);
	QuickSort2(a, hole+1, end);
}

三、前后指针法

前后指针法思想:

  1. 定义一个keyi存入随机一个数key的下标换到数组首元素,这里先直接默认key为数组首元素
  2. 定义一个prev为开头元素的下标,定义一个cur为prev下一个元素的下标
  3. cur下标处的值与key比较,直到cur找到比key小的值则停下来
  4. prev下标后移一位然后与cur下标处的值交换,然后cur后移一位(prev相当于前面比key小的那些数的最后一个的下标,所以要先后移一位再交换)
  5. cur继续寻找比key小的值,反复执行直到cur的值大于n
  6. 将key与prev下标处的值交换,此时key左边都是小于等于它的,右边都是大于等于它的
  7. 如此单趟排序便结束,然后继续对key左右序列继续反复执行以上操作,直到左右序列只有一个或者没有元素时停止操作,数列即可有序

前后指针法单趟排序图示:

在这里插入图片描述

前后指针法代码:

//交换
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//前后指针
void QuickSort3(int* a, int begin, int end)
{
	//递归结束条件
	if (begin >= end)
	{
		return;
	}

	int keyi = begin;
	int prev = begin;
	int cur = begin + 1;
	//每趟排序直到cur下标大于end
	while (cur <= end)
	{
		//cur找比key小的值
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	//将key换到中间
	Swap(&a[keyi], &a[prev]);
	//更新key的下标
	keyi = prev;
	//对左右序列继续排序
	QuickSort3(a, begin, keyi - 1);
	QuickSort3(a, keyi + 1, end);
}

快速排序是一种不稳定的排序,它的时间复杂度为O(N*logN),但最坏可以达到O(N2) ,它的空间复杂度为O(logN)

四、非递归快排

以上三种方法都是采用了分治法递归实现的快排,其实快速排序也可以非递归实现,非递归实现快排需要利用栈来实现

思路:

将数组首尾下标存入栈中,在循环中依次取出作为left和right对数组进行排序,然后对得到的key的左右两边序列也进行相同的操作,其中左边为left到keyi-1,右边为keyi+1到right,这些下标的入栈顺序需要看取出的顺序,如下面代码中是先取出后面元素下标的,所以入栈时要先入后面的,因为栈的特点是先入后出。

在这里插入图片描述

非递归快排代码:

(该代码中用到的栈需自己实现,C语言实现栈可参考:栈的实现)

//非递归快速排序
void QuickSortNonR(int* a, int begin, int end)
{
	//创建一个栈
	ST st;
	//初始化栈
	STInit(&st);
	//插入尾元素下标
	STPush(&st, end);
	//插入首元素下标
	STPush(&st, begin);
	//栈为空停下
	while (!STEmpty(&st))
	{
		//取出栈顶元素作为left
		int left = STTop(&st);
		//取出后在栈中删除
		STPop(&st);

		//取出栈顶元素作为right
		int right = STTop(&st);
		//取出后在栈中删除
		STPop(&st);

		int keyi = begin;
		//每趟排序直到左右相遇
		while (left < right)
		{
			//右边先走,找到比key值小的
			while (left < right && a[right] >= a[keyi])
			{
				right--;
			}
			//right找到比key值小的之后换到left走,找到比key值大的
			while (left < right && a[left] <= a[keyi])
			{
				left++;
			}
			//交换
			Swap(&a[left], &a[right]);
		}
		//将key值换到中间
		Swap(&a[keyi], &a[left]);
		//更新key的下标
		keyi = left;
		// 当前数组下标样子  [left,keyi-1] keyi [keyi+1, right]

		//右边还有元素,按顺序插入right和keyi+1
		if (keyi + 1 < right)
		{
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}
		//左边还有元素,按顺序插入keyi-1和left
		if (left < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}

	STDestroy(&st);
}

五、快速排序优化

1、三数取中选key值

前面三种快速排序的方法起初都要随机选取一个值作为key,我们之前是直接默认为数组首元素的,这样不够随机,容易出现最坏的情况,使得它的时间复杂度接近O(N2),所以我们可以写一个函数来选取这个key,使得它比较随机,而不是直接为首元素。

三数取中:

在一个数组最前面、最后面,中间这三个位置的数中选出大小处于中间的数

// 三数取中
int GetMidi(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[mid]>a[right]&&a[mid]<a[left])
		{
			return mid;
		}
		else
		{
			return left;
		}
	}
	else
	{
		if (a[left] > a[mid])
		{
			return left;
		}
		else if (a[mid] > a[left] && a[mid] < a[right])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}
}

在快排时用三数取中法选取key值再将它换到数组开头,可以有效避免出现最坏的情况,大大提升算法效率

2、小区间优化

当递归到数据较小时可以使用插入排序,使得小区间不再递归分割,降低递归次数

六、代码测试

//打印数组
void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void TestQuickSort1()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	QuickSort1(a, 0, sizeof(a) / sizeof(int) - 1);
	printf("hoare版本快速排序:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}
void TestQuickSort2()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	QuickSort2(a, 0, sizeof(a) / sizeof(int) - 1);
	printf("挖坑法快速排序:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}
void TestQuickSort3()
{
	int a[] = { 9,1,2,5,7,4,8,6,3,5,1,2,3,5,1,8,3 };
	QuickSort3(a, 0, sizeof(a) / sizeof(int) - 1);
	printf("前后指针法快速排序:\n");
	PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
	TestQuickSort1();
	TestQuickSort2();
	TestQuickSort3();
	return 0;
}

在这里插入图片描述

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

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

相关文章

云中的网络Qos

在 Linux 下&#xff0c;可以通过 TC 控制网络的 QoS&#xff0c;主要就是通过队列的方式。 第一大类称为无类别排队规则&#xff08;Classless Queuing Disciplines&#xff09;。pfifo_fast 分为三个先入先出的队列&#xff0c;称为三个 Band。根据网络包里面 TOS&#xff0…

VScode调试复杂C/C++项目

以前都是用的VScode调试c/cpp的单个文件的编译和执行, 但是一遇到大型项目一般就用gdb了, gdb的调试效率和VScode差距还是比较大的, 但最近发现VScode其实也能调试复杂的cpp项目, 所以记录一下. 首先明确一下几点: 首先cpp文件需要经过编译, 生成可执行文件, 然后通过运行/调…

百度搜索逐步恢复优质网站权限

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 从9月25日开始&#xff0c;有越来越多的站长和卢松松反馈&#xff0c;说他们的站可以正常入驻百度搜索资源平台了。我也试了试卢松松博客&#xff0c;果然&#xff0c;可以正常提交了。还是以前的…

iTOP-3568开发板Ubuntu下安装ADB工具

在虚拟机 Ubuntu 使用以下命令安装 ADB&#xff0c;如下图所示&#xff1a; sudo apt install adb 接下来进行测试&#xff0c;执行如下命令&#xff0c;确认 adb 安装是否成功。 adb version 如上图所示&#xff0c;执行命令能成功获取到 adb 版本信息&#xff0c;说明 adb …

防火墙网络接口下面多个外网地址,只有第一地址可以访问通其他不通

环境&#xff1a; 主备防火墙 8.0.75 AF-2000-FH2130B-SC 问题描述&#xff1a; 两台防火墙双击热备&#xff0c;高可用防火墙虚拟网络接口Eth4下面有多个外网地址&#xff0c;只有第一地址可以访问通其他不通 解决方案&#xff1a; 1.检查防火墙路由设置&#xff08;未解决…

3D WEB轻量化引擎HOOPS助力3D测量应用蓬勃发展:效率、精度显著提升

在3D开发工具领域&#xff0c;Tech Soft 3D打造的HOOPS SDK已经崭露头角&#xff0c;成为了全球领先的3D领域开发工具提供商。HOOPS SDK包括四种不同的3D软件开发工具&#xff0c;已成为行业的翘楚。 其中&#xff0c;HOOPS Exchange以其CAD数据转换的能力脱颖而出&#xff0c…

5.wifi开发【智能家居:上】,开发准备:智能开关灯,智能采集温湿,智能调彩灯

一。wifi智能家居项目开发 【开发准备1】&#xff1a;继电器控制开发 1.智能开关 器件准备&#xff1a;wifi&#xff08;esp8266&#xff0c;使用CP2102&#xff09;继电器 结果&#xff1a; 2.继电器工作原理 &#xff08;1&#xff09;继电器是一种自动电气开关 &#xff…

Multisim14.0仿真(二十六)基于LM555定时器的施密特触发器

一、仿真原理图&#xff1a; 二、仿真效果图&#xff1a;

01-工具篇-windows与linux文件共享

一般来说绝大部分PC上装的系统均是windows&#xff0c;为了开发linux程序&#xff0c;会在PC上安装一个Vmware的虚拟机&#xff0c;在虚拟机上安装ubuntu18.04&#xff0c;由于windows上的代码查看软件、浏览器&#xff0c;通信软件更全&#xff0c;我们想只用ubuntu进行编译&a…

【VUE复习·5】插值语法(使用 计算属性 的插值语法)

总览 1.计算属性 2.使用 methods 的 插值语法 3.使用 computed 的 插值语法 一、计算属性 1.解释 首先&#xff0c;如果我们要写一个插值语法&#xff0c;而 {{ }} 内的内容&#xff0c;是一个经过计算的值&#xff0c;那么按照原本 JS 的写法&#xff0c;应该是这样的&…

目标检测YOLO实战应用案例100讲-区域卷积网络在阴影环境目标检测上的研究与应用(下)

目录 5.2 阴影检测模块与街景检测模块实验设置 5.2.1 实验环境与工具 5.2.3 损失函数 5.2.4 实验具体流程 5.3 实验评估与对比 5.3.1 两类实验的评估标准 5.3.2 两类实验结果对比 阴影环境下街景目标检测系统的实现 6.1 系统概述 6.2 系统软硬件环境 6.3 功能模块设计 6.4 系统…

嵌入式Linux应用开发-IMX6ULL板硬件资源及开发环境

嵌入式Linux应用开发-IMX6ULL板硬件资源及开发环境 第一章 硬件资源1.1 板上资源1.2 板外模块 第二章 准备开发环境2.1 IMX6ULL 开发板接线与启动1) 连接串口线和电源线2) 安装 USB 串口驱动3) 选择启动方式4) 设置串口工具&#xff0c;启动开发板 第一章 硬件资源 iMX6ULL板是…

vlc将本地文件推流成ts实时流

推流 打开vlc &#xff0c;打开 媒体----打开网络串流 选择文件选项卡&#xff0c;打开本地文件 点击添加&#xff0c;选择本地的mp3文件 选择串流 点击下拉框&#xff0c;选择udp&#xff0c;点击右边的【添加】按钮 输入媒体流输出地址&#xff0c;点击【下一个】 选择正确的…

《C++ Primer》第4章 表达式(一)

参考资料&#xff1a; 《C Primer》第5版《C Primer 习题集》第5版 4.1 基础&#xff08;P120&#xff09; 表达式由一个或多个运算对象组成&#xff0c;对表达式求值将得到一个结果。字面量和变量是最简单的表达式。 4.1.1 基本概念&#xff08;P120&#xff09; C 定义了…

淘宝拍立淘插件转链和商业化图片生成接口介绍,图片搜索商品接口,按图搜索接口,图片识别商品接口介绍

淘宝拍立淘是淘宝网推出的一种搜索方式&#xff0c;通过拍立淘&#xff0c;用户可以输入文字描述或上传图片来搜索商品。拍立淘通过与淘宝网进行数据接入和授权&#xff0c;使用淘宝提供的API获取商品信息和操作权限&#xff0c;拍立淘使用图像识别技术&#xff0c;通过深度学习…

【Java 进阶篇】深入理解SQL的数据操作语言(DML)

SQL&#xff08;Structured Query Language&#xff09;是一种用于管理和操作关系数据库的强大语言。SQL语言被分为多个子语言&#xff0c;其中之一是DML&#xff08;Data Manipulation Language&#xff09;&#xff0c;用于执行与数据的操作和管理相关的任务。在本文中&#…

Ci2451-2.4g无线MCU收发芯片

Ci2451 是一款集成无线收发器和8位RISC(精简指令集)MCU的SOC芯片。 无线MCU解决方案,集成丰富的MCU资源、更小尺寸,来满足设计中的各种内存、功率、尺寸要求,充分缩短2.4GHz无线产品设计周期并优化产品成本。 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff…

聚观早报 | 智界S7正式亮相;ChatGPT重磅更新

【聚观365】9月27日消息 智界S7正式亮相 ChatGPT重磅更新 PICO应用数量已超530款 泡泡玛特上半年海外营收大增 亚马逊投资Anthropic40亿美元 智界S7正式亮相 在日前举行的华为秋季全场景新品发布会上&#xff0c;华为智选车业务首款轿车智界S7正式亮相&#xff0c;定位高…

华为云云耀云服务器L实例评测|使用华为云耀云服务器L实例的CentOS部署Docker并运行Tomcat应用

目录 前言 步骤1&#xff1a;登录到华为云耀云服务器L实例 步骤2&#xff1a;安装Docker 并验证Docker安装 步骤3&#xff1a;拉取Tomcat镜像并运行Tomcat容器 步骤4&#xff1a;放行8080端口 步骤5&#xff1a;访问tomcat 步骤6&#xff1a;管理Tomcat容器 小结 前言 …

干洗店收银系统、上门洗鞋小程序,干洗店会员管理系统

干洗店收银系统、上门洗鞋小程序&#xff0c;干洗店会员管理系统可以增加洗衣店的优势&#xff0c;让干洗店回本快&#xff0c;通过发行会员卡卡促销回收资金带来效益&#xff0c;减少投资压力。 干洗店管理软件功能介绍 预约上门收衣 智能提醒&#xff0b;员工手机APP抢单AP…