【数据结构】-快速排序的四种方法实现以及优化

这篇具有很好参考价值的文章主要介绍了【数据结构】-快速排序的四种方法实现以及优化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

作者:小树苗渴望变成参天大树
作者宣言:认真写好每一篇博客
作者gitee:gitee
快速排序优化,数据结构初阶,数据结构,算法,排序算法
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!


前言

今天讲一种不一样的排序,听名字就知道这个排序不拐弯抹角的,我们来看看它又多快速,并且快速排序的前三种方法都是递归思想,最后一种是非递归,我们就来重点介绍着集中方法来实现快排(以升序为例)


一、hoare法(左右指针法)

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

我们来看看动图,再来画图详细给大家介绍一下:

我们选择一个keyi,保持不动,a[keyi]作为关键字,一趟排序下来这个关键字左边的值都比它小,右边的值都比它大,说明这个关键字来到了它最终来到的地方
快速排序优化,数据结构初阶,数据结构,算法,排序算法

通过左右指针将小的数跳到关键字的左边,大的数跳到关键字的右边,这个图表示的一趟排序接下来看看画图分析
快速排序优化,数据结构初阶,数据结构,算法,排序算法

通过这个图,我们看到要注意的两个细节,一个是找大找小的左右判断,不能错过了,而是判断条件要不要加等于号,把这两个细节掌握号,单趟就掌握好了,我们来看看单趟排序的代码吧:

int partsort1(int* a, int left, int right)
{
	int keyi = left;
	while (left < right)
	{
		while (left<right && a[right]>=a[keyi])//找小
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi])//找大
		{
			left++;
		}
		swap(&a[left], &a[right]);//交换
	}
	swap(&a[keyi], &a[left]);//结束后,把keyi位置的值和左边的值交换
	keyi = left;//改变keyi的位置
}

看单趟排序结果:
快速排序优化,数据结构初阶,数据结构,算法,排序算法
相信大家看到这里对于函数体里面的内容已经了解了,但是为什么我的函数设计要带返回值呢??

原因是我再前言就讲过,快排的前三种都是递归的方法,思想都是一样的,就是单趟排序的思想不一样,因为一趟排序之后,关键字会跳到它最终的位置,下一趟排序,它就不需要参加排序,只需要把它左边区间和右边你区间进行排序,返回这个关键字的下标,目的是方便递归时找到左右区间的范围

我们来看图:
快速排序优化,数据结构初阶,数据结构,算法,排序算法
我们来看完整的快排:

void quicksort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int keyi = partsort1(a, left, right);
	quicksort(a, left, keyi - 1);//对关键字左区间排序
	quicksort(a, keyi+1, right);//对关键字右区间排序
}

相比较而言,这里的递归还没有二叉树那些递归难理解,主要理解单趟排序的思想,就行了,接下来的两种方法只需要修改partsort函数体里面的内容就行了,我们开始介绍下面两种思想

二、挖坑法

挖坑法是后面的牛人觉得hoare的方法有点难理解,又想出来的一种新的方法,思想是,把数组的最左边和最右边先当成一个坑,把这个坑的数据先保存起来,这样有数过来就直接放到这个坑位里面,不怕原数据被覆盖了,这个数就变成新的坑位,采取的还是左边找大,右边找小,找到就放到坑位里面,同时找到的位置变成新的坑位
我们先来看看动图演示:

快速排序优化,数据结构初阶,数据结构,算法,排序算法
我们再来看看图解:
快速排序优化,数据结构初阶,数据结构,算法,排序算法
通过这个图,我们要注意结束条件和找大找小条件都是和第一种一样的那我们来看看这个单趟排序的代码时怎样的:

int partsort2(int* a, int left, int right)
{
	int key = a[left];//将坑位的关键字保存起来
	int pivot = left;//定义一个坑位下标
	while (left < right)
	{
		while (left < right && a[right] >= key)//找小
		{
			right--;
		}
		a[pivot] = a[right];//把找到小的数放到左坑
		pivot = right;
		while (left < right && a[left] <= key)//找大
		{
			left++;
		}
		a[pivot] = a[left];//把找到大的数放到右坑
		pivot = left;
	}
	a[pivot] = key;//把保存的关键字放到最后的坑位
	pivot = left;
	return pivot;
}

我们来看看单趟运行结果:
快速排序优化,数据结构初阶,数据结构,算法,排序算法

和我们画的图解结果一样,这两种单趟的大思想都是把大的数快速跳到右边,比较小的跳到左边,关键字跳到它最终要出现的位置,最后都是通过递归再对左右区间进行排序

我们来看最终的快速排序:

void quicksort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int keyi = partsort2(a, left, right);//就改了一下这个函数体,其余和之前一样
	quicksort(a, left, keyi - 1);
	quicksort(a, keyi+1, right);
}

相信大家看到这里对于挖坑法应该理解,希望大家下来可以取尝试理解一下,接下来我将讲解另一种方法,前后指针法

三、前后指针法

这个方法相比较而言比前面两种更好,而且不容易出错,那我就来介绍一下这种方法,思想是:通过一个cur指针来找小,找到把prev指针+1把值进行交换,大的数跳到前面,小的数跳到后面,+1找到的是大的数,因为是cur跳过的数,所以是大的数,然后重复上面的步骤,知道cur结束,最后把keyi的值和prev的值进行交换
我们来看看动图演示:
快速排序优化,数据结构初阶,数据结构,算法,排序算法
接下来看看图解:
快速排序优化,数据结构初阶,数据结构,算法,排序算法
这个方法理解了就很好理解,一定要多画图,接下来我们看一下单趟排序的代码:

int partsort3(int* a, int left, int right)
{
	int prev = left;
	int cur = left + 1;
	int keyi = left;
	while (cur <= right)//结束条件
	{
		if (a[cur] < a[keyi])//找到小
		{
			++prev;//就++prev
			swap(&a[prev], &a[cur]);//两者交换
		}
		cur++;//cur一直都需要++
	}
	swap(&a[prev], &a[keyi]);//循环结束
	keyi = prev;
	return keyi;
}

这个方法的好处就在于些的时候不容易出错,就一趟遍历即可,前两种方法在条件判断的时候容易出错,我们来看看这个方法的完整快排代码:

void quicksort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int keyi = partsort3(a, left, right);
	quicksort(a, left, keyi - 1);
	quicksort(a, keyi+1, right);
}

希望这三种方法你可以学会,接下来我要将这三种方法还可以进行优化。

四、优化版本

快速排序优化,数据结构初阶,数据结构,算法,排序算法

我们递归的思想大致是这样的,每增加一行的递归就会选出两倍的关键字到最终的位置,但是在有序的情况,我们看看会怎么样

快速排序优化,数据结构初阶,数据结构,算法,排序算法

这样看来快排在有序的时间效率最低, 那我们就把它弄成不有序,有两种方法,一个是随机数法,一个是三数取中

4.1随机数

一、我们来看看随机数,随便取一个a[keyi]然后和左边交换,在进行排序,来看代码:

void quicksort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int mid = left+rand() % (right - left + 1);
	if (mid != left)
	{
		swap(&a[left], &a[mid]);
	}
	int keyi = partsort3(a, left, right);
	quicksort(a, left, keyi - 1);
	quicksort(a, keyi+1, right);
}

为什么要加一个left,防止是递归右区间的时候。

4.2三数取中

我们再来看看三数取中,意思是在三个数中选出中间那个数,我们来看看代码:

int GetMid(int* a, int left, int right)
{
	int mid = (right - left) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])//left mid right
		{
			return mid;
		}
		else if (a[right] < a[left])//right left mid
		{
			return left;
		}
		else
		{
			return right;//left  right mid 
		}
	}
	else//a[left] >= a[mid]
	{
		if (a[right] > a[left])//mid left right
		{
			return left;
		}
		else if (a[mid] > a[right]) //right mid left
		{
			return mid;
		}
		else
		{
			return right;//mid right left
		}
	}
}

也是选出来和最左边的数交换一下,但整体而言三叔取中用到比较多,因为随机数选出来的可能刚好是最小的keyi,这样和没选没啥区别,而三数取中保证选出来的keyi不是最小的

这里我就不在给大家测试性能了,如果大家感兴趣可以取我的这篇测排序性能博客调用我的函数去测试,对于快排尽量数据搞多些,效果明显,也可以和其他排序进行对比

对于优化其实还有一种方法就是小区间优化,对于数据量不是那么大的时候不需要使用递归来做,我们可以使用直接插入排序来做,具体为什么我就不做介绍了,大家了解一下就好了,反正越到最后递归的次数越多,浪费的时间就多,用直接插入排序节省一点时间,像希尔排序还要分组没必要和选择排序最好最坏情况一样的,直接插入排序在有序的时候比较好,所以选择了直接插入排序:

我们来看看怎么写的:

void quicksort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int mid = GetMid(a, left, right);

	int keyi = partsort3(a, left, right);
	if (keyi - 1 - left > 10)
	{
		quicksort(a, left, keyi - 1);
		quicksort(a, keyi + 1, right);
	}
	else
	{
		insertsort(a + left, keyi - 1 - left + 1);//a+left是左区间起始位置,keyi - 1 - left + 1元素个数
	}
	if ((right - keyi - 1) > 10)
	{
		quicksort(a, left, keyi - 1);
		quicksort(a, keyi + 1, right);
	}
	else
	{
		insertsort(a + keyi + 1, right - keyi - 1 + 1);// a + keyi + 1是右区间起始位置,right-keyi-1+1元素个数
	}
}

这差不多是最好效率的快排排序了,大家可以把优化的和不优化的都测测看看性能是不是提高了,接下来我们来讲非递归的快排了。

五、非递归版本

我们递归的本质是,开辟先的函数空间,主要保存的左右区间的下标,我们可以通过栈来实现,分别把左右区间入栈,然后在进行单趟排序,调用之前的方法,然后再左右区间入栈
来看看图解:
快速排序优化,数据结构初阶,数据结构,算法,排序算法
大家跟着我的步骤走,我·采用的单趟排序是hoare法,我们来看代码:

void QuickSortNonR(int* a, int left, int right)
{
	stack ST;
	STackInit(&ST);
	if (left >= right)
		return;
	StackPush(&ST, right);//先入右
	StackPush(&ST,left);//再入左
	while (!StackEmpty(&ST))
	{
		int left = StackTop(&ST);//出左
		StackPop(&ST);
		int right = StackTop(&ST);出右
		StackPop(&ST);
		int keyi = partsort1(a, left, right);//前三种方法都可以
		if (keyi - 1 - left >=1)
		{
			StackPush(&ST, keyi - 1);//左区间右边入
			StackPush(&ST, left);//左区间左边入
		}
		if (right - keyi - 1 >= 1)
		{
			StackPush(&ST, right);//右区间右边入
			StackPush(&ST, keyi+1);//右区间左边入
		}
	}
}

这个方法我们要先导入一个之前写过的栈,再之前的博客有介绍过,然后就利用栈的特性来实现快排的非递归,大家一定要多画画图。希望我这个图解能给大家解答困惑

四、总结

今天加的内容非常的多,大家一定要多消化消化,其实快速排序是选择排序家族,冒泡也是选择家族的一种,因为冒泡比较简单,我就没有和快排一起介绍了,希望大家可以知道,今天的,关于时间复杂度我可能再后期的博客会更新,把前面集中博客进行对比的介绍,到时候欢迎大家过来支持一下,今天的知识就先分享到这里了,我们下篇再见
快速排序优化,数据结构初阶,数据结构,算法,排序算法文章来源地址https://www.toymoban.com/news/detail-778201.html

