【问题标题】:Possible Combination of Parentheses in a Matrix Chain Application矩阵链应用程序中括号的可能组合
【发布时间】:2019-03-02 22:06:34
【问题描述】:

我研究过矩阵链乘法,其中给定一个矩阵序列,目标是找到最有效的矩阵乘法方法。问题实际上并不在于执行乘法,而只是决定所涉及的矩阵乘法的顺序。这就是为什么我的任务是编写一个程序,该程序在矩阵乘法中输出所有可能的矩阵组合,给定 n 作为输入矩阵的数量。例如

 n == 1     (A)

 n == 2     (AB)

 n == 3     (AB)C ,  A(BC)

 n== 4      ((AB)C)D,   (A(BC))D, A((BC)D), A(B(CD)), (AB)(CD)

我的初始代码如下,由

调用
 possible_groupings(4) #4 matrices

def possible_groupings(n):
    print("Possible Groupings : ")
    total  = 0
    if(n==1):
        print('A')
        total = total + 1
    elif(n==2):
       print('(AB)')
       total = total + 1
    else:
       a = 2
       while(a <= n-1):
           b = 0
           while((b+a) <= (n )):
               c = b

               d = 0
               substr = ''
               while (d < c):                    
                   substr = substr + chr(65 + d)                    
                   d = d + 1

               if substr != '':
                   if len(substr) == 1:
                      print( substr, end = '')
                   else:
                      print('(' + substr + ')', end = '')

            print('(', end = '')
            while (c < (b +a)):                    
                print(chr(65 + c), end = '');
                c = c + 1
            print(')', end = '')

            e = b+a

            substr = ''
            while (e < n):
                substr = substr + chr(65 + e) 
                e = e + 1
            if substr != '':
                if len(substr) == 1:
                    print( substr, end = '')
                else:
                    print('(' + substr + ')', end = '')
            print('')

            total = total + 1

            b = b + 1
        a = a + 1
print('Total : ' + str(total))

当我的inout是4个矩阵时,上面代码的输出是:

(AB)(CD)
A(BC)D
(AB)(CD)
(ABC)D
A(BCD)

如何修改我的代码。矩阵的数量必须在 1-26 范围内。我现在头很痛。请帮忙。

【问题讨论】:

  • 你能更详细地解释一下你想要什么样的输出。说n==3,它说你希望你的输出是(AB)C , A(BC)。这到底是什么意思? AB和BC?那么ABC、A、B和C呢?我想我不明白你的符号。
  • @Joe --> 我在做矩阵链乘法,我必须尝试所有可能的矩阵组合。这就是为什么我们需要输出矩阵的所有可能组合或优先乘法。
  • 哦,我现在明白了。让我考虑一下。
  • 你知道矩阵乘法顺序问题的动态规划方法吗? (是的,生成可能的括号组合也是一项非常有趣的任务)
  • 所以我想您知道不需要生成所有变体,但希望将它们作为参考或很好的练习。

标签: python python-3.x algorithm math matrix


【解决方案1】:

这是一个从后到前工作的递归方案。

它被实现为一个生成器,part,它以 last 乘法开始。最后一个乘法必须在两个因子之间,左边是第一个 j(下面代码中的变量 cut)矩阵(“左块”)的乘积,右边是剩余矩阵的乘积(“右块”)。 j 可以是 1 到 N-1 之间的任何值,其中 N 是链中矩阵的数量。

因此,要枚举所有分组,我们必须遍历 j。对于每个 j,我们必须将左块的每个分组与右块的每个分组结合起来。为了枚举我们使用 part 本身的块的分组,即递归。

def part(names, top=True):
    lr = ('', '') if top else '()'
    if len(names) <= 1:
        yield names
    elif len(names)==2:
        yield names.join(lr)
    else:
        for cut in range(1, len(names)):
            for left in part(names[:cut], False):
                for right in part(names[cut:], False):
                    yield (left+right).join(lr)

同样的逻辑也可以用于最小化器。这可以利用functools.lru_cache提供的记忆:

from functools import lru_cache
from string import ascii_uppercase

@lru_cache(None)
def _min_no_mult(dims):
    if len(dims) == 2:
        return 0, 'x'
    elif len(dims)==3:
        return dims[0]*dims[1]*dims[2], 'xx'.join('()')
    cuts = ((cut, *_min_no_mult(dims[:cut+1]), *_min_no_mult(dims[cut:]))
            for cut in range(1, len(dims)-1))
    return min((mnl + mnr + dims[0]*dims[-1]*dims[cut], (nml+nmr).join('()'))
                for cut, mnl, nml, mnr, nmr in cuts)

