快速排序的改进
快速排序最坏情况下,要比较O(n^2)次,但平均性能为nlogn,基本达到了比较类排序所需时间的的下界。核心代码为:
void qSort(int *data, int begin, int end)
{
int pivot, i, j;
if(begin>=end)
return;
i = begin;
j = end;
pivot = data[i];
while(i<j)
{
while(i<j && data[j]>=pivot)
j--;
data[i] = data[j];
while(i<j && data[i]<=pivot)
i++;
data[j] = data[i];
}
data[i] = pivot;
qSort(data, begin, i-1);
qSort(data, i+1, end);
}
一种改进:对于一个已排好序的序列来说,插入排序应该是最快了,时间复杂度为O(n)。插入排序代码为:
void iSort(int *data, int begin, int end)
{
int i,j,tmp;
for(i=begin+1;i<=end;i++)
{
j = i-1;
tmp = data[i];
while(j>=begin && data[j]>tmp)
{
data[j+1] = data[j];
j--;
}
data[j+1] = tmp;
}
return;
}
由于快速排序算法在处理小规模数据集时表现的不是很好(可能是因为存在递归调用),因此在数据规模小到一定程度时,改用插入排序。另一方面考虑,对于大规模数据来说,前期采用的快速排序已经将一些元素放到了正确的位置上,因此当规模降低到一定程度时,可以认为一些元素已经基本有序了。对基本有序的序列进行排序,插入排序是最好的选择,不光比较次数少,还免去了递归调用的过程。因此,第一种改进方法为:
当数据规模小于一定程度时,改用插入排序。具体小到何种规模时,采用插入排序,这个理论上还不解,一些文章中说是5到25之间。SGI STL中的快速排序采用的值是10.
第二个有改进空间的地方是:中轴元素的选取。如果简单的只是选择第一个或最后一个作为中轴元素,这样当待排序序列基本有序的时候,它就退化为O(n^2)的时间复杂度。因此,第二中改进方法为:
可以取最左边、最右边、中间这三个位置的元素中的中间值作为中轴元素,来避免这种情况。
对于一个每个元素都完全相同的一个序列来讲,快速排序也会退化到O(n^2)。要将这种情况避免到,可以这样做:
在分区的时候,将序列分为3堆,一堆小于中轴元素,一堆等于中轴元素,一堆大于中轴元素,下次递归调用快速排序的时候,只需对小于和大于中轴元素的两堆数据进行排序,中间等于中轴元素的一堆已经放好。
总结一下,主要有3点有改进空间:
1 问题降到一定规模时,改用插入排序
2 中轴元素的选取
3 分成3堆,一方面避免相同元素这种情况,另一方面也可以降低子问题的规模。这个感觉有点想DFS中寻找剪枝条件来降低搜索规模一样。
从以上3点出来,完成新的快速排序,代码如下:
#include<stdio.h>
#include<stdlib.h>
#define M 10
void exchange(int *data, int p1, int p2)
{
int tmp;
tmp = data[p1];
data[p1] = data[p2];
data[p2] = tmp;
}
void compexch(int *data, int p1, int p2)
{
if(data[p1]>data[p2])
exchange(data, p1, p2);
}
void iSort(int *data, int begin, int end)
{
int i,j,tmp;
for(i=begin+1;i<=end;i++)
{
j = i-1;
tmp = data[i];
while(j>=begin && data[j]>tmp)
{
data[j+1] = data[j];
j--;
}
data[j+1] = tmp;
}
return;
}
//完成一次分区
int partition(int *data, int begin, int end)
{
int i,j,pivot;
i = begin;
j = end;
pivot = data[i];
while(i<j)
{
while(i<j && data[j]>=pivot)
j--;
data[i] = data[j];
while(i<j && data[i]<=pivot)
i++;
data[j] = data[i];
}
data[i] = pivot;
return i;
}
void qSort(int *data, int begin, int end)
{
int pi, povit;
int i, left, right;
if(begin >= end)
return;
if(end-begin <= M)
iSort(data, begin, end);
else
{
//1 选择中轴元素,将其设置到 begin+1 的位置上
exchange(data, begin+1, (begin+end)/2);
compexch(data, begin, begin+1);
compexch(data, begin, end);
compexch(data, begin+1, end);
//2 分区。此时 begin 和 end 两个位置已经不影响本次分区了
pi = partition(data, begin+1, end-1);
povit = data[pi];
//3 构建等于中轴元素的中间堆
left = pi;
right = pi;
for(i=begin;i<left;i++)
if(data[i] == povit)
exchange(data, i, --left);
for(i=end;i>right;i--)
if(data[i] == povit)
exchange(data, i, ++right);
//4 递归调用快速排序
qSort(data, begin, left-1);
qSort(data, right+1, end);
}
}
int main()
{
int data[20];
int n;
int i,j;
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d",&data[i]);
qSort(data, 0, n-1);
for(i=0;i<n;i++)
printf("%d ",data[i]);
printf("\n");
system("pause");
return 0;
}
学习,永无止境!知道、会用一个算法,远远不够,掌握它,改进它,and 不断的改进它!