【问题标题】:is there a difference in time/space complexity between recursive and non-recursive merge sort algorithm?递归和非递归合并排序算法之间的时间/空间复杂度是否存在差异?
【发布时间】:2020-08-16 21:50:40
【问题描述】:

我已经使用分治法实现了一种合并排序算法,其中一个数组被分成两个子数组。

在我的代码中,我重新使用了插入排序算法对合并排序中的子数组进行排序。这是正确的方法还是我必须使用不同的排序方法对合并排序中的子数组进行排序?

至于对归并排序算法的理解,一切都已经很清楚了,但是说到归并排序的实现,如何不使用递归策略将一个数组划分为n-sub数组呢?

是递归还是非递归有效的方式来实现归并排序?

以下是我在 github 中的代码 sn-p: https://github.com/vamsikankipati/algorithms-in-java/blob/master/src/com/algorithms/sort/MergeSort.java

我从实现的角度理解我的代码是错误的,因为我将数组分成了两个子数组而不是 n-sub 数组。

从算法实现角度清楚地理解归并排序所需的任何帮助。

代码如下:

package com.algorithms.sort;

public class MergeSort {

    public static int[] increasing(int[] arr) {
        int[] result = new int[arr.length];
        int q = arr.length / 2;
        System.out.println("q: " + q);
        int[] left = new int[q];
        int[] right = new int[q];
        for (int i = 0; i < q; i++) {
            left[i] = arr[i];
        }
        int k = 0;
        for (int j = q; j < arr.length; j++) {
            right[k] = arr[j];
            k += 1;
        }
        left = InsertionSort.increasing(left);
        right = InsertionSort.increasing(right);

        // Printing
        for (int e : left) {
            System.out.print(e);
        }
        System.out.println("\n");
        for (int e : right) {
            System.out.print(e);
        }
        System.out.println("\n");

        int i = 0;
        int j = 0;
        int s = 0;
        while ((i < left.length) && (j < right.length)) {
            if (left[i] <= right[j]) {
                result[s] = left[i];
                i++;
            } else {
                result[s] = right[j];
                j++;
            }
            s++;
        }
        while (i < left.length) {
            result[s] = left[i];
            i++;
            s++;
        }
        while (j < right.length) {
            result[s] = right[j];
            j++;
            s++;
        }
        return result;
    }

    /**
     * Main method to test an example integer array
     */
    public static void main(String[] args) {
        int[] ar = { 18, 12, 11, 6, 55, 100 };
        int[] res = increasing(ar);
        for (int a : res) {
            System.out.print(a + " ");
        }
    }
}

【问题讨论】:

  • this 问题有帮助吗?
  • 部分是的,但是我们如何在不使用递归的情况下实现归并排序呢?根据您的链接,它说 O(n lg n) 是递归和非递归合并排序的时间复杂度。
  • 这可以通过简单的谷歌搜索轻松获得。不是吗?
  • 我建议您搜索如何以非递归方式编写递归,反之亦然。这会给你的想法。

标签: java algorithm sorting recursion mergesort


【解决方案1】:

比优化更重要的是,你必须首先实现正确性。 increasing静态方法有个bug:如果数组参数的大小不均匀,right子数组分配的大小不正确:int[] right = new int[q];应该是

int[] right = new int[arr.length - q];

此外,如果数组太小,您不应该尝试拆分数组。

关于优化,您应该只在子数组大小低于阈值(介于 16 到 128 个元素之间)时回退到 InsertionSort()。使用不同阈值和各种分布进行仔细的基准测试将有助于确定适合您系统的阈值。

按照目前的实现,您的函数的时间复杂度为 O(N2),因为它在除最后一个合并阶段之外的所有阶段都遵循 InsertionSort。要将复杂度降低到 O(N.log(N)),您必须对子数组进行递归,直到它们的大小低于固定阈值。

这是修改后的版本:

package com.algorithms.sort;

public class MergeSort {

    public static int threshold = 32;

    public static int[] increasing(int[] arr) {
        if (arr.length <= threshold)
            return InsertionSort.increasing(arr);

        int len1 = arr.length / 2;
        int[] left = new int[len1];
        for (int i = 0; i < len1; i++) {
            left[i] = arr[i];
        }
        int len2 = arr.length - len1;
        int[] right = new int[len2];
        for (int i = 0; i < len2; i++) {
            right[i] = arr[i + len1];
        }
        left = increasing(left);
        right = increasing(right);

        int[] result = new int[len1 + len2];
        int i = 0;
        int j = 0;
        int s = 0;
        while (i < len1 && j < len2) {
            if (left[i] <= right[j]) {
                result[s] = left[i];
                i++;
            } else {
                result[s] = right[j];
                j++;
            }
            s++;
        }
        while (i < len1) {
            result[s] = left[i];
            i++;
            s++;
        }
        while (j < len2) {
            result[s] = right[j];
            j++;
            s++;
        }
        return result;
    }

    /**
     * Main method to test an example integer array
     */
    public static void main(String[] args) {
        int[] ar = { 18, 12, 11, 6, 55, 100 };
        int[] res = increasing(ar);
        for (int a : res) {
            System.out.print(a + " ");
        }
    }
}

【讨论】:

    【解决方案2】:

    两者的时间复杂度都是 O(n log n)。

    关于空间复杂度,实现可能因您的数据结构选择而异。 在递归中 如果您选择数组: 空间复杂度:N log N 如果您选择链表: 空间复杂度为 O(1) 在迭代中: 如果您选择数组: 空间复杂度:N (根据您的实现,它是 O(N log N),因为您在每个划分状态中创建了一个新的子数组, 要将其减少到 O(n),您应该使用一个额外的数组作为原始数组的大小和索引) 如果您选择链表: 空间复杂度为 O(1)

    如您所见,链表最适合排序。 由于函数框架的创建,超出该递归可能会消耗比基于编程语言的预期更多的内存。

    Source

    【讨论】:

    • 如您所见,链表最适合排序。 这是一个相当宽泛的说法。如果给定一个数组并要求对其进行排序,那么创建链表是没有意义的。那将使内存O(n)。此外,由于您无法索引到链表,因此您必须对其进行迭代以找到将其一分为二的中点。那是额外的工作。跟随next 指针的时间也比增加数组索引的时间要长。很有可能链表排序有一个更高的常数,会比数组排序慢。
    • 在排序时在数据结构选择方面听起来很有效。无论如何,为什么要选择链表而不是数组?在任何排序算法中,是否存在链接列表优于数组的用例?我不确定是否有。你看到了吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-13
    • 2015-07-22
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 2011-02-12
    相关资源
    最近更新 更多