常见排序算法二、

目录

1、快速排序的概念:

2、快速排序的版本:

2.1、递归版本:

2.1.1、hoare版本:

2.1.2、挖坑法:

2.1.3、前后指针法:

2.2、快速排序递归版本的时间复杂度求解:

2.2.1、最好情况:

2.2.2、最坏情况:

2.3、快速排序的两个优化:

2.3.1、针对基准值key的选择进行优化:

2.3.2、小区间优化:

2.4、非递归版本:


1、快速排序的概念:

快速排序 是Hoare 1962 年提出的一种 二叉树结构的交换排序方法 ,其基本思想为: 任取 待排序元素序列中 的某元素作为 基准值 ,若排 升序 ,则按照该排序码将待
排序集合分割成两子序列,左子序列中所有元素均 小于等于基准值 ,右子序列中所有元素均 大于等于基准值 ,若排 降序 ,则按照该排序码将待排序集合分割成两子
序列,左子序列中所有元素均 大于等于基准值 ,右子序列中所有元素均 小于等于基准值 ,然后左右子序列重复该过程, 直到所有元素都排列在相应位置上为止
注意 :  在选取基准值时,一般是 选取数组中最左边或者最右边或者通过三数取中来选基准值 ,但是由于所给定的 数组是随机的 ,所以 不管是选取数组中最左或者最
右或者三数取中,所选的基准值都是随机的值 ,其次,上述所说的 小于等于和大于等于 中的 等于 指的是, 与基准值相等的数据放在基准值的左边和右边都是可以
的、

2、快速排序的版本:

2.1、递归版本:

对于如何按照基准值将待排序列分为两子序列单趟排序的常见的方式有:

QuickSort(int* a, int begin,int end) 外层递归函数不同的递归方法QuickSort函数都是一样的PartSort(int* a,int left,int right) 是单趟函数,也正是写法不同的地

方,所以我们这里主要探讨的是 PartSort 单趟排序函数、

递归版本的快速排序的整体思路为:

先选出基准值key,一般是给定的随机数组的第一个元素或者是最后一个元素,先使用下面三种方法进行单趟排序若排升序,单趟排序后,则让基准值key左边部

分的所有数据均小于等于key,基准值key右边部分的所有数据均大于等于key若排降序,单趟排序后,则让基准值key左边部分的所有数据均大于等于key,基准

值key右边部分的所有数据均小于等于key,这就完成了单趟排序,但是要注意,升序情况下, 基准值key左边部分的所有数据均小于等于key,但不一定是升序的,

基准值key右边部分的所有数据均大于等于key,但也不一定是升序的,降序情况下, 基准值key左边部分的所有数据均大于等于key,但不一定是降序的,基准值

key右边部分的所有数据均小于等于key,但也不一定是降序的,然后再使用分治的思想去递归基准值key的左右子序列即可、

2.1.1、hoare版本:

所谓单趟排序,其过程为 : 选出一个基准值Key,一般选取给定的随机数组中的第一个元素或者是最后一个元素若选取数组中第一个元素作为基准值Key的话,则

定义变量Keyi,再把left赋值给Keyi,若选取数组中最后一个元素作为基准值Key的话,则定义变量Keyi,再把right赋值给Keyi、若排升序,则单趟排序结束后要

求,基准值左边的序列中的所有数据都小于等于基准值,基准值右边的序列中的所有数据都大于等于基准值、若排降序,则单趟排序结束后要求,基准值左边的序

列中的所有数据都大于等于基准值,基准值右边的序列中的所有数据都小于等于基准值、

思路:

升序为例,若选取给定的随机数组中的第一个元素作为基准值Key的话,则定义变量Keyi,并把left赋值给Keyi,则right必须先走,而right在找比基准值Key小的数

据,直到找到比基准值Key小的数据后,right停止,此时,left 再开始走,而left是在找比基准值Key大的值,直到找到后停止,然后交换left和right所在位置上的数

据,然后right再找小,left再找大,找到之后再进行交换,当left和right相遇时,即相等时,再把该相遇位置上的值与Keyi位置上的数据Key进行交换,从而达到目

