【学习笔记】图解P1177快速排序

题目描述

利用快速排序算法将读入的 N 个数从小到大排序后输出。
快速排序是信息学竞赛的必备算法之一。对于快速排序不是很了解的同学可以自行上网查询相关资料,掌握后独立完成。(C++选手请不要试图使用 STL,虽然你可以使用 sort 一遍过,但是你并没有掌握快速排序算法的精髓。)

输入格式

第 1 行为一个正整数 N,第 2行包含 N 个空格隔开的正整数 ai,为你需要进行排序的数,数据保证了 Ai​ 不超过 10^9。

输出格式

将给定的 N 个数从小到大输出,数之间空格隔开,行末换行且无空格。

原题点击洛谷 快速排序

快排原理概述

下面以这个数组的排序为例
在这里插入图片描述

step1:完成对当前数组第一个数的排序,即将它放到它该在的位置(它左边的数都比它小,它右边的数字都比它大)

在这里我们先使用两个指针指向这个数组的头(left)和尾(right)
在这里插入图片描述
在这里插入图片描述
接下来的操作在一个死循环中进行
1.先从后往前扫(right指针不断左移),扫到第一个比temp(原数组首位)小的数,将它放到left指针指向的位置,空出当前right指针位置,同时left指针右移一格(这样可以保证右边的数都是比temp大的数,left指针右移原因看完后面自然懂)
在这里插入图片描述
2.接下来从左往右扫(left指针不断右移),扫到第一个比temp大的数,将它放到right指针指向的位置,空出当前left指针的位置,同时将right指针左移一格
在这里插入图片描述
由于上面两步操作在一个死循环中进行,所以当第二步结束后,又会重复执行第一步,最后当left指针于right指针相交时,死循环结束。
在这里插入图片描述
在这里插入图片描述
最后将temp的值放到left/right指针的位置
在这里插入图片描述
step2:对上一步排好序的数(6)左边的数组(4,1,2,5,3)和右边的数组(7,8,9,10)重复step1的操作

step3:当待排序的数组只剩一个元素或者少于1个元素的时候,结束操作。至此,一个简单的快排就已经完成了

分析:快排相当于将问题规模二分,每次操作排完一个数后,又将一个大数组分割成两个小数组再进行排序
在这里插入图片描述

下面附上未优化的快排代码

int split(int l,int r,int *a){//对分割数(数组首项)进行排序    
    int temp=a[l];     
    for(;;){        
	while(a[r]>=temp&&l<r)r--;        
	if(l>=r)break;        
	a[l]=a[r];        
	l++;
	       
	while(a[l]<=temp&&l<r)l++;        
	if(l>=r)break;        
	a[r]=a[l];        
	r--;     
    }    
    a[l]=temp;    
    return l;
}

void quickSort(int l,int r,int *a){    
    if(l>=r)return;
    
    int mid=split(l,r,a);    
    quickSort(l,mid-1,a);    
    quickSort(mid+1,r,a)}

但这样是过不了的,如果每次分割数的位置都是靠近边上的,O(nlogn)的算法会退化成O(n^2)

举个例子
10 9 8 7 6 5 4 3 2 1
每次分割将数组分割成长度为1和n-1的两个小数组,这样总共要分割n层

快排优化1——寻找合适的分割数

如果我们每次分割的位置都在数组正中间附近,数组的规模很快就会变小,总分割层数也会降低。
选择分割数的方法很多,这里仅介绍三元素取中法
即每次选取数组的头尾和中间项,三个数比大小,选取中间的数当分割数。(需要将其与首项进行换位)。下面附上代码

void threeMid(int l,int r,int *a){
    int mid=(l+r)>>1;    
    if((a[l]>a[mid]&&a[mid]>a[r]) || (a[l]<a[mid]&&a[mid]<a[r]))
    {        
        swap(a[l],a[mid]);    
    }    
    else if((a[l]>a[r]&&a[r]>a[mid])||(a[mid]>a[r]&&a[r]>a[l]))
    {        
	swap(a[l],a[r]);    
    }
}

将其放到split最前面即可
在这里插入图片描述

但一个数组中难免有许多相同的数,当相同的数字很多的时候,O(nlogn)的算法同样会退化成O(n^2)

