2.3.16最佳情况。编写一段程序来生成使算法2.5中的sort()方法表现最佳的数组(无重复无素):数组大小为N且不包含重复元素,每次切分后两个子数组的大小最多差1(子数组的大小与含有N个相同元素数组的切分情况相同)。(对于这道练习,我们不需要在排序开始时打乱数组。)以下练习描述了快速排序的几个变体。它们每个都需要分别实现,但你也很自然地希望使用SortCompare进行实验来证估每种改动的效果。
答:对于一个数组,它的中位数元素作为切分元素时可以平分这个数组。快速排序时,使用第一个位置的元素作为切分元素,如果将数组的中位数元素放在数组的第一个位置,那么就可以平分这个数组。如果快速排序时每一个子数组都是将中位数元素放在子数组的第一个位置,那么这个排列就是快速排序的最佳情况。对于一个有序数组,它的中位数就是数组中间位置的元素。由于快速排序是从上到下进行排序,所以可以从下向上将所有子数组的第一位置元素与中间位置元素进行交换,使得每一个子数组的第一个位置的元素都是中位数元素,最终得到这个数组的快速排序的最佳情况的排列。
子数组长度划分到多长时不再划分?
数组长度为0、1时快速排序不需要进行比较。数组长度为2时任意一个元素作为切分元素时比较次数都相同。数组长度大于等于3时,不同的元素作为切分元素时使用的比较次数也不相同,其中以中位数作为切分元素时比较次数最少,3元素的6个排列与快速排序的比较次数如下:
1)1,2,3 比较次数:7
2)1,3,2 比较次数:6
3)2,1,3 比较次数:4
4)2,3,1 比较次数:4
5)3,1,2 比较次数:5
6)3,2,1 比较次数:6
由此可见,长度3是需要将中位数作为切分元素的最小数组。
另外,一个长度为3的数组可以再划分成长度为1和2的两个子数组。
一个长度为4的数组可以再划分成长度为2和2的两个子数组。
一个长度为5的数组可以再划分成长度为2和3的两个子数组。
由于长度为3的数组是需要将中位数作为切分元素的最小数组,长度3,4数组划分出的子数组长度都小于3,仅长度为5的数组划分的子数组才出现等于3的情况,所以长度为5的数组是可划分的最短数组。
考虑到使用递归代码同时实现数组长度小于5时不再划分子数组、长度大于等于3时需要将中位数元素交换到第一个位置,代码不好写。为便于使用递归实现代码,可以将长度3的数组作为可划分的最短数组,这样并不影响最后效果。那么最后可按以下方式得到最佳排列:
1)接受一个参数N
2)生成一个长度为N的数组,元素值不重复。
3)排序这个数组
4)从下到上递归平分这个数组,直到数组长度小于3时不再平分,然后将中间位置的元素交换到第一个位置。
代码如下:
import java.util.Arrays;
public class E2d3d16
{
public static void main(String[] args)
{
//接受一个参数,用来表示数组的长度
int N=Integer.parseInt(args[0]);
//生成长度为N的不重复的元素数
Double[] arrayA=new Double[N];
for(int i=0;i<N;i++)
arrayA[i]=StdRandom.random();
//排序这个数组
Arrays.sort(arrayA);
//按快速排序的逆规则排列元素。
P(arrayA,0,N-1);
//显示这个最佳情况的排列
for(int i=0;i<N;i++)
StdOut.println(arrayA[i]);
}
private static void P(Comparable[] a,int lo,int hi)
{
//将长度为3的数组作为最短可平分数组
if (hi-lo+1<3) return;
int mid=lo+(hi-lo)/2;
P(a,lo,mid );
P(a,mid+1,hi);
//将中位数元素交换到左端位置
exch(a,lo,mid);
}
private static void exch(Comparable[] a,int i,int j)
{
Comparable t=a[i];
a[i]=a[j];
a[j]=t;
}
}