【问题标题】:Combination of a Collection with Repetitions集合与重复的组合
【发布时间】:2016-02-04 18:21:01
【问题描述】:

http://stackoverflow.com 上有很多关于如何进行组合的链接:Generating combinations in c++ 但这些链接假定从无限集合中提取而没有重复。当给定一个确实有重复的有限集合时,这些算法会构造重复。例如,您可以在我在这里构建的测试用例中看到链接问题的公认解决方案失败:http://ideone.com/M7CyFc

给定输入集:vector<int> row {40, 40, 40, 50, 50, 60, 100};

我希望看到:

  • 40 40 40
  • 40 40 50
  • 40 40 60
  • 40 40 100
  • 40 50 50
  • 40 50 60
  • 40 50 100
  • 40 60 100
  • 50 50 60
  • 50 50 100
  • 50 60 100

显然,我可以使用旧方法存储输出并在生成时检查重复项,但这需要大量额外的内存和处理能力。有没有 C++ 提供给我的替代方案?

【问题讨论】:

  • 您是否要求从七个不考虑相同值的元素中选择三个元素?在这种情况下,您应该得到 7C3 = 35 套。您只列出了 11 个。一个想法是组合索引 0 - 7 并返回索引指示的元素。
  • @NathanOliver 是的,Jarod42 有权利在我的 row 声明中输入 30s。
  • @RandomGuy 我要求不重复的组合。您会注意到我在问题中的ideone.com 链接产生了35 个结果,但即使是前2 个也是重复的,因为它希望我提供一个唯一的输入集。我不是。我确实为我的示例手动完成了组合,但我相信我拥有一切。
  • 我认为如果您对术语更加谨慎,您可能会更容易找到相关算法。根据定义,集合从不包含重复项,因此您调用集合的输入根本不是真正的集合。
  • @Howard Hinnant 就该主题写了一个完整的迷你库,请参阅here。我打赌你会在其中找到解决方案。

标签: c++ unique combinations permutation repeat


【解决方案1】:

根据定义,组合不尊重顺序。这让我们可以按照我们认为合适的任何顺序排列数字。最值得注意的是,我们可以依靠提供组合排名。当然,对组合进行排名最合乎逻辑的方式是按排序顺序,因此我们将根据输入的排序。

标准库中已经有这方面的先例。例如lower_bound,我们将在此解决方案中实际使用它。但是,当通常使用时,这可能需要用户在通过之前进行排序。

我们将为此编写的函数将接收指向要从中提取下一个组合的排序集合的迭代器,以及指向当前组合的迭代器。我们还需要大小,但这可以从组合迭代器之间的距离得出。

template <typename InputIt, typename OutputIt>
bool next_combination(InputIt inFirst, InputIt inLast, OutputIt outFirst, OutputIt outLast) {
    assert(distance(inFirst, inLast) >= distance(outFirst, outLast));

    const auto front = make_reverse_iterator(outFirst);
    const auto back = make_reverse_iterator(outLast);
    auto it = mismatch(back, front, make_reverse_iterator(inLast)).first;

    const auto result = it != front;

    if (result) {
        auto ub = upper_bound(inFirst, inLast, *it);

        copy(ub, next(ub, distance(back, it) + 1), next(it).base());
    }
    return result;
}

该函数以其他算法函数的格式编写,因此任何支持双向迭代器的容器都可以使用它。对于我们的示例,尽管我们将使用:const vector&lt;unsigned int&gt; row{ 40U, 40U, 40U, 50U, 50U, 60U, 100U };,这必然是已排序的:

vector<unsigned int> it{ row.cbegin(), next(row.cbegin(), 3) };

do {
    copy(it.cbegin(), it.cend(), ostream_iterator<unsigned int>(cout, " "));
    cout << endl;
} while(next_combination(row.cbegin(), row.cend(), it.begin(), it.end()));

Live Example


写完这个答案后,我做了更多的研究,发现N2639 提出了一个标准化的next_combination,它是:

  • 正在积极考虑未来的 TR,when work on TR2 was deferred pending
  • 当时好评
  • 在任何采用之前至少再进行一次修订
  • 需要进行一些修改以反映 C++11 语言工具的添加

