三路合并排序问题无法正确排序

时间:2020-09-29 16:40:11

标签: java mergesort

我一直在研究这种三向合并排序算法,该算法基于我的常规合并排序代码;但是,它的排序不正确,因此我认为我的代码可能存在小错误。有什么帮助吗?我已经研究了3个小时的代码以尝试发现问题,但是事实证明这很困难。

public class TriMergeSort {

    void merge(int arr[], int low, int mid1, int mid2, int high) { 
        int sizeA = mid1 - low + 1; 
        int sizeB =  mid2 - mid1;
        int sizeC = high - mid2;

        int A[] = new int[sizeA]; 
        int B[] = new int[sizeB]; 
        int C[] = new int[sizeC];

        for (int i = 0; i < sizeA; i++) 
            A[i] = arr[low + i]; 
        for (int j = 0; j < sizeB; j++) 
            B[j] = arr[mid1 + j + 1]; 
        for (int x = 0; x < sizeC; x++) 
            C[x] = arr[mid2 + x + 1];

        int i = 0, j = 0, x = 0; 
        int k = low; 
        
        while (i < sizeA && j < sizeB && x < sizeC) {
            
            if (A[i] < B[j] && A[i] < C[x]) { 
                arr[k] = A[i]; 
                i++; 
            } else
            if (A[i] >= B[j] && B[j] < C[x]) { 
                arr[k] = B[j]; 
                j++; 
            } else
            if (A[i] > C[x] && B[j] >= C[x]) { 
                arr[k] = C[x]; 
                x++; 
            } 
            k++; 
        } 

        while (i < sizeA) { 
            arr[k] = A[i]; 
            i++; 
            k++; 
        } 

        while (j < sizeB) { 
            arr[k] = B[j]; 
            j++; 
            k++; 
        } 
        
        while (x < sizeC) { 
            arr[k] = C[x]; 
            x++; 
            k++; 
        }
    } 

    void sort(int arr[], int low, int high) { 
        
        if (low < high) {  
            int mid1 = low + ((high - low) / 3); 
            int mid2 = low + 2 * ((high - low) / 3) + 1;

            sort(arr, low, mid1); 
            sort(arr, mid1 + 1, mid2); 
            sort(arr, mid2 + 1, high);

            merge(arr, low, mid1, mid2, high); 
        } 
    } 

    static void print(int arr[]) { 
        int n = arr.length; 
        for (int i = 0; i < n; ++i) 
            System.out.print(arr[i] + " "); 
        System.out.println(); 
    } 

    public static void main(String args[]) { 
        int arr[] = { 15, 2, 6, 7, 55, 0, 28, 41, 12 }; 

        TriMergeSort test = new TriMergeSort(); 
        test.sort(arr, 0, arr.length - 1); 

        print(arr); 
    }
} 

1 个答案:

答案 0 :(得分:1)

问题中张贴的代码可以正常工作。您没有发布遇到问题的三向合并代码。

请注意,不要将high作为索引传递到要排序的切片中的最后一项,而应该将第一个元素的索引传递到切片之外。这样可以简化代码,而不会造成混乱和容易出错的+1 / -1调整。

这是修改后的版本:

public class MergeSort { 

    void merge(int arr[], int low, int mid, int high) { 
        int sizeA = mid - low; 
        int sizeB = high - mid; 

        int A[] = new int[sizeA]; 
        int B[] = new int[sizeB]; 

        for (int i = 0; i < sizeA; i++) 
            A[i] = arr[low + i]; 
        for (int j = 0; j < sizeB; j++) 
            B[j] = arr[mid + j]; 

        int i = 0, j = 0; 
        int k = low; 
        
        while (i < sizeA && j < sizeB) { 
            if (A[i] <= B[j]) { 
                arr[k++] = A[i++]; 
            } else { 
                arr[k++] = B[j++]; 
            } 
        } 

        while (i < sizeA) {
            arr[k++] = A[i++];
        } 

        while (j < sizeB) { 
            arr[k++] = B[j++];
        } 
    } 

    void sort(int arr[], int low, int high) { 
        if (high - low >= 2) {  
            int mid = low + (high - low) / 2; 
            sort(arr, low, mid); 
            sort(arr, mid, high); 
            merge(arr, low, mid, high); 
        } 
    } 

