三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析

这篇具有很好参考价值的文章主要介绍了三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析

目录

 一.前言

二. 三路快排

😍算法思想:

😍算法实现步骤:

😍三指针单趟排序的实现:​

😍非递归快排完全体:

🤔与C标准库里的快排进行对比测试:

三.快排时间复杂度再分析


 一.前言

http://t.csdn.cn/mz8dghttp://t.csdn.cn/mz8dghttp://t.csdn.cn/1TqDphttp://t.csdn.cn/1TqDp

  • 😄关于快排的基本思想和实现及其优化
  • 😄利用双指针单趟排序实现的快速排序有一个无法避免的缺陷:当待排序序列中有大量(或全部)元素相同时,快排的时间复杂度会升阶为O(N^2),此时快排的递归树线型结构,递归的深度为O(N),时间消耗空间消耗都非常巨大:三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析
  • 😄为了避免这种情况出现,就需要重新设计一下快排的单趟排序,目的在于:当待排序序列中存在大量相同元素时,减小快排递归树的深度

二. 三路快排

😍算法思想:

  • 🤪三路快排的单趟排序是利用三指针算法来实现的
  • 🤪其基本思想是利用三个指针将数组从左到右划分为三个部分,第一部分中所有元素都比key小,第二部分中所有元素都等于key,第三部分中所有元素都大于key
  • 🤪后续就可以对数组第一部分和第三部分进行分治,数组的第二部分所有元素已经处于它们在有序序列中的最终位置,无须再进行处理
  • 🤪三路快排的边界条件有点折磨人​​

😍算法实现步骤:

  • 🤪三个指针的初始位置如图所示三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析
  • 🤪left是待排序区间的左边界,right是待排序区间的右边界,待排序区间闭区间
  • 🤪算法实现思路:
  • 🤪利用midi指针遍历待排序序列,遍历限制条件为:midi<greater.
  1. 😝若arr[midi]比key大,令grater指针减一,并将arr[midi]交换到[greater,right]区间中,midi指针不动
  2. 😝若arr[midi]比key小,令small指针加一, 并将arr[midi]交换到[left+1,small]区间中,midi指针向前走一步
  3. 😝若arr[midi]与key相同,midi指针向前走一步,其余指针不动,目的是将等于key元素的arr[midi]"括入"[small+1,midi)区间
  • 😝重复上述过程直到midi指针和geater指针相遇,算法gif:三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析
  • 😝经过上述过程,最终[left+1,small]区间中的所有元素一定比key小,[greater,right]区间中的所有元素一定比key大,[small+1,midi)区间中的所有元素一定等于key:三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析
  • 😝同时注意,迭代过程结束后,small最终指向的元素一定小于key,所以最后一步就是将arr[small]和arr[left]进行交换,于是数组就被划分成了三个部分:三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析
  • 😝接下来就可以对区间[left,small-1]区间[greater,right]进行分治形成递归完成快速排序

😍三指针单趟排序的实现:三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析

void QuickSort(int* arr, int left, int right)
{
	assert(arr);
	int key = left;
	int midi = left + 1;
	int small = left;
	int greater = right + 1;
	while (midi < greater)
	{
		if (arr[midi] < arr[key])	   //将arr[midi]交换到[left + 1, small]区间中,同时注意small位置的元素一定比key元素小
		{
			++small;
			if (small != midi)
			{
				swap(&arr[small], &arr[midi]);
			}
			++midi;
		}
		else if (arr[midi] > arr[key]) //将arr[midi]交换到[greater,right]区间
		{
			--greater;
			swap(&arr[midi], &arr[greater]);
		}
		else
		{
			++midi;					   //将等于key元素的arr[midi]"括入"[small+1,midi)区间中
		}
	}
	swap(&arr[small], &arr[key]);      //small最终指向的元素一定小于key
}

接下来再进行分治递归并给出递归出口完成快速排序:

