递归、迭代、单向快排的实现和两种优化方法

news2024/11/16 9:16:48

目录

快速排序

实现代码​​​​​​​

 时间复杂度

快排的优化

随机选择策略

三位取中法

非递归的快排

单向快排


快速排序

        快速排序算法是基于分治策略的一个排序算法,其基本思想是对于输入的子数组进行分解、递归求解,最后合并。

分解:以数组中左边第一个数作为基准元素,将数组划分为三段,nums[left,mid-1],nums[mid],nums[mid+1,right],第一段中所有元素都比nums[mid](这就是那个基准数)小,第三段中所有元素都大于等于基准数。

递归求解:递归调用快排分别对nums[mid]的左右两段分别排序

合并:当每一小段就地排序都排好序后,整个数组就已经排完了

实现代码

void Print(int* nums, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%-5d", nums[i]);
	}
	printf("\n");
}
int Partition(int* nums,int left,int right)
{
	int l = left, r = right;
	int tmp = nums[l];
	while (l < r)
	{//确保l,r不错位每个都要判断l<r
		while (l<r && nums[r]>tmp)r--;//先找右边比tmp小的值
		if (l < r)nums[l] = nums[r];
		while (l< r && nums[l]<= tmp)l++;//在找左边比tmp大的值
		if (l< r)nums[r] = nums[l];
	}
	//l,r指针重合
	nums[l] = tmp;
	return l;
}
void QuickSort(int* nums, int left ,int right)
{
	if(left<right)
	{
		int mid = Partition(nums, left, right);//划分函数
		QuickSort(nums, left, mid - 1);
		QuickSort(nums, mid + 1, right);
	}
}

划分时一定要先从右边开始扫描 

 时间复杂度

         一般在问题规模为n时时间复杂度T(n)= Ο(nlogn)。最坏情况,数据从小到大或从大到小有序时T(n) =  Ο(n^2) 。


快排的优化

随机选择策略

      在快排的每一步当数组还没有被划分时,在数组中随机选出一个元素作为划分基准,划分基准随机则划分是比较对称的。用stand()指定一个随机起始点,然后将随机数模数组长度。因为这个right - left是相对位置的长度,但使用的下标rapos必须为绝对位置的下标,所以需要+left。

        将随机选择的基准值交换到第一个位置,然后再使用划分函数。

int RandomPartition(int* nums, int left, int right)
{
	srand(time(nullptr));//随机种子
	int rapos = rand() % (right - left + 1)+left;//取模
	std::swap(nums[left], nums[rapos]);//交换两个下标的值
	return Partition(nums, left, right);
}

三位取中法

        left ,right ,mid三个位置的元素取中间大小的元素作为基准值。如nums[left] = 12,nums[mid]=8,nums[right]=10;则取nums[right]作为基准。

struct Index
{
	int val;
	int index;
};
int MedionThree(int* nums, int left, int right)
{
	int mid = left + (right - left >> 1);
	struct Index idx[] = { {nums[left],left},{nums[mid],mid},{nums[right],right} };
	for (int i = 0; i < 3; i++)//找到中间大的数放到数组头做基准值
	{
		switch(i)
		{
		case 0:
			if ((idx[i].val - idx[1].val) * (idx[i].val - idx[2].val) < 0) 
			{ std::swap(nums[idx[i].index], nums[left]); }
			break;
		case 1:
			if ((idx[i].val - idx[0].val) * (idx[i].val - idx[2].val) < 0)
			{
				std::swap(nums[idx[i].index], nums[left]);
			}
			break;
		case 2:
			if ((idx[i].val - idx[0].val) * (idx[i].val - idx[1].val) < 0)
			{
				std::swap(nums[idx[i].index], nums[left]);
			}
			break;
		}
	}
	return Partition(nums, left, right);
}

非递归的快排

算法递归的部分在于划分后将不同子串输入进去继续划分。非递归时可以将子串的边界装在队列中,之后依次取出作为划分的左右边界。当队列为空,即划分完所有的以后结束。

void QuickSort(int* nums, int left, int right)
{
	queue<int> qu;//队列
	qu.push(left);
	qu.push(right);
	while(!qu.empty())
	{
		int sleft = qu.front(); qu.pop();//取队头,出left
		int sright = qu.front(); qu.pop();
		int mid = Partition(nums, sleft, sright);
		if (sleft < mid - 1)//左边有一个以上元素,仍需划分
		{
			qu.push(sleft);
			qu.push(mid - 1);
		}
		if (mid + 1 < right)
		{
			qu.push(mid + 1);
			qu.push(sright);
		}
	}
}

