Задача о числе путей в ациклическом графе — различия между версиями
Nafanya (обсуждение | вклад) м (→Метод динамического программирования) |
Nafanya (обсуждение | вклад) |
||
Строка 1: | Строка 1: | ||
− | '''Задача о числе путей в ациклическом графе''' - одна из классических задач на тему динамического программирования. В этой задаче нам дан граф | + | '''Задача о числе путей в ациклическом графе''' - одна из классических задач на тему динамического программирования. В этой задаче нам дан ациклический граф <tex>G</tex> и две вершины <tex>s</tex> и <tex>t</tex>. Необходимо посчитать количество путей из вершины <tex>s</tex> в вершину <tex>t</tex> по рёбрам графа <tex>G</tex>. |
− | Число таких путей может быть велико даже на небольших графах, поэтому перебор всех возможных вариантов займет много времени. Однако, данную задачу можно решить гораздо быстрее | + | Число таких путей может быть велико даже на небольших графах, поэтому перебор всех возможных вариантов займет много времени. Однако, данную задачу можно решить гораздо быстрее с помощью динамики. |
− | |||
− | |||
− | |||
== Решение задачи == | == Решение задачи == | ||
Строка 10: | Строка 7: | ||
=== Перебор всех возможных путей === | === Перебор всех возможных путей === | ||
− | Небольшая модификация алгоритма [[Обход в глубину, цвета вершин|обхода в глубину]]. Запустим обход в глубину от вершины <tex>s</tex>. При каждом посещении вершины <tex>v</tex> проверим, не является ли она искомой вершиной <tex>t</tex>. Если это так, то ответ увеличивается на единицу и обход прекращается. В противном случае производится запуск обхода в глубину от всех | + | Небольшая модификация алгоритма [[Обход в глубину, цвета вершин|обхода в глубину]]. Запустим обход в глубину от вершины <tex>s</tex>. При каждом посещении вершины <tex>v</tex> проверим, не является ли она искомой вершиной <tex>t</tex>. Если это так, то ответ увеличивается на единицу и обход прекращается. В противном случае производится запуск обхода в глубину от всех вершин, ребра в которые выходят из <tex>v</tex>, причем он производится независимо от того, были ли эти вершины посещены ранее, или нет. |
+ | |||
+ | Функция <tex>countPaths(s, t)</tex> принимает начальную вершину <tex>s</tex> и конечную вершину <tex>t</tex>. В глобальной переменной <tex>answer</tex> содержится ответ. | ||
<code> | <code> | ||
− | count(v) | + | answer = 0 |
− | if v == t | + | |
− | + | '''count'''(v) | |
− | else | + | '''if''' v == t |
− | for( | + | answer += 1 |
− | count( | + | '''else''' |
+ | '''for'''(всех <tex>to</tex> смежных с <tex>v</tex>) | ||
+ | '''count'''(to) | ||
+ | |||
+ | '''countPaths'''(s, t) | ||
+ | answer = 0 | ||
+ | count(s) | ||
+ | return answer | ||
</code> | </code> | ||
− | Время работы данного алгоритма <tex>O(Ans)</tex>, где <tex>Ans</tex> - количество путей в графе. | + | Время работы данного алгоритма в худшем случае <tex>O(Ans)</tex>, где <tex>Ans</tex> - количество путей в графе. |
=== Метод динамического программирования === | === Метод динамического программирования === | ||
Пусть <tex>P(v)</tex> - количество путей до вершины <tex>v</tex>. | Пусть <tex>P(v)</tex> - количество путей до вершины <tex>v</tex>. | ||
− | Можно заметить, что <tex>P(v)</tex> зависит только от вершин, ребра из которых входят в <tex>v</tex>. Тогда <tex>P(v) = \sum\limits_{c}P(c)</tex>, | + | Можно заметить, что <tex>P(v)</tex> зависит только от вершин, ребра из которых входят в <tex>v</tex>. Тогда <tex>P(v) = \sum\limits_{c}P(c)</tex> таких <tex>c</tex>, что <tex>\exists</tex> ребро из <tex>c</tex> в <tex>v</tex>. Мы свели нашу задачу к более мелким подзадачам, причем мы также знаем, что <tex>P(s) = 1</tex>. Это позволяет решить задачу методом динамического программирования. |
− | |||
− | |||
− | |||
=== Псевдокод === | === Псевдокод === | ||
− | Пусть <tex>s</tex> - стартовая вершина, а <tex>t</tex> - конечная, для нее и посчитаем ответ. Будем поддерживать массив <tex>d</tex>, где <tex>d[v]</tex> - количество путей до вершины <tex>v</tex> и массив <tex>w</tex>, где <tex>w[v] = true</tex>, если ответ для вершины <tex>v</tex> уже посчитан, и <tex>w[v] = false</tex> в противном случае. Изначально <tex>w[i] = false</tex> для всех вершин, кроме <tex>s</tex>, а <tex>d[s] = 1</tex>. Функция <tex>count(v)</tex> будет возвращать ответ для вершины <tex>v</tex>. Удобнее всего это реализовать с помощью ленивой рекурсии, тогда значения массива <tex>d</tex> будут вычисляться по мере необходимости, а засчет запоминания результатов они не будут считаться лишний раз: | + | Пусть <tex>s</tex> - стартовая вершина, а <tex>t</tex> - конечная, для нее и посчитаем ответ. Будем поддерживать массив <tex>d</tex>, где <tex>d[v]</tex> - количество путей до вершины <tex>v</tex> и массив <tex>w</tex>, где <tex>w[v] = true</tex>, если ответ для вершины <tex>v</tex> уже посчитан, и <tex>w[v] = false</tex> в противном случае. Изначально <tex>w[i] = false</tex> для всех вершин <tex>i</tex>, кроме <tex>s</tex>, а <tex>d[s] = 1</tex>. Функция <tex>count(v)</tex> будет возвращать ответ для вершины <tex>v</tex>. Удобнее всего это реализовать с помощью ленивой рекурсии, тогда значения массива <tex>d</tex> будут вычисляться по мере необходимости, а засчет запоминания результатов они не будут считаться лишний раз: |
<tex> count(v) = \left \{ | <tex> count(v) = \left \{ | ||
\begin{array}{ll} | \begin{array}{ll} | ||
d[v], & w[v]=true \\ | d[v], & w[v]=true \\ | ||
− | \sum\limits_{ | + | \sum\limits_{c}count(c), & w[v]=false |
\end{array} | \end{array} | ||
\right. | \right. | ||
Строка 45: | Строка 48: | ||
<code> | <code> | ||
− | count(v) | + | '''count'''(v) |
− | if w[v] | + | '''if''' w[v] |
− | return d[v] | + | '''return''' d[v] |
− | else | + | '''else''' |
sum = 0 | sum = 0 | ||
− | for( | + | '''for'''(всех <tex>c</tex> смежных с <tex>v</tex>): |
− | sum += count( | + | sum += '''count'''(c) |
d[v] = sum | d[v] = sum | ||
w[v] = true | w[v] = true | ||
− | return sum | + | '''return''' sum |
− | + | '''countPaths'''(s, t): | |
d[s] = 1 | d[s] = 1 | ||
w[s] = true | w[s] = true | ||
− | answer = count(t) | + | answer = '''count'''(t) |
+ | '''return''' answer | ||
</code> | </code> | ||
+ | |||
+ | Значение функции <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> - количество ребер. | ||
+ | |||
+ | == Пример работы == | ||
+ | |||
+ | Рассмотрим пример работы алгоритма на следующем графе: | ||
+ | |||
+ | [[Файл: count-path-graph-example.png|thumb|300px| description ]] | ||
[[Категория:Дискретная математика и алгоритмы]] | [[Категория:Дискретная математика и алгоритмы]] | ||
[[Категория:Динамическое программирование]] | [[Категория:Динамическое программирование]] |
Версия 01:36, 27 декабря 2013
Задача о числе путей в ациклическом графе - одна из классических задач на тему динамического программирования. В этой задаче нам дан ациклический граф
и две вершины и . Необходимо посчитать количество путей из вершины в вершину по рёбрам графа .Число таких путей может быть велико даже на небольших графах, поэтому перебор всех возможных вариантов займет много времени. Однако, данную задачу можно решить гораздо быстрее с помощью динамики.
Содержание
Решение задачи
Перебор всех возможных путей
Небольшая модификация алгоритма обхода в глубину. Запустим обход в глубину от вершины . При каждом посещении вершины проверим, не является ли она искомой вершиной . Если это так, то ответ увеличивается на единицу и обход прекращается. В противном случае производится запуск обхода в глубину от всех вершин, ребра в которые выходят из , причем он производится независимо от того, были ли эти вершины посещены ранее, или нет.
Функция
принимает начальную вершину и конечную вершину . В глобальной переменной содержится ответ.
answer = 0 count(v) if v == t answer += 1 else for(всехсмежных с ) count(to) countPaths(s, t) answer = 0 count(s) return answer
Время работы данного алгоритма в худшем случае
, где - количество путей в графе.Метод динамического программирования
Пусть
- количество путей до вершины . Можно заметить, что зависит только от вершин, ребра из которых входят в . Тогда таких , что ребро из в . Мы свели нашу задачу к более мелким подзадачам, причем мы также знаем, что . Это позволяет решить задачу методом динамического программирования.Псевдокод
Пусть
- стартовая вершина, а - конечная, для нее и посчитаем ответ. Будем поддерживать массив , где - количество путей до вершины и массив , где , если ответ для вершины уже посчитан, и в противном случае. Изначально для всех вершин , кроме , а . Функция будет возвращать ответ для вершины . Удобнее всего это реализовать с помощью ленивой рекурсии, тогда значения массива будут вычисляться по мере необходимости, а засчет запоминания результатов они не будут считаться лишний раз:
count(v) if w[v] return d[v] else sum = 0 for(всехсмежных с ): sum += count(c) d[v] = sum w[v] = true return sum countPaths(s, t): d[s] = 1 w[s] = true answer = count(t) return answer
Значение функции
считается для каждой вершины один раз, а внутри нее рассматриваются все такие ребра . Всего таких ребер для всех вершин в графе , следовательно, время работы алгоритма в худшем случае оценивается как , где - количество вершин графа, - количество ребер.Пример работы
Рассмотрим пример работы алгоритма на следующем графе: