快速排序*

快排思想及基础步骤




任意定枢轴版

基本思想:

采用无重复循环(每个地方在一次循环中指针只经过一次)每经过一次循环,枢轴左边的数都比它小,右边的数都比它大,
即差不多可以理解为走一遭就定好了枢轴的位置。然后递归,左边递归左边的,右边递归右边的,典型分治问题(大问题分割慢慢变成子问题的过程)最后递归出口就是两指针出发位置一样了,也就是越分越小分到只有自己一个数了,就结束了。

!!!一个轮回结束是以当前选定的枢轴归位为标志的

需要的东西:

  • 定枢轴
  • 前后两指针
  • 递归

step1: 确定好枢轴。就是中间点,我们设为key,不是说它一定要在中间,就是定个偶像标杆,明白吗?但是呢,定在中间最好,为方便选择,一般是让key=q[0]或q[n-1]或q[n-1/2]这三种。

step2: 定两个指针,我们设为i,j。i从0位置的前一个位置出发,j从最后一个元素的后面出发,没有数组越界哈,因为真正用的时候还是0和n-1开始的,这里直接看代码比较好。

step3: 写递归式,注意边界(敲重要)

代码模板

递归出口
走的过程
递归式

void quick_sort3(int q[],int l,int r)
{
    if(l>=r)return;
    int i=l-1,j=r+1,key=q[l+r>>1];
    while(i<j)
    {
        do i++;while(q[i]<key);
        do j--;while(q[j]>key);
        if(i<j) swap(q[i],q[j]);
    }
    quick_sort3(q,l,j);
    quick_sort3(q,j+1,r);
}

细节问题(边界致命)

可能不明白的地方:

  1. l不一定要>=r,就=r也是可以的;
  2. l+r>>1是二进制除以2的方法,速度快些;
  3. do-while循环不用while-do的原因是:
while判断的是<key,如果某一时刻ij都=key,你说你怎么收场
两指针就会停滞不前到第三步交换,换了发现还是没变,就会陷
入无限循环;
  1. 只能<key不能<=Key的原因:
如果=Key,那key这个位置的数岂不是永远不动忽略掉了吗?
会造成两边都有所谓的基准数,其实这种并不是什么让基准数归
位的做法,而是让左边都比它小,右边都比它大那种,然后它
自己归位

而下面那种原始的快排 就要=
  1. 为什么swap是需要i<j的条件呀?
非常美好的理想就是ij相遇即一次循环结束,但是鉴于里面不止
一个while循环,所以它会在里面自己走,ij可能来不及经历那
两个比较就跳着跳着交叉了错过了彼此,注意,只要交叉了,那
就说明已经结束一轮路程了;
---------
目前遇到的例子ij一轮后都是相邻的,所以j,j+1和i-1,i一样
这个还是需要数学推导证明;(等我遇到回来补充)
  1. 递归式的两个边界需要注意什么呢?怎么确定的?
    结论
//枢轴选的q[l]:递归式边界就
quick_sort3(q,l,j);
quick_sort3(q,j+1,r);
//枢轴选的q[r]:递归式边界就
quick_sort3(q,l,i-1);
quick_sort3(q,i,r);
//其它无所谓
此处有一个小小的数学推导:
由算法的过程我们可以知道q[l...i-1]<key,不包括i,因为此
时i停下来了,q[i]>=x,同理,q[j]<=x,因为走完一轮j<=i了
所以j必在l...i-1之中,所以q[l..j]均<=key;
--------------------------------------------------

>>>现在我们再来看为什么选i或j定边界还有枢轴限定条件:

若定j位分界点,而枢轴选择q[r]就可能会遇到无限划分(n,0)
和(0,n)就是分块分来分去都是这样,因为j有可能=r,在r处
停下来,而此时正好r前面所有的数都<q[r],j就在r处等,一直
等到i的那个循环停止,此时i也=r,停下来了,由于也不满足
swap条件,它就出去了,结果子子孙孙都是这样,也就是不断
n和0(枢轴左边n个数,右边0个数,枢轴作为最后一个数)
---------------
同理,选i定界不能让l作为枢轴,j从右边一直过来,i在l处等
l右边的数都比枢轴大,就是0和n的情况
  1. j-1,j+1这个为什么不行?
