Проблема в методе merge()
: в последних 2 случаях в l oop вы копируете значения из a
вместо aux
. Это не проблема при копировании a[j++]
, но при копировании a[i++]
значение может быть уже перезаписано.
Учитывая, что значения в правом срезе записываются только после того, как они уже скопированы, вам нужно только сохранить левый фрагмент.
Вот модифицированная версия с этим упрощением:
public static void merge(int[] a, int aux[], int lo, int mid, int hi) {
int i = lo;
int j = mid + 1;
for (int k = lo; k <= mid; k++) // save a[lo..mid] to aux
aux[k] = a[k];
for (int k = lo; k <= hi; k++) {
if (i > mid)
a[k] = a[j++];
else if (j > hi)
a[k] = aux[i++];
else if (less(a[j], aux[i]))
a[k] = a[j++];
else
a[k] = aux[i++];
}
}
Обратите внимание, что было бы менее склонным к ошибкам считать mid
началом правого среза и hi
- индекс после конца среза. sort()
l oop будет проще, без хитрых настроек +/- 1. Кстати, внутренний тест l oop в вашей версии отключен одним, хотя и без последствий, кроме неэффективности. Это должно быть:
for (int lo = 0; lo < N - sz - 1; lo += sz + sz)
Вот еще одна упрощенная реализация с включенными / исключенными слайсами и комбинированным тестом:
public static void sort(int[] a) {
int N = a.length;
int[] aux = new int[N];
for (int sz = 1; sz < N; sz = sz + sz)
for (int lo = 0; lo < N - sz; lo += sz + sz)
merge(a, aux, lo, lo + sz, Math.min(lo + sz + sz, N));
}
public static void merge(int[] a, int aux[], int lo, int mid, int hi) {
for (int i = lo; i < mid; i++) { // save a[lo..mid[ to aux
aux[i] = a[i];
}
for (int i = lo, j = mid, k = lo; i < mid; k++) {
if (j < hi && less(a[j], aux[i]))
a[k] = a[j++];
else
a[k] = aux[i++];
}
}
Эта версия очень проста, но все же не очень эффективна для больших массивов, потому что каждый проход проходит через весь массив, побеждая схему кэширования процессора. Было бы более эффективно постепенно выполнять слияние снизу вверх, используя стек отсортированных подмассивов увеличивающихся размеров.