[数据结构] 基于交换的排序 冒泡排序快速排序

标题:[数据结构] 基于交换的排序 冒泡排序&&快速排序

@水墨不写bug


(图片来源于网络) 


目录

(一)冒泡排序

优化后实现:

(二)快速排序

I、实现方法: 

(1)hoare法

hoare法实现快排:

 (2)挖坑法

挖坑法实现:

(3)双指针法 

双指针法实现: 

 II、快速排序复杂度分析:

比较完备的快速排序实现如下:


正文开始:

(一)冒泡排序

 

时间复杂度:O(N^2)

空间复杂度:O(1)

特点:数组接近有序时,可通过优化来提高效率。

稳定性:稳定

冒泡排序:

        基本思想:大数下沉,小数上浮。

        实现思路:从内循环到外循环,从一趟到多趟。

        冒泡排序通过两层循环来实现,内层循环实现其中的一趟遍历,在一趟的遍历中,需要注意要控制好左右区间边界,冒泡排序的实现方式是多样的,无论用何种实现方式,最主要的是控制好边界,以及内外层循环的衔接;以下是一种冒泡排序的写法:


void BubbleSort(vector<int>& nums)
{
	int n = nums.size();
	for (int j = 0; j < n - 1; ++j)
	{
		for (int i = 0; i < n - 1 - j; ++i)
		{
			if (nums[i] > nums[i + 1])
			{
			
				::swap(nums[i], nums[i + 1]);
			}
		}
	}
}

优化:

        如果在一次遍历的过程中,没有进入if()交换,这已经说明数组已经有序,可以直接停止排序。

优化后实现:


void BubbleSort(vector<int>& nums)
{
	int n = nums.size();
	for (int j = 0; j < n - 1; ++j)
	{
		int ex = 0;
		for (int i = 0; i < n - 1 - j; ++i)
		{
			if (nums[i] > nums[i + 1])
			{
				ex = 1;
				::swap(nums[i], nums[i + 1]);
			}
		}
		if (ex == 0)
			break;
	}
}


(二)快速排序

时间复杂度:O(NlogN)

空间复杂度:

特点:当数据接近有序,比如逆序的时候;或者选取的key在数据中大量存在时,快速排序时间复杂度会退化为O(N^2),这是相当严重的缺陷。

稳定性:不稳定。

        快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。

        基本思想:任取待排序元素序列中的某元素(记为key)作为基准值,按照key值将待排序集合分割成两子序列,左子序列中所有元素均小于基准值key,右子序列中所有元素均大于基准值key。然后递归,左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

        本质就是将数组根据key值分为两部分,一部分比key大,一部分比key小。对于每一个部分,再次进行如上操作,直到数组不可再分为止。这就是递归实现的浅层次的直观理解。但是递归不是本文的重点,关于递归,我会在后面与你分享。 

I、实现方法: 

(1)hoare法

        hoare法是快排的提出者hoare提供的方法,是一种经典的实现方法。

实现原理:

        定义两个下标:左下标L  和  右下标R;

        随机选取一个key值,习惯上我们选择区间的最左侧的元素为key;

        让右下标先走,向左寻找比key小的值,找到后停下来:

 停下来后:

 左下标再向右寻找比key大的值,找到后停下来:

此时,交换两个下标对应的值:

 

接下来继续执行上述步骤(R先走),我展示关键步骤:

 两下标找到要求目标并交换:

直到遇到特殊情况:两下标相遇

 

 这时停止停止循环,将最左侧元素与相遇处的元素交换位置:
即可完成一次二分。

        接下来,对于左右区间再次进行上述操作,直到区间不可再分为止。


但是如何保证相遇处的值一定小于key呢?

对于相遇这个事件,有且仅有两种情况:

只可能是R向左移动遇到L;或者是L向右移动遇到R;

        (1)R向左移动遇到L:有两种可能情况

                        a,第一次R由于没有找到比key小的值,直接一路向左遇到left,此时left就是key,然后执行自己与自己交换:此时相遇的位置的值就是key本身。

                        b,第一次之后R向左遇到L,你要想清楚,在此之前L停止的地方是被交换后的原先R的值(它是比key小的);在这样的前提条件下,R向左移动与L相遇了,说明R没有找到小于key的元素,与L相遇后指向L的比key小的值。

        (2)L向右移动遇到R

                        R先走,在找到比key小的位置停下,在此前提下,L向右移动与R相遇了,L指向R的比key小的值。