单向快排

 一般的快速排序都是左右两个指针向中间找基准值应放的位置划分的,而单向快速排序是从一边开始的,此方法可以用于单链表的快速排序。

取第一个值为基准值,如上为6,int tmp = 6;

slow为第一个元素的下标,fast为第二个元素的下标(单链表中这里为指针)

fast++,如果元素小于基准值tmp,则slow先++,然后nums[slow],nums[fast]再交换,这样可以保证slow下标左边元素的值都小于等于tmp;

当fast遍历完数组后,nums[left]与nums[slow]交换,此时slow即为mid(划分的中间段)。

代码:

int Partition(int* nums, int left, int right)
{
	int fast, slow;
	slow = left;
	fast = slow;
	int tmp = nums[left];
	while(left < right)
	{
		fast++;
		if (fast > right)
		{
			std::swap(nums[left], nums[slow]);
			break;
		}
		if (nums[fast] < tmp)
		{
			slow++;
			std::swap(nums[slow], nums[fast]);
		}
	}
	return slow;
}
void QuickSort(int* nums, int left, int right)
{
	if (left < right)
	{
		int mid = Partition(nums, left, right);
		QuickSort(nums, left, mid - 1);
		QuickSort(nums, mid + 1, right);
	}
}

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

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

相关文章

Fiddler - 夜神模拟器证书安装App抓包

Fiddler- 夜神模拟器证书安装App抓包 文章目录Fiddler- 夜神模拟器证书安装App抓包前言一、软件安装1.Openssl安装1.1下载安装1.2配置环境变量1.3查看openssl版本&#xff0c;输入命令&#xff1a;openssl version2.夜神模拟器安装1.1 下载安装1.2工具准备&#xff0c;MT管理器…

React相关扩展一(setState、lazyLoad、Hooks相关)(九)

系列文章目录 第一章&#xff1a;React基础知识&#xff08;React基本使用、JSX语法、React模块化与组件化&#xff09;&#xff08;一&#xff09; 第二章&#xff1a;React基础知识&#xff08;组件实例三大核心属性state、props、refs&#xff09;&#xff08;二&#xff0…

基于轻量级CNN的WHDLD多标签遥感分类识别系统

WHDLD数据成像波段包括R、G、B波段&#xff0c;数据覆盖包括6类地貌&#xff1a;裸地、建筑物、人行道、道路、植被、水域。数据集中包含4940张遥感影像及对应地物分类标记样本&#xff0c;影像大小为256x256像素&#xff0c;影像以jpg格式存储&#xff0c;标签数据格式为单通道…

高级前端常考手写面试题合集

解析 URL Params 为对象 let url http://www.domain.com/?useranonymous&id123&id456&city%E5%8C%97%E4%BA%AC&enabled; parseParam(url) /* 结果 { user: anonymous,id: [ 123, 456 ], // 重复出现的 key 要组装成数组&#xff0c;能被转成数字的就转成数字…

React(coderwhy)- 09(项目实战 - 1)

创建React项目 ◼ 创建项目的方式&#xff1a;create-react-app ◼ 项目配置:  配置项目的icon  配置项目的标题  配置jsconfig.json 新建jsconfig.json文件&#xff0c;在文件中粘贴以下内容{"compilerOptions": {"target": "es5","…

【数据结构趣味多】循环队列

目录 函数介绍及模拟实现 Front()函数 Rear()函数 enQueue()函数 deQueue()函数 isEmpty()函数 isFull()函数 循环队列模拟题 定义&#xff1a;把队列的头尾相连接的的顺序存储结构称为循环队列&#xff1b;循环队列的是由顺序表实现的。 为什么要使用循环队列&#…

Android MVVM之SavedStateHandle数据保存之详解与使用。

一、介绍 SavedStateHandle从名字可以看出&#xff0c;是保存状态的。这个类常和MVVM中的ViewModel搭配使用&#xff0c;对页面生命周期的数据状态的缓存与恢复做一个容器。这个容易相对onSaveInstanceState(Bundle)要更强一点&#xff0c;保存的数据类型也比较丰富&#xff0c…

算法刷题打卡第60天:回文链表

回文链表 难度&#xff1a;简单 给定一个链表的 头节点 head &#xff0c;请判断其是否为回文链表。 如果一个链表是回文&#xff0c;那么链表节点序列从前往后看和从后往前看是相同的。 示例 1&#xff1a; 输入: head [1,2,3,3,2,1] 输出: true示例 2&#xff1a; 输入:…

文本摘要,基于Pytorch和Hugging Face Transformers构建示例,有源码