[Source]

使用 N2639 的参考实现需要可变性,因此我们将使用:vector&lt;unsigned int&gt; row{ 40U, 40U, 40U, 50U, 50U, 60U, 100U };。我们的示例代码变为:

vector<unsigned int>::iterator it = next(row.begin(), 3);

do {
    copy(row.begin(), it, ostream_iterator<unsigned int>(cout, " "));
    cout << endl;
} while(next_combination(row.begin(), it, row.end()));

Live Example

【讨论】:

    【解决方案2】:

    你可以这样做(也许避免递归):

    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    using std::cout;
    using std::vector;
    
    void perm( const vector<int> &v, vector<vector<int>> &p, vector<int> &t, int k, int d) {
        for ( int i = k; i < v.size(); ++i ) {
            // skip the repeted value
            if ( i != k && v[i] == v[i-1]) continue;
            t.push_back(v[i]);
            if ( d > 0 ) perm(v,p,t,i+1,d-1);
            else p.push_back(t);
            t.pop_back();
        }
    }
    
    int main() {
        int r = 3;
        vector<int> row {40, 40, 40, 50, 50, 60, 100};
        vector<vector<int>> pp;
        vector<int> pe;
    
        std::sort(row.begin(),row.end()); // that's necessary
        perm(row,pp,pe,0,r-1);
    
        cout << pp.size() << '\n';
        for ( auto & v : pp ) {
            for ( int i : v ) {
                cout << ' ' << i;
            }
            cout << '\n';
        }
        return 0;
    }
    

    哪些输出:

    11
     40 40 40
     40 40 50
     40 40 60
     40 40 100
     40 50 50
     40 50 60
     40 50 100
     40 60 100
     50 50 60
     50 50 100
     50 60 100
    

    我知道,这远非高效,但如果你明白了,你可能会想出一个更好的实现。

    【讨论】:

    • 如问题中所述,目标是生成所有排列。这正是它的作用,使用set 是一个不错的选择,因为它为我们节省了构建所有这些所需的内存,但它并没有避免所需的处理时间。
    • @JonathanMee 我添加了另一个算法。看看它是否能帮助你找到有用的东西。
    • 对递归解决方案给予 +1。原始答案显然没有帮助。我输入了my own solution,但我相信对于需要所有组合的情况,你的更好。因此,如果您能编辑掉不正确的答案,我将不胜感激。
    【解决方案3】:

    这是我在大学时代曾经写过的一门处理玻色子的课程。它很长,但它通常可用并且似乎运行良好。此外,它还提供排名和取消排名功能。希望能有所帮助——但永远不要问我当时在做什么...... ;-)

    struct SymmetricIndex
    {
        using StateType = std::vector<int>;
        using IntegerType = int;
        int M;
        int N;
        StateType Nmax;
        StateType Nmin;
        IntegerType _size;
        std::vector<IntegerType> store;
        StateType state;
        IntegerType _index;
    
        SymmetricIndex() = default;
        SymmetricIndex(int _M, int _N, int _Nmax = std::numeric_limits<int>::max(), int _Nmin = 0)
            : SymmetricIndex(_M, _N, std::vector<int>(_M + 1, std::min(_Nmax, _N)), StateType(_M + 1, std::max(_Nmin, 0)))
        {}
    
        SymmetricIndex(int _M, int _N, StateType const& _Nmax, StateType const& _Nmin)
            : N(_N)
            , M(_M)
            , Nmax(_Nmax)
            , Nmin(_Nmin)
            , store(addressArray())
            , state(M)
            , _index(0)
        {
            reset();
            _size = W(M, N);
        }
    
        friend std::ostream& operator<<(std::ostream& os, SymmetricIndex const& sym);
    
        SymmetricIndex& reset()
        {
            return setBegin();
        }
    
    
        bool setBegin(StateType& state, StateType const& Nmax, StateType const& Nmin) const
        {
            int n = N;
            for (int i = 0; i<M; ++i)
            {
                state[i] = Nmin[i];
                n -= Nmin[i];
            }
    
            for (int i = 0; i<M; ++i)
            {
                state[i] = std::min(n + Nmin[i], Nmax[i]);
                n -= Nmax[i] - Nmin[i];
                if (n <= 0)
                    break;
            }
    
            return true;
        }
    
    
        SymmetricIndex& setBegin()
        {
            setBegin(state, Nmax, Nmin);
            _index = 0;
            return *this;
        }
    
    
        bool isBegin() const
        {
            return _index==0;
        }
    
        bool setEnd(StateType& state, StateType const& Nmax, StateType const& Nmin) const
        {
            int n = N;
            for (int i = 0; i < M; ++i)
            {
                state[i] = Nmin[i];
                n -= Nmin[i];
            }
    
            for (int i = M - 1; i >= 0; --i)
            {
                state[i] = std::min(n + Nmin[i], Nmax[i]);
                n -= Nmax[i] - Nmin[i];
                if (n <= 0)
                    break;
            }
            return true;
        }
        SymmetricIndex& setEnd()
        {
            setEnd(state, Nmax, Nmin);
            _index = _size - 1;
            return *this;
        }
    
        bool isEnd() const
        {
            return _index == _size-1;
        }
    
        IntegerType index() const
        {
            return _index;
        }
    
        IntegerType rank(StateType const& state) const
        {
            IntegerType ret = 0;
            int n = 0;
    
            for (int i = 0; i < M; ++i)
            {
                n += state[i];
                for (int k = Nmin[i]; k < state[i]; ++k)
                    ret += store[(n - k) * M + i];
            }
    
            return ret;
        }
    
        IntegerType rank() const
        {
            return rank(state);
        }
    
        StateType unrank(IntegerType rank) const
        {
            StateType ret(M);
    
            int n = N;
            for (int i = M-1; i >= 0; --i)
            {
                int ad = 0;
                int k = std::min(Nmax[i] - 1, n);
    
                for (int j = Nmin[i]; j <= k; ++j)
                    ad+=store[(n - j) * M + i];
    
                while (ad > rank && k >= Nmin[i])
                {
                    ad -= store[(n - k) * M + i];
                    --k;
                }
    
                rank -= ad;
                ret[i] = k+1;
                n -= ret[i];
                if (n <= 0)
                {
                    return ret;
                }
            }
    
            return ret;
        }
    
        IntegerType size() const
        {
            return _size;
        }
    
        operator StateType& ()
        {
            return state;
        }
    
        auto operator[](int i) -> StateType::value_type& { return state[i]; }
    
        operator StateType const& () const
        {
            return state;
        }
        auto operator[](int i) const -> StateType::value_type const& { return state[i]; }
    
    
        bool nextState(StateType& state, StateType const& Nmax, StateType const& Nmin) const
        {
            //co-lexicographical ordering with Nmin and Nmax:
    
            // (1) find first position which can be decreased
            //     then we have state[k] = Nmin[k]  for k in [0,pos]
            int pos = M - 1;
            for (int k = 0; k < M - 1; ++k)
            {
                if (state[k] > Nmin[k])
                {
                    pos = k;
                    break;
                }
            }
    
            // if nothing found to decrease, return
            if (pos == M - 1)
            {
                return false;
            }
    
            // (2) find first position after pos which can be increased
            //     then we have state[k] = Nmin[k]  for k in [0,pos]
            int next = 0;
            for (int k = pos + 1; k < M; ++k)
            {
                if (state[k] < Nmax[k])
                {
                    next = k;
                    break;
                }
            }
            if (next == 0)
            {
                return false;
            }
            --state[pos];
            ++state[next];
    
            // (3) get occupation in [pos,next-1] and set to Nmin[k]
            int n = 0;
            for (int k = pos; k < next; ++k)
            {
                n += state[k] - Nmin[k];
                state[k] = Nmin[k];
            }
    
            // (4) fill up from the start
            for (int i = 0; i<M; ++i)
            {
                if (n <= 0)
                    break;
                int add = std::min(n, Nmax[i] - state[i]);
                state[i] += add;
                n -= add;
            }
    
            return true;
        }
    
    
        SymmetricIndex& operator++()
        {
            bool inc = nextState(state, Nmax, Nmin);
            if (inc) ++_index;
            return *this;
        }
        SymmetricIndex operator++(int)
        {
            auto ret = *this;
            this->operator++();
            return ret;
        }
    
        bool previousState(StateType& state, StateType const& Nmax, StateType const& Nmin) const
        {
            ////co-lexicographical ordering with Nmin and Nmax:
    
            // (1) find first position which can be increased
            //     then we have state[k] = Nmax[k]  for k in [0,pos-1]
            int pos = M - 1;
            for (int k = 0; k < M - 1; ++k)
            {
                if (state[k] < Nmax[k])
                {
                    pos = k;
                    break;
                }
            }
    
            // if nothing found to increase, return
            if (pos == M - 1)
            {
                return false;
            }
    
            // (2) find first position after pos which can be decreased
            //     then we have state[k] = Nmin[k]  for k in [pos+1,next]
            int next = 0;
            for (int k = pos + 1; k < M; ++k)
            {
                if (state[k] > Nmin[k])
                {
                    next = k;
                    break;
                }
            }
            if (next == 0)
            {
                return false;
            }
            ++state[pos];
            --state[next];
    
            int n = 0;
            for (int k = 0; k <= pos; ++k)
            {
                n += state[k] - Nmin[k];
                state[k] = Nmin[k];
            }
            if (n == 0)
            {
                return true;
            }
    
            for (int i = next-1; i>=0; --i)
            {
                int add = std::min(n, Nmax[i] - state[i]);
                state[i] += add;
                n -= add;
                if (n <= 0)
                    break;
            }
    
            return true;
        }
    
    
        SymmetricIndex operator--()
        {
            bool dec = previousState(state, Nmax, Nmin);
            if (dec) --_index;
            return *this;
        }
        SymmetricIndex operator--(int)
        {
            auto ret = *this;
            this->operator--();
            return ret;
        }
    
        int multinomial() const
        {
            auto v = const_cast<std::remove_reference<decltype(state)>::type&>(state);
            return multinomial(v);
        }
    
        int multinomial(StateType& state) const
        {
            int ret = 1;
            int n = state[0];
            for (int i = 1; i < M; ++i)
            {
                n += state[i];
                ret *= binomial(n, state[i]);
            }
            return ret;
        }
    
        SymmetricIndex& random(StateType const& _Nmin)
        {
            static std::mt19937 rng;
    
            state = _Nmin;
            int n = std::accumulate(std::begin(state), std::end(state), 0);
            auto weight = [&](int i) { return state[i] < Nmax[i] ? 1 : 0; };
    
            for (int i = n; i < N; ++i)
            {
                std::discrete_distribution<int> d(N, 0, N, weight);
                ++state[d(rng)];
            }
            _index = rank();
    
            return *this;
        }
        SymmetricIndex& random()
        {
            return random(Nmin);
        }
    
    private:
        IntegerType W(int m, int n) const
        {
            if (m < 0 || n < 0) return 0;
            else if (m == 0 && n == 0) return 1;
            else if (m == 0 && n > 0) return 0;
            //else if (m > 0 && n < Nmin[m-1]) return 0;
            else
            {
                //static std::map<std::tuple<int, int>, IntegerType> memo;
    
                //auto it = memo.find(std::make_tuple(k, m));
                //if (it != std::end(memo))
                //{
                //  return it->second;
                //}
    
                IntegerType ret = 0;
                for (int i = Nmin[m-1]; i <= std::min(Nmax[m-1], n); ++i)
                    ret += W(m - 1, n - i);
    
                //memo[std::make_tuple(k, m)] = ret;
                return ret;
            }
        }
    
        IntegerType binomial(int m, int n) const
        {
            static std::vector<int> store;
            if (store.empty())
            {
                std::function<IntegerType(int, int)> bin = [](int n, int k)
                {
                    int res = 1;
    
                    if (k > n - k)
                        k = n - k;
    
                    for (int i = 0; i < k; ++i)
                    {
                        res *= (n - i);
                        res /= (i + 1);
                    }
    
                    return res;
                };
    
                store.resize(M*M);
                for (int i = 0; i < M; ++i)
                {
                    for (int j = 0; j < M; ++j)
                    {
                        store[i*M + j] = bin(i, j);
                    }
                }
            }
            return store[m*M + n];
        }
    
        auto addressArray() const -> std::vector<int>
        {
            std::vector<int> ret((N + 1) * M);
    
            for (int n = 0; n <= N; ++n)
            {
                for (int m = 0; m < M; ++m)
                {
                    ret[n*M + m] = W(m, n);
                }
            }
            return ret;
        }
    };
    
    std::ostream& operator<<(std::ostream& os, SymmetricIndex const& sym)
    {
        for (auto const& i : sym.state)
        {
            os << i << " ";
        }
        return os;
    }
    

    像这样使用它

    int main()
    {
        int M=4;
        int N=3;
        std::vector<int> Nmax(M, N);
        std::vector<int> Nmin(M, 0);
        Nmax[0]=3;
        Nmax[1]=2;
        Nmax[2]=1;
        Nmax[3]=1;
    
        SymmetricIndex sym(M, N, Nmax, Nmin);
    
        while(!sym.isEnd())
        {
            std::cout<<sym<<"   "<<sym.rank()<<std::endl;
            ++sym;
        }
        std::cout<<sym<<"   "<<sym.rank()<<std::endl;
    }
    

    这将输出

    3 0 0 0    0    (corresponds to {40,40,40})
    2 1 0 0    1    (-> {40,40,50})
    1 2 0 0    2    (-> {40,50,50})
    2 0 1 0    3    ...
    1 1 1 0    4
    0 2 1 0    5
    2 0 0 1    6
    1 1 0 1    7
    0 2 0 1    8
    1 0 1 1    9
    0 1 1 1    10   (-> {50,60,100})
    

    DEMO

    请注意,我在这里假设您的集合元素的升序映射(即数字 40 由索引 0 给出,50 的数目由索引 1 给出,依此类推)。



    更准确地说:将您的列表变成map&lt;std::vector&lt;int&gt;, int&gt;

    std::vector<int> v{40,40,40,50,50,60,100};
    
    std::map<int, int> m;
    
    for(auto i : v)
    {
        ++m[i];
    }
    

    然后使用

    int N = 3;
    int M = m.size();
    std::vector<int> Nmin(M,0);
    std::vector<int> Nmax;
    std::vector<int> val;
    
    for(auto i : m)
    {
        Nmax.push_back(m.second);
        val.push_back(m.first);
    }
    
    SymmetricIndex sym(M, N, Nmax, Nmin);
    

    作为SymmetricIndex 类的输入。

    要打印输出,请使用

        while(!sym.isEnd())
        {
             for(int i=0; i<M; ++i)
             {
                  for(int j = 0; j<sym[i]; ++j)
                  {
                      std::cout<<val[i]<<" ";
                  }
             }
             std::cout<<std::endl;
        }
    
        for(int i=0; i<M; ++i)
        {
            for(int j = 0; j<sym[i]; ++j)
            {
                std::cout<<val[i]<<" ";
            }
        }
        std::cout<<std::endl;
    

    所有未经测试,但它应该给出的想法。

    【讨论】:

    • 这不仅是 tldr,而且在输出中的第 2 行和第 3 行都是重复的。
    • @JonathanMee:读不读,但没有重复。 2 1 0 0 对应于{40,40,50},而1 2 0 0 对应于{40,50,50},依此类推。此外,请注意它很长,因为它可以做的不仅仅是列出组合。它可以对它们进行排名和取消排名——例如,还可以生成一个随机元素。这些是人们经常需要的功能......此外,它只需要与组合数量成线性关系的时间。在我看来,这实际上就是你想要的......
    • 啊,谢谢。我误解了我在看什么。也许如果这被削减它将是可行的。如果我的工作失败了,我会回来看看。
    • 你的解决方案是一个可行的解决方案,并且可以放在后袋中,但它也过于复杂了应该是一个简单的解决方案:stackoverflow.com/a/35215540/2642059
    • @Jonathan Mee:我同意,您的解决方案更加简洁,当然应该优先用于您要求的任务。我的代码——或者更好的是它的改进版本——应该只用于执行更一般的任务,如排名、取消排名、构建随机组合,或回答具有更多约束的问题——例如,只给出那些具有至少一个 40 和一个 50。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-29
    • 2022-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多