hoare法实现快排:


void QuickSort(vector<int>& nums,int left,int right)
{
	//递归出口,此时区间不存在或者只有一个值
	if (left >= right)
		return;
	//保存左右下标的值,便于在递归时找到原来的区间边界
	int begin = left, end = right;
	//keyi来记录左区间的下标
	int keyi = left;

	while (left < right)
	{
		//右下标先走,找小
		while (left < right && nums[keyi] <= nums[right])
		{
			--right;
		}//左区间再走,找大
		while (left < right && nums[left] <= nums[keyi])
		{
			++left;
		}
		::swap(nums[left], nums[right]);
	}//此时一趟完毕,将key与相遇位置交换
	::swap(nums[left], nums[keyi]);
	keyi = left;//更新keyi

	//递归左右区间
	QuickSort(nums, begin, keyi);
	QuickSort(nums, keyi+1, end);
}

 (2)挖坑法

实现原理:

        由于左边有坑,所以右下标先走。

        找小,找到后停下来,将找到的值放在坑中,R的位置就成为了新的坑:

此时坑在右边,左边下标先走,找大:

找到后交换,左边又成为新的坑。

以此类推,直到左右相遇,相遇的位置一定是坑,此时将key放到坑中,完成单趟:

依然是左边都是比6小,右边都是比6大,说明没有错误。

挖坑法实现:

void QuickSort_(vector<int>& nums, int left, int right)
{
	if (left >= right)
		return;
	int begin = left, end = right;
	int key = nums[left];//记住key的值
	int hole = left;//开始挖坑
	while (left < right)
	{
		//右先找比key大的
		while (left < right && nums[right] >= key)
			right--;
		//找到后,填坑,然后挖新坑
		nums[hole] = nums[right];
		hole = right;
		//左找比key小的
		while (left < right && nums[left] <= key)
			left++;
		//找到后,填坑,然后挖新坑
		nums[hole] = nums[left];
		hole = left;
	}
	//此时相遇了,把key值放在坑里
	nums[hole] = key;

	QuickSort_(nums,begin,hole);
	QuickSort_(nums,hole + 1,end);
}

(3)双指针法 

 

// 设置前指针prev,指向首元素,遍历指针cur指向第二个元素,
// 接下来cur开始找小,如果没找到小,就一直往前走;

//如果找到小的了,就先停下来,然后prev往前走一步,再和cur交换值,然后cur继续向后,重复上述步骤,
// 最后cur走出数组后,循环终止!此时prev指向的位置和keyi的位置交换。

我们详细看一下过程:

        

双指针法实现: 


void QuickSort__(vector<int>& nums, int left, int right)
{
	if (left >= right)
		return;
	int begin = left, end = right;
	int prev = left;
	int cur = left + 1;
	int keyi = left;
	while (cur <= right)//cur走出数组循环停止
	{
		//cur一直在走,如果遇到比keyi小的,就停下来等perv走一步后交换,再接着走
		if (nums[cur] < nums[keyi] && ++prev != cur)
			swap(nums[prev], nums[cur]);
		cur++;
	}
	//cur出去后,prev的值和keyi交换
	swap(nums[keyi], nums[prev]);
	QuickSort__(nums,left,keyi);
	QuickSort__(nums,keyi+1,end);
	
}

 II、快速排序复杂度分析:

        传统的快速排序在处理一些极端问题时会显得无力,接下来我们就来讨论这些极端情况,并且给出应对极端情况的方法:

1.对于选择key不合适的问题:

        在数组元素接近逆序的时候,由于我们总是在区间的最左侧选取key,如果数组接近逆序,这时选取的key无法有效的将数组分为两个等大的数组,这就导致一次只能排序一个元素,这导致快速排序的复杂度会退化为O(N^2),这就需要我们不能随便取key。可以通过随机数法在区间内随机取一个元素作为key即可。

2.关于选择key大量出现的问题:

        如果key大量出现,也会导致上述的情况,一次只能排序一个数,所以我们不能随便分区间,这就要求我们将数组分为三部分,左侧区间都小于key,中间区间则是数值等于key的元素,右侧区间是大于key的数值。

