【问题标题】:Dutch national flag - Deducing solution荷兰国旗 - 推断解决方案
【发布时间】:2017-05-11 11:11:41
【问题描述】:

在 Coursera 的 sedgewick course 学习基本排序、选择排序插入排序堆排序

选择排序 - 最终排序顺序开始形成每次迭代

插入排序 - 升序(非最终)开始形成每次迭代。

堆排序 - 插入排序的激进版本。


在学习排序算法(如上所示)之后,我在上述课程中有一个作业,

荷兰国旗。给定一个n 桶数组,每个桶都包含一个红色、白色或蓝色的卵石,按颜色对它们进行排序。允许的操作是:

swap(i,j):swap桶中的卵石i与桶中的卵石j

color(i): 桶中鹅卵石的颜色i.

性能要求如下:

最多n 调用color()

最多n 调用swap()

恒定的额外空间。


通过阅读这个问题,

根据给定的性能要求,我得到一个提示,即需要n 交换(最多)。从这个提示,我想,

1) 插入排序,可确保 n 个反转对的 n 次交换。

2) 我假设存储在桶中的颜色,给定 n 个桶,相似的颜色很有可能坐得更近。这会导致数组部分排序的情况,如果反转次数 cn,则数组是部分排序的。

基于以上两个结论,

问题:

1)

推断插入排序作为解决方案的思维过程是否正确?

2)

散列颜色并将颜色作为部分排序数组插入的散列函数是什么?我需要线性探测方法吗?

3) 为什么我们需要额外的空间?因为插入排序是就地算法

注意:为了确认我的思路,在写代码之前

【问题讨论】:

  • 我现在正在使用智能手机,无法发布正确的答案,但是——您需要跳出框框思考。标准排序算法的概念是相关的,但没有特定的标准排序算法满足这些约束,即使在这个受限的输入上也是如此。 (明天我会发布一个完整的答案,如果没有人超过我的话。)
  • @ruakh 谢谢你,期待你的评论
  • @ruakh 请在你的回答中评论我哪里出错了

标签: algorithm sorting hash hashtable insertion-sort


【解决方案1】:

要详细解决这个问题,你不需要提到算​​法。

相反,请熟悉快速排序的分区例程实现 - 这真的很有帮助。

附言

堆排序 - 插入排序的激进版本

不,你错了,这是完全不同的方法

您可以考虑一下 Shell 排序如何在内部利用插入排序

【讨论】:

  • 是的,她会排序,不确定,我为什么要写堆排序?
  • 如你所说,我今天已经完成了快速排序,但相应的 query 就在上面
【解决方案2】:

我认为您不应该(或不需要)假设距离较近的桶具有相同颜色的可能性更高。保持三种颜色中的两种颜色(在排序中不被视为“第一个”的颜色)的第一次出现的索引可以解决该问题。用值0、1、2替换颜色,我相信这段代码满足要求:

public class Z {
  private static int first1, first2;

  private static void swap(int[] v, int i, int j) {
    int tmp = v[i];
    v[i] = v[j];
    v[j] = tmp;
  }

  private static void sort(int[] v) {
    first1 = -1;
    first2 = -1;
    for(int i=0; i<v.length; i++) {
      switch(v[i]) {
        case 0:
          if (first1 != -1) {
            swap(v, i, first1);
            if (first2 != -1) {
              swap(v, i, first2);
              first2++;
            }
            first1++;
          } else if (first2 != -1) {
            swap(v, i, first2);
            first2++;
          }
          break;
        case 1:
          if (first2 != -1) {
            swap(v, i, first2);
            if (first1 == -1) {
              first1 = first2;
            }
            first2++;
          } else if (first1 == -1) {
            first1 = i;
          }
          break;
        case 2:
          if (first2 == -1) {
            first2 = i;
          }
          break;
      }
    }
  }
  public static String toString(int[] v) {
    StringBuilder sb = new StringBuilder();
    sb.append("[");
    for(int i=0; i<v.length; i++) {
      sb.append(v[i]);
      sb.append(" ");
    }
    sb.append("]");
    return sb.toString();
  }

