做快排题目时,发现同是二分,基准数不同取法,会让相同数据的时间复杂度不同(好像理所应当),不过由于某些测试数据本身比较特殊,比如10000个自然数升序:(1,2,3,...,9997,9998,9999,10000)
我知道会有人说这不已经排好了吗,,,可事实是某平台测试数据就是如此。。。
将其输入后运用快排1:(耗时3.20s)
//简化普通快排
void qs_my(int l,int r)
{
int i,j,t,temp;
if(l>r)//
return;
temp=a[l];//temp用于存储基准数
i=l;
j=r;
while(i!=j)
{
while(a[j]>=temp && i<j)j--;
while(a[i]<=temp && i<j)i++;
if(i!=j)
{ t=a[i],a[i]=a[j],a[j]=t; }//交换
}
a[l]=a[i];
a[i]=temp;
qs_my(l,i-1);
qs_my(i+1,r);
}
而利用快排2:竟然只用了28ms!整体上(五组数据)比sort函数还快了11ms!
void quickersort(int l,int r)
{
int mid = a[(l+r)/2];
int i=l,j=r;
do{
while(a[i]<mid)i++;
while(a[j]>mid)j--;
if(i<=j){
swap(a[i],a[j]);
i++;
j--;
}
} while(i<=j);
if(l<j) quickersort(l,j);
if(i<r) quickersort(i,r);
}
我仔细考量了一下后发现:
快排本身时间复杂度在O(NlogN)~ O(N^2),结合测试数据,第一种代码由于取第一个数为基准数,对于正常升序每次需要遍历大于它的所有数,一共n次,即1-n的等差数列,约是n*(n+1)/2,可视作O(N^2)的时间复杂度;对于第二种写法,每次遍历n-1次但分为两个等大的part,而次数每次减半,不妨设为k次,则要经历n=2^k(近似),取对数得k=logn(以2为底),乘积大约为n*logn,可视作O(N*logN)的时间复杂度;显然后者更底。
但 此时我们还有一个疑惑:那么对于快排法1什么时候的数据的时间复杂度是O(NlogN)?
利用快排2的思想,我们不妨举个实例:(1..7)对于数据我们要让二等分的数放在每个part的首位,以便我们每次都能取到二等分的数。
则有序数列(1,2,...5,6,7)取如下混序时:
( 4 , 2 , 1 , 3 , 6 , 5 , 7 )
可以取到时间复杂度O(NlogN),对于一般情况,我们留给读者自证。(哈哈哈哈)
其实是这样的递推过程:
取有序数列N0(1,2,3,...,n-2,n-1,n)的中间一项M0=[(n-1)/2](取整函数),根据其所在位置将原数列左右分为两个part,我们不妨记为N1,N1',紧接着,对N1、N1'分别进行同样的操作(取中间元M1和M1'),最终递推到Nm数列有且仅有一个元素时停止,组成的新数列即为最佳混序列。
可见,如果要想混AC(bushi)还是要用些许技巧(doge)。
不过你要用STL的sort快排当我没说。