def min_no_mult(dims, names=None):
    mn, argmn = _min_no_mult(tuple(dims))
    names = iter(ascii_uppercase if names is None else names)
    argmn = argmn[1:-1] if len(dims) > 2 else argmn
    argmn = ''.join(next(names) if a=='x' else a for a in argmn)
    return mn, argmn

演示:

>>> for i, j in enumerate(part(ascii_uppercase[:6])):
...     print(i, j)
... 
0 A(B(C(D(EF))))
1 A(B(C((DE)F)))
2 A(B((CD)(EF)))
3 A(B((C(DE))F))
4 A(B(((CD)E)F))

...

38 ((A((BC)D))E)F
39 (((AB)(CD))E)F
40 (((A(BC))D)E)F
41 ((((AB)C)D)E)F

由于 memoization,最小化器可以轻松处理大量维度:

>>> import numpy as np
>>> dims = np.clip(np.arange(-1, 26), 1, None)
>>> np.random.shuffle(dims)
>>> dims
array([ 5, 25,  1,  4, 14, 24,  7, 15,  2, 12, 11,  9, 18,  8, 19, 13, 23,
       17,  1, 22, 21,  1, 16,  6,  3, 20, 10])

>>> min_no_mult(dims)
(3383, '(AB)((((((((((CD)E)F)G)H)(I(J(K(L(M(N(O(P(QR))))))))))((ST)U))((VW)X))Y)Z)')

我们可以查询一些基本的缓存统计数据:

>>> _min_no_mult.cache_info()
CacheInfo(hits=5450, misses=351, maxsize=None, currsize=351)

这可能看起来不起眼,但请记住,每次点击都会切割整个子树。

确实,我们可以再次循环使用递归方案,计算括号的数量:

@lru_cache(None)
def count(n):
    if n <= 2:
        return 1
    else:
        return sum(count(cut) * count(n-cut) for cut in range(1, n))

对于 26 个矩阵,有很多方法可以给它们加上括号:

>>> print(f"{count(26):,d}")
4,861,946,401,452

【讨论】:

    【解决方案2】:

    Catalan(nmatrices-1)组合,我们可以使用简单的平衡括号算法来生成预组合。这是获取括号(用于比较)和矩阵预组合的代码。

    但我还没有找到设置右括号的简洁方法(cx 参数是我尝试计算乘法并推断给定点的右括号数)。

    也许有人可能会看到简单的公式/法则来得到最终结果。

    def genparens(s, maxlen, l, r):
        if l + r == maxlen * 2:
            print(s)
            return
        if l < maxlen:
            genparens(s + '(', maxlen, l + 1, r)
        if r < l:
            genparens(s + ')', maxlen, l, r + 1)
    
    alpha = "ABCDEFGHIJK"
    
    def genmatparens(s, n, l, r, ci, cx):
        if l + r == n * 2:
            s = s + alpha[ci] # + ")" * cx
            print(s)
            return
        if l < n:
            genmatparens(s + '(', n, l + 1, r, ci, cx + 1)
        if r < l:
            s += alpha[ci]
            #s += ")" * cx
            s += "x"
            genmatparens(s, n, l, r + 1, ci + 1, 1)
    
    
    genparens("", 3, 0, 0)
    print()
    genmatparens("", 3, 0, 0, 0, 0)
    
    ((()))
    (()())
    (())()
    ()(())
    ()()()
    current           should be
    (((AxBxCxD        (((AxB)xC)xD)
    ((Ax(BxCxD        ((Ax(BxC))xD)
    ((AxBx(CxD        ((AxB)x(CxD))
    (Ax((BxCxD        (Ax((BxC)xD))
    (Ax(Bx(CxD        (Ax(Bx(CxD)))
    

    【讨论】:

      【解决方案3】:

      看起来您希望将字符集划分为所有可能的子集,尽管您似乎没有考虑到非连续分组(例如 (AC)(DB))。如果是这样,这是一个众所周知的问题,存在众所周知的解决方案。参见例如How to find all partitions of a set

      【讨论】:

      • 是的,只有传染性元素。
      • 您可以创建一个函数来查找所有分区,但拒绝子集中包含三个以上元素或没有顺序的分区ABCDE...我看到这种方法的唯一问题是{A} {B,C} {D} 可能有两个不同的链乘法:(A(BC))DA((BC)D),所以你的函数也必须考虑到这一点......
      • @JoePatten --> 我迷路了。我该怎么做。我不知道。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多