的,在此,即使数组元素个数为偶数个,则left和right也一定会相遇的,和数组元素个数奇数偶数没有关系,right找到比Key小的数就不动了,left找到比Key大的数

就不动了,每次移动过程中,一定是其中一个去与另外一个相遇,即,两者同时只有一个在移动,另外一个不动,所以一定会相遇,其次,最后一步需要把相遇位

置上的数据和Keyi位置上的数据Key进行交换,若相遇位置上的数小于Keyi位置上的数据Key是可以的,此时相遇位置上的数据不会等于Key,是因为right找小,left

找大,两者直接把等于Key的值跳过了,但若大于的话,就是不对的,怎么保证相遇位置上的数据一定是小于Keyi位置上的数据Key呢?,left和right两者不会在一

个数据比Key大的位置上相遇,是因为,right先走已经保证了,即每一次交换完之后都是right先走,left再走,此时的相遇只有两种情况,一,right遇到left,二,left

遇到right,若是第二种情况的话,right不动,left与right相遇,此时相遇位置上的数据一定比Key小,若是第一种情况的话,left不动,right与left相遇,因为上一次交

换后,left所在位置上的值一定比Key小,所以,right与left相遇的位置一定也比key要小降序的话则right找大,left找小、

若选取给定的随机数组中的最后一个元素作为基准值Key的话,则定义变量Keyi,并把right赋值给Keyi,则left必须先走,其他的不需要变动,这样就保证了相遇位

置上的元素一定比Key大,分析同上、        

升序为例,若经过一次单趟排序,使得key左边的所有数据均小于等于key,key右边的所有数据均大于等于key,再将key左序列和右序列进行上述单趟排序,如

此反复进行上述操作,直到子区间中只有一个数据或者不存在数据时,即子区间中只有一个数据或者子区间不存在时,为子问题的最小规模,则递归结束因为子

区间中只有一个数据或者不存在数据时,可以看成升序,也可看成降序、

总结:

升序并且第一个元素作为基准值,则right先走,left后走,并且,left找大,right找小升序并且最后一个元素作为基准值,则left先走,right后走,并且,left找大,

right找小降序并且第一个元素作为基准值,则right先走,left后走,并且,left找小,right找大降序并且最后一个元素作为基准值,则left先走,right后走,并

且,left找小,right找大、

代码实现:

