[英]Java implementation of MergeSort; cannot find the bug
好的,这是那些令人绝望的问题之一。 我正在尝试实现自下而上的 MS 来排序和整数数组。 但是天哪,我似乎无法找到错误...
import java.util.Scanner;
public class A2 {
public static boolean less(Integer v, Integer w) {
return v.compareTo(w) < 0;
}
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 - 1, Math.min(lo + sz + sz - 1, N - 1));
}
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 <= hi; k++)
aux[k] = a[k];
for (int k = lo; k <= hi; k++)
if (i > mid)
a[k] = aux[j++];
else if (j > hi)
a[k] = aux[i++];
else if (less(aux[j], aux[i]))
a[k] = a[j++];
else
a[k] = a[i++];
}
public static void main(String[] args) {
int next = 0;
Scanner scanner = new Scanner(System.in);
int size = Integer.parseInt(scanner.nextLine());
int[] v = new int[size];
String s = scanner.nextLine();
scanner.close();
String[] sa = s.split("[\\s]+");
while (next < size) {
v[next] = Integer.parseInt(sa[next]);
next ++;
}
for (Integer i : v)
System.out.print(i + " ");
System.out.println();
System.out.println("----------------------------------");
sort(v);
for (int i = 0; i < size; i++)
System.out.print(v[i] + " ");
System.out.println();
}
}
在main
函数中,我打印了数组的元素,只是为了确定问题出在排序上。 第一个数字只是数组的大小。 错误要么在sort()
要么在merge()
。 以下是一些示例输出:
9
10 45 20 5 -6 80 99 -4 0
10 45 20 5 -6 80 99 -4 0
----------------------------------
-6 -4 -4 -6 -4 -4 -6 0 99
6
6 7 3 2 4 1
6 7 3 2 4 1
----------------------------------
1 1 1 4 6 7
5
6 5 2 3 4
6 5 2 3 4
----------------------------------
2 3 4 5 6
最后一个看起来还不错。
请帮助我,我一直在四处走动,但似乎找不到错误。
问题出在merge()
方法中:在循环的最后两种情况下,您从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()
循环会更简单,没有棘手的 +/-1 调整。 顺便说一句,您的版本中的内循环测试被关闭了一个,尽管除了效率低下没有任何后果。 它应该是:
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++];
}
}
这个版本非常简单,但在大型阵列上仍然不是很有效,因为每次通过整个阵列,击败处理器缓存方案。 使用一堆大小递增的已排序子数组,以增量方式执行自下而上的合并会更有效。
通过此更改,它可以在我的系统上运行。
else if(less(aux[j], aux[i]))
a[k] = aux[j++]; // fix (aux)
else
a[k] = aux[i++]; // fix (aux)
如果合并排序通过在每次传递中更改合并方向来避免复制步骤,如果在合并传递结束时还剩下一次运行,则需要复制它。 这个答案的第三部分有一个例子。
当我使用带有随机值的较大数组(如 800 万个整数)进行测试时,less(...) 的使用会间歇性地使系统上的运行时间加倍。 将 if(less(aux[j], aux[i])) 更改为 if(aux[j] < aux[i]) 似乎已解决此问题或使其非常罕见。
更有效的合并排序的示例代码,除非有奇数次通过,否则避免进行复制。 这可以通过首先计算传递次数来避免,如果传递次数为奇数,则原地交换。 通过在初始传递中对 32 或 64 个元素的组使用插入排序,这可以扩展到更大的子组。
public static void sort(int[] a) {
int n = a.length;
if(n < 2)
return;
int[] dst = new int[n];
int[] src = a;
int[] tmp;
for(int sz = 1; sz < n; sz = sz+sz){
int lo;
int md;
int hi = 0;
while(hi < n){
lo = hi;
md = lo+sz;
if(md >= n){ // if single run remaining, copy it
System.arraycopy(src, lo, dst, lo, n-lo);
break;
}
hi = md+sz;
if(hi > n)
hi = n;
merge(src, dst, lo, md, hi);
}
tmp = src; // swap references
src = dst; // to change direction of merge
dst = tmp;
}
if(src != a) // copy back to a if needed
System.arraycopy(src, 0, a, 0, n);
}
public static void merge(int[] src, int[] dst, int lo, int md, int hi) {
int i = lo;
int j = md;
int k = lo;
while(true){
if(src[j]< src[i]){
dst[k++] = src[j++];
if(j < hi)
continue;
System.arraycopy(src, i, dst, k, md-i);
return;
} else {
dst[k++] = src[i++];
if(i < md)
continue;
System.arraycopy(src, j, dst, k, hi-j);
return;
}
}
}
你可以试试这个代码:
import java.util.Arrays;
public class MergeSort
{
public static void merge(double[] a,
int iLeft, int iMiddle, int iRight,
double[] tmp)
{
int i, j, k;
i = iLeft;
j = iMiddle;
k = iLeft;
while ( i < iMiddle || j < iRight )
{
if ( i < iMiddle && j < iRight )
{ // Both array have elements
if ( a[i] < a[j] )
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
else if ( i == iMiddle )
tmp[k++] = a[j++]; // a is empty
else if ( j == iRight )
tmp[k++] = a[i++]; // b is empty
}
/* =================================
Copy tmp[] back to a[]
================================= */
for ( i = iLeft; i < iRight; i++ )
a[i] = tmp[i];
}
public static void sort(double[] a, double[] tmp)
{
int width;
for ( width = 1; width < a.length; width = 2*width )
{
// Combine sections of array a of width "width"
int i;
for ( i = 0; i < a.length; i = i + 2*width )
{
int left, middle, right;
left = i;
middle = i + width;
right = i + 2*width;
merge( a, left, middle, right, tmp );
}
System.out.println("After 1 iter: " + Arrays.toString(a) );
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.