N选择K,K-N,K-2N等递归内的递归

时间:2014-01-14 02:41:16

标签: c++ c algorithm recursion

我知道如何使用递归来生成所有可能的组合,即N选择K.但是如何创建K的所有可能的N / K组? N当然总是可以被K整除。为了澄清:例如,如果N是20而K是4,那么我想生成所有可能的五组四。如果说,N包含1,2,3 ... 20且K为4,那么一个这样的分组是{1,2,3,4},{5,6,7,8},{9,10 ,11,12},{13,14,15,16},{17,18,19,20}。我们假设N相对较小,因此递归是可行的

我觉得这是一个递归内递归问题,因为生成所有可能的单组四(又名N选K)需要递归,然后生成下一组四个变为N - 4选择K ,下一个N - 8选择K等。但是我在实施这个问题时遇到了麻烦...任何帮助?

3 个答案:

答案 0 :(得分:1)

术语:你想要的是一组N个元素中的partitions个,每个元素大小为K.有n!/((k!)^(n / k)(n / k)!)这样的分区(见answer on math.SE)。

为了避免过度生成,让我们决定每个的“规范形式”   分区:假设它是按递增顺序写入每个部分,然后   按照最小元素的递增顺序自己编写这些部分。

现在,您可以将分区的生成可视化为丢球的过程   编号为1到N,一个接一个地进入N / K箱。每个bin的约束   (部分)必须按递增顺序然后自动满足(当然   你应该按插入元素的顺序“读取”每个bin,和   如果我们确保不跳过空箱,则满足对零件的约束   在填充下一个之前。

现在将其转换为代码。让我们将我们的分区(或一堆箱子)建模为矢量矢量。

#include <iostream>
#include <vector>
#include <cassert>

std::vector< std::vector<int> > partition;
int N, K;
int num_partitions_found = 0;

void OutputPartition();

通常,在递归生成结构时(或编码任何递归   算法),你的递归函数被赋予一个特定的“状态”,并尝试   扩展该状态的每种增量方式。在不同的选择之间,只是   确保将州恢复到你所得到的状态。

所以让我们把我们的递归函数写成一个状态,其中一个直到n-1的球被放置在箱中,并通过将球n放在所有可能的位置来扩展状态。 / p>

void GeneratePartitions(int n) {
  // When we've placed all N elements, output and stop.
  if (n == N + 1) {
    OutputPartition();
    return;
  }
  // Place ball n into each allowed bin.
  for (int i = 0; i < N / K; ++i) {
    // Cannot place into a full bin
    if (partition[i].size() == K) continue;
    // Cannot skip an empty bin
    if (i > 0 && partition[i-1].empty()) break;
    // Place the ball here: extending the state
    partition[i].push_back(n);
    // How to extend the new state is left to the recursive call
    GeneratePartitions(n + 1);
    // Make sure you restore state after each recursive call!
    partition[i].pop_back();
  }
}

这是递归的核心。该计划的其余部分是脚手架。

void OutputPartition() {
  assert(partition.size() == N/K);
  ++num_partitions_found;
  for (int i = 0; i < N/K; ++i) {
    assert(partition[i].size() == K);
    std::cout << "{";
    for (int j = 0; j < K; ++j) {
      std::cout << partition[i][j] << (j == K - 1 ? "}" : ", ");
    }
    if (i < N/K - 1) std::cout << ", ";
  }
  std::cout << std::endl;
}

int main() {
  std::cin >> N >> K;
  assert(N % K == 0);
  partition.resize(N / K);
  GeneratePartitions(1);
  std::cout << num_partitions_found << " found" << std::endl;
}

注意:这篇文章是literate program:如果你把代码片段放在一起,你就有了一个有效的工作程序。

或者,如果你想尝试该程序(看它运行的速度等),不同的版本是here on github:它不使用全局变量,使用普通数组(更快) )对于分区,并仅为小N打印所有找到的分区(编辑代码以更改它)。

回到你的问题,我们看到通过仔细考虑问题一段时间,你可以避免有不同类型的递归。即使你这样做,编写递归程序的一般方法仍然是:编写一个递归函数,它接受一个状态,以所有可能的方式将它扩展一步(将这些状态的进一步扩展转移到递归调用),然后清除达到其原始状态。 (有时,当状态很小时,你不需要进行任何清理 - 你可以通过函数调用传递状态 - 但有时候,递归调用可以使状态更加清晰。)