😍非递归快排完全体:

  • 😝同时辅以三数取中优化
    void swap(int* e1, int* e2)
    {
    	assert(e1 && e2);
    	int tem = *e1;
    	*e1 = *e2;
    	*e2 = tem;
    }
    
    //三数取中优化
    int GetMid(int* arr,int left,int right)
    {
    	int mid = left + ((right - left) >> 2);     //在arr[left],arr[mid],arr[right]三者中取中间值作为key,返回key的下标
    	if (arr[left] < arr[right])
    	{
    		if (arr[left] < arr[mid] && arr[mid] < arr[right])
    		{
    			return mid;
    		}
    		else if (arr[mid] > arr[right])
    		{
    			return right;
    		}
    		else
    		{
    			return left;
    		}
    	}
    	else
    	{
    		if (arr[left] > arr[mid] && arr[mid] > arr[right])
    		{
    			return mid;
    		}
    		else if (arr[mid] > arr[left])
    		{
    			return left;
    		}
    		else
    		{
    			return right;
    		}
    	}
    }
    void QuickSort(int* arr, int left, int right)
    {
    	if (left >= right)                 //递归出口
    	{
    		return;
    	}
    	assert(arr);
    	int key = left;
        swap(&arr[left], &arr[GetMid(arr, left, right)]);
    	int midi = left + 1;
    	int small = left;
    	int greater = right + 1;
    	while (midi < greater)
    	{
    		if (arr[midi] < arr[key])	   //将arr[midi]交换到[left + 1, small]区间中,同时注意small位置的元素一定比key元素小
    		{
    			++small;
    			if (small != midi)
    			{
    				swap(&arr[small], &arr[midi]);
    			}
    			++midi;
    		}
    		else if (arr[midi] > arr[key]) //将arr[midi]交换到[greater,right]区间
    		{
    			--greater;
    			swap(&arr[midi], &arr[greater]);
    		}
    		else
    		{
    			++midi;					   //将等于key元素的arr[midi]"括入"[small+1,midi)区间中
    		}
    	}
    	//small指向的元素一定小于key
    	swap(&arr[small], &arr[key]);      //将key交换到其应该出现的最终位置
    	QuickSort(arr, left, small - 1);   //分治左子数组
    	QuickSort(arr, midi,right);		   //分治右子数组
    }
  • 🤔经过三数取中三指针优化后的快排就可以对任意序列进行高效排序,不会再出现时间复杂度升阶为O(N^2)的情况

  • 🤔力扣排序测试:(该测试非常针对未经优化和非三指针的快排)三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析912. 排序数组 - 力扣(Leetcode)https://leetcode.cn/problems/sort-an-array/description/

🤔与C标准库里的快排进行对比测试:

int main()
{
    srand((unsigned int)time(0));
	const int N = 10000000;
	int* arr1 = (int*)malloc(sizeof(int) * N);
	int* arr2 = (int*)malloc(sizeof(int) * N);
	int* arr3 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		arr1[i] = rand();
		arr2[i] = arr1[i];
		arr3[i] = arr1[i];
	}

	int begin2 = clock();
	qsort(arr2, N, sizeof(int), cmp);
	int end2 = clock();
	printf("qsort:%d\n", end2 - begin2);

	int begin3 = clock();
	QuickSort(arr3, 0,N-1);
	int end3 = clock();
	printf("QuickSort:%d\n", end3 - begin3);
    
    free(arr1);
    free(arr2);
    free(arr3);
}

三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析

  • 🤔有点奇怪的是在我的机器环境中,我自己写的快排比标准库里的快排还要快一倍左右(可执行程序为release版本) 

三.快排时间复杂度再分析

  • 😍设N为待排序序列元素个数
  • 😍以下分析中的log都表示以2为底的对数
  • 😍经过三数取中三指针优化后的快排分治递归递归树可以认为在处理任何序列时都接近一颗满二叉树:(注意数组的分割点不参与后续的单趟排序)三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析
  • 😍从递归树第一层开始,递归树每一层所有单趟排序所需遍历元素总个数依次为:N+(N-1)+(N-3)+(N-7)......即快排的时间复杂度计算公式为:
  • 三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析
  • 😍将上述复杂度公式进行求和运算,取b = logN可得:
  • 三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析
  • 😍再化简可得:
  • 三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析
  • 😍可见快速排序时间复杂度O(NlogN)的基础上存在进一步的微收敛,这使得快速排序在四个时间复杂度数量级O(NlogN)的排序算法中独占鳌头进而成为工业级排序中用的最多的排序算法。(四个时间复杂度为O(NlogN)数量级的排序算法分别为:希尔排序,堆排序,归并排序和快速排序)

 三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析文章来源地址https://www.toymoban.com/news/detail-414085.html

