【数据结构】快速排序

快排其实本质就是把我们这个数放到它应该所在的位置上。就比如7,1,2,3,4,5,那么7是不是最后要排的位置要在最后的那个位置上。

我们实现快排有三种方式,分别是霍尔大佬的方法,挖坑法,前后指针法,

一、霍尔大佬的方法

就拿7,5,4,9,3,1,6来举例。

1.首先我们要选一个基准值,我们就取第一位。然后定义一个i指向最左边,定义一个j,指向右边。

2.然后我们先从右边找,然后再从左边找。(为什么?下面有解释)

我们思考一下,要让key的值处于它本该所在的位置上,那么它右边的值是不是要比它大。它左边的值是不是要比它小。所以我们右边是要找出比key小的,放到key的左边,左边要找比key大的,放到key的右边。

我们开始从右边走,找到了6比key = 7小,左边开始找,找到9比key = 7大,然后交换它们的值。

找到位置

交换值

接着继续右边找小,左边找大。i  < j

最后我们发现i == j,此时找到的值肯定是比key小的。(下面再做解释)

最后我们在相遇的位置和key交换,即完成了一次排序。

接着我们通过递归,去将key的左边排,将key的右边排。划分成一个个小区间。

划分到最后,我们就会发现left  > = right,那么这个就是我们递归的出口。

代码如下:

首先我们首先一个每个区间的步骤

#include<iostream>
#include<vector>
#include<algorithm>

void QuickSort(vector<int>& a,int left, int right)
{
    int i = left;
    int j = right;
    int keyi = left;
    while(i < j)
    {
        //右边找小,循环结束j指向的位置就是比key小的位置
        while(i < j && a[j] >= a[keyi])    j--;

        //左边找大,循环结束i指向比key大的位置
        while(i < j && a[i] <= a[keyi])    i++;
        
        //交换它们的值,使比key大的在key右边,比key小的在左边
        swap(a[i],a[j]);
    }
    //循环结束,i和j相遇,相遇位置必然比key小
    swap(a[i],a[keyi]);

    .....
    划分小区间,递归
    
}

然后我们一次排序完,要划分区间,分别是key左边的区间,key右边的区间。然后递归,递归出口是left大于等于right

#include<iostream>
#include<vector>
#include<algorithm>

void QuickSort(vector<int>& a,int left, int right)
{
    if(left >= right)    return;

    int i = left;
    int j = right;
    int keyi = left;
    while(i < j)
    {
        //右边找小,循环结束j指向的位置就是比key小的位置
        while(i < j && a[j] >= a[keyi])    j--;

        //左边找大,循环结束i指向比key大的位置
        while(i < j && a[i] <= a[keyi])    i++;
        
        //交换它们的值,使比key大的在key右边,比key小的在左边
        swap(a[i],a[j]);
    }
    //循环结束,i和j相遇,相遇位置必然比key小
    swap(a[i],a[keyi]);

    .....
    划分小区间,递归
    QuickSort(a, left , i - 1);
    QuickSort(a, i + 1, right);
}

这就是一次完整的霍尔大佬的快排方法。

二、为什么要先右后左?快排的缺陷有哪些?

首先,我们先说结论,如果选左边为key,就要先走右边,最后循环结束i指向的位置必然比key小。如果右边为key,那么就要先走左边,最后循环结束i指向的位置必然大于key。

为什么左边为key,先走右边,最后位置必然比key小呢?

我们先来想想,先走右边,无非就是两种情况。(R是right,L是left)

第一种情况:R找到了小,到L找大,但是没找到,L遇到R。那么最后的位置是不是就是R的位置,R的位置是一个比key小的位置。

第二种情况:R找小,没找到,直接到L的位置,这种情况下,有两种结果,第一种结果,就是L还没有动,这时候L位置就是key的位置,key和key自己交换,没什么影响。第二种结果,就是R第一次找到了小,L找到了大,然后它们完成了交换,此时还未相遇,交换后,L位置是比key小的位置,然后R继续找小,没找到,遇到了L,那么L此时位置上的值比key小的。

所以两种情况下,最后相遇时,i位置上的值都是比key小的,我们key选左边,左边刚好需要的就是比key小的,直接交换就可以了。


那么快排是最好的排序算法吗?是否所有场景都可以用?

我们可以仔细思考思考,如果我们要排序的数组是个降序的,但我们又想排升序呢?那么快排每次是不是都要让我们的第一个key跑到最后一个位置上,然后key的右区间是没有的,就一直划分成左区间,这时候的效率是不是非常低,会导致右边区间不起作用,时间复杂度直接来到了O(n^2),数据量大的时候,还可能会造成栈溢出。

那么我们要如何解决?首先,引起这个问题出现的原因是我们key的选择,我们key只选了最左边,如果我们取的不是最左边,而是其他位置的值,那么我们的右区间就可以发挥作用起来了。

这里我们提供了两种选key的方法,第一种是随机数法,就是取一个随机数rand % (区间的长度)+left,就可以得到一个随机位置的key。但这种方法不是很稳定。第二种方法是我们的三数取中法,就是left , right, mid这三个对应位置上的数取大小在中间的作为key。

实现方法也很简单,无非就是利用if语句来判断大小即可。

int Getmid(vector<int>& a,int left,int right)
{
    int mid = (left + right) / 2;
    if(a[left] > a[right])
    {
        if(a[left] < a[mid])
            return left;
        else
        {
            if(a[mid] > a[right])
                return mid;
            else
                return right;
        }
    }
    else
    {
        if(a[right] < a[mid])
            return right;
        else
        {
            if(a[left] > a[mid])
                return left;
            else
                return mid;
        }
    }
}

三、挖坑法

在我看来,挖坑法和霍尔大佬的方法差不多,它就是将key的值存好,然后将key位置视作一个坑,然后右边找小,将值填入那个坑,然后右边小的那个位置就变成了新的坑,然后左边找大,将大的填入那个坑,然后左边位置又变成了新的坑。

代码如下

void _QuickSort3(vector<int>& a, int left, int right)
{
	if (left >= right) return;
	int i = left;
	int j = right;
	int key = a[left];
	int hole = left;
	while (i < j)
	{
		//右边找小
		while (i < j && a[j] >= key) j--;
		a[hole] = a[j];
		hole = j;

		//左边找大
		while (i < j && a[i] <= key)	i++;
		a[hole] = a[i];
		hole = i;
	}
	a[hole] = key;
	_QuickSort3(a, left, i - 1);
	_QuickSort3(a, i + 1, right);

}

三、前后指针法

就是定义一个prev指针,和一个cur指针,然后cur指针走,,如果a[cur]  > key,那么就只++cur,如果a[cur] < key,那么就先++prev,然后再交换a[prev],a[cur],然后++cur,直到cur走完数组。

代码如下:

void _QuickSort4(vector<int>& a, int left, int right)
{
	if (left >= right)	return;
	int prev = left;
	int cur = left + 1;
	int key = a[left];
	while (cur <= right)
	{
		if (a[cur] < key)
		{
			++prev;
			swap(a[prev], a[cur]);
			++cur;
		}
		else
		{
			++cur;
		}
	}
	swap(a[prev], a[left]);
	_QuickSort4(a, left, prev-1);
	_QuickSort4(a, prev + 1, right);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ck837

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

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

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

打赏作者

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

抵扣说明:

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

余额充值