  public static void main(String[] args) {
    int c = Integer.parseInt(args[0]);
    int[] v = new int[c];
    while(true) {
      int[] tmp = v.clone();
      System.out.print(toString(tmp) + "=>");
      sort(tmp);
      System.out.println(toString(tmp));
      int prev = -1;
      for(int j=0; j<tmp.length; j++) {
        if (tmp[j] < prev) {
          throw new Error();
        }
      }
      int j;
      for(j=0; j<c; j++) {
        v[j]++;
        if (v[j] == 3) {
          v[j] = 0;
        } else {
          break;
        }
      }
      if (j == c) {
        break;
      }
   }
  }
}

【讨论】:

    【解决方案3】:

    乍一看就像是桶排序的介绍:将每个红色小石子移入 Reds 数组,将每个白色小石子移入 Whites 数组,将每个蓝色小石子移入 Blues 数组,然后将这 3 个数组复制到最终输出数组中。简单的实现将需要 3*ncolor() 调用、2*nswap() 调用和n+恒定的额外内存。

    但是限制有点超出限制......我们无法承担简单的决定,我们无法为临时数组提供额外的内存。

    请注意,n 是输入数组的长度,而不是排列数,也不是乘以某物。那就是:

    • swap() 的每次调用都应在其最终位置放置一个鹅卵石
    • 交换应该就地完成
    • color() 的每次调用都应该导致 swap() 的适当调用

    只有 3 种颜色是可能的。为了使它成为可能,我们可以将输入数组划分为 3(4) 个部分:

    r) 数组开头的红色鹅卵石

    w) 白色鹅卵石从一开始就跟随红色鹅卵石

    u) 未加工的鹅卵石跟随白色鹅卵石直至蓝色鹅卵石

    b) 数组末尾的蓝色鹅卵石

    假设我们使用从 0 开始的数组索引。因此,索引 a[0] 是数组的第一个元素,而 a[n-1] 是数组的最后一个元素。

    现在要跟踪数组状态,我们可以使用几个变量:

    • 变量 r 将保存已处理的红色鹅卵石的数量

    • 变量 w 将保存处理过的白色鹅卵石的数量

    • 变量 b 将保存处理过的蓝色鹅卵石的数量

    • 变量 i 将保存当前的 pebble 索引以进行处理(已处理)

    这对于处理循环来说是不变的。开始时 r=0, w=0, b=0, i=0。 r、w、b 端对应的鹅卵石个数,i=n-b。

    所以,数组一开始是这样的:

    呜呜呜 ^ ^ r n=n-0=n-b

    在处理过程中数组将如下所示:

    呜呜呜呜呜呜 ^ ^ ^ r w+r n-b 一世

    艾莉亚最终会是这个样子:

    呜呜呜呜呜呜 ^ ^ ^ ^ 0 r n-b n w+r 一世

    处理循环的每个步骤(在此之后 i 递增)我们做出决定并执行 3 种可能的事情之一:

    • 如果 a[i] 是红色的,我们做 swap(r, i) 并增加 r
    • 如果 a[i] 为白色,我们就增加 w - 实际上这是无用的操作,因为我们并没有真正使用 w,但我认为这样做更好地执行
    • 如果 a[i] 为蓝色,我们增加 b 并进行 swap(i, n-b) - 如果 i=n-b,我们可以跳过交换,但通常算法应该在没有这个跳过的情况下工作

    循环结束一次 i>=n-b

    现在,对于您的问题:

    推断插入排序作为解决方案的思维过程是否正确?

    不,插入排序比 color()n 次调用和 swap() 的 n 次调用更多

    散列颜色并将颜色作为部分排序数组插入的散列函数是什么?

    你没有指定颜色的顺序,所以我自己决定了。但是鹅卵石不需要哈希,因为如果将鹅卵石放入临时数据结构中,它只会增加交换次数

    我需要线性探测方法吗?

    这是一个棘手的部分 - color() 每个输入 pebble 调用一次(不超过 1 个)。您可以将 color() 结果存储在局部变量中,然后在案例运算符中使用局部变量。严格地说,这是错误的 - 你仍然比需要做更多的颜色比较,但可能你的老师会接受这种方法,因为你仍然只调用 color() 那个次数。真的不需要哈希。

    不过,如果您的编程语言允许,您可以尝试使用数组或成员函数引用,并以卵石颜色为索引。如上所述,每个函数都应该执行适当的步骤来处理循环决策。

    由于您没有指定 PL 以及颜色的表示方式,因此很难建议如何有效地做到这一点。

    为什么我们需要额外的空间?因为插入排序是就地算法

    即使是插入排序也会为局部变量使用一些额外的空间。

    附:我不编写即用型代码,因为这是您的执行。如果在代码中实现时出现问题,请随时使用您的代码更新答案。

    【讨论】:

      【解决方案4】:

      1) 确保 n 个反转对的 n 次交换的插入排序。

      2)假设颜色存储在桶中,给定 n 个桶,相似的颜色很有可能更靠近。这会导致数组部分排序的情况,如果反转次数 cn,则数组是部分排序的。

      这种推理有两个问题。

      首先是最坏情况下反转的次数不是O(n)。要了解原因,请假设您有 n/2 个蓝色鹅卵石,然后是 n/2 个红色鹅卵石(没有白色鹅卵石)。那么总的反转次数为(n2)/4。 (此外,从我刚刚运行的一些快速实验来看,即使是平均情况下的反转总数也是 (n2) /6 — 仍然不是 O(n)。)

      这种推理的第二个问题是,您被要求将交换次数限制为恰好 n,而不是 O(n)。因此,即使插入排序只需要(比如说)2 次n 次交换,那仍然太多了。


      2) [¶] 散列颜色并将颜色作为部分排序数组插入的散列函数是什么?我需要线性探测方法吗?

      插入排序不需要哈希函数;而且我认为您无法在恒定额外空间的约束下构建任何有意义的哈希表。

      相反,您可以只将三种颜色表示为(例如)0、1 和 2。

      3) 为什么我们需要额外的空间?因为插入排序是就地算法

      即使是插入排序也需要一些额外的变量,例如数组索引。


      Alexander Anikin 已经提出了与我将建议的相同的解决方案,但我不确定他的回答是否清楚你如何自己提出这个问题,所以这个答案的其余部分将引导你通过那个。


      所以,假设您从尝试插入排序开始。然后我们可以逐步改进它,直到它满足要求。 (这确实需要一些洞察力,但每个单独的改进只需要一点洞察力,而不是一次解决整个问题。)

      第一个改进:color(i) 的调用次数。

      当我们想将一个新的 pebble 插入到数组的已排序部分中时,我们需要找到它应该在该部分中的位置。

      对于纯插入排序,我们通过检查数组中已排序部分中的卵石来做到这一点。但是我们只能准确地调用color n 次;我们预计必须为每个 pebble 至少调用一次 - 你不能在不检查的情况下对 pebble 进行排序 - 这意味着要完成这项工作,我们需要为每个 pebble 调用一次准确卵石。我们不能为我们已经分类的鹅卵石重新调用它。

      要解决这个问题,关键的见解是,在数组的已排序部分中,我们实际上不需要检查一个桶来知道它有什么颜色的鹅卵石,只要我们一直跟踪如何我们已经看到了许多不同颜色的鹅卵石。如果我们之前见过r红色鹅卵石、w白色鹅卵石和b蓝色鹅卵石,那么红色鹅卵石在桶中0r-1,白色在r到@ 987654330@,蓝色的在桶中r+wr+w+b-1。所以我们可以找出在哪里插入一个新的鹅卵石,而不必在任何其他鹅卵石上实际调用color

      第二个改进:对swap(i,j) 的调用次数,第 1 部分。

      下一个问题是,对于纯插入排序,插入一个鹅卵石意味着“移动”它之后的所有鹅卵石,这可能意味着大量的交换。 (例如,如果我们已经看到 10 个白色鹅卵石和 5 个蓝色鹅卵石,那么插入一个红色鹅卵石意味着“移动”15 个鹅卵石,这需要 15 次交换。)

      要解决这个问题,关键在于我们实际上并不关心颜色组中鹅卵石的顺序;红色鹅卵石可以以任何顺序出现,其次是白色鹅卵石以任何顺序排列,然后是蓝色鹅卵石以任何顺序排列。因此,我们可以将单个 pebble 从颜色组的开头移动到它的结尾,而不是将大量鹅卵石移动一个位置。

      例如,如果我们之前看到过r红色鹅卵石、w白色鹅卵石和b蓝色鹅卵石,并且我们需要插入一个新的红色鹅卵石,那么我们可以将其交换到@987654338位置@(给我们一个白色鹅卵石(如果w &gt; 0)),然后将白色鹅卵石交换到位置r+w(给我们一个蓝色鹅卵石(如果b &gt; 0))。

      这使我们在最坏的情况下减少到 O(n) 次交换,而在平均情况下则低于 n 交换:一个红色卵石将需要两次交换(如果 w &gt; 0b &gt; 0),白色鹅卵石需要一次交换(如果 b &gt; 0),蓝色鹅卵石需要零次交换。

      第三项改进:对swap(i,j) 的调用次数,第 2 部分。

      但这仍然给我们留下可能过多的交换:如果红色鹅卵石多于蓝色鹅卵石,那么我们(通常)最终会得到超过 n 交换。

      要解决这个问题,我们需要最后一点洞察力,即我们知道蓝色鹅卵石最终会在哪里(因为我们不关心它们最终的顺序);因此,我们可以将蓝色鹅卵石放在数组的 end 之后,而不是在已经看到的白色鹅卵石结束后立即放置蓝色鹅卵石。这样我们就不需要不断地重新移动蓝色鹅卵石的范围;一旦放置了蓝色鹅卵石,它就可以原地不动。

      因此,例如,如果我们看到了r 红色鹅卵石、w 白色鹅卵石和b 蓝色鹅卵石,那么红色鹅卵石应该位于0r-1 的位置,白色鹅卵石应位于rr+w-1 的位置,蓝色鹅卵石应位于n-bn-1 的位置。从r+wn-b-1 的位置是尚未排序的范围。


      所以,我们的最终算法如下所示:

      • rwb初始化为0
      • i初始化为0
      • i &lt; n - b:
        • 检查color(i):
          • 如果是 RED,则 swap(i, r) 并递增 ri
          • 如果是 WHITE,则只增加 wi
          • 如果是蓝色,则swap(i, n-b-1) 并递增b不要增加i(因为i位置的鹅卵石是我们刚刚从数组末尾附近拉出的鹅卵石,我们还没有检查过)。

      (请注意,通过仔细检查被交换的两个位置是否实际上不同,可以消除一些交换;但是,这不是必需的。无论哪种方式,我们都处于 n 个交换之下。)


      现在,如果我们从不同的排序开始,而不是插入排序呢?某些种类仍然会带我们朝着正确的方向前进;例如,如果我们从桶排序(或只有一个数字 0-2 的基数排序)开始,我们仍然可以进行增量修复以结束上述算法。其他种类的方法效果不会那么好,但仍然可能会带来有用的见解,一旦我们走上更好的道路,这些见解就会有所帮助。所以尝试不同的事情并保持开放的心态很重要。您探索问题的次数越多,获得的见解就越多,即使您偶尔会走错路,需要退出并尝试新事物。

      【讨论】:

        【解决方案5】:

        作为提示,Wikipedia 说以下内容,强调我的:

        荷兰国旗问题是由 Edsger Dijkstra 提出的计算机科学编程问题。荷兰国旗由红、白、蓝三种颜色组成。给定这三种颜色的球在一条线上随机排列(球的实际数量无关紧要),任务是将它们排列成相同颜色的所有球都在一起,并且它们的集体颜色组的顺序正确。

        这个问题的解决方案对于设计排序算法很有意义;特别是,必须对重复元素具有鲁棒性的快速排序算法的变体需要一个三向分区函数,该函数将小于给定键(红色)、等于键(白色)和更大的项分组比键(蓝色)。存在几种具有不同性能特征的解决方案,专门用于对具有少量或大量重复元素的数组进行排序。

        (解决方案,the article also has pseudocode算法)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-10-26
          • 2012-06-28
          • 2011-05-04
          • 1970-01-01
          • 2022-11-19
          • 1970-01-01
          • 2014-12-09
          • 1970-01-01
          相关资源
          最近更新 更多