7-1(Hoare划分的正确性) 本章中的PARTITION算法并不是其最初版本。下面给出的是最早由C.R.Hoare所设计的划分算法:
HOARE-PARTITION(A,p,r)
1. x=A[p]
2. i=p-1
3. j=r+1
4. while TRUE
5. repeat
6. j=j-1
7. until A[j]<=x
8. repeat
9. i=i+1
10. until A[j]>=x
11. if i<j
12. exchange A[i] with A[j]
13. else return j
a. 试说明 HOARE-PARTITION 在数组A={13,19,9,5,12,8,7,4,11,2,6,21}上的操作过程,并说明在每一次执行4-13行while循环时数组元素的值和辅助变量的值。后续的三个问题要求
读者仔细论证HOARE-PARTITION 的正确性。在这里假设子数组A[p..r]至少包含来2个元素,试证明下列问题。
a)X=13作为主元。
A={13,19,9,5,12,8,7,4,11,2,6,21} 第一次大循环后A[i=0]=13与A[j=10]=6交换
A={6,19,9,5,12,8,7,4,11,2,13,21} 第二次大循环后A[i=1]=19与A[j=9]=2交换
A={6,2,9,5,12,8,7,4,11,19,13,21} 第三次循环后(i=9)>(j=8) 函数返回j j以前的数[0,8](包括j)比i以后的数[9,11](包括i)小
这样完成了一次以X=13作为主元的划分。
b. 下标i和j可以使我们不会访问在子数组A[p..r]以外的数组A的元素。
i初始=p-1 并且是递增的,所以i>=p-1。而j初始=r+1 并且是递减的,所以j<=r+1。外层循环运行的条件是p-1<=i<j<=r+1.所以i递增不会大于r,而j递减不会小于p,又因为当i>j时,程序终止。i和j比较作为外层循环与否的条件限制了子数组的访问。
c.当HOARE-PARTITION结束时,它返回的值j 满足p<=j<r。
第一个内层循环,j--最多减少到p,因为A[p]=x,所以第一个内层循环不得不终止,所以p<=j.
第一个内层循环,j--如果只循环了一次就终止,那么有A[r]=x,所以A[p]=A[r]=x 所以子数组内只有一个元素,这与题目
假设不符,所以第一个内层循环要循环2次,j=r-1<r 所以p<=j<r
d.当HOARE-PARTITION结束时,A[p..r]中的每一个元素都小于或等于A[j+1..r]中的元素。
第一个内层循环的作用是找到数组右半部分小于主元的位置j.第二个内层循环的作用是找到数组左半部分大于主元的位置i
然后位置j的元素A[j]和位置i的元素A[i]互换,这样循环数次后,左半部分都小于主元,右半部分都大于主元,完成了划分。
在7.1节的PARTITION过程中,主元(原来存储在A[r]中)是与它划分的两个分区分离的。与之对应,在HOARE-PARTITION中,主元(原来存储在A[p]中)是存在与分区A[p..r]与A[j+1..r]中的。因为有p<=j<r,所以这一划分总是平凡的。
e).利用HOARE-PARTITION,重写QUICKSORT算法。//C++中的do-while循环相当于伪代码的repeat-until循环。于是就有下面的代码。
#include <iostream>
using namespace std;
int PARTITION(int A[],int p,int r)
{
int x=A[p];
int i=p-1;
int j=r+1;
while (1)
{
do
{
j--;
} while (A[j]>x);
do
{
i++;
} while (A[i]<x);