【问题标题】:Partition for randomised quicksort (with few unique elements)随机快速排序的分区(具有很少的独特元素)
【发布时间】:2020-06-14 14:31:08
【问题描述】:

我的任务是为具有少量元素的随机快速排序编写一个分区函数(通过包括 3 个分区而不是 2 个分区对其进行优化)。我试过实现我的版本,发现它没有通过测试用例。

但是,通过使用同学版本的分区,它似乎可以工作。从概念上讲,我看不出他和我之间的区别,而且我不知道是什么导致我的版本崩溃。我用他(我认为)的概念编写它,其中涉及使用计数器(j 和 k)将数组划分为 3。

如果有人能指出为什么我的方法不起作用,以及我应该做些什么来尽量减少再次发生这种情况的机会,我将不胜感激。我觉得这个学习点对我作为开发人员来说很重要,谢谢!

为了比较,会有3个代码块,下面的sn-p是我的分区版本,后面是我同学的版本,最后是运行我们分区的实际算法。

我的版本(不工作)

vector<int> partition2(vector<int> &a, int l, int r) {
  int x = a[l];
  int j = l;
  int k = r;

  vector<int> m(2);

  // I've tried changing i = l + 1
  for (int i = l; i <= r; i++) {
    if (a[i] < x) {
      swap(a[i], a[j]);
      j++;
    }

    else if (a[i] > x) {
      swap(a[i], a[k]);
      k--;
    }
  }

  // I've tried removing this
  swap(a[l], a[j]);

  m[0] = j - 1;
  m[1] = k + 1;

  return m;
}

我的同学们(有效)

vector<int> partition2(vector<int> &a, int l, int r) {
    int x = a[l];
    int p_l = l;
    int i = l;
    int p_e = r;
    vector<int> m(2);
    while (i <= p_e) {
        if (a[i] < x) {
            swap(a[p_l], a[i]);
            p_l++;
            i++;
        } else if (a[i] == x) {
            i++;
        } else {
            swap(a[i], a[p_e]);
            p_e -= 1;
        }
        m[0] = p_l - 1;
        m[1] = p_e + 1;
    }
    return m;
}

实际的快速排序算法

void randomized_quick_sort(vector<int> &a, int l, int r) {
  if (l >= r) {
    return;
  }

  int k = l + rand() % (r - l + 1);
  swap(a[l], a[k]);
  vector<int> m = partition2(a, l, r);

  randomized_quick_sort(a, l, m[0]);
  randomized_quick_sort(a, m[1], r);
}

【问题讨论】:

  • 你能看出区别吗?在值大于枢轴的情况下,迭代器变量i 会发生什么情况?当您使用铅笔和纸浏览一个简单的示例数组时,您应该能够明白为什么您的代码不起作用。

标签: c++ algorithm sorting quicksort


【解决方案1】:

三路分区的两个函数的区别在于你的代码在每次循环中都前进i,但是你同学的函数只有在i位置的值小于或等于时才前进i到枢轴。

让我们看一个示例数组。第一个值 3 是枢轴。字母表示每次循环后变量的位置。

j               k
3   1   5   2   4
    i

下一个值更小:将其交换到左侧并前进j

    j           k
1   3   5   2   4
        i

下一个值 5 更大,因此向右移动:

    j       k
1   3   4   2   5
            i

这是一个糟糕的举动:您的i 现在已经跳过了 4,它也必须转到正确的部分。你同学的代码在这里没有推进i,在下一个pass中捕捉到了4。

您的循环有一些不变量,在所有通过后必须为真:

  • 索引低于i 的所有项目都小于枢轴。
  • 索引大于k 的所有项目都大于枢轴。
  • 索引从ji - 1 的所有项目都等于枢轴。
  • 尚未处理从ik 的所有项目。

您还可以从中确定循环条件:

  • 根据定义,枢轴是最左边的元素,因为快速排序功能将其交换到那里。它必须属于等于枢轴的元素组,因此您可以在l + 1 开始循环。
  • k 开始的所有项目都已经在数组的正确部分。这意味着您可以在i 到达k 时停止。更进一步将不必要地在“大于”分区内交换元素并移动k,这将返回错误的分区边界。

【讨论】:

  • @M Oehm 非常感谢。事后很明显,但事情总是如此。我喜欢你的回答如此详细,我希望有一天我能像你一样清醒。
猜你喜欢
  • 2019-08-18
  • 2013-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-22
  • 1970-01-01
  • 1970-01-01
  • 2011-12-01
相关资源
最近更新 更多