【问题标题】:How to merge 3 sorted arrays into 1 sorted array in Big-O(N) time?如何在 Big-O(N) 时间内将 3 个排序数组合并为 1 个排序数组?
【发布时间】:2016-10-22 10:56:15
【问题描述】:

尝试将 3 个数组合并为一个,以使最终数组有序。

给定

int[] a = {1,3};
int[] b = {2,4};
int[] c = {1,5};

合并数组,使最终数组 d = {1,1,2,3,4,5}

不能只是连接它们然后对 d 数组进行排序,因为这会使时间复杂度大于 Big-O(N)。

这是我到目前为止所得到的。遇到索引超出范围异常的问题:

public static void main(String[] args) {
    // Sort these 3 arrays. The final array should be d = {1,1,2,3,4,5}
    int[] a = {1,3};
    int[] b = {2,4};
    int[] c = {1,5};
    int[] d = new int[a.length + b.length + c.length];

    int i = 0;
    int j = 0;
    int k = 0;
    int l = 0;

    for (int iteration = 0; iteration <= d.length; iteration++){
        if ((i != a.length || j != b.length) && a[i] < b[j]){
            if (a[i] < c[k]){
                // then a[i] is smallest
                d[l] = a[i];
                i++;
                l++;
                displayArrayContents(a,b,c,d,i,j,k,l);
            }
            else if (a[i] > c[k]){
                // then c[k] is smallest
                d[l] = c[k];
                k++;
                l++;
                displayArrayContents(a,b,c,d,i,j,k,l);
            }
            else if (a[i] == c[k]){
                d[l] = a[i];
                i++;
                l++;
                d[l] = c[k];
                k++;
                l++;
                displayArrayContents(a,b,c,d,i,j,k,l);
            }
        }
        else if(b[j] < a[i]){
            if (b[j] < c[k]){
                // b[j] is smallest
                d[l] = b[j];
                l++;
                j++;
                displayArrayContents(a,b,c,d,i,j,k,l);
            }
            else if (b[j] > c[k]){
                // c[k] is smallest
                d[l] = c[k];
                l++;
                k++;
                displayArrayContents(a,b,c,d,i,j,k,l);
            }
            else if (b[j] == c[k]){
                d[l] = b[j];
                j++;
                l++;
                d[l] = c[k];
                k++;
                l++;
                displayArrayContents(a,b,c,d,i,j,k,l);
            }
        }
    }
}

【问题讨论】:

  • that would make the time complexity larger than Big-O(N) ...实际上一般可以达到的最佳性能大约是O(N*lgN),比O(N)。因此,在一个初始的 O(N) 操作中将三个数组存储在一个位置很可能不会损害排序的整体性能。
  • 研究分治法,如合并排序和快速排序。
  • @TimBiegeleisen 如果我能够通过 3 个数组中的每一个,将最小值分配给最终数组(如上面的实现),时间复杂度将如何超过 O( N)?
  • 三个数组已经排序了吗?如果是,那么您可以在O(N) 中执行此操作。在一般情况下,这与任何其他排序问题没有什么不同。
  • 合并排序数组的最佳情况是 O(n log k),其中 n 是项目的总数,k 是列表的数量。用于合并两个数组,结果为 O(n * log(2)),与 O(n) 相同。您的示例只是基于一般优先级队列的合并,硬编码有条件代替显式优先级队列。分析会发现最坏情况下的比较次数是O(n log k)。

标签: java algorithm sorting data-structures big-o


【解决方案1】:

假设这是 java,数组名称是对数组的引用,可以像 C / C++ 中的指针一样交换。这可以用来减少主合并循环中的条件数,使代码更简单一些,但代价是交换。空数组检查在主合并循环之前完成。此方法可以轻松扩展以处理 4 路或更多路合并,否则需要大量条件。

static int[] Merge(int[] a, int[] b, int[] c)
{
    int[] d = new int[a.length + b.length + c.length];
    int[] e;   // temp used for swap
    int i = 0;
    int j = 0;
    int k = 0;
    int l = 0;
    int t;
    // empty array checks
    if(0 == b.length){      // if b empty
        if(0 == c.length){  // if b and c empty
            c = a;          //   c = a
            a = b;          //   a = b = empty
        } else {            // if b empty, c not empty
            e = a;          //   swap a and b
            a = b;
            b = e;
        }
    } else {                // else b not empty
        if(0 == c.length){  // if c empty
            e = c;
            c = b;          //   shift c = b, b = a
            b = a;
            a = e;          //   a = empty
        }
    }
    // main merge loop
    while(i < a.length){    // 3 way merge
        if(a[i] > b[j]){    // if b smaller swap
            e = a;
            a = b;
            b = e;
            t = i;
            i = j;
            j = t;
        }
        if(a[i] > c[k]){    // if c smaller swap
            e = a;
            a = c;
            c = e;
            t = i;
            i = k;
            k = t;
        }
        d[l++] = a[i++];
    }
    while(j < b.length){    // 2 way merge
        if(b[j] > c[k]){    // if c smaller swap
            e = b;
            b = c;
            c = e;
            t = j;
            j = k;
            k = t;
        }
        d[l++] = b[j++];
    }
    while(k < c.length)     // copy rest of c
        d[l++] = c[k++];
    return d;
}

