1,快速排序的一个关键是找枢轴,我的处理方式是将起始位置和中间位置以及最后位置的值进行比较取中间值,如果枢轴不在起始位置可以交换后统一只考虑枢轴在起始位置时的处理方式,因为枢轴不同,处理方式有点小差异。
2,另一改善点是如果有和枢轴相等的数据,在一次快排后应该和枢轴放一起,这样下一次快排的规模会变小,这在数据集中有很多相同数据时很有用。处理方式是在遍历过程中遇到比枢轴大和小的数据还是交换,遇到相等的就放到对应方向的一端,最后在找到枢轴位置后再互换,使得和枢轴相同的放一起。
具体代码如下:这是用来在数据集中找枢轴的代码:处理方式是找起始位置和中间位置以及末端位置的值,取中间值,统一换到起始位置,因为枢轴在不同位置,处理方式有点小差异。
#include "stdafx.h"
#include <iostream>
#include <windows h="">
#include <ctime>
using namespace std;
void middl( int &p, int &q, int &r ) /* 找枢轴,然后把枢轴位置都换到第一位 */
{
if ( (p <= q && q <= r) || (r <= q && q <= p) )
{
int term; term = p; p = q; q = term;
} else if ( (r <= q && p <= r) || (q <= r && r <= p) )
{
int term; term = p; p = r; r = term;
}
}
下边是主要代码:
void quicksort( int *a, int L, int R ) /* 和枢轴相同的数可以和枢轴放一起来缩小下次快排的规模 */
{
if ( L < R )
{
int mid = (L + R) / 2;
middl( a[L], a[mid], a[R] );
int count1 = L, count2 = R;
int i = L, j = R, key = a[L];
while ( i < j ) /* 一趟快排的总控制 */
{
while ( i < j && a[j] > key )
j--; /* 控制j从后往前找第一比key小的 */
while ( i < j )
{
if ( a[j] == key )
{
int term;
term = a[count2];
a[count2] = a[j];
a[j] = term; /* 在右边找到和枢轴相同的数放到数组最右端 */
count2--;
j--;
while ( i < j && a[j] > key )
j--; /* 找到和key相同的数移动后还要继续找比key小的 */
} else {
a[i++] = a[j];
break; /* 加个break是为了一旦找到比key值小的就不再循环 */
}
}
while ( i < j && a[i] < key )
i++; /* 控制i从右往左找第一个比key大的 */
while ( i < j )
{
if ( a[i] == key )
{
int term; term = a[count1];
a[count1] = a[i];
a[i] = term; /* 在左边找到的和key相同的值放到数组最左端 */
count1++;
i++;
while ( i < j && a[i] < key )
i++;
} else {
a[j--] = a[i]; break;
}
}
}
a[i] = key; /* 接下来应该把和枢轴相同的数放在枢轴相邻的位置 */
int pivot = i; /* 用一个变量保存当前枢轴位置,因为下边ij都变了, */
for ( int t1 = L; t1 < count1; t1++ )
{
int term;
term = a[--i];
a[i] = a[t1];
a[t1] = term;
}
/* for(int t2 = count2;t2 < R;t2 ++)//这循环有错,改后为 */
for ( int t2 = R; t2 > count2; t2-- )
{
int term;
term = a[++j];
a[j] = a[t2];
a[t2] = term;
}
quicksort( a, L, pivot - count1 - 1 + L );
quicksort( a, pivot + R - count2 + 1, R );
}
}
int _tmain( int argc, _TCHAR* argv[] )
{
int num;
cout << "请输入排序的规模!" << endl
cin = "" >> num;
srand( time( 0 ) );
int *a = new int[num];
int i;
cout << "输出原始数组!" << endl;
for ( i = 0; i < num; i++ )
{
a[i] = rand() % 50;
}
cout << endl;
double start = GetTickCount();
quicksort( a, 0, num - 1 );
double end = GetTickCount();
cout << endl; cout << "运行时间为:" << end - start << "毫秒!" << endl;
system( "pause" );
return(0);
}
< / endl>
快速排序复杂度分析
我们来分析一下快速排序法的性能。快速排序的时间性能取决于快速排序递归的深度,可以用递归树来描述递归算法的执行情况。如图9‐9‐7所示,它是{50,10,90,30, 70,40,80,60,20}在快速排序过程中的递归过程。由于我们的第一个关键字是50,正好是待排序的序列的中间值,因此递归树是平衡的,此时性能也比较好。
图9-9-7 |
在最优情况下,Partition每次都划分得很均匀,如果排序n个关键字,其递归树的深度就为.log2n.+1(.x.表示不大于x的最大整数),即仅需递归log2n次,需要时间为T(n)的话,第一次Partiation应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以平分两半)。于是不断地划分下去,我们就有了下面的不等式推断。
- T(n)≤2T(n/2) +n,T(1)=0
- T(n)≤2(2T(n/4)+n/2) +n=4T(n/4)+2n
- T(n)≤4(2T(n/8)+n/4) +2n=8T(n/8)+3n
- ……
- T(n)≤nT(1)+(log2n)×n= O(nlogn)
也就是说,在最优的情况下,快速排序算法的时间复杂度为O(nlogn)。
在最坏的情况下,待排序的序列为正序或者逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为 ,最终其时间复杂度为O(n2)。
平均的情况,设枢轴的关键字应该在第k的位置(1≤k≤n),那么:
由数学归纳法可证明,其数量级为O(nlogn)。
就空间复杂度来说,主要是递归造成的栈空间的使用,最好情况,递归树的深度为log2n,其空间复杂度也就为O(logn),最坏情况,需要进行n‐1递归调用,其空间复杂度为O(n),平均情况,空间复杂度也为O(logn)。
可惜的是,由于关键字的比较和交换是跳跃进行的,因此,快速排序是一种不稳定的排序方法。