【问题标题】:Algorithm for ordered set of partitions有序分区集算法
【发布时间】:2016-06-02 18:11:09
【问题描述】:

给定一组输入标记(例如 {a,b,c}),我正在寻找一种算法,该算法可以为我提供一组尊重输入元素顺序的分区。换句话说,我正在寻找在不改变其顺序的情况下将括号括在令牌周围的所有可能性。

对于{a,b,c},这将是(ab)ca(bc)

对于{a,b,c,d},它将是(ab)cda(bc)dab(cd)(abc)da(bcd)((ab)c)d(a(bc))da((bc)d)a(b(cd))(我希望我得到了全部)。

我认为这与Bell Number 有某种关系,尽管它会太大,因为它也在考虑像 (ac)b 这样的分区。

我听说这个问题可以通过派生CYK-Algorithm 来解决,尽管我不明白这应该如何工作,因为 CYK 旨在解析 CNF 语法。

【问题讨论】:

  • abc{a,b,c} 的有效分区吗? abcd{a,b,c,d} 的有效分区吗?换句话说,输出没有括号的标记集
  • (ab)(cd) 呢?

标签: algorithm math


【解决方案1】:

假设您只在顶层进行分区,这意味着对于集合{a,b,c,d},您有以下分区:

(ab)cd
a(bc)d
ab(cd)
(abc)d
a(bcd)

您可以使用两个 for 循环生成这些,外部循环用于括号内的项目数(从 2 到集合中的项目数减 1),内部循环用于之前的项目数左括号(从 0 到集合中的项目数减去括号内的数字)。因此,在上面的示例中,外循环从 2 迭代到 3(含),内循环第一次从 0 迭代到 2(含),第二次从 0 迭代到 1。

完成此操作后,只需递归地对括号内的项目进行分区以获取完整的分区集就很容易了。一个棘手的部分是,在顶层,您(显然)不希望将所有不带括号的项目作为有效分区输出,但是当您递归时,您会这样做。

下面是一些用 Java 编写的简单代码来处理字符串:

public static void outputPartitions(String head, String partition, String tail, boolean top)
{
    int len = partition.length();
    if(!top) // only output the items without brackets when not at the top level
        System.out.println(head + partition + tail);

    for(int i = 2; i <= len-1; i++)
    {
        for(int j = 0; j <= len-i; j++)
        {
            outputPartitions(head + partition.substring(0, j)+"(",
               partition.substring(j, j+i),
               ")"+partition.substring(j+i)+tail, false);
        }
    }
}

public static void main (String[] args) throws java.lang.Exception
{
    outputPartitions("", "abcd", "", true);
}

输出:

(ab)cd
a(bc)d
ab(cd)
(abc)d
((ab)c)d
(a(bc))d
a(bcd)
a((bc)d)
a(b(cd))

【讨论】:

    【解决方案2】:

    每个完整的括号(其中每个括号对只包含两个元素)都可以被认为是一个树结构,其中每个括号对是一个节点,它包含的两个元素是它的子节点。比如(a((bc)d)),就是:

      ()
     /  \
    a   ()
       /  \
      ()   d
     /  \
    b    c
    

    所以问题是:给定输入序列,可以构造多少棵这样的树?这可以通过尝试根节点的所有可能的“分裂点”(在abcd 之间,在abcd 之间,以及在abcd 之间)来回答,并递归地尝试出子序列的分割点。

    请注意,这与matrix-chain multiplication 密切相关。

    【讨论】:

      【解决方案3】:

      这个问题很有趣,我期待其他人的解决方案,用一些已知的有前途的算法来解决这个问题......同时我只是给出我的想法。

      我不知道您的有序集是什么数据结构,但是 如果我没有错误地理解你的问题,我有一个简单的想法:

      也许我们可以使用如下递归公式的简单递归

      Split(ordered set A){
          if(len(A) == 1) print(A[0])
          string group = "";
          for(int i in [1, len(A)]){
              group = group+" "+A[i];
              A remove A[i];
              print(group + "," + Split(A));    
          }
      }
      

      基本上它是循环集合,从第一个元素到最后一个元素,在每次迭代中,将第一个 i 元素作为第一个分区,然后删除这些 i 元素,再次调用相同的函数(递归)(取决于您的数据结构,您可能不需要物理删除,而只需传递集合A 的一部分,反正这是概念)

      这种方式性能很慢,但考虑到您必须获得所有组合,我认为它可能是可以接受的。

      您可以通过一些扭曲来加快速度:事实上,我们只需要知道集合的大小即可获得分区,对于前 4 个元素,或中间 4 个连续元素,或 4 个连续元素在集合中的任何位置,它们都可以用相同的方式进行分区。所以我们确实只需要保存用n元素划分集合的所有方法,这可以增强上述对动态规划的递归:

      vector<vector<int>> pos[n]; 
      // pos[i] is my way to store the "configuration" of the partition for i elements
      // for example:  pos[7] may store [[1,3,7], [2,4,7]]
      // Means (1..1),(2..3),(4..7) is one way to partition 7 elements
      // (1..2),(3..4),(5..7) is another way
      // I want to find pos with this dynamic programming method
      
      Split(int size){
          if(pos[size] is not empty){
             return pos[size];
          }
          if(size == 1){
             push [1] into pos;
             return pos;
          }
          vector<vector<int>> ret;
          for(int i in [1..n]){
              vector<vector<int>> subPartitions= Split(n-i);
              for(vector<int> partition in [1..size(subPartitions)]){
                   Add i (offset) to all element in partition
                   vector<int> newPartition = Merge([i], parition);
                   push newPartition to ret;
              }
          }
          return pos[size] = ret;
      }
      

      这是一个高级概念伪代码,取决于数据结构和实现,它可以以可接受的性能解决您的问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-02-18
        • 2018-09-18
        • 1970-01-01
        • 2016-01-08
        • 2015-08-22
        • 1970-01-01
        • 2017-07-24
        相关资源
        最近更新 更多