    static void print(int arr[]) { 
        int n = arr.length; 
        for (int i = 0; i < n; ++i) {
            System.out.print(arr[i] + " ");
        }
        System.out.println(); 
    } 

    public static void main(String args[]) { 
        int arr[] = { 15, 2, 6, 7, 55, 0, 28, 41, 12, 10, 59 }; 
        MergeSort test = new MergeSort(); 
        test.sort(arr, 0, arr.length); 
        print(arr); 
    } 
}

要将其转换为3向合并版本,sort3必须遵循以下步骤:

  • 将范围分为2个而不是2个。第一个切片从lowmid1 = low + (high - low)/3,第二个切片从mid1mid2 = low + (high - low)*2/3,第三个切片排除mid2high
  • 递归地对3个子切片进行排序
  • 致电merge3(arr, low, mid1, mid2, high)
    • 制作3个子切片的副本
    • 为运行3个切片的3个索引值编写一个循环,直到用完其中之一为止
    • 为剩余的两个切片(A和B)或(B和C)或(A和C)写3个循环,
    • 编写3个循环以复制其余切片A,B或C中的其余元素

编辑merge类中的TriMergeSort函数缺少3个循环,这些3个循环会在3个初始切片之一耗尽后合并2个切片。这解释了为什么数组未正确排序。在三向合并循环之后,您应该具有:

    while (i < sizeA && j < sizeB) {
        ...
    }
    while (i < sizeA && x < sizeC) {
        ...
    }
    while (j < sizeB && x < sizeC) {
        ...
    }

为避免所有这些重复的循环,可以将对索引值的测试组合到一个循环主体中:

public class TriMergeSort {

    void merge(int arr[], int low, int mid1, int mid2, int high) { 
        int sizeA = mid1 - low; 
        int sizeB = mid2 - mid1;
        int sizeC = high - mid2;

        int A[] = new int[sizeA]; 
        int B[] = new int[sizeB]; 
        int C[] = new int[sizeC];

        for (int i = 0; i < sizeA; i++) 
            A[i] = arr[low + i]; 
        for (int j = 0; j < sizeB; j++) 
            B[j] = arr[mid1 + j]; 
        for (int k = 0; k < sizeC; k++) 
            C[k] = arr[mid2 + k];

        int i = 0, j = 0, k = 0;
        
        while (low < high) {
            if (i < sizeA && (j >= sizeB || A[i] <= B[j])) {
                if (k >= sizeC || A[i] <= C[k]) {
                    arr[low++] = A[i++];
                } else {
                    arr[low++] = C[k++];
                }
            } else {
                if (j < sizeB && (k >= sizeC || B[j] <= C[k])) {
                    arr[low++] = B[j++];
                } else {
                    arr[low++] = C[k++];
                }
            }
        } 
    } 

    void sort(int arr[], int low, int high) { 
        if (high - low >= 2) {  
            int mid1 = low + (high - low) / 3; 
            int mid2 = low + (high - low) * 2 / 3;
            sort(arr, low, mid1); 
            sort(arr, mid1, mid2); 
            sort(arr, mid2, high);
            merge(arr, low, mid1, mid2, high); 
        } 
    } 

    static void print(int arr[]) { 
        int n = arr.length; 
        for (int i = 0; i < n; ++i) {
            System.out.print(arr[i] + " ");
        }
        System.out.println(); 
    } 

    public static void main(String args[]) { 
        int arr[] = { 15, 2, 6, 7, 55, 0, 28, 41, 12 }; 
        TriMergeSort test = new TriMergeSort(); 
        test.sort(arr, 0, arr.length); 
        print(arr); 
    }
}

上面的while循环可以进一步简化,但可读性较差:

    while (low < high) {
        if (i < sizeA && (j >= sizeB || A[i] <= B[j])) {
            arr[low++] = (k >= sizeC || A[i] <= C[k]) ? A[i++] : C[k++];
        } else {
            arr[low++] = (j < sizeB && (k >= sizeC || B[j] <= C[k])) ? B[j++] : C[k++];
        }
    } 

甚至更进一步:

    while (low < high) {
        arr[low++] = (i < sizeA && (j >= sizeB || A[i] <= B[j])) ?
            ((k >= sizeC || A[i] <= C[k]) ? A[i++] : C[k++]) :
            (j < sizeB && (k >= sizeC || B[j] <= C[k])) ? B[j++] : C[k++];
    }