最近重新研究了快速排序,整理了一下快速排序的各种版本。
一、单向扫描
ok,先来看单向扫描。
算法导论上版本是这样的:
PARTITION(A, p, r)
1 x ← A[r] //以最后一个元素,A[r]为主元
2 i ← p - 1
3 for j ← p to r - 1 //注,j从p指向的是r-1,不是r。
4 do if A[j] ≤ x
5 then i ← i + 1
6 exchange A[i] <-> A[j]
7 exchange A[i + 1] <-> A[r] //最后,交换主元
8 return i + 1
下面是c++代码:
void my_swap(int *a,int *b)
{
if(a==b)
return;
*a=*a^*b;
*b=*b^*a;
*a=*a^*b;
}
int partition(int a[],int left,int right)//以最后一个元素为主元
{
int key=a[right];
int k=left-1;
for(int i=left;i<right;i++)
{
if(a[i]<=key) //可以不加=号
{
k++;
if(k!=i)
my_swap(&a[i],&a[k]);
}
}
k++;
my_swap(&a[k],&a[right]);
return k;
}
void quickSort(int a[],int left,int right)
{
if(left>=right)
return;
int k=partition(a,left,right);
quickSort(a,left,k-1);
quickSort(a,k+1,right);
}
算法导论上取最后一个元素为主元,当然可以选第一个为主元,请看下面的代码:
int partition2(int a[],int left,int right)//以第一个元素为主元
{
int key=a[left];
int k=left;
for(int i=left+1;i<=right;i++)
{
if(a[i]<=key) //可以不加=号
{
k++;
if(k!=i)
my_swap(&a[i],&a[k]);
}
}
//注意此处没有k++
my_swap(&a[left],&a[k]);
return k;
}
这两种情况如果出现最坏情况,则算法效率就将为O(n*n),比如说取第一个为主元,刚好第一个是最小的数。
我们可以采用三数取中分割法避免最坏情况的发生。
ok,下面看一下什么是三数取中分割法,意思很简单,就是取第一个,中间一个,最后一个,取这三个数中的中间大的那个数作为主元。
好,意思明白,下面直接看代码:
int partition3(int a[],int left,int right) //三数取中分割法
{
int mid=(left+right)/2;
if(a[left]<a[mid])
my_swap(&a[left],&a[mid]);
if(a[right]<a[mid])
my_swap(&a[right],&a[mid]); //经过以上两步后mid是最小的。
if(a[left]>a[right])
my_swap(&a[left],&a[right]);
//以上已经将中间的那个移动到第一个位置
int key=a[left];
int k=left;
for(int i=left+1;i<=right;i++)
{
if(a[i]<=key) //可以不加=号
{
k++;
if(k!=i)
my_swap(&a[i],&a[k]);
}
}
my_swap(&a[left],&a[k]);
return k;
}
代码测试:采用随机数极限测试,取0-100的随机数进行测试。
int main()
{
srand(time(NULL));
const int N = 100; //“极限”测试。为了保证程序的准确无误,你也可以让N=10000。
int *data = new int[N];
for(int i =0; i<N; i++)
data[i] = rand()%100; //同样,随机化的版本,采取随机输入。
cout<<"初试值:"<<endl;
for(int i=0; i<N; i++)
cout<<data[i]<<" ";
cout<<endl;
quickSort(data,0,N-1);
cout<<"after quick sort:"<<endl;
for(int i=0; i<N; i++)
cout<<data[i]<<" ";
cout<<endl;
delete []data;
return 0;
}
二、双向扫描版
双向扫描主要是Hoare版本和其变形。
那么,什么是霍尔提出的快速排序版本列?如下,即是:
HOARE-PARTITION(A, p, r)
1 x ← A[p]
2 i ← p - 1
3 j ← r + 1
4 while TRUE
5 do repeat j ← j - 1
6 until A[j] ≤ x
7 repeat i ← i + 1
8 until A[i] ≥ x
9 if i < j
10 then exchange A[i] <-> A[j]
11 else return j
int partition5(int a[],int left,int right)
{
int key=a[left];
int i=left-1;
int j=right+1;
while(true)
{
do
{
j--;
}
while(a[j]>key);
do
{
i++;
}
while(a[i]<key);
if(i<j)
{
my_swap(&a[i],&a[j]);
}
else
{
return j; //此处返回的不是主元,只是满足a[left...j] <=a[j+1...right]
}
}
}
使用上述分割法,需要如下的代码:
void quickSort1(int a[],int left,int right)
{
if(left>=right)
return ;
int k=partition5(a,left,right);
quickSort1(a,left,k); //因为使用的是partition5的分割方法,所以此处是k而不是k-1
quickSort1(a,k+1,right);
}
考虑这样一种情况:
int partition5(int a[],int left,int right)
{
int key=a[left];
int i=left;
int j=right;
while(true)
{
while(a[j]>key)
{
j--;
}
while(a[i]<key)
{
i++;
}
if(i<j)
{
my_swap(&a[i],&a[j]);
}
else
{
return j;
}
}
}
这种情况会出现死循环,考虑如下数组:2,3,4,5,6,2 数组中a[left]=a[right]=key 交换后还是2,3,4,5,6,2 进入死循环,有人说while循环加个等号,
这种情况的话如果数组全部相等,2,2,2,,,,,2,则会出现数组越界的错误。
一种变种算法:
int partition4(int a[],int left,int right) //双向遍历分割
{
int key=a[left];
int i=left;
int j=right;
while(i<j) //最终一定是i=j结束
{
while(a[j]>=key&&i<j)
j--;
my_swap(&a[j],&a[i]);
while(a[i]<=key&&i<j)
i++;
my_swap(&a[j],&a[i]);
}
a[i]=key; //可以不用这句,因为a[i]肯定是key
return i;
}
看下面事例:
a:3
8 7 1 2 5 6 4 //以第一个元素为主元
2 8 7 1 5 6 4
b:2 7 1 8 5 6 4
c:2 1 7 8 5 6 4
d:2 1 7 8 5 6 4
e:2 1 3
7 8 5 6 4 //最后补上,关键字3