要求编写一个不改变数组的归并排序,它返回一个int[]数组perm,其中perm[i]的值时原数组中第i小的元素的位置。
约定:arr是待排序的数组,perm是记录排序索引的数组。
最初看到这个题目,我只是重写了归并排序的merge方法,例如比较arr[j]与arr[i],我只是改成了比较arr[perm[j]]与arr[perm[i]],像下面这样:
public static int[] sort(Comparable[] arr)
{
// 初始化perms并设置默认索引序列0,1,2,3...
int[] perm = new int[arr.length];
for (int i = 0; i < arr.length; i++)
{
perm[i] = i;
}
sort(arr, perm, 0, arr.length - 1);
return perm;
}
public static void sort(Comparable[] arr, int[] perm, int lo, int hi)
{
if (hi <= lo)
{
return;
}
int mid = lo + (hi - lo) / 2;
sort(arr, perm, lo, mid);
sort(arr, perm, mid + 1, hi);
//merge(arr, perm, lo, mid, hi);
m2(arr, perm, lo, mid, hi);
}
private static void m2(Comparable[] arr, int[] perm, int lo, int mid, int hi)
{
int len = hi - lo + 1;
StdOut.println("打印是否有序");
showAfter(arr, perm, len);
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++)
{
if (i > mid)
{
perm[k] = j++;
}
else if (j > hi)
{
perm[k] = i++;
}
else
{
Comparable aj = arr[perm[j]];
Comparable ai = arr[perm[i]];
if (less(aj, ai))
{
perm[k] = j++;
}
else
{
perm[k] = i++;
}
}
}
StdOut.println();
//StdOut.println();
}sort方法中,初始化perm很重要。
这个m2方法使用间接排序的方式对比数字:
Comparable aj = arr[perm[j]];
Comparable ai = arr[perm[i]];
if (less(aj, ai))
{
perm[k] = j++;
}
else
{
perm[k] = i++;
}但是这个方法是不能正常工作的,例如arr:[M, C, N, D], 进行前2次排序后,perm是:[1, 0, 3, 2]。进行第2轮比较,i:0, j:2, M与N进行比较,由于N并不小于M,所以按m2方法的逻辑,perm[0]变为了0,这就不对了。
对于进行第一遍的归并排序,它的顺序是没问题的。我尝试使用选择排序最终排序了perm,它可以完成工作,不过明显不符合题目。因为选择排序不需要归并。
后来想到用插入排序,归并一段小数组使用插入排序,再使用插入排序归并更大数组,因为左半部分与右半部分是有效,使用插入排序,也可以提高效率:
// 使用插入排序
private static void merge(Comparable[] arr, int[] perm, int lo, int mid, int hi)
{
int len = hi - lo + 1;
StdOut.println("打印是否有序");
showAfter(arr, perm, len);
for (int i = 1; i < len; i++)
{
for (int j = i; j > 0 && less(arr[perm[j]], arr[perm[j-1]]); j--)
{
exchInt(perm, j, j-1);
}
}
}