【问题标题】:merging of two sorted halfs without extra memory!在没有额外内存的情况下合并两个排序的一半!
【发布时间】:2011-09-03 11:29:34
【问题描述】:

给定一个整数数组,其中前半部分和后半部分已排序。编写一个函数来合并这两个部分以创建一个单独的排序数组(不要使用额外的空间)。 一种方法是 例如: // 1,3,6,8,-5,-2,3,8

int l = 0;
int u = size;
int mid = (l+u)/2;
int i;
for (i = 0; i < mid; i++) {
  for (j = mid; j < size; j++) {
    if (a[i] >= a[j]) {
      temp = a[j];
      for (i = mid-1; i >= 0; i--)
        a[i+1] = a[i];
      a[0] = temp;
    }
  }
}

但我认为这必须有一些 O(n) 算法..

【问题讨论】:

  • 我已经对您的代码进行了一些格式化(使用编辑器上方的 {} 按钮),但我认为它可能会更整洁 - 您可以尝试编辑您的问题以使其更清晰。
  • @Damien,更好地格式化它,并添加了缺失的变量类型。
  • 这只是归并排序的最后一步。 en.wikipedia.org/wiki/Merge_sort 因此 O(n) 因为合并排序是 O(n log n)
  • @Hogan 只有两个块。

标签: c sorting merge


【解决方案1】:

确实存在用于就地合并的 O(n) 算法。不过,它们通常非常复杂。一些想法,请参阅Huang, Langston (PDF)Katajainen, Pasanen, Teuhola (PostScript)等论文。这些实际上是基于允许什么样的额外内存的精确术语定义的,而不是通过使用递归堆栈等作弊。