//单趟排序->hoare版本、
int PartSortHoare(int*a, int left, int right)//一级指针传参,一级指针接收、
{
	assert(a);

	//方法一:
	//选取数组首元素作为基准值、

	//三数取中法,取出数组首元素,尾元素,中间位置元素,选择这三者中,不是最大,也不是最小的那个值的下标、
	int midi = GetMidIndex(a, left, right);
	//若选择数组首元素作为基准值key,则应为:Swap(&a[midi], &a[left]); 这样就让数组首元素,尾元素,中间位置元素,三者中,不是最大,也不是最小的那个值作为了基准值key、
	Swap(&a[midi], &a[left]);
	//下面的代码不需要进行改变,在单趟排序中,不管使用何种方法,仍选取数组首元素或者尾元素作为基准值key、

	int keyi = left;   //用keyi记录key的下标、
	while (left < right)
	{
		//升序、
		//right先走、
		while (left < right && a[right] >= a[keyi])//right找小,找不到则right--,降序则right找大,找不到则right--,变为<=、
		{
			right--;
		}
		//left再走、
		while (left < right && a[left] <= a[keyi])//left找大,找不到则left++,降序则left找小,找不到则left++,变为>=、
		{
			left++;
		}
		//上面的>=和<=中的=,一是表明与基准值相等的数据在基准值的左边和右边都是就可以的,二是避免死循环,比如: 5 5 2 3 5,若两者不加等于的话,就会造成死循环、
		//上面两个while中的 left < right 主要是为了防止越界访问的,若选择数组首元素作为基准值排升序和排降序时都可能会越界访问,比如1 2 3 4 5、

		//交换、
		Swap(&a[left], &a[right]);//找到后就交换a[left]和a[right],让小的去左边,大数去右边,降序则让小的去右边,大的去左边、
	}
	//交换相遇位置和keyi位置上的数据、
	Swap(&a[keyi], &a[left]);
	//Swap(&a[keyi], &a[right]);
	return right;
	//return left;

	方法二:
	选取数组尾元素作为基准值、

	三数取中法,取出数组首元素,尾元素,中间位置元素,选择这三者中,不是最大,也不是最小的那个值的下标、
	//int midi = GetMidIndex(a, left, right);
	若选择数组尾元素作为基准值key,则应为:Swap(&a[midi], &a[right]); 这样就让数组首元素,尾元素,中间位置元素,三者中,不是最大,也不是最小的那个值作为了基准值key、
	//Swap(&a[midi], &a[right]);
	下面的代码不需要进行改变,在单趟排序中,不管使用何种方法,仍选取数组首元素或者尾元素作为基准值key、

	//int keyi = right;   //用keyi记录key的下标、
	//while (left < right)
	//{
	//	//升序、
	//	//left先走、
	//	while (left < right && a[left] <= a[keyi])//left找大,找不到则left++,降序则left找小,找不到则left++,变为>=、
	//	{
	//		left++;
	//	}
	//	//right再走、
	//	while (left < right && a[right] >= a[keyi])//right找小, 找不到则right--, 降序则right找大, 找不到则right--, 变为<= 、
	//	{
	//		right--;
	//	}
	//	//上面的>=和<=中的=,一是表明与基准值相等的数据在基准值的左边和右边都是就可以的,二是避免死循环,比如: 5 5 2 3 5,若两者不加等于的话,就会造成死循环、
	//	//上面两个while中的 left < right 主要是为了防止越界访问的,若选择数组尾元素作为基准值排升序和排降序时都可能会越界访问,比如1 2 3 4 5、

	//	//交换、
	//	Swap(&a[left], &a[right]);//找到后就交换a[left]和a[right],让小的去左边,大数去右边,降序则让小的去右边,大的去左边、
	//}
	交换相遇位置和keyi位置上的数据、
	//Swap(&a[keyi], &a[left]);
	Swap(&a[keyi], &a[right]);
	return right;
	//return left;
}

2.1.2、挖坑法

思路:

和hoare方法类似,但有一些不同,若选择数组首元素为基准值key,则数组中第一个位置即为坑1,此时需要把数组第一个位置上的数据,即数组首元素保存到key

中,那么数组中第一个位置上的数据就已经被保存下来了,也就代表着数组第一个位置上的数据可以被覆盖了,即称之为坑1,若进行升序排列的话,则left找大,

right找小,由于坑1在左边,而升序排序需要把小于等于key的数据放在key的左边,并且right找的是比key小的值,跳过大于等于key的值,所以很自然的就先让

right先走,找到比key小的数据放在坑1里,然后该比key小的数据所在的位置变成新的坑2,left再走,找到比key大的值放在坑2中,然后该比key大的数据所在的位

置变成新的坑3,重复上述操作,直到left与right相遇,则停止,并且两者相遇的时候,所在的位置一定是一个坑位,然后再把key中的值放到该相遇位置的这个坑位

上,此时就满足了基准值key的左边所有的数据均小于等于key,基准值key的右边所有的数据均大于等于key,这就是所谓的挖坑法与hoare方法有共同点,也有

不同点挖坑法相比于hoare方法的优势是,挖坑法更好理解,不需要理解在升序并且选择数组首元素作为基准值的的条件下,left和right相遇的位置上的数据一定

比key小,不需要理解在升序并且选择数组尾元素作为基准值的的条件下,left和right相遇的位置上的数据一定比key大,不需要理解在降序并且选择数组首元素作为

基准值的的条件下,left和right相遇的位置上的数据一定比key大,不需要理解在降序并且选择数组尾元素作为基准值的的条件下,left和right相遇的位置上的数据一

定比key小其次就是,不需要理解为什么选取数组首元素为key,则right必须先走,left后走,为什么选取数组尾元素为key,则left先走,right后走的原因,因为这

种情况下关于谁先走的问题就比较自然,具体解释见上述,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脱缰的野驴、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值