比较完备的快速排序实现如下:

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        srand(time(NULL));
        qsort(nums,0,nums.size()-1);
        return nums;
    }
    void qsort(vector<int>& nums,int l,int r){
        //递归出口
        if(l >= r) return;
        //数组分三块
        int key = GetRandom(nums,l,r);
        int cur = l,left = l-1,right = r+1;
        while(cur < right){
            if(nums[cur] < key) swap(nums[++left],nums[cur++]);
            else if(nums[cur] == key) cur++;
            else swap(nums[--right],nums[cur]); 
        }
        //递归排序子区间
        qsort(nums,l,left);
        qsort(nums,right,r);
    }
    int GetRandom(vector<int>& nums,int left,int right){
        return nums[ rand() % ( right - left + 1 ) + left];
    }
};

 完~

未经作者同意禁止转载

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

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

相关文章

中英双语介绍百老汇著名歌剧:《猫》(Cats)和《剧院魅影》(The Phantom of the Opera)

中文版 百老汇著名歌剧 百老汇&#xff08;Broadway&#xff09;是世界著名的剧院区&#xff0c;位于美国纽约市曼哈顿。这里汇集了许多著名的音乐剧和歌剧&#xff0c;吸引了全球各地的观众。以下是两部百老汇的经典音乐剧&#xff1a;《猫》和《剧院魅影》的详细介绍。 1.…

C++友元函数和友元类的使用

1.友元介绍 在C++中,友元(friend)是一种机制,允许某个类或函数访问其他类的私有成员。通过友元,可以授予其他类或函数对该类的私有成员的访问权限。友元关系在一些特定的情况下很有用,例如在类之间共享数据或实现特定的功能。 友元可以分为两种类型:类友元和函数友元。…

推荐好玩的工具之OhMyPosh使用

解除禁止脚本 Set-ExecutionPolicy RemoteSigned 下载Oh My Posh winget install oh-my-posh 或者 Install-Module oh-my-posh -Scope AllUsers 下载Git提示 Install-Module posh-git -Scope CurrentUser 或者 Install-Module posh-git -Scope AllUser 下载命令提示 Install-Mo…

云端AI大模型群体智慧后台架构思考

1 大模型的调研 1.1 主流的大模型 openai-chatgpt 阿里巴巴-通义千问 一个专门响应人类指令的大模型。我是效率助手&#xff0c;也是点子生成机&#xff0c;我服务于人类&#xff0c;致力于让生活更美好。 百度-文心一言&#xff08;千帆大模型&#xff09; 文心一言"…

【Linux】进程创建和终止 | slab分配器

进程创建 fork 1.fork 之后发生了什么 将给子进程分配新的内存块和内核数据结构&#xff08;形成了新的页表映射&#xff09;将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表当中fork 返回&#xff0c;开始调度器调度 这样就可以回答之前返回两个值&#xff1f…

线程池理解及7个参数

定义理解 线程池其实是一种池化的技术实现&#xff0c;池化技术的核心思想就是实现资源的复用&#xff0c;避免资源的重复创建和销毁带来的性能开销。线程池可以管理一堆线程&#xff0c;让线程执行完任务之后不进行销毁&#xff0c;而是继续去处理其它线程已经提交的任务。 …

web缓存代理服务器

一、web缓存代理 web代理的工作机制 代理服务器是一个位于客户端和原始&#xff08;资源&#xff09;服务器之间的服务器&#xff0c;为了从原始服务器取得内容&#xff0c;客户端向代理服务器发送一个请求&#xff0c;并指定目标原始服务器&#xff0c;然后代理服务器向原始…

【IT领域新生必看】 Java编程中的重载(Overloading):初学者轻松掌握的全方位指南

文章目录 引言什么是方法重载&#xff08;Overloading&#xff09;&#xff1f;方法重载的基本示例 方法重载的规则1. 参数列表必须不同示例&#xff1a; 2. 返回类型可以相同也可以不同示例&#xff1a; 3. 访问修饰符可以相同也可以不同示例&#xff1a; 4. 可以抛出不同的异…

【踩坑】解决undetected-chromedriver报错cannot connect to-chrome

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 网上方法都试了&#xff0c;什么指定version_main、添加option等。 最终&#xff0c;放弃&#xff0c;直接换selenium自己的吧&#xff1a; from web…

【论文解读】LivePortrait:具有拼接和重定向控制的高效肖像动画

&#x1f4dc; 文献卡 英文题目: LivePortrait: Efficient Portrait Animation with Stitching and Retargeting Control;作者: Jianzhu Guo; Dingyun Zhang; Xiaoqiang Liu; Zhizhou Zhong; Yuan Zhang; Pengfei Wan; Di ZhangDOI: 10.48550/arXiv.2407.03168摘要翻译: *旨在…

IDEA配Git

目录 前言 1.创建Git仓库&#xff0c;获得可提交渠道 2.选择本地提交的项目名 3.配置远程仓库的地址 4.新增远程仓库地址 5.开始进行commit操作 6.push由于邮箱问题被拒绝的解决方法&#xff1a; 后记 前言 以下操作都是基于你已经下载了Git的前提下进行的&#xff0c…

基于机器学习(支持向量机,孤立森林,鲁棒协方差与层次聚类)的机械振动信号异常检测算法(MATLAB 2021B)

机械设备异常检测方法流程一般如下所示。 首先利用传感器采集机械运行过程中的状态信息&#xff0c;包括&#xff0c;振动、声音、压力、温度等。然后采用合适的信号处理技术对采集到机械信号进行分析处理&#xff0c;提取能够准确反映机械运行状态的特征。最后采用合理的异常决…

算法系列--分治排序|再谈快速排序|快速排序的优化|快速选择算法

前言:本文就前期学习快速排序算法的一些疑惑点进行详细解答,并且给出基础快速排序算法的优化版本 一.再谈快速排序 快速排序算法的核心是分治思想,分治策略分为以下三步: 分解:将原问题分解为若干相似,规模较小的子问题解决:如果子问题规模较小,直接解决;否则递归解决子问题合…

Debezium报错处理系列之第110篇: ERROR Error during binlog processing.Access denied

Debezium报错处理系列之第110篇:ERROR Error during binlog processing. Last offset stored = null, binlog reader near position = /4 Access denied; you need at least one of the REPLICATION SLAVE privilege for this operation 一、完整报错二、错误原因三、解决方法…

智能化客户服务:提升效率与体验的新模式

在数字化浪潮的推动下&#xff0c;客户服务领域正经历着一场深刻的变革。智能化客户服务的兴起&#xff0c;不仅重塑了企业与客户之间的互动方式&#xff0c;更在提升服务效率与增强客户体验方面展现出了巨大潜力。本文将深入探讨智能化客户服务的新模式&#xff0c;分析其如何…

Error in onLoad hook: “SyntaxError: Unexpected token u in JSON at position 0“

1.接收页面报错 Error in onLoad hook: "SyntaxError: Unexpected token u in JSON at position 0" Unexpected token u in JSON at position 0 at JSON.parse (<anonymous>) 2.发送页面 &#xff0c;JSON.stringify(item) &#xff0c;将对象转换为 JSO…

InspireFace-商用级的跨平台开源人脸分析SDK

InspireFace-商用级的跨平台开源人脸分析SDK InspireFaceSDK是由insightface开发的⼀款⼈脸识别软件开发⼯具包&#xff08;SDK&#xff09;。它提供了⼀系列功能&#xff0c;可以满⾜各种应⽤场景下的⼈脸识别需求&#xff0c;包括但不限于闸机、⼈脸⻔禁、⼈脸验证等。 该S…

运维锅总详解CPU

本文从CPU简介、衡量CPU性能指标、单核及多核CPU工作流程、如何平衡 CPU 性能和防止CPU过载、为什么计算密集型任务要选择高频率CPU、超线程技术、CPU历史演进及摩尔定律等方面对CPU进行详细分析。希望对您有所帮助&#xff01; 一、CPU简介 CPU&#xff08;中央处理器&#…

2024年马蹄杯专科组第三场初赛 解题报告 | 珂学家

前言 题解 VP了这场比赛&#xff0c;整体还是偏简单&#xff0c;最难的题是数论相关&#xff0c;算一道思维题。 也看了赛时榜单&#xff0c;除了数论&#xff0c;大模拟和图论题也是拦路虎。 打工人 有趣的一道数学题&#xff0c;有点绕 很像数列和 ∑ i 1 i n i n ∗ …

14-20 Vision Transformer用AI的画笔描绘新世界

概述 毫无疑问,目前最受关注且不断发展的最重要的主题之一是使用人工智能生成图像、视频和文本。大型语言模型 (LLM) 已展示出其在文本生成方面的卓越能力。它们在文本生成方面的许多问题已得到解决。然而,LLM 面临的一个主要挑战是它们有时会产生幻觉反应。 最近推出的新模…