【讨论】:

    【解决方案2】:

    您可以通过遍历数组一次来做到这一点,从 0 到它的最后一个元素。您只需要考虑需要比较或交换“当前”元素需要什么,这变得非常容易。

    您必须保留一些额外的变量来跟踪当前元素以及您需要与之比较的“其他”元素。

    此外,您可能还想了解主要的 C 代码格式样式。没有一种风格可以让每个人都 100% 开心,但有很多风格会让每个人都不开心。

    ----已编辑----

    好的,这更像是一个“脑筋急转弯”问题,而不是一个严肃的计算机科学问题。问题是“额外内存”的定义非常不明确,因此如果您只记得编程语言中允许递归和that a recursive call requires extra stack(没人会考虑内存),那么有很多方法可以实现结果根据编程语言实现的要求进行分配)。

    基本上,这样的问题是为了看看你是否看到了一个问题并看到了一个递归解决方案。

    #include <stdio.h>
    
    void sort(int index, int start1, int end1, int start2, int end2, int* array) {
      if (index >= end2) {
        return;
      }
      int lower;
      if (array[start1] <= array[start2]) {
        lower = array[start1];
        sort(index+1, start1+1, end1, start2, end2, array);
      } else {
        lower = array[start2];
        sort(index+1, start1, end1, start2+1, end2, array);
      }
      array[index]=lower;
    }
    
    int main(int argc, char** argv) {
      int a[] = {1,3,6,8,-5,-2,3,8};
      sort(0, 0, 3, 4, 7, a);
      int i;
      for (i = 0; i <= 7; i++) {
        printf("%d, ", a[i]);
      }
      printf("\n");
      return 0;
    }
    

    这是作弊吗?你决定。但是,排序是就地完成的,缓存的数字整齐地隐藏在调用堆栈的本地空间中,您无法真正分配/取消分配它们。

    额外的优化是可能的,但它们是对核心思想的改进:使用递归和调用堆栈来缓存信息。

    【讨论】:

    • @Edwin Buck 你确定你可以在没有辅助记忆的情况下一次性完成吗?
    • @sverre,是的,你可以。您确实需要内存来保存索引,并且让内存充当被交换元素的持有者会更容易一些,但即使您没有内存来充当被交换元素的持有者,您可以通过旧的 XOR 交换技巧来做到这一点。
    • @Edwin:这实际上是一个严重的计算机科学问题,请参阅此问题的previousinstances。额外内存有一个精确的定义:它是您使用的所有内存,而不是存储原始数据。在这里,您显然使用的是 Θ(递归深度)内存,而您的递归深度是 Θ(数组大小) - 实际上,您正在与一半数组的副本进行合并,而问题的重点是不用这个额外的内存。
    • @Gilles:确实,这个答案不是问题的解决方案。手动挥动递归的内存使用是一种不正确且极其危险的编程实践。
    • 如果您通过询问堆栈溢出来进行认真的计算机科学研究,那么存在更大的问题。
    【解决方案3】:

    假设您不能使用任何辅助内存,我发现的最佳答案是对数组进行堆排序,即theta(n log n),比您当前的插入排序方案快一些。

    【讨论】:

      【解决方案4】:

      我又看了一遍,这里是你的 O(n-1) 和 memory(n+1):

      /**
       * Created by deian on 2016-12-22.
       */
      public class Merge {
          public static void swap(int[] a, int i1, int i2) {
              int t = a[i1];
              a[i1] = a[i2];
              a[i2] = t;
          }
      
          public static void merge(int[] a) {
              int i1 = 0;
              int i2 = a.length / 2;
      
              System.out.printf("    %s,      i(%d,%d)       \n",    Arrays.toString(a),    i1, i2   );
              for (int di = 0; di < a.length-1; di++) {
                  int ni;
                  int oi1=i1;
                  int oi2=i2;
                  // i1 and i2 - always point to the smallest known numbers
                  if (a[i1] > a[i2]) {
                      ni = i2;
                      i2++;
                      if (i2>=a.length) {
                          i2--;
                      }
                  } else {
                      ni = i1;
                      i1++;
                      if (i1 >= i2) {
                          i1 = di;
                      }
                  }
                  if (di == i1) {
                      i1 = ni;
                  }
                  swap(a, di, ni);
                  System.out.printf("#%d: %s, i(%d,%d)s(%d>%d)i(%d,%d) \n", di+1, Arrays.toString(a), oi1, oi2,ni, di, i1,i2);
              }
              System.out.printf("    %s\n", Arrays.toString(a));
          }
      
          public static void main(String[] args) {
      //        int[] a = new int[]{1, 3, 6, 8, -5, -2, 3, 8};
      //        int[] a = new int[]{1, 3, 6, 8, -5, 2, 3, 8};
              int[] a = new int[]{1, 5, 6, 8, -5, 2, 3, 4};
              merge(a);
          }
      }
      

      【讨论】:

      • 我尝试运行您的代码(将ri == rs 更改为ri = rs 之后),但它似乎不起作用。
      • 在 else 之后如果它应该是 li++; ri = rs; 但如果你重置其中一个索引,这不是 O(n) - 这就是 O(n log n)
      • @Hogan 这根本不可能是正确的,因为如果你在if 的两个分支上都有li++,你肯定会在O(n) 中完成,只是并不总是有正确的答案。
      • @sverre:你可能是对的,因为很明显这个解决方案是第二个内部循环。也许像 if (a[li] =
      • @sverre 和@hogan 你是对的。看看最新的 - 想法是一样的 - 我们需要在进行交换时保持数组排序
      【解决方案5】:

      以下任何一项

      void Merge(int array[N])
      {
      for(int i=N/2;i<N;i++){
          int j=i;
          while(array[j]<array[j-1]&& j>=1){
              int temp=array[j];
              array[j]=array[j-1];
              array[j-1]=temp;
              j--;
          }
      }
      }
      
      void Merge2(int array[N])
      {
      for(int i=N/2;i<N;i++){
         int key=array[i];
         int j=i-1;
         while(array[j]>key&& j>=0){
                 array[j+1]=array[j];
                 j--;
         }
         array[j+1]=key;
      }
      }
      

      【讨论】:

      • 兄弟,有两个类似冒泡排序的嵌套循环
      猜你喜欢
      • 1970-01-01
      • 2017-06-17
      • 1970-01-01
      • 2015-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多