快速排序的改進
快速排序最壞情況下,要比較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 不斷的改進它!