快速排序(排序中篇)

1.快速排序的概念及实现

2.快速排序的时间复杂度

3.优化快速排序

4.关于快速排序的细节

5.总代码

1.快速排序的概念及实现

1.1快速排序的概念

快速排序的单趟是选一个基准值,然后遍历数组的内容把比基准值大的放右边,比基准值小的放在左边,下面的动图可以简单了解是这么排序的。

选第一个6作为基准,然后让俩个变量作为下标值去遍历数组,L要选出一个比key(6)要大的值然后停下,R要找到一个比key要小的值然后停下,最后把L和R停下的位置对应的值进行交换,交换完后有继续走,直到L与R相遇,最后把碰面的地方对应的值与key交换,完成一次单趟的快排 ,这样key的左边都是比key小的值,而右边都是比key大的值,与之前比较变得相对有序。

走完单趟后,后面就会以key的左右边重读第一次操作,就是把数组以key为分界线分为俩个数组(逻辑上),再选出一个基准值,使得基准值的俩边要么比key大要么比key小,这样就会想到递归,一直分到后面就会出现只有一个值的数组,或者不存在的(后面会提到),递归完后的数组就是排好序的了。

1.2快速排序的实现

代码:

#include<stdio.h>
void swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void QuickSort(int* a,int left, int right)
{
	if (left >= right)
		return;
	int begin = left;
	int end =right;
	int keyi = left;
	while (begin< end)
	{
		
		while (begin < end && a[keyi] <= a[end])
		{
			end--;
		}
		while (begin < end && a[keyi] >= a[begin])
		{
			begin++;
		}
		swap(&a[begin], &a[end]);
	}
	swap(&a[keyi], &a[begin]);
	keyi = begin;
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}
int main()
{
	int arr[] = { 11,7,5,9,6,1,4,3,8,8,8,101 };
	int size = sizeof(arr) / sizeof(arr[0]);
	QuickSort(arr,0,size-1);
	for (int i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

代码分析:

创建begin和end来作为L和R去遍历数组,while循环执行的条件是begin<end(这个是begin和end相遇的情况)与基准值比end下标对应的值小,满足就end--(就是往前走),begin就去找比基准值大的值才会执行条件,否则就begin++(往后走),但俩个while都执行完,就说明L与R都停下来了,就可以进行交换,交换完后while循环结束,出循环后就交换基准值与相遇地方对应的值,然后递归去keyi两边的数组排序,则keyi+-1就是它们的新边界,最前面的if判断是否存在可以排序,因为一个和不存在是排不了的。

下图是不存在的情况:

2.快速排序的时间复杂度

本文只是简单计算快速排序的时间复杂度。

 

3.优化快速排序 

3.1三数取中

快速排序遇到有序的数组就会出现问题,时间复杂度会变为O(N^2).

 基准值到最后还是原来的位置,则递归时,每次只会去掉第一个元素,不会像对半分那种,这样每层大约n个,层数也为n个,时间复杂度就为O(N^2),速度会慢很多,所以基准值不应该每次取第一个,可以用随机函数来获取一个拟随机数作为key值,但是也是有可能是最小或者最大(在排升序或者降序会慢),所以用三数取中的方法来定key,但是得到key值还是要和第一个值交换一下,保证上面的代码逻辑不变,假如下标为3的值适合作key就把它和第一个交换。

在有序的情况下插入排序都比快速排序快很多,而且在数据量大的时候会发生栈溢出,是一直递归开辟空间而导致的。

三数取中就是在数组中找到一个值不是最大也不是最小。

三数取中代码实现:

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

代码分析:

就是在数组的最左右边及它们和一半值共三个数,然后比较三个值找到处于中间值的下标,这个值一定不是最小或者最大,比较数组的值返回下标,这样尽管处理有序数组也不会有栈溢出的风险,也能提高运行速率。

 3.2小区间优化

因为在二叉树中知道越到后面递归的次数越多,最下面的一层是占总的约一半,而快速排序也有这样的问题,在区间小的时候递归会非常多,所以在小区间就可以用其他的排序方法来代替,但区间小于一个范围时可以采用插入排序来排。

代码实现:

void QuickSort(int* a,int left, int right)
{
	if (left >= right)
		return;
	if ((right - left + 1) < 10)
	{
		InsertSort(a+left, right - left + 1);
	}
	else
	{
		int begin = left;
		int end = right;
		int keyi = GetMidi(a, left, right);
		while (begin < end)
		{

			while (begin < end && a[keyi] <= a[end])
			{
				--end;
			}
			while (begin < end && a[keyi] >= a[begin])
			{
				++begin;
			}
			swap(&a[begin], &a[end]);
		}
		swap(&a[keyi], &a[begin]);
		keyi = begin;
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
	
}

这里需要注意的是用插入排序时参数应该是a+left而不是a,因为在递归过程中数组已经被分成很多个区间了(逻辑上),插入排序的是指定位置且指定大小排。 

4.关于快速排序的细节

在代码实现里是让R先走的,R停下来才到L走,都停下来才交换,最后在交换L和key的值,为什么key的值一定比L与R相遇的位置对应的值小呢?

下面是分析:

1.L遇R:R先走然后找到了比key小的值停下来了,L开始走,但是没有找到比key大的值,最后于R相遇,此时相遇的地方对应的值是比key小的。

2.R遇L:R找到比key小的值停下来,L也遇到比key大的值停下来了,然后交换,此时R继续走,但是没有找到比key小的值了,就会一直走与L相遇,此时L所在的位置是上一次交换完的位置,也就是说L的位置的值原本是在R上一次停下来的位置对应的值,而这个值是一定比key小的,不然R是不会停下来的。

综上可知,最后L所在的位置是一定小于key对应的值的。

5.总代码:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>


void swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

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 (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
				break;
		}
		a[end + 1] = tmp;
	}
}
int GetMidi(int*a,int left, int right)
{
	int midi = (left + right) / 2;
	if (a[midi] < a[right])
	{
		if (a[midi] > a[left])
		{
			return midi;
		}
		else
		{
			if (a[left] > a[right])
				return right;
			else
				return left;
		}
	}
	else//a[midi]>a[right]
	{
		if (a[midi] > a[left])
		{
			if (a[right] > a[left])
				return right;
			else
				return left;
		}
		else
			return midi;
	}
}
void QuickSort(int* a,int left, int right)
{
	if (left >= right)
		return;
	if ((right - left + 1) < 10)
	{
		InsertSort(a+left, right - left + 1);
	}
	else
	{
		int begin = left;
		int end = right;
		int keyi = GetMidi(a, left, right);
		while (begin < end)
		{

			while (begin < end && a[keyi] <= a[end])
			{
				--end;
			}
			while (begin < end && a[keyi] >= a[begin])
			{
				++begin;
			}
			swap(&a[begin], &a[end]);
		}
		swap(&a[keyi], &a[begin]);
		keyi = begin;
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
	
}

void TestOP()
{
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		// Öظ´²»¶à
		a1[i] = rand() + i;
		// Öظ´½Ï¶à
		//a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
	}

	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin5 = clock();
	QuickSort(a1, 0, N - 1);
	int end5 = clock();
	printf("InsertSort:%d\n", end1 - begin1);
	printf("QuickSort:%d\n", end5 - begin5);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
}

int main()
{
	int arr[] = { 11,7,5,9,6,1,4,3,8,8,8,101 };
	int size = sizeof(arr) / sizeof(arr[0]);
	//TestOP();

	//InsertSort(arr, size);
	QuickSort(arr,0,size-1);
	for (int i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

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

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

相关文章

编译原理【第四+七章】

考试题 1、简述语法制导翻译的基本思想 将语法分析和语义分析结合起来&#xff0c;通过语法规则驱动语义动作执行&#xff0c;用于将源程序翻译成目标代码或中间代码。 通过使用属性和语义动作&#xff0c;编译器可以在语法分析的同时生成目标代码或中间代码&#xff0c;实现…

网络原理——TCP/IP--数据链路层,DNS

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 目录 数量链路层目的地址和原地址类型校验和 DNS 数量链路层 主要的协议是以太网协议.一个横跨数据链路层和 物理层的协议,既包含了数据链路层的内容, 也包含了⼀些物理层的内容 我们来了解一…

STM32作业实现(五)温湿度传感器dht11

目录 STM32作业设计 STM32作业实现(一)串口通信 STM32作业实现(二)串口控制led STM32作业实现(三)串口控制有源蜂鸣器 STM32作业实现(四)光敏传感器 STM32作业实现(五)温湿度传感器dht11 STM32作业实现(六)闪存保存数据 STM32作业实现(七)OLED显示数据 STM32作业实现(八)触摸按…

官网上线,一款令人惊艳的文本转语音模型:ChatTTS

近日&#xff0c;一个名为 ChatTTS 文本转语音模型的项目在github上横空出世&#xff0c;一经推出便引发极大关注&#xff0c;短短四天时间&#xff0c;已经狂揽了14.2k的Start量。 ChatTTS是一款专为对话场景设计的支持中英文的文本转语音&#xff08;TTS&#xff09;模型&…

AGM DAP-LINK 离线烧录报错信息分析

DAP-LINK 支持离线烧录。 即&#xff1a;先把要烧录的bin 烧录到DAP-LINK 中&#xff1b;然后DAP-LINK 可以脱离PC&#xff0c;上电后通过按键对目标板进行烧录。 CMSIS-DAP模式 跳线JGND断开&#xff0c;状态LED D4快闪&#xff0c;D3常亮&#xff08;串口状态&#xff09;。…

服务失败后如何重试?

服务失败后如何重试&#xff1f; 在分布式系统和网络应用程序中&#xff0c;重试策略对于有效处理瞬时错误和网络不稳定性至关重要。 重试策略能让系统在发生故障时多次尝试操作&#xff0c;从而提高最终成功的可能性。 下图显示了 4 种常见的重试策略。 01 线性回退 线性回…

LabVIEW开发中对RS-232、RS-485、RS-422通讯的比较及注意事项

本文介绍了LabVIEW开发中常用的RS-232、RS-485和RS-422通讯方式的区别及各自特点&#xff0c;详细说明了它们的适用场景和开发过程中需要注意的问题&#xff0c;帮助开发人员在选择和实现通讯方式时做出最佳决策。 详细说明 RS-232、RS-485、RS-422通讯简介 RS-232、RS-485和…

虚幻引擎5 Gameplay框架(四)

Gameplay重要类及重要功能使用方法&#xff08;三&#xff09; 虚幻的委托机制 虚幻委托之间的区别序列化就是是否可以在蓝图中执行 多播与单播的创建 制作功能&#xff1a;使用多播与单播将血条与血量进行实时更新首先新建一个单播与一个多播委托 实例化这两个委托的标签…

西门子电梯控制保姆级教程

一、电梯运行控制 1.电梯控制系统结构 可以理解是通过ip进行访问的 2.基于PLCSIM Adv与电梯仿真软件的控制环境搭建 虽然都是用一台电脑来控制&#xff0c;但是还是用以太网来连接 在FC块里面也要用两个DB块来放输入和输出 二、电梯对象的分析 在eet里面&#xff0c;用手动控制…

关于高版本 Plant Simulation 每次保存是 提示提交comm对话框的处理方法

关于高版本 Plant Simulation 每次保存是 提示提交comm对话框的处理方法 如下图 将model saving history 修改为None即可 关于AutoCAD 2022 丢失模板库的问题 从新从以下地址打开即可&#xff1a; D:\Program Files\Autodesk\AutoCAD 2022\UserDataCache\zh-cn\Template

LabVIEW步进电机的串口控制方法与实现

本文介绍了在LabVIEW环境中通过串口控制步进电机的方法&#xff0c;涵盖了基本的串口通信原理、硬件连接步骤、LabVIEW编程实现以及注意事项。通过这些方法&#xff0c;用户可以实现对步进电机的精确控制&#xff0c;适用于各种自动化和运动控制应用场景。 步进电机与串口通信…

python--面向对象-文件读写-异常

一、继承 定义一个类时&#xff0c;需要使用另外一个类的方法或属性&#xff0c;就可以通过继承实现 object是Python的顶级类&#xff0c;创建类是会自动继承&#xff0c;就拥有object中的方法 定义格式 # 类的定义 # 旧式类定义 一般在定义单个类时使用 class 类名:name N…

Nginx01-HTTP简介与Nginx简介(安装、命令介绍、目录介绍、配置文件介绍)

目录 HTTP简介HTTP原理查看访问网站的详细流程curl -vwget --debug 查看网站访问量HTTP协议版本HTTP协议交互HTTP 请求请求报文起始行请求头 HTTP响应响应报文起始行响应头 Nginx常见的Web服务常见网站服务 安装NginxNginx目录结构Nginx启动管理Nginx常用命令 Nginx配置文件主配…

牛客周赛 Round 45VP

这场应该是十分仁慈的一场了 1.签到&#xff1a;https://ac.nowcoder.com/acm/contest/84244/A AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int a,b,c,d,e; int main() {cin>>a>>b>>c>>d>>e;int sabcde;if(s>1…

关于nodejs单线程

Node是使用C++语言写的一款JavaScrip解析器。 高并发 一般来说,高并发的解决方案就是多线程模型,服务器为每隔客户端请求分配一个线程,使用同步I/o,比如Apache就是这种策略,由于I/O一般都是耗时操作,因为这种策略很难实现高性能,但非常简单,可以实现复杂的交互逻辑。…

深度学习复盘与论文复现B

文章目录 1、Knowledge Review1.1 NLLLoss vs CrossEntropyLoss1.2 MNIST dataset1.2.1 Repare Dataset1.2.2 Design Model1.2.3 Construct Loss and Optimizer1.2.4 Train and Test1.2.5 Training results Pytorch-Lightning MNIST :rocket::fire:1.3 Basic Convolutional Neu…

都说美国去工业化了,那美国人都做什么工作啊?

美国&#xff0c;这个全球经济的重要参与者&#xff0c;经历了一场深刻的变革——去工业化。这一过程意味着&#xff0c;曾经以制造业为荣的美国&#xff0c;逐渐将重心转移到了其他领域。那么&#xff0c;美国人都做什么工作呢&#xff1f;让我们走近这位“经济体巨人”&#…

Nginx企业级负载均衡:技术详解系列(17)—— 长连接优化策略与下载服务器高效搭建

你好&#xff0c;我是赵兴晨&#xff0c;97年文科程序员。 今天咱们来聊聊Nginx的两个知识点&#xff1a;Nginx的长连接优化、如何将Nginx配置成下载服务器。 长连接配置详解 在Nginx的配置中&#xff0c;长连接是一个重要的性能优化手段。它允许一个TCP连接上发送多个请求和…

centos7下安装MySQL,Oracle数据库

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 操作系统版本为CentOS 7 使⽤ MySQ…

“人工智能AI+” 应用场景盘点

在这个科技与梦想交相辉映的时代&#xff0c;人工智能已不再停留于遥不可及的概念构想&#xff0c;而是化身为一股汹涌的创新洪流&#xff0c;深刻塑造着社会的每一个角落。从文化艺术的智慧火花到生命科学的精密探索&#xff0c;从工业制造的革新升级到日常生活的细致入微&…