答案 1 :(得分:0)

该解决方案使用两个级别的递归:

  • 找到组合C(n,k):k步
  • 找到n / k C(n,k)的序列:n / k步骤

为避免以不同的顺序重复组:每组中的第一个元素必须小于或等于位置。 结果是第一组总是从1开始,例如。

一些结果:

  • 4-2的组合
  • 6-3的10种组合
  • 15种组合,6-2

我想知道进一步验证结果的计算公式。

#include <vector>
#include <set>
#include <iostream>
#include <cassert>
#include <fstream>

namespace lan
{

struct Solution
{
    virtual bool Next(const std::vector<unsigned>&) = 0;
};
void SetCombination(const size_t, const size_t, Solution*);

struct ToPrint: public Solution
{
    ToPrint(): m_cnt(0) {};

    void Print(const std::vector<unsigned>& solution, std::ostream& out, size_t grouped = 0)
    {
        assert(grouped <= solution.size());
        if (grouped)
            out<<"{";
        for (size_t i = 0; i < solution.size(); ++i)
        {
            if (i)
            {
                if (grouped && !(i%grouped))
                    out<<"} {";
                else
                    out<<" ";
            }
            out<<solution[i];
        }
        if (grouped)
            out<<"}";
        out<<std::endl;
    }

    bool Next(const std::vector<unsigned>& solution)
    {
        Print(solution, std::cout);
        ++m_cnt;
        return true;
    }

    size_t Count() const {return m_cnt;};

protected:
    size_t m_cnt;
};

struct GroupOf: public ToPrint
{
    typedef ToPrint Super;

    GroupOf(int length, int group, bool print): m_length(length), m_group(group), m_print(print)
    {
        assert(!(length%group));
        //m_out.open("out.txt");
        //assert(m_out);
    }

    bool Next(const std::vector<unsigned>& partial)
    {
        assert(m_group == partial.size());
        size_t i;
        for (i = 0; i < m_group-1; ++i)
            assert(partial[i] < partial[i+1]);

        //  first element in each partial solution must be <= position (to avoid groups recombination)

        if (partial[0] > 0)
            return false;

        //  add the next group to the solution

        assert(m_sum <= m_length-m_group);
        size_t j, k; 
        for (i = j = k = 0; (i < m_length) && (j < m_group); ++i)
        {
            if (!m_solution[i] && (k++ == partial[j]))
            {
                j++;
                m_solution[i] = j + m_sum;
            }
        }
        assert(j == m_group);

        //  process a solution or go after the next group, recursively

        m_sum += m_group;
        if (m_sum == m_length)
        {
            std::vector<unsigned> print;
            Translate(m_solution, print); 
            if (m_print)
                //Print(print, m_out, m_group);
                Print(print, std::cout, m_group);
            assert(Validate(print));
            ++m_cnt;
        }
        else 
            SetCombination(m_length-m_sum, m_group, this);

        //  restore "stack" of recursive calls

        m_sum -= m_group;
        for (i = 0; i < m_length; ++i)
            if (m_solution[i] > m_sum)
                m_solution[i] = 0;

        return true;
    };

    void Start()
    {   
        m_sum = 0;
        m_solution.assign(m_length, 0);

        SetCombination(m_length, m_group, this);
    };

protected:
    //  transform positions to "real" solution
    static 
    void Translate(const std::vector<unsigned>& a, std::vector<unsigned>& b)
    {
        b.resize(a.size());
        for (size_t i = 0; i < a.size(); ++i)
        {
            assert(a[i]);
            b[a[i]-1] = 1+i;
        }
    }

#ifdef _DEBUG
    static 
    bool Validate(const std::vector<unsigned>& a)
    {
        std::set<unsigned> elements; 
        for (size_t i = 0; i < a.size(); ++i)
            elements.insert(a[i]);
        if (elements.size() != a.size())
            return false;

        return true;
    }
#else
    static 
    bool Validate(const std::vector<unsigned>&)
    {
        return true;
    }
#endif 

private:
    GroupOf& operator= (const GroupOf&);

protected:
    const size_t m_length;
    const size_t m_group;
    const bool m_print;