【讨论】:

    【解决方案2】:

    你的想法是正确的,代表了一个O(n)的解决方案。但是,你的代码确实存在一些问题,其中一些会导致越界异常:

    • 您访问c[k],而没有首先确保k &lt; c.length
    • 即使您在length进行 测试,也不会避免这种无效访问:(i != a.length || j != b.length) &amp;&amp; a[i] &lt; b[j] 仍将导致a[i]i === a.length 时被访问(尤其是j != b.length时);
    • 外部循环需要迭代的次数通常是错误的,因为有时(在相等的情况下)您将两个值存储在目标数组中,这使得数组填满的速度比循环预期的要快。事实上,相等的情况(如a[i] == c[k])并不真正需要单独处理。如果你把它和&gt;(所以:&gt;=)一起处理,算法仍然是正确的:那么第二个(相等的)值将在下一次迭代中被复制;
    • 即使您修复了上一个问题,您的外部循环仍然会使一次迭代过多; for 条件应该是 &lt; d.length 而不是 &lt;= d.length

    没有问题,但你的代码中有很多重复:

    • 您可以将调用移至 displayArrayContents(a,b,c,d,i,j,k,l);if 构造之外,因此它始终会被执行,这正是您真正想要的;
    • 由于您总是在if 构造中分配给d,因此您可以使用三元运算符? ... : 将该分配放在“if 之外”;
    • 尽管像i != a.length 这样的测试可以达到预期的目的,但是像这样测试:i &lt; a.length 是一种很好的做法。

    这是考虑到上述内容的代码:

    import java.util.Arrays; // for easy output of arrays with Arrays.toString().
    
    class Main {
      public static void main(String[] args) {
        // Sort these 3 arrays. The final array should be d = {1,1,2,3,4,5}
        int[] a = {1,3};
        int[] b = {2,4};
        int[] c = {1,5};
        int[] d = new int[a.length + b.length + c.length];
    
        int i = 0;
        int j = 0;
        int k = 0;
        for (int l = 0; l < d.length; l++) {
          d[l] = i < a.length && (j >= b.length || a[i] < b[j])
                    ? (k >= c.length || a[i] < c[k]
                        ? a[i++]
                        : c[k++])
                    : (j < b.length && (k >= c.length || b[j] < c[k])
                        ? b[j++]
                        : c[k++]);
           // Uncomment this if you still need it:
           //displayArrayContents(a,b,c,d,i,j,k,l); 
        }
    
        System.out.println(Arrays.toString(d));
      }
    }
    

    最后一条语句的输出:

    [1, 1, 2, 3, 4, 5]
    

    查看它在repl.it 上运行。

    【讨论】:

    • 有趣的是,这段代码实际上只是基于标准优先级队列的 k-way 合并的一个实现,对于 k == 3 的情况进行了硬编码。你会注意到数字最坏情况下的比较次数是 O(n log k),而不是 O(n)。
    • 比较的次数确实是O(n logk),但是由于k在这个问题中是一个常数,我们可以正确地说它是O(n).
    【解决方案3】:

    按照以下步骤操作:

    • 从这里获取答案代码:How to merge two sorted arrays into a sorted array?
    • ab 上调用该函数,得到结果数组ab
    • abc 上调用该函数,得到你的结果abc
    • 您已经调用了两次O(n) 函数,所以它仍然是O(n)。轰隆隆。

    事实上,摆弄数组索引是令人沮丧的。如果你可以将这些数组作为队列或迭代器来代替,只需take()next() 每次迭代中的最小值并将其放入结果列表中,它会干净很多。

    【讨论】:

    • @trincot 我一定错过了,抱歉。
    • 抱歉,这不是 O(n)。如果将示例扩展到 5 个数组,您可以很容易地看到复杂度大于 O(n)。第一次查看数组ab。然后你看看abc。然后是abcd等。最坏情况复杂度是O(k^2 * n),其中k是列表数,n是项目总数。当然,如果你说k 是常数,那么,是的,我想你可以说算法是 O(n)。此外,即使使用三个数组,您进行的比较次数也取决于您合并数组的顺序。
    • 我知道这一点,但问题是“3 个排序数组”。我不是决定 k 是常数的人。我认为我的解决方案非常出色,如果 Dijkstra 还活着,我想他会对此印象深刻[巨魔脸]
    【解决方案4】:

    您需要清楚 N 会发生什么变化。如果您总是只有三个数组,并且它们的大小或最大大小会随着 N 而变化,那么几乎所有重复选择三个数组中任意一个可用的最小数字的代码,删除它,并将其附加到结果数组的末尾,将是 O(N)。您选择最小数字的代码可能既笨拙又昂贵,但它只是一个常数因子,不会随着 N 的增加而改变。

    如果要合并的数组数量随着 N 的增加而增加,那么您需要更加小心如何选择可用的最小数字,最终您会遇到排序问题,这是您无法在线性时间内完成的通常的假设。

    通常,外部排序将使用堆(例如http://www.geeksforgeeks.org/external-sorting/)合并磁盘上保存的大量列表。这对于一次合并大量列表会更有效,但只会为您获得一个常数因子,

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-13
      • 1970-01-01
      • 1970-01-01
      • 2017-01-08
      • 1970-01-01
      • 2016-05-18
      • 1970-01-01
      相关资源
      最近更新 更多