【问题标题】:How do you prove or illustrate that fast merge sort is an unstable algorithm?你如何证明或说明快速归并排序是一种不稳定的算法?
【发布时间】:2019-10-28 17:57:14
【问题描述】:

当我阅读算法第 4 版第 2 章的问题 2.2.10 时,一个问题让我感到困惑。书上说快速合并算法的结果是不稳定的,我找不到证据。帮帮我,谢谢!

public static void sort(Comparable[] a, int lo, int hi){
    if hi <= lo {
    return;
    }
    int mid = lo + (hi - lo) / 2;
    sort(a, lo, mid);
    sort(a, mid+1, hi);
    merge(a, lo, mid, hi);
}

// Why is the result of this sort not stable
private static void merge(Comparable[] a, int lo, int mid, int hi) { 
   for (int i = lo; i <= mid; i++)
      aux[i] = a[i]; 

   for (int j = mid+1; j <= hi; j++)
      aux[j] = a[hi-j+mid+1];

   int i = lo, j = hi; 
   for (int k = lo; k <= hi; k++) 
      if (less(aux[j], aux[i])) a[k] = aux[j--];
      else                      a[k] = aux[i++];
}

我找不到不稳定的结果,我怎么能得到呢?

【问题讨论】:

  • 好的,那么我的回答应该可以解决您在作业上取得进展所需的一切。如果您需要更多信息,请给我留言,否则请考虑在某个时候接受答案。

标签: java algorithm mergesort array-algorithms


【解决方案1】:

使“相等”元素保持相同顺序的排序算法被认为是稳定的。因此 unstable 意味着:您有多个相等的元素,并且当您对整个列表/数组进行排序时,该排序的输出具有那些 equal 元素(可能)显示在顺序不同。

假设您有一个 Person 类,并且实现了相等性以仅查看姓氏,而忽略名字。

现在,假设您有两个 Person 对象,分别代表“John Doe”和“Jane Doe”。它们按该顺序在您的未排序列表中。

稳定意味着:您总是以“John Doe”出现在“Jane Doe”之前。对于不稳定的排序,您没有这种保证。

换句话说:你需要创建一个至少有两个属性的类。然后你需要定义compareTo() 只依赖这两个属性之一。

然后创建该类对象的示例列表,然后进行足够长的试验,直到找到排序列表显示相等对象更改顺序的示例。

换句话说:创建一个列表 (p1, p2, p3, p4, ...),对其进行排序,然后寻找可能显示为 ... p4, p3 ... 尽管 p4 和 p3 的结果被认为是“平等的”。

最后:这实际上是使用一些基于属性的测试框架的非常好的用例,例如QuickCheck。使用这样的框架,您需要:

  • 创建一个“生成器”,它可以创建您稍后排序的某个类的“随机”对象(您可以倾斜生成器以确保从中得到一堆“相等”的对象)
  • 然后让框架测试底层“断言”,即排序前后“相等”对象的顺序不得改变。

然后让框架发挥它的魔力......

【讨论】:

    【解决方案2】:

    为了证明算法的不稳定性,一个反例就足够了:让我们考虑对包含 4 个元素 A B C D 的数组进行排序所采取的步骤,这些元素对于 less 谓词比较相等。

    • sort(a, 0, 3) 递归 2 个子数组:
    • sort(a, 0, 1) 再次递归
    • sort(a, 0, 0) 立即返回
    • sort(a, 1, 1) 立即返回
    • merge(a, 0, 0, 1) 不会改变A B 的顺序
    • sort(a, 2, 3) 递归于
    • sort(a, 2, 2) 立即返回
    • sort(a, 3, 3) 立即返回
    • merge(a, 2, 2, 3) 不会改变C D 的顺序
    • merge(a, 0, 1, 3)A B D C 的顺序将项目A B C D 复制到t,然后合并循环中的所有比较结果为false,因此复制回a 的元素的顺序相同,从@987654339 复制@:A B D C,证明了排序算法的不稳定性,即:比较相等的元素的相对顺序没有被保留。

    【讨论】:

      【解决方案3】:

      要证明一个排序算法是不稳定的,只需要找到一个失败。证明排序算法是稳定的会更复杂。检查失败的一种方法是使用整数数组并将整数分成两部分,高 8 位作为伪随机值,低 24 位等于整数的索引(0 到 count-1)。然后运行排序,仅使用高 8 位进行比较,例如在 C 中:

          if((b[j]&0xff000000) < (b[i]&0xff000000)) ...
      

      排序完成后,使用所有32位检查数组是否有序。

      使用这种方法,我能够确认这种归并排序的变体是不稳定的。

      显然,这被称为“快速”合并排序的原因是在进行合并时没有检查运行结束。左边的 run 以从 lo 到 mid 的正序复制到 aux[] 中,而右边的 run 以相反的顺序从 hi 到 mid+1 复制到 aux[] 中。然后合并从两端(lo 和 hi)开始,向中间(mid 和 mid+1)进行,左侧使用 i 从 lo 向前运行到 mid,右侧使用 j 从 hi 到 mid+1。由于没有检查是否到达运行结束,i 可能会在 mid 以上增加(潜在的稳定性问题),或者 j 可能会在 mid+1 以下减少(不是稳定性问题)。在 i 高于 mid 和 aux[mid+1] == aux[mid+2](原始右侧运行的两个最高元素)的情况下,稳定性被破坏。在这种情况下,元素以相反的顺序复制。

      虽然书上称它为快速归并排序,但在aux中避免复制数据会更快,而是根据递归的级别改变归并的方向。对于自上而下,这可以通过递归调用中的单一类型复制和交换数组引用来完成,例如这个 wiki 示例:

      https://en.wikipedia.org/wiki/Merge_sort#Top-down_implementation

      可以使用一对相互递归的函数来避免初始复制,一个以 a[] 的结果结束,另一个以 b[] 的结果结束。

      自底向上的归并排序稍快一些,因为它跳过了所有递归拆分和在堆栈上存储索引的过程。在这种情况下,合并的方向基于合并通道。为了保持通过次数均匀,可以提前检查奇数通过次数,并在开始第一次自下而上合并排序之前将元素对交换到位。

      【讨论】:

        猜你喜欢
        • 2017-12-16
        • 2019-09-13
        • 1970-01-01
        • 2018-09-29
        • 2017-05-13
        • 2012-04-04
        • 1970-01-01
        • 1970-01-01
        • 2011-10-18
        相关资源
        最近更新 更多