代码之家  ›  专栏  ›  技术社区  ›  Greg Rogers

是否有可能编写一个类似next_permutation的函数,但只排列r值,而不是n?

  •  2
  • Greg Rogers  · 技术社区  · 16 年前

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

    可以写这样的函数吗:

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

    这会排列范围内的元素 [第一,最后] 但只选择范围内的元素 [first, choice_last) 也就是说,我们可能有20个元素,想迭代其中10个选项的所有排列,20P10个选项对20P20个选项。

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

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

    以下是我为解决这个问题所做的基本工作。我们也欢迎就如何改进这一点提出建议。

    • 从向量开始 V 属于的 N 我想访问的每个排列的元素 R 从中选择的元素( R <= N ).
    • 构建一个向量 I 长度 R 有价值观 { 0, 1, 2, ... R - 1 } 作为元素的索引 五、
    • 在每次迭代中,构建一个向量 C 长度 R 有价值观 { V[I[0]], V[I[1]], ... V[I[R - 1]] }
    • 用中的值做点什么 C .
    • 应用一个函数来置换以下元素 一、 如果可以的话,再次迭代。

    该函数看起来像这样:

    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 回复  |  直到 16 年前
        1
  •  3
  •   fizzer    16 年前

    为了迭代nPk排列,我使用了 for_each_permutation() 算法在 this old CUJ article 之前。它使用了Knuth的一种很好的算法,该算法在原地旋转元素,并在最后保持原始顺序。因此,它满足您的无外部存储器要求。它也适用于双向致畸剂。它不符合你对外观的要求 next_permutation() 然而,我认为这是一个胜利——我不喜欢有状态的API。

        2
  •  1
  •   Community CDub    8 年前

    Java组合生成器的源代码位于 http://www.merriampark.com/comb.htm .去掉Java习惯用法,它几乎正是您所寻找的,作为生成器实现,以控制内存使用情况。


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

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

        3
  •  0
  •   David Schmitt    16 年前

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

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

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

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

        4
  •  0
  •   Community CDub    8 年前

    不管它值多少钱,这里有一个可行的实现。

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

    这种方式的一个好处是 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;
    }