那请问j它自己怎么办?这个适用于枢轴取的是第一个或最后
一个且为本列数中的最小值或最大值;这个时候就不会造成
无限划分,因为j-1刚好跳过了无限划分的情况,反正最后一
个都是最大,它就会确定好它的位置去排别人就恢复正常了

一旦无限划分,就会造成内存出限,因为它一直在递归
(要记得递归是要有递归出口才能溜的)

但是 如果很不巧 你选的某个枢轴刚好是最大的或最小的,记得更改哈,不然排鬼序,笑死,永远卡在那。

如一组测试数据:

3 1 7 2 6 4 5 你选这中间某个比如7作为枢轴,你看,好像不是第一个也不是最后一个,应该不怕,但是注意这只是第一轮,一轮结束,7就跑到最后一个地方去了,无限划分开始了…

到这里 你就会发现 真正的结论并不是上面那个固定的什么,其实是避免最大最小的情况,而不是要避免定在l或r

  1. 内存超限(无限递归)和没排好自己出来了的区别:
没排好出来一定是到了递归出口,这是j-1,j+1的情况,因为j
没人管啊,它终有一天是独自在那,所以还是乱序的
-------
而内存超限一定是因为递归递不出来了hhh



正常普通版:

枢轴选第一个:

基本思想: ij相遇的位置就是选定当前枢轴(第一个数)最终的位置,每次一轮确定一个;

定的q[l],就要从右边j开始(从对立面开始可保
证ij相遇时temp是小于基准数的,最后换到第一位去)

第一种写法:边换边归位
首先,j停下来(可能是找到比key小的数也可能是遇上i)让
q[i]存下来j停下来的数,现在的i就是第一个枢轴
i停下来,让j位置换上i的数,

现在的情况就是key存了枢轴的,枢轴存了j的,j换上了i的
key=枢轴

枢轴=j(第一轮)i=j(后面)
j=i

i=key
最终交换了枢轴和ij相遇点
key=q[l],key=q[i]
q[i]=q[j]
q[j]=q[i]
q[i]=key
#include<iostream>
using namespace std;

const int N = 1e6;
int n, q[N];

void quick_sort(int q[],int l, int r)
{
	if (l >= r) return;
	int i = l, j = r, key = q[l];
	while (i < j)
	{
		while (q[j] >= key && i < j) j--;
		q[i] = q[j];
		while (q[i] <= key && i < j) i++;
		q[j] = q[i];
	}
	q[i] = key;//ij都可 反正指的同一个数
	quick_sort(q, l, j - 1);
	quick_sort(q, j + 1, r);
}
int main()
{
	cin >> n;
	for (int i = 0; i < n; i++) cin >> q[i];
	quick_sort(q,0, n - 1);
	for (int i = 0; i < n; i++) cout << q[i];
	return 0;
}
第二种写法:先交换后归位
#include<iostream>
using namespace std;

const int N = 1e6;
int n, q[N];

void quick_sort(int l, int r)
{
	if (l >= r) return;
	int i = l, j = r, key = q[l];
	while (i!=j)
	{
		while (q[j] >= key && i < j) j--;
		while (q[i] <= key && i < j) i++;
		if(i<j) swap(q[i],q[j]);
	}
	//基准数和相遇点交换
	q[l]=q[i];
	q[i]=key;
	quick_sort(l, j - 1);
	quick_sort(j + 1, r);
}
int main()
{
	scanf("%d",&n);
	for (int i = 0; i < n; i++) scanf("%d",&q[i]);
	quick_sort(0, n - 1);
	for (int i = 0; i < n; i++) printf("%d ",q[i]);
	return 0;
}


更新~
无敌至尊版快排诞生了
枢轴随便选且不会造成无限划分

void quick_sort3(int q[],int l,int r)
{
    if(l>=r)return;
    int i=l-1,j=r+1,key=q[l+r>>1];
    while(i<j)
    {
        do i++;while(q[i]<key);
        do j--;while(q[j]>key);
        if(i<j) swap(q[i],q[j]);
    }
    //更新过的代码
    quick_sort3(q,l,j-(j==r));
    quick_sort3(q,j+1,r);
}

ps:普通版-边走边交换的还不怎么特清晰(待补充)


鉴于特殊版快排可以随机选枢轴 但是有特殊情况会造成无限划分,普通版呢又不能随机选枢轴;此问题有待研究…
但是取中值效率应该蛮高的~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值