    size_t m_sum;
    std::vector<unsigned> m_solution;
    //std::ofstream m_out;
};

//  get a 'length' by 'group' combination; since it's strictly ordered ( = ... the order doesn't matter), the first index is "next"
bool SetCombination(const size_t length, const size_t group, size_t next, std::vector<unsigned>& solution, Solution* cbk)
{
    assert(length);
    assert(cbk);
    assert(group <= length);
    assert(next < length);

    for (size_t i = next; i < length; ++i)
    {
        solution.push_back(i);
        next = i+1;

        if (solution.size() == group)
        {
            if (!cbk->Next(solution))
                return false;
        }
        else if (next != length)
        {
            if (!SetCombination(length, group, next, solution, cbk))
                return false; //break;
        }

        solution.pop_back();
    }

    return true;
}

void SetCombination(const size_t length, const size_t group, Solution* cbk)
{
    std::vector<unsigned> solution;
    (void)SetCombination(length, group, 0, solution, cbk);
}

size_t Test(size_t total, size_t grouped, bool print = false)
{
    GroupOf test(total, grouped, print);
    test.Start();

    return test.Count();
}

}

到目前为止的所有结果:

assert(3 == lan::Test(4, 2));
assert(10 == lan::Test(6, 3));
assert(15 == lan::Test(6, 2));
assert(126 == lan::Test(10, 5));
assert(280 == lan::Test(9, 3));
assert(945 == lan::Test(10, 2));
if (0)
{
    assert(1716 == lan::Test(14, 7));
    assert(5775 == lan::Test(12, 4));
    assert(126126 == lan::Test(15, 5)); //  1s, Release
}

答案 2 :(得分:-1)

std::next_permutation可能会有所帮助:

#include <algorithm>
#include <iostream>

int main()
{
    int v[] = {1, 2, 3, 4, 5, 6};

    do
    {
        std::cout << "{" << v[0] << "," << v[1] << "," << v[2] << "}, "
                  << "{" << v[3] << "," << v[4] << "," << v[5] << "}" << std::endl;

    } while (std::next_permutation(std::begin(v), std::end(v)));
    return 0;
}

哪个输出(6个!解决方案)

{1,2,3}, {4,5,6}
{1,2,3}, {4,6,5}
...    
{6,5,4}, {3,1,2}
{6,5,4}, {3,2,1}

或者如果组内的订单无关紧要,您可以尝试以下方法:

#include <algorithm>
#include <iostream>


int getNextIndex(const int (&v)[6], int start, int value)
{
    return std::find(std::begin(v) + start, std::end(v), value) - std::begin(v);
}

void print(const int (&v)[6])
{
    // Filter if you want that
    // `{1, 2, 3}, {4, 5, 6}` is equivalent to `{4, 5, 6}, {1, 2, 3}`
    if (getNextIndex(v, 0, 1) > getNextIndex(v, 0, 2)) return;
    //if (getNextIndex(v, 0, 2) > getNextIndex(v, 0, 3)) return; // And so on if you have more groups
    const char* sep[] = {", ", ", ", "}"};

    for (int i = 1; i != 3; ++i) {
        int index = -1;
        std::cout << "{";
        for (int j = 0; j != 3; ++j) {
            index = getNextIndex(v, index + 1, i);
            std::cout << 1 + index << sep[j];
        }
        std::cout << ", ";
    }
    std::cout << std::endl;
}

int main()
{
    int v[] = {1, 1, 1, 2, 2, 2};

    do
    {
        print(v);
    } while (std::next_permutation(std::begin(v), std::end(v)));
    return 0;
}

输出(10个解决方案):

{1, 2, 3}, {4, 5, 6},
{1, 2, 4}, {3, 5, 6},
{1, 2, 5}, {3, 4, 6},
{1, 2, 6}, {3, 4, 5},
{1, 3, 4}, {2, 5, 6},
{1, 3, 5}, {2, 4, 6},
{1, 3, 6}, {2, 4, 5},
{1, 4, 5}, {2, 3, 6},
{1, 4, 6}, {2, 3, 5},
{1, 5, 6}, {2, 3, 4},