Задача о числе путей в ациклическом графе — различия между версиями
м (→Перебор всех возможных путей) |
м (→Псевдокод) |
||
| Строка 37: | Строка 37: | ||
</tex> | </tex> | ||
| − | + | '''count'''(g, v) | |
| − | '''count'''(v) | ||
'''if''' w[v] | '''if''' w[v] | ||
| − | + | return d[v] | |
'''else''' | '''else''' | ||
sum = 0 | sum = 0 | ||
| − | '''for''' | + | '''for''' c '''in''' g[v] |
| − | sum += '''count'''(c) | + | sum += '''count'''(g, c) |
d[v] = sum | d[v] = sum | ||
w[v] = true | w[v] = true | ||
'''return''' sum | '''return''' sum | ||
| − | '''countPaths'''(s, t) | + | '''countPaths'''(g, s, t) |
d[s] = 1 | d[s] = 1 | ||
| − | w[s] = true | + | w[s] = '''true''' |
answer = '''count'''(t) | answer = '''count'''(t) | ||
'''return''' answer | '''return''' answer | ||
| − | |||
Значение функции <tex>count(v)</tex> считается для каждой вершины один раз, а внутри нее рассматриваются все такие ребра <tex>\{e\ |\ end(e) = v\}</tex>. Всего таких ребер для всех вершин в графе <tex>O(E)</tex>, следовательно, время работы алгоритма в худшем случае оценивается как <tex>O(V+E)</tex>, где <tex>V</tex> - количество вершин графа, <tex>E</tex> - количество ребер. | Значение функции <tex>count(v)</tex> считается для каждой вершины один раз, а внутри нее рассматриваются все такие ребра <tex>\{e\ |\ end(e) = v\}</tex>. Всего таких ребер для всех вершин в графе <tex>O(E)</tex>, следовательно, время работы алгоритма в худшем случае оценивается как <tex>O(V+E)</tex>, где <tex>V</tex> - количество вершин графа, <tex>E</tex> - количество ребер. | ||
Версия 23:08, 2 января 2014
Задача о числе путей в ациклическом графе - одна из классических задач на тему динамического программирования. В этой задаче нам дан ациклический граф и две вершины и . Необходимо посчитать количество путей из вершины в вершину по рёбрам графа .
Содержание
Решение задачи
Перебор всех возможных путей
Небольшая модификация алгоритма обхода в глубину. Запустим обход в глубину от вершины . При каждом посещении вершины проверим, не является ли она искомой вершиной . Если это так, то ответ увеличивается на единицу и обход прекращается. В противном случае производится запуск обхода в глубину для всех вершин, в которые есть ребро из , причем он производится независимо от того, были эти вершины посещены ранее, или нет.
Функция принимает граф , начальную вершину и конечную вершину .
countPaths(g, v, t)
if v == t
return 1
else
s = 0
for to in g[v]
s += count(g, to, t)
return s
Время работы данного алгоритма в худшем случае , где - количество путей в графе из в .
Метод динамического программирования
Пусть - количество путей от вершины до вершины . Тогда зависит только от вершин, ребра из которых входят в . Тогда таких , что есть ребро из в . Мы свели нашу задачу к меньшим подзадачам, причем мы также знаем, что . Это позволяет решить задачу методом динамического программирования.
Псевдокод
Пусть - стартовая вершина, а - конечная, для нее и посчитаем ответ. Будем поддерживать массив , где - количество путей из вершины до вершины и массив , где , если ответ для вершины уже посчитан, и в противном случае. Изначально для всех вершин , кроме , а . Функция будет возвращать ответ для вершины . Удобнее всего это реализовать в виде рекурсивной функции с запоминанием. В этом случае значения массива будут вычисляться по мере необходимости и не будут считаться лишний раз:
count(g, v)
if w[v]
return d[v]
else
sum = 0
for c in g[v]
sum += count(g, c)
d[v] = sum
w[v] = true
return sum
countPaths(g, s, t)
d[s] = 1
w[s] = true
answer = count(t)
return answer
Значение функции считается для каждой вершины один раз, а внутри нее рассматриваются все такие ребра . Всего таких ребер для всех вершин в графе , следовательно, время работы алгоритма в худшем случае оценивается как , где - количество вершин графа, - количество ребер.
Пример работы
Рассмотрим пример работы алгоритма на следующем графе:
Изначально массивы и инициализированы следующим образом:
| вершина | S | 1 | 2 | 3 | 4 | T |
| w | true | false | false | false | false | false |
| d | 1 | 0 | 0 | 0 | 0 | 0 |
Сначала функция будет вызвана от вершины . Ответ для нее еще не посчитан (), следовательно будет вызвана от вершин и . Для вершины ответ также не посчитан (), следовательно будет вызвана уже для вершин и . А вот для них ответ мы уже можем узнать: для он равен , так как это - единствнная вершина, ребро из которой входит в нее. Непосредственно для ответ нам также известен. На текущий момент таблица будет выглядеть следующим образом:
| вершина | S | 1 | 2 | 3 | 4 | T |
| w | true | false | true | false | false | false |
| d | 1 | 0 | 1 | 0 | 0 | 0 |
Теперь мы знаем значения для вершин и , что позволяет вычислить . Также обновим значения в массиве : .
| вершина | S | 1 | 2 | 3 | 4 | T |
| w | true | false | true | true | false | false |
| d | 1 | 0 | 1 | 2 | 0 | 0 |
В самом начале для вычисления нам требовались значения и . Теперь нам известно значение , поэтому проследим за тем, как будет вычисляться . , но , следовательно значения и мы уже знаем, и нам необходимо вызвать . Ответ для этой вершины равен , так как это единственная вершина, ребро из которой входит в . Обновим соответствующие значения массивов и :
| вершина | S | 1 | 2 | 3 | 4 | T |
| w | true | true | true | true | false | false |
| d | 1 | 1 | 1 | 2 | 0 | 0 |
Теперь нам известны все три значения, требующиеся для вычисления ответа для вершины . :
| вершина | S | 1 | 2 | 3 | 4 | T |
| w | true | true | true | true | true | false |
| d | 1 | 1 | 1 | 2 | 4 | 0 |
Наконец, вычислим и обновим таблицы и:
| вершина | S | 1 | 2 | 3 | 4 | T |
| w | true | true | true | true | true | true |
| d | 1 | 1 | 1 | 2 | 4 | 6 |
Этот алгоритм позволяет вычислить количество путей от какой-либо вершины не только до , но и для любой вершины, лежащей на любом из путей от до . Для этого достаточно взять значение в соответствующей ячейке .
