【问题标题】:Sort an increasing array对递增数组进行排序
【发布时间】:2013-12-11 01:16:47
【问题描述】:

伪代码:

S = {};
Loop 10000 times:
    u = unsorted_fixed_size_array_producer();
    S = sort(S + u);

我需要一个高效的排序实现,它接受一个排序的数组和一个未排序的数组,然后将它们全部排序。但是这里我们知道经过几次迭代,size(S) 会比 size(u) 大很多,这是先验的。

更新:还有另一个先验:u 的大小是已知的,比如 10 或 20,循环时间也是已知的。

更新:我实现了 @Dukelnig 在 C https://gist.github.com/blackball/bd7e5619a1e83bd985a3 中建议的算法,它适合我的需求。谢谢!

【问题讨论】:

  • S 作为一个 array 有多重要?您可以将其保留为树或列表,然后在所有完成后将其转换为数组吗?
  • ^^^ 是的,只维护一个排序好的集合比对一个数组排序很多次要好
  • 我想知道教授们是否会在这个网站上搜索作弊者! ;-)
  • 您对“高效”的标准是什么?你有更多关于约束的信息吗?在这个循环完成之前是否需要查询 S?
  • @AndreyT 事实上,在每次 sort 之后,我都会将 S 用于其他目的。所以我认为这里 S 需要是一个数组。

标签: c algorithm sorting


【解决方案1】:

排序u,然后合并Su

合并只涉及同时遍历两个已排序的数组,并选择较小的元素并在每一步递增该迭代器。

运行时间为O(|u| log |u| + |S|)

这与merge sort 所做的非常相似,因此它会导致可以从那里派生排序数组。

一些用于合并的 Java 代码,源自 Wikipedia:(C 代码看起来并没有什么不同)

static void merge(int S[], int u[], int newS[])
{
   int iS = 0, iu = 0;

   for (int j = 0; j < S.length + u.length; j++)
      if (iS < S.length && (iu >= u.length || S[iS] <= u[iu]))
         newS[j] = S[iS++];  // Increment iS after using it as an index
      else
         newS[j] = u[iu++];  // Increment iu after using it as an index
}

这也可以从后面就地完成(在 S 中,假设它有足够的额外空间)。
以下是执行此操作的一些有效 Java 代码:

static void mergeInPlace(int S[], int SLength, int u[])
{
   int iS = SLength-1, iu = u.length-1;

   for (int j = SLength + u.length - 1; j >= 0; j--)
      if (iS >= 0 && (iu < 0 || S[iS] >= u[iu]))
         S[j] = S[iS--];
      else
         S[j] = u[iu--];
}

public static void main(String[] args)
{
   int[] S = {1,5,9,13,22, 0,0,0,0}; // 4 additional spots reserved here
   int[] u = {0,10,11,15};
   mergeInPlace(S, 5, u);
   // prints [0, 1, 5, 9, 10, 11, 13, 15, 22]
   System.out.println(Arrays.toString(S));
}

为了减少比较次数,我们还可以使用二分查找(虽然时间复杂度保持不变 - 这在比较昂贵时很有用)。

// returns the first element in S before SLength greater than value,
//   or returns SLength if no such element exists
static int binarySearch(int S[], int SLength, int value) { ... }

static void mergeInPlaceBinarySearch(int S[], int SLength, int u[])
{
   int iS = SLength-1;
   int iNew = SLength + u.length - 1;

   for (int iu = u.length-1; iu >= 0; iu--)
   {
      if (iS >= 0)
      {
         int index = binarySearch(S, iS+1, u[iu]);
         for ( ; iS >= index; iS--)
            S[iNew--] = S[iS];
      }
      S[iNew--] = u[iu];
   }
   // assert (iS != iNew)
   for ( ; iS >= 0; iS--)
      S[iNew--] = S[iS];
}

如果S 不必是数组

以上假设S 必须是一个数组。如果没有,binary search tree 之类的可能会更好,具体取决于 uS 的大小。

运行时间为O(|u| log |S|) - 只需替换一些值即可查看哪个更好。

【讨论】:

  • 如果我们在与 S 合并期间完成从排序的 'u' 中添加。从 'S' 中添加其余部分不会导致未排序的最终数组吗?我可能误会了……
  • 换句话说,这是从预先排序的u 开始的插入排序,S 中的每个下一次搜索都从最后一次插入的点开始。但是,插入时间将受S 中的数组元素重定位时间支配,这意味着对于大S 和小uu 是否排序并不重要。来自未排序的 u 的普通插入同样有效(或同样糟糕)。
  • @namar0x0309 由于合并的方式,这将导致一个排序数组。 Merge sort 取决于它给出了一个排序数组,并且可以从中得出正确性。
  • @AndreyT 将 u 逐个元素插入 S 是 O(|u||S|),合并是 O(|u| + |S|),差别很大。
  • @Dukeling:我不知道你从哪里得到你的O(|u||S|)。这没有任何意义。 S 中每个插入点的搜索应该通过二分搜索来完成,而不是通过线性搜索。这给了我们O(|u| log|S|) 搜索的复杂性,当|u||S| 小时,这比你的O(|u| + |S|) 好得多。这实际上就是当数组大小显着不同时经典数组合并算法的实现方式:通过二分查找而不是线性查找。您的 O(|u| + |S|) 方法仅在数组大小彼此接近时可用。在这种情况下,它们不是。
【解决方案2】:

如果您真的必须始终对S 使用文字数组,那么最好的方法是将新元素单独插入到已经排序的S 中。 IE。基本上对每个新批次中的每个元素使用经典的插入排序技术。从某种意义上说,这会很昂贵,因为插入数组很昂贵(您必须移动元素),但这就是必须为S 使用数组的代价。

【讨论】:

    【解决方案3】:

    因此,如果 S 的大小远大于 u 的大小,您想要的不就是对大多数已排序的数组进行有效排序吗?传统上这将是插入排序。但是你只能通过实验和测量知道真正的答案——尝试不同的算法并选择最好的。如果不实际运行您的代码(也许更重要的是,使用您的数据),您将无法可靠地预测性能,即使使用像排序算法这样经过深入研究的东西也是如此。

    【讨论】:

      【解决方案4】:

      假设我们有一个大小为n 的大排序列表和一个大小为k 的小排序列表。

      二分查找,从末尾(位置n-1n-2n-4,&c)开始寻找较小列表中最大元素的插入点。将较大列表k元素的尾端向右移动,插入较小列表中最大的元素,然后重复。

      因此,如果我们有 [1,2,4,5,6,8,9][3,7] 列表,我们会这样做:

      [1,2,4,5,6, , ,8,9]
      [1,2,4,5,6, ,7,8,9]
      [1,2, ,4,5,6,7,8,9]
      [1,2,3,4,5,6,7,8,9]
      

      但我建议您在使用有趣的合并过程之前,仅对连接列表和排序整个事物进行基准测试。

      【讨论】: