快速排序(递归版)

前言

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

快速排序

每一次排序,随便选出一个中间值,将待排序列中所有小于它的值放到左边,大于它的值放到右边,以中间值的下标为分界线,形成左右两个区间,这样进行一次排序后就将中间值放到相应的位置上了,然后左右区间进行重复的动作,直到序列有序。  

主框架:

// 假设按照升序对array数组中[left, right]区间中的元素进行排序
void QuickSort(int array[], int left, int right)
{
 //区间中只有一个元素或没有元素存在,直接返回
 if(left>=right)
     return;

 //递归
 // 按照基准值对array数组的 [left, right]区间中的元素进行划分
 int keyi= partion(array, left, right);

 // 划分成功后以keyi为边界形成了左右两部分 [left, keyi-1] 和 [keyi+1, right]
 
 QuickSort(array, left, keyi-1);
 
 QuickSort(array, keyi+1, right);
}

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,写递归框架时可想想二叉 树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

将区间按照基准值划分为左右两半部分的常见方式有:

1.Hoare法

单趟图解:

代码:

void swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
//1.Hoare
int Hoare(int arry[], int begin, int end)
{
	//选最左侧作key
	int keyi = begin;
	
	int left = begin;
	int right = end;

	while (left<right)
	{
		//右边找小,先走
		while (left < right && arry[right] >= arry[keyi])
		{
			right--;
		}
		//左边找大
		while (left < right && arry[left] <= arry[keyi])
		{
			left++;
		}
		//交换:将大的放到右边,小的放到左边
		swap(&arry[left], &arry[right]);
	}
	//left与right相遇
	//交换:将key值放到相遇处(应该存放的位置)
	swap(&arry[keyi], &arry[left]);
	return left;
}
//快速排序
void QuickSort(int arry[], int left, int right)
{
	if (left >= right)
		return;

	int keyi = Hoare(arry, left, right);
	QuickSort(arry, left, keyi - 1);
	QuickSort(arry, keyi + 1, right);
}

注意:

1.在左右找值时,首先要判断是否越界,其次,一定要加等于判断:

为什么会越界? 

因为,你是在循环中一直找符合条件的值,如果一直找不到,就会一直找,这样就会发生越界。

举例:

 为什么要加 = 号?

因为,不加 = 号,就会发生死循环。

举例:

2.选左边作key,一定要右边先走;右边作key,左边先走。

以左边作key为例,因为,最后left与right相遇,我们需要将key与相遇位置进行交换。那么要保证交换后,keyi的左边都是小于key的值,右边都是大于key的值。就必须右边先走。

毕竟,right与left相遇,只有两种情况:

left遇到right:

right遇到left:

总结:right找小,left找大,只有找到或者相遇它们才会停下,然后发生交换,这是一个回合制游戏,right先走,那它走的次数一定不小于left。 所以,不论left与right在何处相遇,所指向的值一定不大于key。

由于,hoare 法有太多需要注意的点,所以又衍生出了以下几种方法:

2.挖坑法

单趟图解:

 代码:

//2.挖坑
int PartSort2(int* arry, int left, int right)
{
	int key = arry[left];
	
	int piti = left;//坑位下标
	while (left < right)
	{
		//右边找小,先走
		while (left < right && arry[right] >= key)
		{
			right--;
		}
		arry[piti] = arry[right];
		piti = right;
		
		//左边找大
		while (left < right && arry[left] <= key)
		{
			left++;
		}
		arry[piti] = arry[left];
		piti = left;
	}
	arry[piti] = key;
	return piti;
}

3.指针法

单趟图解:

 代码:

//3.前后指针
int PartSort3(int* a, int left, int right)
{
	int prev = left;
	int cur = left + 1;
	int keyi = left;

	while (cur <= right )
	{
		//找小,处理
        //根据图解:我们只会在cur找到小值时发生交换
        //其次,自己和自己交换没有太大的必要
        //++prev:prev在后,所以先++,再交换。
        //++prev != cur:++prev后,两者不重叠,再交换。
		if (a[cur] < a[keyi] && ++prev != cur)
			swap(&a[prev], &a[cur]);
		//cur会一直往后走
		cur++;
	}
	swap(&a[keyi], &a[prev]);
	keyi = prev;
	return keyi;
}

三种方法都大同小异,你喜欢哪种就用哪种。另外快排还有些优化性能的方法,下次再说啦!拜拜!😊😊

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值