到了这里,关于三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构与算法篇】手撕八大排序算法之快排的非递归实现及递归版本优化(三路划分)

    ​👻内容专栏: 《数据结构与算法篇》 🐨本文概括: 利用数据结构栈(Stack)来模拟递归,实现快排的非递归版本;递归版本测试OJ题时,有大量重复元素样例不能通过,导致性能下降,优化快速排序通过将数组划分为三个区域,可以更有效地处理重复元素。 🐼本文作者:

    2024年02月11日
    浏览(49)
  • 排序算法大全集,从时间复杂度和空间复杂度上对各个排序算法进一步的分析和评估,从插入排序、交换排序、归并排序、基数排序到外部排序,通晓堆排序、希尔排序、快速排序等算法

    目录 1.基本概念和排序方法概述 排序方法的分类 2.插入排序 1.直接插入排序 2.折半插入排序 3.希尔排序 3.交换排序 1.冒泡排序 2.快速排序 3.简单选择排序 4.堆排序 4.归并排序 5.基数排序 6.外部排序 7.各类排序方法的综合比较 1.时间性能 2.空间性能 3.排序方法的稳定性能 4.关于

    2024年02月08日
    浏览(37)
  • 【排序算法】 快速排序(快排)!图解+实现详解!

    🎥 屿小夏 : 个人主页 🔥个人专栏 : 算法—排序篇 🌄 莫道桑榆晚,为霞尚满天! 什么是快排?快排的速度到底有多快呢?它们的思想和实现是什么样的? 本文会对这快速排序进行详解,绝对细致入微!让你彻底搞懂快排! 英国计算机科学家Tony Hoare在1960年为了解决计算

    2024年02月05日
    浏览(30)
  • leetcodeT912-快排优化-三路划分

    因为快排的名声太大 并且快排在某些场景下比较慢,所以leetcode\\\"修理\\\"了一下快排 特意设计了一些专门针对快排的测试用例 所以用快排过不了这一题 我们遇到了第一个为难快排的测试用例全是重复值2 我们发现快排在遇到全是重复值的数据时, 这里以左右指针法为例 每次右指

    2024年02月08日
    浏览(40)
  • 数据结构——三路划分(快排优化)

    刷Leetcode时遇到的问题,用普通的快排去跑,发现有问题。  普通的Hoare或者其他的快排好像都没有直接解决掉这个问题,当一个数重复出现的时候,用普通的快排效率其实并没有那么高。所以,这也是普通快排的缺点之一。 所以,在一个元素重复出现多次的时候,可以用三

    2024年02月08日
    浏览(35)
  • 三路快排Java版(带思路分析)

    这里我们直接开始讲相对的最优解 带随机数的三路快排 好了,中间还有很多版本的快排,但是都有一些问题导致在某种极端情况下造成耗费时间极多。 基础快排:在 序列本身有序 的情况下复杂度为O(n²) 带随机数的快排:在 序列本身有序 的情况下复杂度为O(nlogn),但

    2024年02月06日
    浏览(25)
  • 八大排序算法之快速排序(下篇)(快排的优化+非递归快排的实现)

    目录 一.前言 1.快速排序的实现: 快速排序的单趟排序(排升序)(快慢指针法实现):​ 2.未经优化的快排的缺陷 二.快速排序的优化 1.三数取中优化 优化思路: 2. 小区间插入排序优化 小区间插排优化的递归快排: 三.非递归快速排序的实现 1.快排一个难以避免的缺陷(暂不考虑三指针

    2024年02月02日
    浏览(36)
  • 快速排序(快排) (C语言实现)

    👋我是Brant_zero,一名学习C/C++的在读大学生。 🌏️我的博客主页​​​​​​➡➡Brant_zero的主页 🙏🙏 欢迎大家的关注,你们的关注是我创作的最大动力 🙏🙏         本篇博客学习内容是快速排序,快速排序有多种不同的版本和优化,我们这次的目的就是将这些版本

    2023年04月09日
    浏览(26)
  • 八大排序算法之快速排序(上篇)(未经优化的快排)

    目录 一.关于快速排序的总体算法思想 1.冒泡排序(交换排序) (以排升序为例) 2.快速排序的总体思想简介(以排升序为例)  二.快速排序单趟排序的算法接口设计(以排升序为例) 单趟排序实现的方法一:hoare版本(左右指针法) 代码实现:  单趟排序实现的方法二:挖坑法  代码实现

    2023年04月08日
    浏览(29)
  • (排序3)希尔排序时间复杂度与直接选择排序

    希尔排序分组预排的目的就在于比如说我要对数据进行升序排序,那么就是可以达到让大的数尽快的调到后面 然后接下来随着gap的不断缩小,间隔越来越小,组也就越来越多,最终整个数组的话是越来越接近有序。 最终的话,你必须要保证这个gap为1,就是说最终必须得进行

    2023年04月08日
    浏览(23)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包