​ 文本摘要的常见问题和解决方法概述&#xff0c;以及使用Hugging Face Transformers库构建基于新浪微博数据集的文本摘要示例。 作 者丨程旭源 学习笔记 1 前言简介 文本摘要旨在将文本或文本集合转换为包含关键信息的简短文本。主流方法有两种类型&#xff0c;抽取式和生…

Nodejs模块的封装(数据库Mysql)

文章目录项目结构本次演示需要使用的第三方包为1.app.js相关配置2.router下的user.js相关配置3.db/index.js文件相关操作4.router_handler下的user.js相关操作项目结构 后面的项目相关文件的创建步骤按照我写的博客从上往下一步一步来 本次演示需要使用的第三方包为 "cor…

【操作系统实验/Golang】实验4:虚拟内存页面置换算法

目录 1 实验问题描述 2 测试数据 3 流程图 4 实验结果 4 实验代码 1 实验问题描述 设计程序模拟先进先出FIFO&#xff0c;最佳置换OPT和最近最久未使用LRU页面置换算法的工作过程。 假设内存中分配给每个进程的最小物理块数为m&#xff0c;在进程运行过程中要访问的页面个…

【Leetcode面试常见题目题解】1. 两数相加

题目描述 本文是leetcode第2题的题解&#xff0c;题目描述摘自leetcode。如下 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个…

海外服务器提供商选择中存在哪些风险?

开展海外业务时&#xff0c;毫无疑问&#xff0c;选择一个高质量的海外服务器提供商可以省去不少麻烦。但是&#xff0c;同时有一些海外服务商需要避开。毕竟一个服务器不靠谱&#xff0c;这跟提供商有很大的原因。下面主要是关于低于标准的海外服务器提供商的一些潜在风险。 1…

ES6中字符串和数组新增的方法

ES6中字符串和数组新增的方法一、字符串中新增的方法1、模板字符串 (表达式、函数的调用、变量)2、repeat(次数)函数 : 将目标字符串重复N次&#xff0c;返回一个新的字符串&#xff0c;不影响目标字符串3、includes()函数 :判断字符串中是否含有指定的子字符串&#xff0c;返回…

mysql 8 新旧密码可以同时生效

在MySQL8.0以前版本&#xff0c;给MySQL更改密码&#xff0c;明确写到开发规范中&#xff0c;拒绝更在线更改更密码&#xff0c;因为在8.0以前操作非常麻烦且不太完美。 MySQL 8.0之前的处理方法&#xff1a; 1. 创建一个同样权限的帐号通过 show grants for ‘user_name’1…

通用vue编辑按钮和新建按钮事件逻辑

一、编辑按钮对话框 1.首先先创建一个文件夹page-model&#xff0c;在里面使用elemengt-plus提供的对话框组件el-dialog。 2.在page-model里面去使用之前封装好的form表单&#xff0c;就是之前封装好的搜索组件的hy-form 3.在form组件里面加一个插槽&#xff0c;对应 page-m…

微信小程序:会议OA项目-首页

目录 一、flex布局 Flex布局简介 什么是flex布局&#xff1f; flex属性 flex的属性 二、轮播图组件及mockjs的使用 三、会议OA小程序首页布局 一、flex布局 Flex布局简介 布局的传统解决方案&#xff0c;基于盒状模型&#xff0c;依赖 display属性 position属性 float…

CompletableFuture详解

CompletableFuture详解 概要 RunnableThread虽然提供了多线程的能力但是没有返回值。CallableThread的方法提供多线程和返回值的能力但是在获取返回值的时候会阻塞主线程。 上述的情况只适合不关心返回值&#xff0c;只要提交的Task执行了就可以。另外的就是能够容忍等待。 C…

Java并发容器

一、并发容器总结1、大部分在 java.util.concurrent 包中。ConcurrentHashMap: 线程安全的HashMapCopyOnWriteArrayList: 线程安全的List&#xff0c;在读多写少的场合性能非常好&#xff0c;远远好于Vector.ConcurrentLinkedQueue: 高效的并发队列&#xff0c;使用链表实现。可…

[ 数据结构 ] 平衡二叉树(AVL)--------左旋、右旋、双旋

0 引出 数列{1,2,3,4,5,6}&#xff0c;要求创建一颗二叉排序树(BST), 并分析问题所在 回顾:二叉搜索树 左子树全部为空&#xff0c;从形式上看&#xff0c;更像一个单链表.插入速度没有影响查询速度明显降低(因为需要依次比较), 不能发挥 BST的优势&#xff0c;因为每次还需要…