当考虑到具有与主元素相同的元素存在时,算法将数组分成三部分,分别小于主元素,等于主元素,大于主元素,然后递归排序第一部分和第三部分,重点是该怎样通过一次循环便可以达到划分目的,因为循环越少,常数因子越小:
代码如下:
#include<iostream>
using namespace std;
void partition(int A[], int p, int r, int &q, int &t)
{
// A[q..t]中所有元素都等于主元素
// A[p..q-1]中每个元素都小于主元素
// A[t+1..r]中每个元素都大于主元素
int x = A[r];
q = p-1, t = r;
for (int i = p; i < r; i++)
{
if (i >= t)
break;
while (A[i] > x && i < t)
swap(A[--t], A[i]);
if (A[i] < x)
swap(A[++q], A[i]);
}
swap(A[t], A[r]);
q++;
}
void quick_sort(int A[], int p, int r)
{
if (p < r)
{
int q, t;
partition(A, p, r, q, t);
quick_sort(A, p, q - 1);
quick_sort(A, t + 1, r);
}
}
void main()
{
while (1)
{
cout << "输入数组个数:" << endl;
int n;
cin >> n;
int A[1000];
cout << "输入数组元素:" << endl;
for (int i = 0; i < n; i++)
cin >> A[i];
quick_sort(A, 0, n - 1);
cout << "排序后如下:" << endl;
for (int i = 0; i<n; i++)
{
cout << A[i] << " ";
}
cout << endl;
}
}
重点是函数partition(),该函数的参数是数组,数组起始索引p,数组的最后一个元素的索引r, 用于划分数组的索引q和t
函数执行完成后有:
1. A[q..t]中所有元素都等于主元素
2. A[p..q-1]中每个元素都小于主元素
3. A[t+1..r]中每个元素都大于主元素
下面证明其正确性:
其中x是A[r]的值,
构造的循环不变式如下:
1. 当 p <= k <= q, A[k] < x
2. 当 q+1 <= k <= i-1 A[k] = x
3. 当 t <= k <= r-1 A[k] > x
初始化:q = p-1, 所以不变式1中的k取空集,不变式1成立;i=p,不变式2中的k取空集,成立:t = r,不变式3中的k取空集,成立
保持:假设第i次迭代前循环不变式成立,当进行第i次迭代时,经过while循环,A[i]如果大于x,那么t先自减,然后交换A[i]和A[t],如果还是大于,那么t继续自减然后交换,直到A[i]不再大于x。易知while循环结束后,索引t不断左移,循环不变式3始终成立,由于索引i未变,所以循环不变式1和2也成立。
然后判断A[i]是否小于x,如果小于,则q自增,交换A[i]和A[q], 交换之后A[q]小于x,所以循环不变式1仍然成立。由循环不变式2的假设可知在第i次迭代之前,A[q+1] 等于x, 所以第i次迭代中q自增并且与A[i]交换后,A[i]等于x。因此,在第i次迭代之后,循环不变式2也成立。由于在交换A[i]和A[q]时候索引t不变,所以循环不变式3成立,因此经过第i次迭代后,循环不变式1,2,3始终成立,得证。
一开始写代码时候,函数partition()没有while循环,取而代之的是一个if语句,判断A[i]是否大于x,大于则与A[--t]交换。然后构造了以上循环不变式,发现没法保证循环不变式不变,发现改成了while循环就可以保证循环不变式不变了,可以看出循环不变式的思想真的很强大,不仅仅可以证明已有算法的正确性,甚至可以帮助创造正确的算法。