【幼儿园算法】快速排序以及快排求区间K大以及扩展优化的学习
发现自己一直都不会快排orz orz orz ,快排原理一直不懂,从来都用stl::sort,,,并安慰自己没用,今天就遇到了一道必须要运用快速排序思想的题。。爆炸。。应该深刻铭记唐老师的话:任何算法都是有用的,以后再也不敢拉下任何基础算法了。于是去学了一波幼儿园孩童都会的快速排序orz orz。并且这里会记录下那道要用手写快排的题。
(这里若不添加说明,默认排序从小到大,如从大到小是同理)
快速排序原理:其本质是分治的思想,对于一个序列[L,R],我们选择一个数作为基准数,然后把所有大于等于他的放在他的右边,然后将所有小于等于他的放在他的左边。然后这个数的位置就确定了是第K个这个位置,这个数就是第K小,然后我们就分开成两个序列[L,K-1]和[K+1,R]递归下去求解。
具体实现上是用两个指针i和j,初始i=L,j=R,然后j左移至第一个比基准数小的位置,i右移至第一个比基准数大的位置,然后他们交换之后继续这样操作至到两个指针到同一个位置然后这个位置设定为基准数。
当然话是这么说,实际实现上还是有些不太一样的。基本上看代码就能理解。
朴素版code:
期望希望可以分开成一半一半,时间O(nlogn)。但实际上luogu数据卡成只有40分就是答案。因为考虑在一个有序的数列中,每次我们其实只讲基准数一个数给分出去了,每次只会使考虑的数字-1,这样是O(n^2)的,同样,我们发现如果他有很多重复数字的时候,每次也只会分出去一个,也是O(n^2)的。
那么我们考虑一下优化。对于有序数列,我们每次选取其中位数就可以解决,至于寻找中位数,我们用nth_element搞定(这个的背后实现原理实际上也是快排(STL优化后)?),这样会增加很大常数(网上或许有更好的优化,但这个很实用),luogu上+=20分。
对于重复数字,每次我们在K左右将和a[K]相同的数字去除考虑中再递归下去就可以了。就可以愉快AC了!
code:
int n; int a[10005]; void qsort(int a[],int l,int r) { if(l>=r) return; int i=l; int j=r; int temp = a[l]; while(i<j) { while(i<j&&a[j]>=temp) --j; a[i]=a[j];//a[i]已经存下来,将a[i]替换为a[j] while(i<j&&a[i]<=temp) ++i; a[j]=a[i];//将a[j]替换为a[i] } a[i] = temp;//最后将两个指针在一起的位置替换为基准数 qsort(a,l,i-1); qsort(a,i+1,r); }在朴素版中我们基准数就选a[l],我们考虑一下时间复杂度,我们期望在一个随机数列中每次我们
int n; int a[100005],b[100005]; void qsort(int a[],int l,int r) { if(l>=r) return; //中位数优化 for(int i=l;i<=r;i++) b[i]=a[i]; nth_element(b+l,b+((l+r)>>1),b+r); int temp = b[(l+r)>>1]; for(int i=l;i<=r;i++) { if(a[i]==temp) { swap(a[l],a[i]); break; } } // int i=l; int j=r; while(i<j) { while(i<j&&a[j]>=temp) --j; a[i]=a[j]; while(i<j&&a[i]<=temp) ++i; a[j]=a[i]; } a[i] = temp; //去重优化 int ilol=i-1; int ilor=i+1; while(ilol>=l&&a[ilol]==a[i]) ilol--; while(ilol<=r&&a[ilor]==a[i]) ilor++; // qsort(a,l,ilol); qsort(a,ilor,r); }然后就是快排求K小(K大类同,或者N-K+1小) 我们发现每次都能期望(中位数优化)求到[L,R]中mid的数的位置,那么我们如果K>mid那么我们只执行[mid+1,R]的递归,否则执行[L,mid]的递归。如果我们刚好发现诶,mid就是K,那么就求到了。略算一下,期望时间O(n)。下面一个朴素版本代码(没加优化就A了就不想优化了orz)。这个由于其实际是尾递归,可以写成非递归形式。
void qskth(int a[],int l,int r,int k) { int i=l; int j=r; int temp = a[l]; while(i<j){ while(i<j&&a[j]>=temp) j--; a[i]=a[j]; while(i<j&&a[i]<=temp) i++; a[j]=a[i]; } a[i]=temp; if(i==k) { printf("%d",a[i]); return; } if(k>i) qskth(a,i+1,r,k); else qskth(a,l,i-1,k); }STL求的话就是nth_element(a+1,a+k,a+1+n)这样就会把a[k]全部<=a[k],右边全部>=a[k],发现这和快排的原理一致。
#32. Sort
就是这道题!NOI.AC sor 排版有毒,自己打开网站看吧。题目大意:给一个5*1e5的序列,只能使用翻转(就是STL里的reverse),每次翻转的代价为翻转的长度。在翻转代价<=1e7的情况下,给出一种方案。 1e7数据,要么nlog^2,要么n^(3/2),很不幸,我都不会,考场上甚至连n^2都敲错了。。。 标算:注意到他有一个特殊数据为数列为0/1。考虑到归并排序0/1的过程,就是两个有序数列的合并。对于0/1数列,就是一串0在前面一串1在后面他们合并,用翻转实现就是将左区间所有1和有区间所有0翻转一下,就完成了合并操作。O(nlogn)就搞定了50%的特殊数据。 那么对于更加普遍的情况来说呢?想到快排的过程,我们选定一个基准数然后比他大的全部放右边,比他小的全部放左边。那么我们模拟这个快排的过程,对于[L,R],将所有>基准数a[k]的全部设为1,<=基准数a[k]的全部设为0.然后就可以愉快地转化为特殊情况跑一遍mergesort,然后由于基准数和其他比他小的数在mergesort里面都是当作0考虑的,之后我们强行把基准数调整到中间来就可以了。 考虑时间复杂度O(nlog^2n),操作复杂度也是和时间复杂度同阶的O(nlog^2n)。 这里必须使用快排的中位数优化+去重优化,否则会被卡成doge。#include<iostream> #include<algorithm> #include<cstdio> using namespace std; int n; const int maxn = 50005; char buf[1<<20],*p1,*p2; #define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++) inline int R() { char t=GC; int x=0; while(!isdigit(t)) t=GC; while(isdigit(t)) x=x*10+t-48,t=GC; return x; } int a[maxn]; int pa[maxn]; void mergesort(int l,int r) { if(l==r) return; int mid = (l+r)>>1; mergesort(l,mid); mergesort(mid+1,r); int fl = l; while((!pa[fl])&&fl<=mid) fl++; if(fl>mid) return; int fr = r; while(pa[fr]&&fr>mid) fr--; if(fr<=mid) return; printf("%d %d\n",fl,fr); reverse(a+fl,a+fr+1); reverse(pa+fl,pa+fr+1); } void qsort(int l,int r) { if(l>=r) return; int temp=a[l]; for(int k=l;k<=r;k++) if(a[k]>temp) pa[k]=1; else pa[k]=0; mergesort(l,r); int i=l; for(;i<=r;i++) if(a[i]==temp&&a[i+1]!=temp) break; if(i>r) i--; int k = r; for(;k>=l;k--) { if(!pa[k]) { reverse(a+i,a+k+1); printf("%d %d\n",i,k); break;} } int klol = k-1; int klor = k+1; while(klol>=l&&a[klol]==a[k]) klol--; while(klor<=r&&a[klor]==a[k]) klor++; qsort(l,klol); qsort(klor,r); } int main() { // freopen("sort.in","r",stdin); // freopen("sort.out","w",stdout); n=R(); for(int i=1;i<=n;i++) a[i]=R(); qsort(1,n); // for(int i=1;i<=n;i++) cout<<a[i]<<' '; puts("-1 -1"); }最后,当我们需要排序的时候,该用stl::sort就用stl::sort orz orz orz。