是否有可能编写像next_permutation这样的函数但只能置​​换r值而不是n?

时间:2008-10-26 08:08:02

标签: c++ algorithm

std :: next_permutation(和std :: prev_permutation)置换总共为n的[first, last)范围内的所有值!排列(假设所有元素都是唯一的)。

是否可以编写这样的函数:

template<class Iter>
bool next_permutation(Iter first, Iter last, Iter choice_last);

这会置换[first, last)范围内的元素,但只会选择范围[first, choice_last)中的元素。也就是说,我们可能有20个元素,并希望迭代10个选项的所有排列,20个P 10选项和20个P 20。

  • Iter是一个随机访问迭代器,但是如果它可以实现为双向迭代器,那就太棒了!
  • 需要的外部内存量越少越好,但就我的目的而言,这并不重要。
  • 每次迭代的所选元素都输入到序列的第一个元素。

这样的功能是否可以实现?有没有人知道任何现有的实现?

基本上我正在做的就是解决这个问题。关于如何改善这一点的建议也是受欢迎的。

  • VN元素的向量开始,我要访问其中R元素的R <= N元素的每个排列。{/ 1}。
  • 构建长度为I的向量R,其值为{ 0, 1, 2, ... R - 1 },以作为V
  • 元素的索引
  • 在每次迭代中,构建长度为C的向量R,其值为{ V[I[0]], V[I[1]], ... V[I[R - 1]] }
  • 使用C中的值执行某些操作。
  • 应用一个函数来置换I的元素并重新迭代(如果能够)。

该功能如下所示:

bool NextPermutationIndices(std::vector<int> &I, int N)
{
    const int R = I.size();
    for (int i = R - 1; ; --i) {
        if (I[i] < N - R + i) {
            ++I[i];
            return true;
        }

        if (i == 0)
            return false;

        if (I[i] > I[i-1] + 1) {
            ++I[i-1];
            for (int j = i; j < R; ++j)
                I[j] = I[j-1] + 1;
            return true;
        }
    }
}

由于所有可能的逐个错误,该功能非常复杂,使用它的一切都比可能需要的更复杂。


编辑:

事实证明,它比我想象的更容易显着。从here开始,我能够找到我需要的许多精确算法的精确实现(组合,排列等)。

template<class BidirectionalIterator>
bool next_partial_permutation(BidirectionalIterator first,
                              BidirectionalIterator middle,
                              BidirectionalIterator last)
{
    std::reverse(middle, last);
    return std::next_permutation(first, last);
}

此外,还有一种组合算法以类似的方式工作。然而,实现这一点要复杂得多。

4 个答案:

答案 0 :(得分:3)

要迭代nPk排列,我之前使用了this old CUJ article中提供的for_each_permutation()算法。它使用了Knuth的一个很好的算法,它可以原位旋转元素,最后将它们保留在原始顺序中。因此,它满足您无需外部存储器的要求。它也适用于BidirectionalIterators。它不符合您next_permutation()的要求。但是,我认为这是一个胜利 - 我不喜欢有状态的API。

答案 1 :(得分:1)

Java组合生成器的源代码位于 http://www.merriampark.com/comb.htm 。删除Java习语,它几乎就是你正在寻找的东西,作为一个生成器实现,以限制你的内存使用。


此问题来自称为 Combinatorics 的数学领域,它是离散数学的一部分。离散数学对计算机科学从业者至关重要,因为它包括我们日常使用的几乎所有数学(如逻辑,算法,计数,关系,图论等)。我强烈推荐Discrete and Combinatorial Mathematics: An applied introductionDiscrete Mathematics and Its Applications,如果你负担得起的话。

(注意:这个问题与“Algorithm for Grouping”有关,但不完全重复,因为这个问题要求在一般情况下解决它。)

答案 2 :(得分:0)

算法简化将分为两个单独的步骤。

  • 生成原始数据中所有可能的R元素选择列表。
  • 对于每个选择,请创建所选元素的所有可能排列。

通过交错这些操作,您可以避免分配中间列表。

可以通过跳过非选定项来在双向迭代器上实现选择。生成所有选择,例如通过置换R个和(N-R)个零的序列。这将需要O(N)额外的内存,但是您可以将原始序列置换到位。

答案 3 :(得分:0)

它的价值在于,这是一种有效的实现。

它要求上面选择的元素按排序顺序开始。它只有在序列中没有重复元素时才有效(如果有,它会错过一些排列,并且不会以正确的方式结束)。它也可能缺少一些边缘情况,因为我没有真正测试它,因为我没有计划实际使用它。

这种方式相对于this answer's的一个好处是,这种方式不会访问字典顺序中的排列,这可能(但可能不是)很重要。使用boost :: bind有时候创建一个函子来传递给for_each也是一种痛苦。

template<class Iter>
bool next_choice_permutation(Iter first, Iter choice, Iter last)
{
    if (first == choice)
        return false;

    Iter i = choice;
    --i;
    if (*i < *choice) {
        std::rotate(i, choice, last);
        return true;
    }

    while (i != first) {
        Iter j = i;
        ++j;
        std::rotate(i, j, last);
        --i;
        --j;
        for (; j != last; ++j) {
            if (*i < *j)
                break;
        }
        if (j != last) {
            std::iter_swap(i, j);
            return true;
        }
    }
    std::rotate(first, ++Iter(first), last);
    return false;
}