到了这里,关于【数据结构】-快速排序的四种方法实现以及优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Hive的四种排序方法

    hive排序方法,hive的排序方式 hive有四种排序方法: ORDER BY 、SORT BY 、DISTRIBUTE BY 、CLUSTER BY 0. 测试数据准备 uuid dept salary 1001 研发部 16000 1002 市场部 17000 1003 销售部 11000 1004 研发部 15000 1005 销售部 12000 1006 研发部 21000 1007 产品部 16000 1008 研发部 18000 1009 市场部 17000 1010 产品部 16

    2024年02月02日
    浏览(10)
  • [数据结构 -- 手撕排序算法第六篇] 递归实现快速排序(集霍尔版本,挖坑法,前后指针法为一篇的实现方法,很能打)

    [数据结构 -- 手撕排序算法第六篇] 递归实现快速排序(集霍尔版本,挖坑法,前后指针法为一篇的实现方法,很能打)

    目录 1、常见的排序算法 1.1 交换排序基本思想 2、快速排序的实现方法 2.1 基本思想 3 hoare(霍尔)版本 3.1 实现思路 3.2 思路图解 3.3 为什么实现思路的步骤2、3不能交换 3.4 hoare版本代码实现 3.5 hoare版本代码测试 4、挖坑法 4.1 实现思路 4.2 思路图解 4.3 挖坑法代码实现 4.4 挖坑

    2024年02月16日
    浏览(15)
  • List按指定规则排序的四种方法

    使用Collections.sort(list)可对一个List对象进行升序排序,但如果要按某种指定规则进行排序,可使用如下四种方法: 1. 使用list.sort(comparator)方法 List的sort()方法中可以传入一个自定义Comparator比较器。实现Comparator接口, 重写compare方法 来定义排序规则。 如果compare()方法返回负整

    2024年02月05日
    浏览(10)
  • 【数据结构】快速排序,归并排序

    【数据结构】快速排序,归并排序

    1.hoare版本 根据动图的演示,整理的思路如下, 1.定义left,right,key。key默认是左边第一个元素,像两个指针,左边找比key大的,右边找比k小的,找到的话,交换二者,往返这个过程,当left与right相遇时,交换key和此时相遇的值. 单趟下来,6出现在正确的位置。 1.为什么大循环

    2024年01月20日
    浏览(9)
  • 【数据结构】排序(2)—冒泡排序 & 快速排序

    【数据结构】排序(2)—冒泡排序 & 快速排序

                                      目录 一. 冒泡排序 基本思想 代码实现 时间和空间复杂度 稳定性 二. 快速排序 基本思想 代码实现 hoare法 挖坑法 前后指针法 时间和空间复杂度 稳定性            冒泡排序是一种交换排序。两两比较数组元素,如果是逆序(即排列顺序

    2024年02月08日
    浏览(36)
  • 【数据结构--八大排序】之快速排序

    【数据结构--八大排序】之快速排序

    💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃 个人主页 :阿然成长日记 👈点击可跳转 📆 个人专栏: 🔹数据结构与算法🔹C语言进阶 🚩 不能则学,不知则问,耻于问人,决无长进 🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍 前言: 前面,我花费

    2024年02月08日
    浏览(37)
  • 数据结构——排序算法之快速排序

    数据结构——排序算法之快速排序

        个人主页: 日刷百题 系列专栏 : 〖C/C++小游戏〗 〖Linux〗 〖数据结构〗   〖C语言〗 🌎 欢迎各位 → 点赞 👍+ 收藏 ⭐️+ 留言 📝  ​ ​ 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。 基本思想: 任取待排序元素序列中 的某元素作为基准值,按照

    2024年01月21日
    浏览(13)
  • 【数据结构】快速排序详解

    【数据结构】快速排序详解

    目录 一、基本介绍 二、快排的实现 1. 调试环境 2.快排的单趟排序 (1)Hoare版本 (2)挖坑法 (3)前后指针法 2.递归过程 三、快排的优化 1. 优化取key方式,防止栈溢出 2. 小区间优化 四、快排的非递归方式         排序算法是日常使用最频繁的一个算法,生活中也很常

    2024年02月09日
    浏览(12)
  • 数据结构--快速排序

    数据结构--快速排序

    快速排序是通过二叉树的思想,先设定一个值,通过比较,比它大的放在它的右边,比它小的放在它的左边;这样相当于在二叉树中,小的放在左子树,大的放在右子树,设定的值就是根;再通过递归的思想,将它们继续按这种方式进行排序,排到最后就排好了;这就是快速

    2024年02月08日
    浏览(13)
  • 【数据结构与算法】:选择排序与快速排序

    【数据结构与算法】:选择排序与快速排序

    🔥 个人主页 : Quitecoder 🔥 专栏 :数据结构与算法 我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:腾讯云 欢迎来到排序的第二个部分:选择排序与快速排序! 选择排序是一种简单直观的比较排序算法。该算法的基本思想是在每一轮中选出当前未排序部分的最

    2024年03月17日
    浏览(12)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包