计算算法的复杂性以打印n对括号的所有有效(即,正确打开和关闭)组合

时间:2015-07-17 15:43:03

标签: python algorithm catalan

我希望您对我在Python中实现的算法的时间和空间复杂性有所了解,以计算算法的复杂性,以打印n对括号的所有有效(即正确打开和关闭)组合(请参阅all valid combinations of n-pair of parenthesis

def find_par_n(n):
    s = set(["()"])
    for i in range(2, n + 1):
        set_to_add = set()
        for str_ in s:
            set_temp = set()
            ana = set()
            for j in range(len(str_) + 1):
                str_c = str_[0:j] + '(' + str_[j:]
                if str_c in ana:
                    continue
                ana.add(str_c)
                for k in range(j + 1, len(str_) + 1):
                    str_a = str_c
                    str_a = str_a[0:k] + ')' + str_a[k:]
                    set_temp.add(str_a)
            set_to_add.update(set_temp)
        s = set_to_add
    return s

最有可能的是,算法可以在时间和空间方面得到改善。请分享您的想法。

5 个答案:

答案 0 :(得分:1)

为获得最佳效果,请避免使用集合。如果您只生成一次可能性,则可以将它们放入向量中。

这是一个非常简单的递归,比@ bcdan的答案稍慢:

def rget(n):
  if n == 0:
    return ['']
  else:
    return [fst + '(' + snd + ')' for i in range(n)
                                  for fst in rget(i)
                                  for snd in rget(n-i-1)]

如果你想要一个生成器而不是完整的结果列表,上面的变体可能是理想的,但对于完整列表的情况,计算从0到n的每个j的结果要快得多,使用先前计算的列表而不是递归调用:

def iget(n):
  res = [['']]
  for j in range(1, n+1):
    res.append([fst + '(' + snd + ')' for i in range(j)
                                      for fst in rget(i)
                                      for snd in rget(j-i-1)])
  return res[n]

结果显示速度提高了一个数量级(使用Python 3.3.2)。

为什么会起作用

您可以将任何平衡字符串分解为两个平衡子字符串。这不是一种独特的分解,但可以通过选择尽可能短的非空均衡后缀使其独特。最短的非空均衡后缀具有起始(与结束)匹配的属性;如果不是这种情况,它可以分解为两个较短的非空平衡序列。所以递归包括找到Fst(Snd)形式的所有序列,其中 Fst Snd 的大小总和为 n - 1。

时间复杂度:

n 对括号的平衡序列数是 n th Catalan number C n ,即O(4 n n 2/3 )。上面的代码在O( n )中生成每个序列(因为字符串连接占用O( n )时间),总复杂度为O(4 名词 名词 5/3

答案 1 :(得分:0)

让我们尝试使用更简单的版本。

一些定理:

  1. 正确配对的字符串长度均匀。请不要让我证明这个。
  2. 给定一个长度为2k的正确配对字符串,可以通过在字符串中的每个可能位置插入一对括号2(k + 1)来构造长度为'()'的所有字符串。有2k + 1个职位。
  3. 要生成所有n对,我们需要递归插入步骤n次(并获取长度为2n的字符串。
  4. 请注意,这还不足以生成所有唯一正确配对的字符串,例如,将()插入()会产生两次相同的字符串(()())。但是作为一个上限就足够了。

    def add_parens(s, nparens):    
        if not s:    
           add_parens('()', nparens)    
        max_length = 2 * nparens    
           if len(s) == max_length:    
           print(s)    
        else:    
            for i in range(0, len(s)):    
                add_parens(s[0:i] + '()' + s[i:], nparens)    
    
    n = 5      
    add_parens('', n)  
    

    时间复杂度:

    1. 空字符串有1个插入点。
    2. 3()个插入点。 ...
    3. 总而言之:

      T(n) = 1 * 3 * ... * 2n + 1 ~ O(n!)
      

      递归版本的空间复杂度为O(n(2n + 1)),但我非常确定可以将其降低到线性。

答案 2 :(得分:0)

我好奇并写了我自己的版本。以下是一些规范(这些都在 Python 2.7 中):

n=8

我的:21毫秒

你的:89毫秒

n=10

我的:294毫秒

你的:1564毫秒

def get(n):

    def find(current):
        out = []
        last_index = 0
        for j in range(current.count('(')):
            last_index = current.index('(',last_index) + 1
            out.append(current[:last_index] + '()' + current[last_index:])
        return out

    seed = '()'
    current = '()'
    temp = set(['()'])
    for i in range(n):
        new = set()
        for thing in temp:
            new.update(find(thing))
        temp = new

    return [a[1:-1] for a in temp]

我认为最大的区别是我的只有3个for循环,你的有4个。这发生在str.count函数。使用这种内置功能可能会大大提高速度。此外,在每个阶段之后,我确定了重复项,并且这会按时按指数减少。

实际上,我看到的最大区别是我只在括号后插入,然后在完成时剥离外部的两个。它创建了更少的重复项。因此,((())())已创建,并在删除封闭的(())()后缩减为正确的()形式。

答案 3 :(得分:0)

递归版似乎非常简单,只花时间在有效的分支上:

def gen(sofar, l, r, n):
    """
    sofar: string generated so far
    l: used left parentheses
    r: used right parentheses
    n: total required pairs
    """
    result = []
    if l == n and r == n:
        # solution found
        result.append(sofar)
    else:
        if l > r:
            # can close
            result.extend(gen(sofar + ")", l, r + 1, n))
        if l < n:
            # can open
            result.extend(gen(sofar + "(", l + 1, r, n))
    return result

def generate(n):
    return gen("", 0, 0, n)

print generate(4)

答案 4 :(得分:0)

可能的组合数量是加泰罗尼亚数字。 因此复杂性至少是该数字指定的复杂性。 https://en.wikipedia.org/wiki/Catalan_number