Задача о числе путей в ациклическом графе — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м (Метод динамического программирования)
Строка 1: Строка 1:
'''Задача о числе путей в ациклическом графе''' - одна из классических задач на тему динамического программирования. В этой задаче нам дан граф, не содержащий циклов, в котором необходимо посчитать количество различных путей от одной вершины до другой.
+
'''Задача о числе путей в ациклическом графе''' - одна из классических задач на тему динамического программирования. В этой задаче нам дан ациклический граф <tex>G</tex> и две вершины <tex>s</tex> и <tex>t</tex>. Необходимо посчитать количество путей из вершины <tex>s</tex> в вершину <tex>t</tex> по рёбрам графа <tex>G</tex>.
  
Число таких путей может быть велико даже на небольших графах, поэтому перебор всех возможных вариантов займет много времени. Однако, данную задачу можно решить гораздо быстрее методом динамического программирования.
+
Число таких путей может быть велико даже на небольших графах, поэтому перебор всех возможных вариантов займет много времени. Однако, данную задачу можно решить гораздо быстрее с помощью динамики.
 
 
* '''Постановка задачи'''
 
Дан ациклический граф <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>v</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:
+
         ans += 1
+
'''count'''(v)
     else:
+
     '''if''' v == t
         for(child : v):
+
         answer += 1
             count(child)
+
     '''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>c</tex> - предок <tex>v</tex>. Мы свели нашу задачу к более мелким подзадачам, причем мы также знаем, что <tex>P(s) = 1</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>O(V + E)</tex>, где <tex>V</tex> - количество вершин графа, <tex>E</tex> - количество вершин. Функция <tex>P(v)</tex> вызывается от каждой вершины один раз, а внутри нее рассматриваются все такие ребра <tex>\{e\ |\ begin(e) = v\}</tex>. Всего таких ребер для всех вершин в графе <tex>O(E)</tex>, следовательно, время работы алгоритма оценивается как <tex>O(V+E)</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_{parent}count(parent), & w[v]=false  
+
  \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(parent : v):
+
         '''for'''(всех <tex>c</tex> смежных с <tex>v</tex>):
             sum += count(parent)
+
             sum += '''count'''(c)
 
         d[v] = sum
 
         d[v] = sum
 
         w[v] = true
 
         w[v] = true
         return sum
+
         '''return''' sum
 
   
 
   
  countPaths(s, t):
+
'''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

Задача о числе путей в ациклическом графе - одна из классических задач на тему динамического программирования. В этой задаче нам дан ациклический граф [math]G[/math] и две вершины [math]s[/math] и [math]t[/math]. Необходимо посчитать количество путей из вершины [math]s[/math] в вершину [math]t[/math] по рёбрам графа [math]G[/math].

Число таких путей может быть велико даже на небольших графах, поэтому перебор всех возможных вариантов займет много времени. Однако, данную задачу можно решить гораздо быстрее с помощью динамики.

Решение задачи

Перебор всех возможных путей

Небольшая модификация алгоритма обхода в глубину. Запустим обход в глубину от вершины [math]s[/math]. При каждом посещении вершины [math]v[/math] проверим, не является ли она искомой вершиной [math]t[/math]. Если это так, то ответ увеличивается на единицу и обход прекращается. В противном случае производится запуск обхода в глубину от всех вершин, ребра в которые выходят из [math]v[/math], причем он производится независимо от того, были ли эти вершины посещены ранее, или нет.

Функция [math]countPaths(s, t)[/math] принимает начальную вершину [math]s[/math] и конечную вершину [math]t[/math]. В глобальной переменной [math]answer[/math] содержится ответ.

answer = 0

count(v)
    if v == t
        answer += 1
    else
        for(всех [math]to[/math] смежных с [math]v[/math])
            count(to)

countPaths(s, t)
    answer = 0
    count(s)
    return answer

Время работы данного алгоритма в худшем случае [math]O(Ans)[/math], где [math]Ans[/math] - количество путей в графе.

Метод динамического программирования

Пусть [math]P(v)[/math] - количество путей до вершины [math]v[/math]. Можно заметить, что [math]P(v)[/math] зависит только от вершин, ребра из которых входят в [math]v[/math]. Тогда [math]P(v) = \sum\limits_{c}P(c)[/math] таких [math]c[/math], что [math]\exists[/math] ребро из [math]c[/math] в [math]v[/math]. Мы свели нашу задачу к более мелким подзадачам, причем мы также знаем, что [math]P(s) = 1[/math]. Это позволяет решить задачу методом динамического программирования.

Псевдокод

Пусть [math]s[/math] - стартовая вершина, а [math]t[/math] - конечная, для нее и посчитаем ответ. Будем поддерживать массив [math]d[/math], где [math]d[v][/math] - количество путей до вершины [math]v[/math] и массив [math]w[/math], где [math]w[v] = true[/math], если ответ для вершины [math]v[/math] уже посчитан, и [math]w[v] = false[/math] в противном случае. Изначально [math]w[i] = false[/math] для всех вершин [math]i[/math], кроме [math]s[/math], а [math]d[s] = 1[/math]. Функция [math]count(v)[/math] будет возвращать ответ для вершины [math]v[/math]. Удобнее всего это реализовать с помощью ленивой рекурсии, тогда значения массива [math]d[/math] будут вычисляться по мере необходимости, а засчет запоминания результатов они не будут считаться лишний раз:

[math] count(v) = \left \{ \begin{array}{ll} d[v], & w[v]=true \\ \sum\limits_{c}count(c), & w[v]=false \end{array} \right. [/math]

count(v)
    if w[v]
        return d[v]
    else
        sum = 0
        for(всех [math]c[/math] смежных с [math]v[/math]):
            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

Значение функции [math]count(v)[/math] считается для каждой вершины один раз, а внутри нее рассматриваются все такие ребра [math]\{e\ |\ end(e) = v\}[/math]. Всего таких ребер для всех вершин в графе [math]O(E)[/math], следовательно, время работы алгоритма в худшем случае оценивается как [math]O(V+E)[/math], где [math]V[/math] - количество вершин графа, [math]E[/math] - количество ребер.

Пример работы

Рассмотрим пример работы алгоритма на следующем графе:

description