Algorithm 具有额外限制的置换

Algorithm 具有额外限制的置换,algorithm,math,permutation,combinatorics,Algorithm,Math,Permutation,Combinatorics,我有一组项,例如:{1,1,1,2,2,3,3},和一组限制集,例如{3}、{1,2}、{1,2,3}、{1,2,3}、{1,2,3}、{1,2,3}、{1,2,3}、{2,3}、{2,3}。我在寻找项的排列,但第一个元素必须是3,第二个元素必须是1或2,等等 其中一种适合的排列方式是: {3,1,1,1,2,2,3} 是否有一个算法来计算这个问题的所有排列?这类问题是否有一个名称 为了举例说明,我知道如何解决某些类型的“限制集”的这个问题。 项目集:{1,1,2,2,3},限制{1,2},{1

我有一组项,例如:{1,1,1,2,2,3,3},和一组限制集,例如{3}、{1,2}、{1,2,3}、{1,2,3}、{1,2,3}、{1,2,3}、{1,2,3}、{2,3}、{2,3}。我在寻找项的排列,但第一个元素必须是3,第二个元素必须是1或2,等等

其中一种适合的排列方式是: {3,1,1,1,2,2,3}

是否有一个算法来计算这个问题的所有排列?这类问题是否有一个名称

为了举例说明,我知道如何解决某些类型的“限制集”的这个问题。 项目集:{1,1,2,2,3},限制{1,2},{1,2,3},{1,2,3},{1,2},{1,2}。这等于2!/(2-1)!/1!*4!/2!/2!。首先有效地排列3,因为它是最严格的,然后在有空间的地方排列剩余的项目

还有…多项式时间,可能吗

更新:这将在下面的链接中进一步讨论。上面的问题称为“计数完美匹配”,上面的每个置换限制由插槽矩阵上的{0,1}表示


您可以考虑使用一个数字池的递归解决方案(在您提供的示例中,它将被初始化为{1,1,1,2,3,3,3}),并在给定的索引中决定将哪个数字放置在该索引上(当然,使用您提供的限制)。 如果您愿意,我可以提供伪代码。

您可以构建一棵树。 级别0:创建根节点。 级别1:将第一个“限制集”中的每个项作为根的子项追加。 级别2:将第二个限制集中的每个项附加为每个级别1节点的子项。 级别3:将第三个限制集中的每个项附加为每个级别2节点的子项。

然后,排列计数是最终树的叶节点数

编辑

不清楚“项集”{1,1,1,2,2,3,3,3}是什么意思。如果这是为了限制每个值可以使用的次数(“1”可以使用3次,“2”两次,等等),那么我们还需要一个步骤:

  • 在将节点追加到树之前,请从项集中删除当前路径上使用的值。如果要追加的值仍然可用(例如,您要追加一个“1”,而“1”到目前为止只使用了两次),请将其追加到树中

为了节省空间,您可以构建一个有向图而不是树

  • 创建一个根节点
  • 为中的每个项目创建一个节点 第一组,并从根链接到 新节点
  • 为中的每个项目创建一个节点 第二组,并从每个第一个链接 将项目设置为每秒设置的项目

排列的数量就是从根节点到最终集节点的路径数量。

这里的所有其他解都是指数时间——即使在不需要的情况下也是如此。这个问题表现出类似的子结构,因此应该用动态规划来解决

您要做的是编写一个类来记录子问题的解决方案:

class Counter {
  struct Problem {
     unordered_multiset<int> s;
     vector<unordered_set<int>> v;
  };

  int Count(Problem const& p) {
    if (m.v.size() == 0)
      return 1;
    if (m.find(p) != m.end())
      return m[p];
    // otherwise, attack the problem choosing either choosing an index 'i' (notes below)
    // or a number 'n'.  This code only illustrates choosing an index 'i'.
    Problem smaller_p = p;
    smaller_p.v.erase(v.begin() + i);
    int retval = 0;
    for (auto it = p.s.begin(); it != p.s.end(); ++it) {
      if (smaller_p.s.find(*it) == smaller_p.s.end())
        continue;
      smaller_p.s.erase(*it);
      retval += Count(smaller_p);
      smaller_p.s.insert(*it);      
    }
    m[p] = retval;
    return retval;
  }

  unordered_map<Problem, int> m;
};
类计数器{
结构问题{
无序多集;
向量v;
};
整数计数(问题常数和p){
如果(m.v.尺寸()==0)
返回1;
如果(m.find(p)!=m.end())
返回m[p];
//否则,请解决选择索引“i”(以下注释)的问题
//或数字“n”。此代码仅说明如何选择索引“i”。
小问题p=p;
较小的p.v.erase(v.begin()+i);
int-retval=0;
用于(自动it=p.s.开始();it!=p.s.结束();+it){
如果(较小的搜索(*it)=较小的搜索结束()
继续;
较小的p.s.擦除(*它);
retval+=计数(较小的p);
较小的p.s.插入(*it);
}
m[p]=retval;
返回返回;
}
无序地图m;
};
代码说明了如何选择索引i,它应该选择在有v[i]的地方。size()很小。另一个选项是选择一个数字n,它应该是一个可以放置v的位置很少的数字。我认为这两个决定因素中的最小值应该获胜

此外,您还必须为这个问题定义一个哈希函数——使用boost的哈希函数应该不会太难

这个解决方案可以通过将向量替换为一个集合,并为无序集合定义一个<运算符来改进。这将把更多相同的子问题压缩为一个单独的映射元素,并进一步减少指数爆炸


此解决方案可以通过生成相同的问题实例来进一步改进,除了将数字重新排列为相同的哈希值并进行比较以使其相同。

您是只寻找一个计数,还是对一种可能打印排列的算法感兴趣?在任何情况下,这必须有多高的效率?您的这个问题对我来说很有趣,但我不完全理解。计数就可以了。因为我可以递归地应用计数算法行走或随机访问第n个排列(如果需要)。算法/分析方法必须是多项式时间,而不是显而易见的“遍历所有排列,并删除不符合规则的排列“算法。好问题。对我来说,方程和算法一样好。参考类似的分析方法或学术出版物也会对我有所帮助。你可能会更幸运。我对一些事情感到困惑:集合通常是无序的,每个元素只有一个,但我想你说的是“有序多集合”或者什么?另外,如果有k个限制集,这是否意味着所有置换的长度都必须是k?输出的长度、输入多集的大小和限制集的数量之间有什么关系?这是正确的想法,但你想记住子问题的解,否则解是