Рекуррентный подход: как мы можем генерировать все возможности на фигурных скобках? - PullRequest
2 голосов
/ 30 ноября 2010

Как мы можем сгенерировать все возможности на фигурных скобках?

N значение дало нам, и мы должны сгенерировать все возможности.

Примеры:

1) если N == 1, то только одна возможность ().

2) если N == 2, тоВозможные варианты: (()), () ()

3) если N == 3, то возможны ((())), (()) (), () () (), () (()) ...

Примечание: левая и правая скобки должны совпадать.Я имею в виду) (НЕДОПУСТИМО для N == 1

Можем ли мы решить эту проблему с помощью рекуррентного подхода?

Ответы [ 6 ]

4 голосов
/ 30 ноября 2010

Из википедии -

Слово Dyck - это строка, состоящая из n X и n Y, такая, что ни один начальный сегмент строки не имеет больше Y, чем X (см. Также язык Dyck).Например, ниже приведены слова Дейка длиной 6:

XXXYYY     XYXXYY     XYXYXY     XXYYXY     XXYXYY.

Повторно интерпретируя символ X как открытые скобки, а Y как закрывающие скобки, Cn подсчитывает числовыражения, содержащие n пар скобок, которые правильно сопоставлены:

((()))     ()(())     ()()()     (())()     (()())

См. также http://www.acta.sapientia.ro/acta-info/C1-1/info1-9.pdf

Аннотация.Представлен новый алгоритм генерации всех слов Дейка, который используется для ранжирования и отмены выбора слов Дика.Мы подчеркиваем важность использования слов Дика в кодировании объектов, связанных с каталонскими числами.Как следствие формул, используемых в алгоритме ранжирования, мы можем получить рекурсивную формулу для n-го каталонского числа.

3 голосов
/ 30 ноября 2010

Для данного N мы всегда должны начинаться с открытой фигурной скобки. Теперь рассмотрим, где находится соответствующая закрывающая скобка. Это может быть в середине, как в ()() или в конце, как (()) для N=2.

Теперь рассмотрим N=3:

Может быть в конце: (()()) и ((())).

Или посередине: ()(()) и ()()(), где он находится в положении 2. Тогда он также может быть в положении 4: (())().

Теперь мы можем по существу объединить 2 случая, реализовав случай, когда закрывающая скобка в конце такая же, как и в середине, но со всеми возможностями для N = 0, добавленными в конец.

Теперь, чтобы решить эту проблему, вы можете отработать все возможности для n между начальной и конечной скобками, и аналогично вы можете отработать все возможности для m после конечной скобки. (Примечание m+n+1 = N) Затем вы можете просто объединить все возможные комбинации, добавить их в свой список возможностей и перейти к следующему возможному месту для конечной скобки.

Просто предупреждаем, что с этими типами проблем можно легко ошибиться: найти все возможности для i и N-i и просто объединить их, но для N=3 будет двойной счет ()()() или по крайней мере, напечатайте его дважды.

Вот код Python 2.x, который решает проблему:

memoise = {}
memoise[0] = [""]
memoise[1] = ["()"]

def solve(N, doprint=True):
    if N in memoise:
        return memoise[N]

    memoise[N] = []

    for i in xrange(1,N+1):
        between = solve(i-1, False)
        after   = solve(N-i, False)
        for b in between:
           for a in after:
               memoise[N].append("("+b+")"+a)

    if doprint:
        for res in memoise[N]:
            print res

    return memoise[N]
2 голосов
/ 30 ноября 2010

попробуй гуглить по каталонским номерам

1 голос
/ 24 декабря 2016

Вот некоторый код, который по сути является компактной версией кода JPvdMerwe, за исключением того, что он возвращает список решений, а не печатает их.Этот код работает как на Python 2, так и на Python 3.

from itertools import product

def solve(num, cache={0: ['']}):
    if num not in cache:
        cache[num] = ['(%s)%s' % t for i in range(1, num + 1)
            for t in product(solve(i - 1), solve(num - i))]
    return cache[num]

# Test

for num in range(1, 5):
    print(num)
    for s in solve(num):
        print(s)

output

1
()
2
()()
(())
3
()()()
()(())
(())()
(()())
((()))
4
()()()()
()()(())
()(())()
()(()())
()((()))
(())()()
(())(())
(()())()
((()))()
(()()())
(()(()))
((())())
((()()))
(((())))

Вот еще пара функций, производных от псевдокодав статье, на которую ссылается Эд Гиннес: Создание и ранжирование слов Дейка .В этой статье используется индексация на основе 1, но я преобразовал их для соответствия индексации на основе Python 0.

Эти функции работают медленнее, чем функция solve, описанная выше, но они все еще могут быть полезны.pos_dyck_words имеет то преимущество, что оно чисто итеративное.unrank является итеративным, но вызывает рекурсивную вспомогательную функцию f;OTOH, f использует кэширование, поэтому оно не такое медленное, как могло бы быть, и это только целые числа кэширования, которые используют меньше оперативной памяти, чем строковое кэширование solve.Основное преимущество unrank заключается в том, что он может возвращать отдельное решение из своего индексного номера, вместо того чтобы создавать все решения заданного размера.

Этот код предназначен только для Python 3.Конвертировать его для использования на Python 2 достаточно просто, нужно просто реализовать собственную схему кэширования вместо lru_cache.Вам нужно нужно кэшировать, иначе f невыносимо медленен для всех, кроме самых маленьких длин Дейка.

from itertools import product
from functools import lru_cache

# Generate all Dyck words of length 2*num, recursively
# fastest, but not lexicographically ordered
def solve(num, cache = {0: ['']}):
    if num not in cache:
        cache[num] = ['0%s1%s' % t for i in range(1, num + 1)
            for t in product(solve(i - 1), solve(num - i))]
    return cache[num]

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# A helper function for `unrank`
# f(i, j) gives the number of paths between (0,0) and (i, j) not crossing
# the diagonal x == y of the grid. Paths consist of horizontal and vertical
# segments only, no diagonals are permitted

@lru_cache(None)
def f(i, j):
    if j == 0:
        return 1
    if j == 1:
        return i
    #if i < j:
        #return 0
    if i == j:
        return f(i, i - 1)
    # 1 < j < i <= n
    return f(i - 1, j) + f(i, j - 1)

# Determine the position array of a Dyck word from its rank number, 
# The position array gives the indices of the 1s in the word; 
# the rank number is the word's index in the lexicographic sequence
# of all Dyck words of length 2n 
# Very slow
def unrank(nr, n):
    b = [-1]
    for i in range(n):
        b.append(1 + max(b[-1], 2 * i))
        ni = n - i - 1
        for j in range(n + i - b[-1], 0, -1):
            delta = f(ni, j)
            if nr < delta or b[-1] >= n + i:
                break
            nr -= delta
            b[-1] += 1
    return b[1:]

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Generate all Dyck word position arrays for words of length 2*n, iteratively
def pos_dyck_words(n):
    b = list(range(1, 2 * n, 2))
    while True:
        yield b
        for i in range(n - 2, -1, -1):
            if b[i] < n + i:
                b[i] += 1
                for j in range(i + 1, n - 1):
                    b[j] = 1 + max(b[j - 1], 2 * j)
                break
        else:
            break

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# Convert a position array to a Dyck word
def pos_to_word(b, n, chars='01'):
    c0, c1 = chars
    word = [c0] * (2 * n)
    for i in b:
        word[i] = c1
    return ''.join(word)

# Some tests

num = 4
print('num: {}, Catalan number: {}'.format(num, f(num, num)))

words = list(solve(num))
words.sort(reverse=True)
print(len(words))

for i, u in enumerate(pos_dyck_words(num)):
    v = unrank(i, num)
    w = words[i]
    ok = u == v and pos_to_word(u, num) == w
    print('{:2} {} {} {} {}'.format(i, u, v, w, ok))
print()

num = 10
print('num: {}, Catalan number: {}'.format(num, f(num, num)))

for i, u in enumerate(pos_dyck_words(num)):
    v = unrank(i, num)
    assert u == v, (i, u, v)
print('ok')

output

num: 4, Catalan number: 14
14
 0 [1, 3, 5, 7] [1, 3, 5, 7] 01010101 True
 1 [1, 3, 6, 7] [1, 3, 6, 7] 01010011 True
 2 [1, 4, 5, 7] [1, 4, 5, 7] 01001101 True
 3 [1, 4, 6, 7] [1, 4, 6, 7] 01001011 True
 4 [1, 5, 6, 7] [1, 5, 6, 7] 01000111 True
 5 [2, 3, 5, 7] [2, 3, 5, 7] 00110101 True
 6 [2, 3, 6, 7] [2, 3, 6, 7] 00110011 True
 7 [2, 4, 5, 7] [2, 4, 5, 7] 00101101 True
 8 [2, 4, 6, 7] [2, 4, 6, 7] 00101011 True
 9 [2, 5, 6, 7] [2, 5, 6, 7] 00100111 True
10 [3, 4, 5, 7] [3, 4, 5, 7] 00011101 True
11 [3, 4, 6, 7] [3, 4, 6, 7] 00011011 True
12 [3, 5, 6, 7] [3, 5, 6, 7] 00010111 True
13 [4, 5, 6, 7] [4, 5, 6, 7] 00001111 True

num: 10, Catalan number: 16796
ok
1 голос
/ 06 августа 2013

Рекурсивное решение:

import java.util.Scanner;

public class Parentheses
{

    static void ParCheck(int left,int right,String str)
    {
            if (left == 0 && right == 0)
            {
                    System.out.println(str);
            }

            if (left > 0)
            {
                    ParCheck(left-1, right+1 , str + "(");
            }
            if (right > 0)
            {
                    ParCheck(left, right-1, str + ")");
            }

    }
    public static void main(String[] args)
    {
            Scanner input=new Scanner(System.in);
            System.out.println("Enter the  number");
            int num=input.nextInt();

            String str="";
            ParCheck(num,0,str);
    }
} 
0 голосов
/ 05 мая 2019

Я придумал следующий алгоритм, который не является рекурсивным, как того требует OP, но его стоит упомянуть, учитывая его непобедимую эффективность .

Как сказано в Ed Guiness ' post , строки из N пар правильно подобранных скобок являются представлением слова Дейка. В другом полезном представлении скобки ( и ) заменены на 1 и 0 соответственно. Следовательно, ()()() становится 101010. Последнее также можно рассматривать как двоичное представление (десятичного) числа 42. Таким образом, некоторые целые числа могут представлять строки из правильно подобранных пар скобок. Используя это представление, ниже приведен эффективный алгоритм генерации работ Дика.

Пусть integer будет любым C / C ++ (или, возможно, и членом языков программирования * семейства C * *) целочисленного типа без знака длиной до 64 бит. Если дано слово Dyck, следующий код возвращает следующее слово Dyck того же размера, если оно существует.

integer next_dyck_word(integer w) {
    integer const a = w & -w;
    integer const b = w + a;
    integer c = w ^ b;
    c = (c / a >> 2) + 1;
    c = ((c * c - 1) & 0xaaaaaaaaaaaaaaaa) | b;
    return c;
} 

Например, если w == 42 (101010 в двоичном виде, то есть ()()()), функция возвращает 44 (101100, ()(())). Можно повторять до получения 56 (111000, ((()))), что является максимальным словом Дика для N == 3.

Выше я упомянул непобедимую эффективность , поскольку в отношении генерации одного слова Дейка этот алгоритм является O (1), без циклов и без ветвлений. Тем не менее, реализация по-прежнему имеет место для улучшения. Действительно, относительно дорогое разделение c / a в теле функции может быть устранено, если мы можем использовать некоторые инструкции по сборке, которые недоступны в строгом стандарте C / C ++.

Вы могли бы сказать. « Возражение! Я не хочу ограничиваться N <= 64». Что ж, мой ответ на этот вопрос таков: если вы хотите сгенерировать все работы Дейка, то на практике вы уже ограничены размером намного меньше, чем 64 Действительно, число произведений Дика размером N увеличивается факториально с N, а для N == 64 время их генерации, вероятно, будет больше, чем возраст вселенной. (Признаюсь, я не рассчитывал это время, но это довольно распространенная анекдотическая особенность проблем такого рода.)

Я написал подробный документ по алгоритму .

...