例如:
6 6 6 6 6 6 6 6 6 6

快排优化2——解决重复键问题

我们的目标是一次性排序完所有和分割数相同的数
我们的方法是每次扫到和temp(分割数)相同的数的时候,就将其放到最边上(从左往右扫 扫到放左边,从右往左扫 扫到放最右边)。最后当指针相遇时,将最边上的数放回中间。下面附代码

void split(int &left,int &right,int *a){    
    //这里我们需要两组指针,一组用来正常扫,另一组用来存边上相同的数    
    int l=left,r=right,ll=left,rr=right;    
    threeMid(l,r,a);    
    int temp=a[l];    
    for(;;){        
	while(a[r]>=temp&&l<r){            
	if(a[r]==temp){//将相同数放到最右边                
	    swap(a[r],a[rr]);                
	    rr--;            
	    }            
	    r--;        
	}        
	if(l>=r)break;        
	a[l]=a[r];        
	l++;
	        
	while(a[l]<=temp&&l<r){            
	    if(a[l]==temp){//将相同数放到最左边                
	    swap(a[l],a[ll]);                
	    ll++;            
	    }            
	    l++;        
	}        
	if(l>=r)break;        
	a[r]=a[l];        
	r--;   
    }  
    a[l]=temp;
	   
    for(int i=left;i<ll;i++){//将相同数从最左边换到temp的左边        
	swap(a[i],a[l-1]);        
	l--;    
    }    

    for(int i=right;i>rr;i--){//将相同数从最右边换到temp的右边        
	swap(a[i],a[r+1]);        
	r++;    
    }    

    //从l到r都是相同的数。此处需要同时传出去两个位置,即l-1和r+1    
    right=r+1;    
    left=l-1;
}

最后附上完整AC代码

#include<iostream>
#include<cstdio>
using namespace std;

const int maxn=1e5+10;
int a[maxn];

void threeMid(int l,int r,int *a){    
    int mid=(l+r)>>1;    
    if((a[l]>a[mid]&&a[mid]>a[r]) || (a[l]<a[mid]&&a[mid]<a[r])){        	
	swap(a[l],a[mid]);    
    }    
    else if((a[l]>a[r]&&a[r]>a[mid])||(a[mid]>a[r]&&a[r]>a[l])){        
	swap(a[l],a[r]);    
    }
}

void split(int &left,int &right,int *a){    
    //这里我们需要两组指针,一组用来正常扫,另一组用来存边上相同的数    
    int l=left,r=right,ll=left,rr=right;    
    threeMid(l,r,a);    
    int temp=a[l];    
    for(;;){        
        while(a[r]>=temp&&l<r){            
            if(a[r]==temp){//将相同数放到最右边                
                swap(a[r],a[rr]);                
                rr--;            
            }            
            r--;        
        }        
        if(l>=r)break;        
        a[l]=a[r];        
        l++;
        
        while(a[l]<=temp&&l<r){            
            if(a[l]==temp){//将相同数放到最左边                
                swap(a[l],a[ll]);                
                ll++;            
            }            
            l++;        
        }        
        if(l>=r)break;        
        a[r]=a[l];        
        r--;    
    }    
    a[l]=temp;
    for(int i=left;i<ll;i++){//将相同数从最左边换到temp的左边   
	swap(a[i],a[l-1]);        
	l--;   
    }    
    for(int i=right;i>rr;i--){//将相同数从最右边换到temp的右边        
        swap(a[i],a[r+1]);        
        r++;    
    }    

    //从l到r都是相同的数。此处需要同时传出去两个位置,即l-1和r+1    
    right=r+1;    
    left=l-1;
}

void quickSort(int l,int r,int *a){    
    if(l>=r)return;
    int left=l,right=r;    
    split(left,right,a);    
    quickSort(l,left,a);    
    quickSort(right,r,a);
}

int main(){   
    int n;    
    cin>>n;    
    for(int i=1;i<=n;i++){        
        cin>>a[i];    
    }    
    quickSort(1,n,a);    
    for(int i=1;i<=n;i++){        
        cout<<a[i]<<" ";    
    }    
    return 0;
}

附上博主b站直播号:22454049。直播新人求关注>_<

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值