<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ikramov</id>
	<title>Алговики - Вклад [ru]</title>
	<link rel="self" type="application/atom+xml" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ikramov"/>
	<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/ru/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/Ikramov"/>
	<updated>2026-10-23T05:55:08Z</updated>
	<subtitle>Вклад</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24426</id>
		<title>Алгоритм Борувки</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24426"/>
		<updated>2017-11-28T20:54:05Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Борувки&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;max O(ln(|V|)) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Борувки&#039;&#039;&#039;&amp;lt;ref&amp;gt;Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.&amp;lt;/ref&amp;gt; предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный неориентированный граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt;. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).&lt;br /&gt;
&lt;br /&gt;
Алгоритм Борувки основан на следующих двух свойствах задачи:&lt;br /&gt;
* &#039;&#039;&#039;Минимальное ребро фрагмента&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева и &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; – ребро наименьшего веса, исходящее из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; (т.е. ровно один его конец является вершиной из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;). Если ребро &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; единственно, то оно принадлежит минимальному остовному дереву.&lt;br /&gt;
* &#039;&#039;&#039;Схлопывание фрагментов&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а граф &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; получен из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; склеиванием вершин, принадлежащих &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Тогда объединение &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и минимального остовного дерева графа &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; даёт минимальное остовное дерево исходного графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма каждая вершина графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основными операциями являются:&lt;br /&gt;
# Поиск минимального по весу исходящего ребра в каждом фрагменте.&lt;br /&gt;
# Объединение фрагментов.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. &lt;br /&gt;
&lt;br /&gt;
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный граф &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;V = ( v_{1}, v_{2}, ..., v_{n} )&amp;lt;/math&amp;gt; и рёбрами &amp;lt;math&amp;gt;E = ( e_{1}, e_{2}, ..., e_{m} )&amp;lt;/math&amp;gt;. Каждому ребру &amp;lt;math&amp;gt;e \in E&amp;lt;/math&amp;gt; приписан вес &amp;lt;math&amp;gt;w(e)&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Требуется построить дерево &amp;lt;math&amp;gt;T^* \subseteq E&amp;lt;/math&amp;gt;, связывающее все вершины и имеющее наименьший возможный вес среди всех таких деревьев:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; w(T^* )= \min_T( w(T)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
где вес множества рёбер есть сумма их весов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;w(T)=\sum_{e \in T} (w(T))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если граф &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; не является связным, то дерева, связывающего все вершины, не существует. &lt;br /&gt;
&lt;br /&gt;
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).&lt;br /&gt;
&lt;br /&gt;
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====&lt;br /&gt;
&lt;br /&gt;
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:&lt;br /&gt;
&lt;br /&gt;
1.	&amp;lt;math&amp;gt;FIND(v) = w&amp;lt;/math&amp;gt; – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда &amp;lt;math&amp;gt;FIND(u) = FIND(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; – объединяет два фрагмента, которым принадлежат вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v.&amp;lt;/math&amp;gt; (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.&lt;br /&gt;
&lt;br /&gt;
==== Последовательная версия====&lt;br /&gt;
&lt;br /&gt;
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя &amp;lt;math&amp;gt;parent(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
1.	Изначально &amp;lt;math&amp;gt;parent(v) := v&amp;lt;/math&amp;gt; для всех вершин.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; выполняется следующим образом: полагаем &amp;lt;math&amp;gt;u := v&amp;lt;/math&amp;gt;, и далее следуем по указателям &amp;lt;math&amp;gt;u := parent(u)&amp;lt;/math&amp;gt; до тех пор, пока не станет &amp;lt;math&amp;gt;u = parent(u)&amp;lt;/math&amp;gt;. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: &amp;lt;math&amp;gt;parent(u_i) := u&amp;lt;/math&amp;gt;, либо производить схлопывание по пути: &amp;lt;math&amp;gt;parent(u) := parent(parent(u)))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; выполняется следующим образом: вначале находим корневые вершины &amp;lt;math&amp;gt;u := FIND(u), v := FIND(v)&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;u = v&amp;lt;/math&amp;gt;, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из &amp;lt;math&amp;gt;parent(u) := v&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;parent(v) := u&amp;lt;/math&amp;gt;. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.&lt;br /&gt;
&lt;br /&gt;
1.	В самом начале каждая вершина является отдельным фрагментом.&lt;br /&gt;
&lt;br /&gt;
2.	На каждом шаге:&lt;br /&gt;
&lt;br /&gt;
*	Для каждого фрагмента определяется минимальное по весу исходящее ребро.&lt;br /&gt;
&lt;br /&gt;
*	Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм останавливается, когда остаётся только один фрагмент либо когда ни у одного из фрагментов нет исходящих рёбер.&lt;br /&gt;
&lt;br /&gt;
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).&lt;br /&gt;
&lt;br /&gt;
Объединение фрагментов также может быть реализовано параллельно с использованием параллельной версии описанного выше алгоритма Union-Find.&lt;br /&gt;
&lt;br /&gt;
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:&lt;br /&gt;
&lt;br /&gt;
1.	В начале итерации счётчик активных фрагментов обнуляется.&lt;br /&gt;
&lt;br /&gt;
2.	На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.&lt;br /&gt;
&lt;br /&gt;
3.	На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция &amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; вернула значение истина.&lt;br /&gt;
&lt;br /&gt;
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.&lt;br /&gt;
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Последовательная сложность алгоритма Борувки для графа с &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершинами и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбрами составляет &amp;lt;math&amp;gt;O(|E| \ln(|V|))&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний) и параллелизм в алгоритме обработки графа, не помещающегося в память.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Нижний уровень параллелизма&#039;&#039;&#039;: поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно с использованием описанного выше алгоритма Union-Find. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Верхний уровень параллелизма&#039;&#039;&#039;: построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. &lt;br /&gt;
&lt;br /&gt;
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим информационные графы и подробное описание каждого из них. Также можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующих операциям параллельного поиска минимальных исходящих ребер, а также на уровнях {6, 7, 8}, соответствующих операциям параллельного объединения деревьев. Также  различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе переход к следующей итерации. &lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального остовного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные операции compute mst. Затем, после параллельных вычислений mst, происходит вычисление итогового основного дерева, после чего полученный результат сохраняется (save results).&lt;br /&gt;
&lt;br /&gt;
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм Борувки обладает двумя уровнями параллелизма.&lt;br /&gt;
&lt;br /&gt;
На верхнем уровне минимальные остовные деревья могут искаться для отдельных частей списка ребер графа (параллельные compute_MST на рисунке 2). Однако затем должно последовать финальное объединение полученных ребер и вычисление минимального остовного дерева для полученного графа, которое будет производиться последовательно.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вычисление каждого из минимальных остовных деревьев (параллельные compute_MST на рисунке 2) обладает внутренним ресурсом параллелизма, описанным далее. Операции инициализации и копирования данных ([1], [2], [9]  на рисунке 1) могут производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Также параллельно могут производиться операции поиска минимальных исходящих ребер ([3],[4],[5]), при этом для каждой дуги и обратной к ней независимо, что даёт &amp;lt;math&amp;gt;2*O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Помимо этого, операции объединения деревьев [6], [7], [8] могут также производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
В результате для классического алгоритма Борувки ширина ярусно-параллельной формы равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, а высота ЯПФ зависит от числа шагов алгоритма и ограничена сверху &amp;lt;math&amp;gt;O(ln(|V|))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j},&lt;br /&gt;
v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.&lt;br /&gt;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &amp;lt;math&amp;gt;\log_2 n&amp;lt;/math&amp;gt;. Отсюда следует и оценка сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
На этапе поиска минимального ребра происходят следующие обращения к памяти:&lt;br /&gt;
# Чтение информации о рёбрах. Может производиться последовательно.&lt;br /&gt;
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; с вероятным промахом по кэшу.&lt;br /&gt;
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.&lt;br /&gt;
&lt;br /&gt;
На этапе схлопывания фрагментов требуется атомарно обновить массив &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.&lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_1.png|thumb|center|700px|Рисунок 3. Алгоритм Борувки. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.&lt;br /&gt;
&lt;br /&gt;
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 3 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.&lt;br /&gt;
&lt;br /&gt;
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 3), которая показана на рис. 4. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 4 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее перейдем к изучению фрагмента 2 (рис. 5). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_3.png|thumb|center|700px|Рисунок 5. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 6 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_4.png|thumb|center|700px|Рисунок 6. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
Отдельное рассмотрение фрагмента 4 (рис. 7) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_5.png|thumb|center|500px|Рисунок 7. Профиль обращений, фрагмент 4]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 8 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 8. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм Борувки, состоит из двух частей: &lt;br /&gt;
&lt;br /&gt;
1. части, отвечающей за общую координацию вычислений&lt;br /&gt;
&lt;br /&gt;
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации &amp;lt;math&amp;gt;MERGE&amp;lt;/math&amp;gt; результаты операций &amp;lt;math&amp;gt;FIND(u)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.&lt;br /&gt;
&lt;br /&gt;
1.	Каждой вершине v соответствует запись &amp;lt;math&amp;gt;A[v] = { parent, rank }&amp;lt;/math&amp;gt;. Изначально &amp;lt;math&amp;gt;A[v] := { v, 0 }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Вспомогательная операция &amp;lt;math&amp;gt;UPDATE(v, rank_v, u, rank_u)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
old := A[v]&lt;br /&gt;
&lt;br /&gt;
if old.parent != v or old.rank != rank_v then return false&lt;br /&gt;
&lt;br /&gt;
new := { u, rank_u }&lt;br /&gt;
&lt;br /&gt;
return CAS(A[v], old, new)&lt;br /&gt;
&lt;br /&gt;
3.	Операция &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    while v != A[v].parent do&lt;br /&gt;
        u := A[v].parent&lt;br /&gt;
        CAS(A[v].parent, u, A[u].parent)&lt;br /&gt;
        v := A[u].parent&lt;br /&gt;
    return v&lt;br /&gt;
&lt;br /&gt;
4.	Операция UNION(u, v):&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        (u, v) := (FIND(u), FIND(v))&lt;br /&gt;
        if u = v then return false&lt;br /&gt;
        (rank_u, rank_v) := (A[u].rank, A[v].rank)&lt;br /&gt;
        if (A[u].rank, u) &amp;gt; (A[v].rank, v) then&lt;br /&gt;
            swap((u, rank_u), (v, rank_v))&lt;br /&gt;
        if UPDATE(u, rank_u, v, rank_u) then&lt;br /&gt;
            if rank_u = rank_v then&lt;br /&gt;
                UPDATE(v, rank_v, v, rank_v + 1)&lt;br /&gt;
            return true&lt;br /&gt;
&lt;br /&gt;
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются&lt;br /&gt;
# пропускная способность памяти при чтении данных графа&lt;br /&gt;
# соперничество потоков при выполнении атомарных операций с памятью&lt;br /&gt;
# барьерная синхронизация после каждого подшага алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2&amp;quot; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Борувки, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Борувки, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Boruvka cpu.png|thumb|center|700px|Рисунок 11. График загрузки CPU при выполнении алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 10%. Это нормальная картина для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Loadavg.png|thumb|center|700px|Рисунок 12. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 2. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 2 процессами, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональное использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L1.png|thumb|center|700px|Рисунок 13. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 40 млн/сек . Интересен факт увеличения числа промахов к концу итерации до уровня в 70 млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L2.png|thumb|center|700px|Рисунок 14. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов тоже достаточно высокое и находится на уровне 30 млн/сек . На графике промахов второго уровня факт увеличения числа промахов к концу итерации проявляется более явно и увеличивается до значения в 50млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L3.png|thumb|center|700px|Рисунок 15. График кэш-промахов L3 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Inf data.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на первом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka inf pckts.png|thumb|center|700px|Рисунок 17. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают алгоритм Борувки и [[алгоритм Крускала]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Boruvka&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24423</id>
		<title>Алгоритм Борувки</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24423"/>
		<updated>2017-11-28T20:35:18Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Борувки&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;max O(ln(|V|)) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Борувки&#039;&#039;&#039;&amp;lt;ref&amp;gt;Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.&amp;lt;/ref&amp;gt; предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный неориентированный граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt;. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).&lt;br /&gt;
&lt;br /&gt;
Алгоритм Борувки основан на следующих двух свойствах задачи:&lt;br /&gt;
* &#039;&#039;&#039;Минимальное ребро фрагмента&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева и &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; – ребро наименьшего веса, исходящее из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; (т.е. ровно один его конец является вершиной из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;). Если ребро &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; единственно, то оно принадлежит минимальному остовному дереву.&lt;br /&gt;
* &#039;&#039;&#039;Схлопывание фрагментов&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а граф &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; получен из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; склеиванием вершин, принадлежащих &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Тогда объединение &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и минимального остовного дерева графа &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; даёт минимальное остовное дерево исходного графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма каждая вершина графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основными операциями являются:&lt;br /&gt;
# Поиск минимального по весу исходящего ребра в каждом фрагменте.&lt;br /&gt;
# Объединение фрагментов.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. &lt;br /&gt;
&lt;br /&gt;
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный граф &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;V = ( v_{1}, v_{2}, ..., v_{n} )&amp;lt;/math&amp;gt; и рёбрами &amp;lt;math&amp;gt;E = ( e_{1}, e_{2}, ..., e_{m} )&amp;lt;/math&amp;gt;. Каждому ребру &amp;lt;math&amp;gt;e \in E&amp;lt;/math&amp;gt; приписан вес &amp;lt;math&amp;gt;w(e)&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Требуется построить дерево &amp;lt;math&amp;gt;T^* \subseteq E&amp;lt;/math&amp;gt;, связывающее все вершины и имеющее наименьший возможный вес среди всех таких деревьев:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; w(T^* )= \min_T( w(T)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
где вес множества рёбер есть сумма их весов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;w(T)=\sum_{e \in T} (w(T))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если граф &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; не является связным, то дерева, связывающего все вершины, не существует. &lt;br /&gt;
&lt;br /&gt;
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).&lt;br /&gt;
&lt;br /&gt;
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====&lt;br /&gt;
&lt;br /&gt;
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:&lt;br /&gt;
&lt;br /&gt;
1.	&amp;lt;math&amp;gt;FIND(v) = w&amp;lt;/math&amp;gt; – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда &amp;lt;math&amp;gt;FIND(u) = FIND(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; – объединяет два фрагмента, которым принадлежат вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v.&amp;lt;/math&amp;gt; (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.&lt;br /&gt;
&lt;br /&gt;
==== Последовательная версия====&lt;br /&gt;
&lt;br /&gt;
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя &amp;lt;math&amp;gt;parent(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
1.	Изначально &amp;lt;math&amp;gt;parent(v) := v&amp;lt;/math&amp;gt; для всех вершин.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; выполняется следующим образом: полагаем &amp;lt;math&amp;gt;u := v&amp;lt;/math&amp;gt;, и далее следуем по указателям &amp;lt;math&amp;gt;u := parent(u)&amp;lt;/math&amp;gt; до тех пор, пока не станет &amp;lt;math&amp;gt;u = parent(u)&amp;lt;/math&amp;gt;. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: &amp;lt;math&amp;gt;parent(u_i) := u&amp;lt;/math&amp;gt;, либо производить схлопывание по пути: &amp;lt;math&amp;gt;parent(u) := parent(parent(u)))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; выполняется следующим образом: вначале находим корневые вершины &amp;lt;math&amp;gt;u := FIND(u), v := FIND(v)&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;u = v&amp;lt;/math&amp;gt;, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из &amp;lt;math&amp;gt;parent(u) := v&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;parent(v) := u&amp;lt;/math&amp;gt;. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.&lt;br /&gt;
&lt;br /&gt;
1.	В самом начале каждая вершина является отдельным фрагментом.&lt;br /&gt;
&lt;br /&gt;
2.	На каждом шаге:&lt;br /&gt;
&lt;br /&gt;
*	Для каждого фрагмента определяется минимальное по весу исходящее ребро.&lt;br /&gt;
&lt;br /&gt;
*	Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм останавливается, когда остаётся только один фрагмент либо когда ни у одного из фрагментов нет исходящих рёбер.&lt;br /&gt;
&lt;br /&gt;
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).&lt;br /&gt;
&lt;br /&gt;
Объединение фрагментов также может быть реализовано параллельно с использованием параллельной версии описанного выше алгоритма Union-Find.&lt;br /&gt;
&lt;br /&gt;
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:&lt;br /&gt;
&lt;br /&gt;
1.	В начале итерации счётчик активных фрагментов обнуляется.&lt;br /&gt;
&lt;br /&gt;
2.	На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.&lt;br /&gt;
&lt;br /&gt;
3.	На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция &amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; вернула значение истина.&lt;br /&gt;
&lt;br /&gt;
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.&lt;br /&gt;
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Последовательная сложность алгоритма Борувки для графа с &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершинами и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбрами составляет &amp;lt;math&amp;gt;O(|E| \ln(|V|))&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний) и параллелизм в алгоритме обработки графа, не помещающегося в память.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Нижний уровень параллелизма&#039;&#039;&#039;: поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно с использованием описанного выше алгоритма Union-Find. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Верхний уровень параллелизма&#039;&#039;&#039;: построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. &lt;br /&gt;
&lt;br /&gt;
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим информационные графы и подробное описание каждого из них. Также можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующих операциям параллельного поиска минимальных исходящих ребер, а также на уровнях {6, 7, 8}, соответствующих операциям параллельного объединения деревьев. Также  различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе переход к следующей итерации. &lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального остовного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные операции compute mst. Затем, после параллельных вычислений mst, происходит вычисление итогового основного дерева, после чего полученный результат сохраняется (save results).&lt;br /&gt;
&lt;br /&gt;
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм Борувки обладает двумя уровнями параллелизма.&lt;br /&gt;
&lt;br /&gt;
На верхнем уровне минимальные остовные деревья могут искаться для отдельных частей списка ребер графа (параллельные compute_MST на рисунке 2). Однако затем должно последовать финальное объединение полученных ребер и вычисление минимального остовного дерева для полученного графа, которое будет производиться последовательно.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вычисление каждого из минимальных остовных деревьев (параллельные compute_MST на рисунке 2) обладает внутренним ресурсом параллелизма, описанным далее. Операции инициализации и копирования данных ([1], [2], [9]  на рисунке 1) могут производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Также параллельно могут производиться операции поиска минимальных исходящих ребер ([3],[4],[5]), при этом для каждой дуги и обратной к ней независимо, что даёт &amp;lt;math&amp;gt;2*O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Помимо этого, операции объединения деревьев [6], [7], [8] могут также производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
В результате для классического алгоритма Борувки ширина ярусно-параллельной формы равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, а высота ЯПФ зависит от числа шагов алгоритма и ограничена сверху &amp;lt;math&amp;gt;O(ln(|V|))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j},&lt;br /&gt;
v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.&lt;br /&gt;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &amp;lt;math&amp;gt;\log_2 n&amp;lt;/math&amp;gt;. Отсюда следует и оценка сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
На этапе поиска минимального ребра происходят следующие обращения к памяти:&lt;br /&gt;
# Чтение информации о рёбрах. Может производиться последовательно.&lt;br /&gt;
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; с вероятным промахом по кэшу.&lt;br /&gt;
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.&lt;br /&gt;
&lt;br /&gt;
На этапе схлопывания фрагментов требуется атомарно обновить массив &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.&lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_1.png|thumb|center|700px|Рисунок 3. Алгоритм Борувки. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.&lt;br /&gt;
&lt;br /&gt;
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 3 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.&lt;br /&gt;
&lt;br /&gt;
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 3), которая показана на рис. 4. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 4 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее перейдем к изучению фрагмента 2 (рис. 5). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_3.png|thumb|center|700px|Рисунок 5. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 6 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_4.png|thumb|center|700px|Рисунок 6. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
Отдельное рассмотрение фрагмента 4 (рис. 7) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_5.png|thumb|center|500px|Рисунок 7. Профиль обращений, фрагмент 4]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 8 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 8. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм Борувки, состоит из двух частей: &lt;br /&gt;
&lt;br /&gt;
1. части, отвечающей за общую координацию вычислений&lt;br /&gt;
&lt;br /&gt;
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации &amp;lt;math&amp;gt;MERGE&amp;lt;/math&amp;gt; результаты операций &amp;lt;math&amp;gt;FIND(u)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.&lt;br /&gt;
&lt;br /&gt;
1.	Каждой вершине v соответствует запись &amp;lt;math&amp;gt;A[v] = { parent, rank }&amp;lt;/math&amp;gt;. Изначально &amp;lt;math&amp;gt;A[v] := { v, 0 }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Вспомогательная операция &amp;lt;math&amp;gt;UPDATE(v, rank_v, u, rank_u)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
old := A[v]&lt;br /&gt;
&lt;br /&gt;
if old.parent != v or old.rank != rank_v then return false&lt;br /&gt;
&lt;br /&gt;
new := { u, rank_u }&lt;br /&gt;
&lt;br /&gt;
return CAS(A[v], old, new)&lt;br /&gt;
&lt;br /&gt;
3.	Операция &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    while v != A[v].parent do&lt;br /&gt;
        u := A[v].parent&lt;br /&gt;
        CAS(A[v].parent, u, A[u].parent)&lt;br /&gt;
        v := A[u].parent&lt;br /&gt;
    return v&lt;br /&gt;
&lt;br /&gt;
4.	Операция UNION(u, v):&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        (u, v) := (FIND(u), FIND(v))&lt;br /&gt;
        if u = v then return false&lt;br /&gt;
        (rank_u, rank_v) := (A[u].rank, A[v].rank)&lt;br /&gt;
        if (A[u].rank, u) &amp;gt; (A[v].rank, v) then&lt;br /&gt;
            swap((u, rank_u), (v, rank_v))&lt;br /&gt;
        if UPDATE(u, rank_u, v, rank_u) then&lt;br /&gt;
            if rank_u = rank_v then&lt;br /&gt;
                UPDATE(v, rank_v, v, rank_v + 1)&lt;br /&gt;
            return true&lt;br /&gt;
&lt;br /&gt;
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются&lt;br /&gt;
# пропускная способность памяти при чтении данных графа&lt;br /&gt;
# соперничество потоков при выполнении атомарных операций с памятью&lt;br /&gt;
# барьерная синхронизация после каждого подшага алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2&amp;quot; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Борувки, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Борувки, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Boruvka cpu.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 10%. Это нормальная картина для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Loadavg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 2. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 2 процессами, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональное использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 40 млн/сек . Интересен факт увеличения числа промахов к концу итерации до уровня в 70 млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов тоже достаточно высокое и находится на уровне 30 млн/сек . На графике промахов второго уровня факт увеличения числа промахов к концу итерации проявляется более явно и увеличивается до значения в 50млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов L3 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Эт указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Inf data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka inf pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают алгоритм Борувки и [[алгоритм Крускала]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Boruvka&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24395</id>
		<title>Алгоритм Борувки</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24395"/>
		<updated>2017-11-28T16:34:50Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Масштабируемость реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Борувки&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;max O(ln(|V|)) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Борувки&#039;&#039;&#039;&amp;lt;ref&amp;gt;Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.&amp;lt;/ref&amp;gt; предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный неориентированный граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt;. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).&lt;br /&gt;
&lt;br /&gt;
Алгоритм Борувки основан на следующих двух свойствах задачи:&lt;br /&gt;
* &#039;&#039;&#039;Минимальное ребро фрагмента&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева и &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; – ребро наименьшего веса, исходящее из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; (т.е. ровно один его конец является вершиной из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;). Если ребро &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; единственно, то оно принадлежит минимальному остовному дереву.&lt;br /&gt;
* &#039;&#039;&#039;Схлопывание фрагментов&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а граф &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; получен из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; склеиванием вершин, принадлежащих &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Тогда объединение &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и минимального остовного дерева графа &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; даёт минимальное остовное дерево исходного графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма каждая вершина графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основными операциями являются:&lt;br /&gt;
# Поиск минимального по весу исходящего ребра в каждом фрагменте.&lt;br /&gt;
# Объединение фрагментов.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. &lt;br /&gt;
&lt;br /&gt;
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный граф &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;V = ( v_{1}, v_{2}, ..., v_{n} )&amp;lt;/math&amp;gt; и рёбрами &amp;lt;math&amp;gt;E = ( e_{1}, e_{2}, ..., e_{m} )&amp;lt;/math&amp;gt;. Каждому ребру &amp;lt;math&amp;gt;e \in E&amp;lt;/math&amp;gt; приписан вес &amp;lt;math&amp;gt;w(e)&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Требуется построить дерево &amp;lt;math&amp;gt;T^* \subseteq E&amp;lt;/math&amp;gt;, связывающее все вершины и имеющее наименьший возможный вес среди всех таких деревьев:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; w(T^* )= \min_T( w(T)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
где вес множества рёбер есть сумма их весов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;w(T)=\sum_{e \in T} (w(T))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если граф &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; не является связным, то дерева, связывающего все вершины, не существует. &lt;br /&gt;
&lt;br /&gt;
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).&lt;br /&gt;
&lt;br /&gt;
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====&lt;br /&gt;
&lt;br /&gt;
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:&lt;br /&gt;
&lt;br /&gt;
1.	&amp;lt;math&amp;gt;FIND(v) = w&amp;lt;/math&amp;gt; – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда &amp;lt;math&amp;gt;FIND(u) = FIND(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; – объединяет два фрагмента, которым принадлежат вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v.&amp;lt;/math&amp;gt; (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.&lt;br /&gt;
&lt;br /&gt;
==== Последовательная версия====&lt;br /&gt;
&lt;br /&gt;
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя &amp;lt;math&amp;gt;parent(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
1.	Изначально &amp;lt;math&amp;gt;parent(v) := v&amp;lt;/math&amp;gt; для всех вершин.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; выполняется следующим образом: полагаем &amp;lt;math&amp;gt;u := v&amp;lt;/math&amp;gt;, и далее следуем по указателям &amp;lt;math&amp;gt;u := parent(u)&amp;lt;/math&amp;gt; до тех пор, пока не станет &amp;lt;math&amp;gt;u = parent(u)&amp;lt;/math&amp;gt;. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: &amp;lt;math&amp;gt;parent(u_i) := u&amp;lt;/math&amp;gt;, либо производить схлопывание по пути: &amp;lt;math&amp;gt;parent(u) := parent(parent(u)))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; выполняется следующим образом: вначале находим корневые вершины &amp;lt;math&amp;gt;u := FIND(u), v := FIND(v)&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;u = v&amp;lt;/math&amp;gt;, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из &amp;lt;math&amp;gt;parent(u) := v&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;parent(v) := u&amp;lt;/math&amp;gt;. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.&lt;br /&gt;
&lt;br /&gt;
1.	В самом начале каждая вершина является отдельным фрагментом.&lt;br /&gt;
&lt;br /&gt;
2.	На каждом шаге:&lt;br /&gt;
&lt;br /&gt;
*	Для каждого фрагмента определяется минимальное по весу исходящее ребро.&lt;br /&gt;
&lt;br /&gt;
*	Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм останавливается, когда остаётся только один фрагмент либо когда ни у одного из фрагментов нет исходящих рёбер.&lt;br /&gt;
&lt;br /&gt;
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).&lt;br /&gt;
&lt;br /&gt;
Объединение фрагментов также может быть реализовано параллельно с использованием параллельной версии описанного выше алгоритма Union-Find.&lt;br /&gt;
&lt;br /&gt;
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:&lt;br /&gt;
&lt;br /&gt;
1.	В начале итерации счётчик активных фрагментов обнуляется.&lt;br /&gt;
&lt;br /&gt;
2.	На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.&lt;br /&gt;
&lt;br /&gt;
3.	На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция &amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; вернула значение истина.&lt;br /&gt;
&lt;br /&gt;
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.&lt;br /&gt;
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Последовательная сложность алгоритма Борувки для графа с &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершинами и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбрами составляет &amp;lt;math&amp;gt;O(|E| \ln(|V|))&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний) и параллелизм в алгоритме обработки графа, не помещающегося в память.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Нижний уровень параллелизма&#039;&#039;&#039;: поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно с использованием описанного выше алгоритма Union-Find. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Верхний уровень параллелизма&#039;&#039;&#039;: построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. &lt;br /&gt;
&lt;br /&gt;
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим информационные графы и подробное описание каждого из них. Также можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующих операциям параллельного поиска минимальных исходящих ребер, а также на уровнях {6, 7, 8}, соответствующих операциям параллельного объединения деревьев. Также  различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе переход к следующей итерации. &lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального остовного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные операции compute mst. Затем, после параллельных вычислений mst, происходит вычисление итогового основного дерева, после чего полученный результат сохраняется (save results).&lt;br /&gt;
&lt;br /&gt;
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм Борувки обладает двумя уровнями параллелизма.&lt;br /&gt;
&lt;br /&gt;
На верхнем уровне минимальные остовные деревья могут искаться для отдельных частей списка ребер графа (параллельные compute_MST на рисунке 2). Однако затем должно последовать финальное объединение полученных ребер и вычисление минимального остовного дерева для полученного графа, которое будет производиться последовательно.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вычисление каждого из минимальных остовных деревьев (параллельные compute_MST на рисунке 2) обладает внутренним ресурсом параллелизма, описанным далее. Операции инициализации и копирования данных ([1], [2], [9]  на рисунке 1) могут производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Также параллельно могут производиться операции поиска минимальных исходящих ребер ([3],[4],[5]), при этом для каждой дуги и обратной к ней независимо, что даёт &amp;lt;math&amp;gt;2*O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Помимо этого, операции объединения деревьев [6], [7], [8] могут также производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
В результате для классического алгоритма Борувки ширина ярусно-параллельной формы равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, а высота ЯПФ зависит от числа шагов алгоритма и ограничена сверху &amp;lt;math&amp;gt;O(ln(|V|))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j},&lt;br /&gt;
v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.&lt;br /&gt;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &amp;lt;math&amp;gt;\log_2 n&amp;lt;/math&amp;gt;. Отсюда следует и оценка сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
На этапе поиска минимального ребра происходят следующие обращения к памяти:&lt;br /&gt;
# Чтение информации о рёбрах. Может производиться последовательно.&lt;br /&gt;
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; с вероятным промахом по кэшу.&lt;br /&gt;
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.&lt;br /&gt;
&lt;br /&gt;
На этапе схлопывания фрагментов требуется атомарно обновить массив &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.&lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_1.png|thumb|center|700px|Рисунок 3. Алгоритм Борувки. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.&lt;br /&gt;
&lt;br /&gt;
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 3 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.&lt;br /&gt;
&lt;br /&gt;
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 3), которая показана на рис. 4. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 4 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее перейдем к изучению фрагмента 2 (рис. 5). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_3.png|thumb|center|700px|Рисунок 5. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 6 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_4.png|thumb|center|700px|Рисунок 6. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
Отдельное рассмотрение фрагмента 4 (рис. 7) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_5.png|thumb|center|500px|Рисунок 7. Профиль обращений, фрагмент 4]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 8 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 8. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм Борувки, состоит из двух частей: &lt;br /&gt;
&lt;br /&gt;
1. части, отвечающей за общую координацию вычислений&lt;br /&gt;
&lt;br /&gt;
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации &amp;lt;math&amp;gt;MERGE&amp;lt;/math&amp;gt; результаты операций &amp;lt;math&amp;gt;FIND(u)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.&lt;br /&gt;
&lt;br /&gt;
1.	Каждой вершине v соответствует запись &amp;lt;math&amp;gt;A[v] = { parent, rank }&amp;lt;/math&amp;gt;. Изначально &amp;lt;math&amp;gt;A[v] := { v, 0 }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Вспомогательная операция &amp;lt;math&amp;gt;UPDATE(v, rank_v, u, rank_u)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
old := A[v]&lt;br /&gt;
&lt;br /&gt;
if old.parent != v or old.rank != rank_v then return false&lt;br /&gt;
&lt;br /&gt;
new := { u, rank_u }&lt;br /&gt;
&lt;br /&gt;
return CAS(A[v], old, new)&lt;br /&gt;
&lt;br /&gt;
3.	Операция &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    while v != A[v].parent do&lt;br /&gt;
        u := A[v].parent&lt;br /&gt;
        CAS(A[v].parent, u, A[u].parent)&lt;br /&gt;
        v := A[u].parent&lt;br /&gt;
    return v&lt;br /&gt;
&lt;br /&gt;
4.	Операция UNION(u, v):&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        (u, v) := (FIND(u), FIND(v))&lt;br /&gt;
        if u = v then return false&lt;br /&gt;
        (rank_u, rank_v) := (A[u].rank, A[v].rank)&lt;br /&gt;
        if (A[u].rank, u) &amp;gt; (A[v].rank, v) then&lt;br /&gt;
            swap((u, rank_u), (v, rank_v))&lt;br /&gt;
        if UPDATE(u, rank_u, v, rank_u) then&lt;br /&gt;
            if rank_u = rank_v then&lt;br /&gt;
                UPDATE(v, rank_v, v, rank_v + 1)&lt;br /&gt;
            return true&lt;br /&gt;
&lt;br /&gt;
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются&lt;br /&gt;
# пропускная способность памяти при чтении данных графа&lt;br /&gt;
# соперничество потоков при выполнении атомарных операций с памятью&lt;br /&gt;
# барьерная синхронизация после каждого подшага алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2&amp;quot; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Борувки, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Борувки, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Boruvka cpu.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 10%. Это нормальная картина для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Loadavg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 2. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 2 процессами, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 40 млн/сек . Интересен факт увеличения числа промахов к концу итерации до уровня в 70 млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже высокое и находится на уровне 30 млн/сек . На графике промахов второго уровня факт увеличения числа промахов к концу итерации проявляется более явно и увеличивается до значения в 50млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов L3 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Эт указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Inf data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka inf pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают алгоритм Борувки и [[алгоритм Крускала]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Boruvka&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24376</id>
		<title>Алгоритм Борувки</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24376"/>
		<updated>2017-11-28T08:23:50Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Локальность данных и вычислений */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Борувки&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;max O(ln(|V|)) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Борувки&#039;&#039;&#039;&amp;lt;ref&amp;gt;Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.&amp;lt;/ref&amp;gt; предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный неориентированный граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt;. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).&lt;br /&gt;
&lt;br /&gt;
Алгоритм Борувки основан на следующих двух свойствах задачи:&lt;br /&gt;
* &#039;&#039;&#039;Минимальное ребро фрагмента&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева и &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; – ребро наименьшего веса, исходящее из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; (т.е. ровно один его конец является вершиной из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;). Если ребро &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; единственно, то оно принадлежит минимальному остовному дереву.&lt;br /&gt;
* &#039;&#039;&#039;Схлопывание фрагментов&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а граф &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; получен из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; склеиванием вершин, принадлежащих &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Тогда объединение &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и минимального остовного дерева графа &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; даёт минимальное остовное дерево исходного графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма каждая вершина графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основными операциями являются:&lt;br /&gt;
# Поиск минимального по весу исходящего ребра в каждом фрагменте.&lt;br /&gt;
# Объединение фрагментов.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. &lt;br /&gt;
&lt;br /&gt;
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный граф &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;V = ( v_{1}, v_{2}, ..., v_{n} )&amp;lt;/math&amp;gt; и рёбрами &amp;lt;math&amp;gt;E = ( e_{1}, e_{2}, ..., e_{m} )&amp;lt;/math&amp;gt;. Каждому ребру &amp;lt;math&amp;gt;e \in E&amp;lt;/math&amp;gt; приписан вес &amp;lt;math&amp;gt;w(e)&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Требуется построить дерево &amp;lt;math&amp;gt;T^* \subseteq E&amp;lt;/math&amp;gt;, связывающее все вершины и имеющее наименьший возможный вес среди всех таких деревьев:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; w(T^* )= \min_T( w(T)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
где вес множества рёбер есть сумма их весов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;w(T)=\sum_{e \in T} (w(T))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если граф &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; не является связным, то дерева, связывающего все вершины, не существует. &lt;br /&gt;
&lt;br /&gt;
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).&lt;br /&gt;
&lt;br /&gt;
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====&lt;br /&gt;
&lt;br /&gt;
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:&lt;br /&gt;
&lt;br /&gt;
1.	&amp;lt;math&amp;gt;FIND(v) = w&amp;lt;/math&amp;gt; – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда &amp;lt;math&amp;gt;FIND(u) = FIND(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; – объединяет два фрагмента, которым принадлежат вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v.&amp;lt;/math&amp;gt; (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.&lt;br /&gt;
&lt;br /&gt;
==== Последовательная версия====&lt;br /&gt;
&lt;br /&gt;
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя &amp;lt;math&amp;gt;parent(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
1.	Изначально &amp;lt;math&amp;gt;parent(v) := v&amp;lt;/math&amp;gt; для всех вершин.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; выполняется следующим образом: полагаем &amp;lt;math&amp;gt;u := v&amp;lt;/math&amp;gt;, и далее следуем по указателям &amp;lt;math&amp;gt;u := parent(u)&amp;lt;/math&amp;gt; до тех пор, пока не станет &amp;lt;math&amp;gt;u = parent(u)&amp;lt;/math&amp;gt;. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: &amp;lt;math&amp;gt;parent(u_i) := u&amp;lt;/math&amp;gt;, либо производить схлопывание по пути: &amp;lt;math&amp;gt;parent(u) := parent(parent(u)))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; выполняется следующим образом: вначале находим корневые вершины &amp;lt;math&amp;gt;u := FIND(u), v := FIND(v)&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;u = v&amp;lt;/math&amp;gt;, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из &amp;lt;math&amp;gt;parent(u) := v&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;parent(v) := u&amp;lt;/math&amp;gt;. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.&lt;br /&gt;
&lt;br /&gt;
1.	В самом начале каждая вершина является отдельным фрагментом.&lt;br /&gt;
&lt;br /&gt;
2.	На каждом шаге:&lt;br /&gt;
&lt;br /&gt;
*	Для каждого фрагмента определяется минимальное по весу исходящее ребро.&lt;br /&gt;
&lt;br /&gt;
*	Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм останавливается, когда остаётся только один фрагмент либо когда ни у одного из фрагментов нет исходящих рёбер.&lt;br /&gt;
&lt;br /&gt;
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).&lt;br /&gt;
&lt;br /&gt;
Объединение фрагментов также может быть реализовано параллельно с использованием параллельной версии описанного выше алгоритма Union-Find.&lt;br /&gt;
&lt;br /&gt;
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:&lt;br /&gt;
&lt;br /&gt;
1.	В начале итерации счётчик активных фрагментов обнуляется.&lt;br /&gt;
&lt;br /&gt;
2.	На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.&lt;br /&gt;
&lt;br /&gt;
3.	На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция &amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; вернула значение истина.&lt;br /&gt;
&lt;br /&gt;
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.&lt;br /&gt;
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Последовательная сложность алгоритма Борувки для графа с &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершинами и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбрами составляет &amp;lt;math&amp;gt;O(|E| \ln(|V|))&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний) и параллелизм в алгоритме обработки графа, не помещающегося в память.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Нижний уровень параллелизма&#039;&#039;&#039;: поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно с использованием описанного выше алгоритма Union-Find. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Верхний уровень параллелизма&#039;&#039;&#039;: построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. &lt;br /&gt;
&lt;br /&gt;
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим информационные графы и подробное описание каждого из них. Также можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующих операциям параллельного поиска минимальных исходящих ребер, а также на уровнях {6, 7, 8}, соответствующих операциям параллельного объединения деревьев. Также  различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе переход к следующей итерации. &lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального остовного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные операции compute mst. Затем, после параллельных вычислений mst, происходит вычисление итогового основного дерева, после чего полученный результат сохраняется (save results).&lt;br /&gt;
&lt;br /&gt;
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм Борувки обладает двумя уровнями параллелизма.&lt;br /&gt;
&lt;br /&gt;
На верхнем уровне минимальные остовные деревья могут искаться для отдельных частей списка ребер графа (параллельные compute_MST на рисунке 2). Однако затем должно последовать финальное объединение полученных ребер и вычисление минимального остовного дерева для полученного графа, которое будет производиться последовательно.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вычисление каждого из минимальных остовных деревьев (параллельные compute_MST на рисунке 2) обладает внутренним ресурсом параллелизма, описанным далее. Операции инициализации и копирования данных ([1], [2], [9]  на рисунке 1) могут производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Также параллельно могут производиться операции поиска минимальных исходящих ребер ([3],[4],[5]), при этом для каждой дуги и обратной к ней независимо, что даёт &amp;lt;math&amp;gt;2*O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Помимо этого, операции объединения деревьев [6], [7], [8] могут также производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
В результате для классического алгоритма Борувки ширина ярусно-параллельной формы равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, а высота ЯПФ зависит от числа шагов алгоритма и ограничена сверху &amp;lt;math&amp;gt;O(ln(|V|))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j},&lt;br /&gt;
v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.&lt;br /&gt;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &amp;lt;math&amp;gt;\log_2 n&amp;lt;/math&amp;gt;. Отсюда следует и оценка сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
На этапе поиска минимального ребра происходят следующие обращения к памяти:&lt;br /&gt;
# Чтение информации о рёбрах. Может производиться последовательно.&lt;br /&gt;
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; с вероятным промахом по кэшу.&lt;br /&gt;
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.&lt;br /&gt;
&lt;br /&gt;
На этапе схлопывания фрагментов требуется атомарно обновить массив &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.&lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_1.png|thumb|center|700px|Рисунок 3. Алгоритм Борувки. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.&lt;br /&gt;
&lt;br /&gt;
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 3 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.&lt;br /&gt;
&lt;br /&gt;
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 3), которая показана на рис. 4. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 4 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее перейдем к изучению фрагмента 2 (рис. 5). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_3.png|thumb|center|700px|Рисунок 5. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 6 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_4.png|thumb|center|700px|Рисунок 6. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
Отдельное рассмотрение фрагмента 4 (рис. 7) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_5.png|thumb|center|500px|Рисунок 7. Профиль обращений, фрагмент 4]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 8 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 8. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм Борувки, состоит из двух частей: &lt;br /&gt;
&lt;br /&gt;
1. части, отвечающей за общую координацию вычислений&lt;br /&gt;
&lt;br /&gt;
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации &amp;lt;math&amp;gt;MERGE&amp;lt;/math&amp;gt; результаты операций &amp;lt;math&amp;gt;FIND(u)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.&lt;br /&gt;
&lt;br /&gt;
1.	Каждой вершине v соответствует запись &amp;lt;math&amp;gt;A[v] = { parent, rank }&amp;lt;/math&amp;gt;. Изначально &amp;lt;math&amp;gt;A[v] := { v, 0 }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Вспомогательная операция &amp;lt;math&amp;gt;UPDATE(v, rank_v, u, rank_u)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
old := A[v]&lt;br /&gt;
&lt;br /&gt;
if old.parent != v or old.rank != rank_v then return false&lt;br /&gt;
&lt;br /&gt;
new := { u, rank_u }&lt;br /&gt;
&lt;br /&gt;
return CAS(A[v], old, new)&lt;br /&gt;
&lt;br /&gt;
3.	Операция &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    while v != A[v].parent do&lt;br /&gt;
        u := A[v].parent&lt;br /&gt;
        CAS(A[v].parent, u, A[u].parent)&lt;br /&gt;
        v := A[u].parent&lt;br /&gt;
    return v&lt;br /&gt;
&lt;br /&gt;
4.	Операция UNION(u, v):&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        (u, v) := (FIND(u), FIND(v))&lt;br /&gt;
        if u = v then return false&lt;br /&gt;
        (rank_u, rank_v) := (A[u].rank, A[v].rank)&lt;br /&gt;
        if (A[u].rank, u) &amp;gt; (A[v].rank, v) then&lt;br /&gt;
            swap((u, rank_u), (v, rank_v))&lt;br /&gt;
        if UPDATE(u, rank_u, v, rank_u) then&lt;br /&gt;
            if rank_u = rank_v then&lt;br /&gt;
                UPDATE(v, rank_v, v, rank_v + 1)&lt;br /&gt;
            return true&lt;br /&gt;
&lt;br /&gt;
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются&lt;br /&gt;
# пропускная способность памяти при чтении данных графа&lt;br /&gt;
# соперничество потоков при выполнении атомарных операций с памятью&lt;br /&gt;
# барьерная синхронизация после каждого подшага алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Борувки, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Борувки, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Boruvka cpu.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 10%. Это нормальная картина для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Loadavg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 2. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 2 процессами, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 40 млн/сек . Интересен факт увеличения числа промахов к концу итерации до уровня в 70 млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже высокое и находится на уровне 30 млн/сек . На графике промахов второго уровня факт увеличения числа промахов к концу итерации проявляется более явно и увеличивается до значения в 50млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов L3 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Эт указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Inf data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka inf pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают алгоритм Борувки и [[алгоритм Крускала]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Boruvka&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24354</id>
		<title>Алгоритм Борувки</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24354"/>
		<updated>2017-11-27T16:36:59Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Борувки&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;max O(ln(|V|)) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Борувки&#039;&#039;&#039;&amp;lt;ref&amp;gt;Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.&amp;lt;/ref&amp;gt; предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный неориентированный граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt;. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).&lt;br /&gt;
&lt;br /&gt;
Алгоритм Борувки основан на следующих двух свойствах задачи:&lt;br /&gt;
* &#039;&#039;&#039;Минимальное ребро фрагмента&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева и &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; – ребро наименьшего веса, исходящее из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; (т.е. ровно один его конец является вершиной из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;). Если ребро &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; единственно, то оно принадлежит минимальному остовному дереву.&lt;br /&gt;
* &#039;&#039;&#039;Схлопывание фрагментов&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а граф &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; получен из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; склеиванием вершин, принадлежащих &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Тогда объединение &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и минимального остовного дерева графа &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; даёт минимальное остовное дерево исходного графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма каждая вершина графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основными операциями являются:&lt;br /&gt;
# Поиск минимального по весу исходящего ребра в каждом фрагменте.&lt;br /&gt;
# Объединение фрагментов.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. &lt;br /&gt;
&lt;br /&gt;
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный граф &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;V = ( v_{1}, v_{2}, ..., v_{n} )&amp;lt;/math&amp;gt; и рёбрами &amp;lt;math&amp;gt;E = ( e_{1}, e_{2}, ..., e_{m} )&amp;lt;/math&amp;gt;. Каждому ребру &amp;lt;math&amp;gt;e \in E&amp;lt;/math&amp;gt; приписан вес &amp;lt;math&amp;gt;w(e)&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Требуется построить дерево &amp;lt;math&amp;gt;T^* \subseteq E&amp;lt;/math&amp;gt;, связывающее все вершины и имеющее наименьший возможный вес среди всех таких деревьев:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; w(T^* )= \min_T( w(T)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
где вес множества рёбер есть сумма их весов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;w(T)=\sum_{e \in T} (w(T))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если граф &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; не является связным, то дерева, связывающего все вершины, не существует. &lt;br /&gt;
&lt;br /&gt;
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).&lt;br /&gt;
&lt;br /&gt;
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====&lt;br /&gt;
&lt;br /&gt;
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:&lt;br /&gt;
&lt;br /&gt;
1.	&amp;lt;math&amp;gt;FIND(v) = w&amp;lt;/math&amp;gt; – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда &amp;lt;math&amp;gt;FIND(u) = FIND(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; – объединяет два фрагмента, которым принадлежат вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v.&amp;lt;/math&amp;gt; (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.&lt;br /&gt;
&lt;br /&gt;
==== Последовательная версия====&lt;br /&gt;
&lt;br /&gt;
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя &amp;lt;math&amp;gt;parent(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
1.	Изначально &amp;lt;math&amp;gt;parent(v) := v&amp;lt;/math&amp;gt; для всех вершин.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; выполняется следующим образом: полагаем &amp;lt;math&amp;gt;u := v&amp;lt;/math&amp;gt;, и далее следуем по указателям &amp;lt;math&amp;gt;u := parent(u)&amp;lt;/math&amp;gt; до тех пор, пока не станет &amp;lt;math&amp;gt;u = parent(u)&amp;lt;/math&amp;gt;. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: &amp;lt;math&amp;gt;parent(u_i) := u&amp;lt;/math&amp;gt;, либо производить схлопывание по пути: &amp;lt;math&amp;gt;parent(u) := parent(parent(u)))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; выполняется следующим образом: вначале находим корневые вершины &amp;lt;math&amp;gt;u := FIND(u), v := FIND(v)&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;u = v&amp;lt;/math&amp;gt;, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из &amp;lt;math&amp;gt;parent(u) := v&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;parent(v) := u&amp;lt;/math&amp;gt;. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.&lt;br /&gt;
&lt;br /&gt;
1.	В самом начале каждая вершина является отдельным фрагментом.&lt;br /&gt;
&lt;br /&gt;
2.	На каждом шаге:&lt;br /&gt;
&lt;br /&gt;
*	Для каждого фрагмента определяется минимальное по весу исходящее ребро.&lt;br /&gt;
&lt;br /&gt;
*	Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм останавливается, когда остаётся только один фрагмент либо когда ни у одного из фрагментов нет исходящих рёбер.&lt;br /&gt;
&lt;br /&gt;
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).&lt;br /&gt;
&lt;br /&gt;
Объединение фрагментов также может быть реализовано параллельно с использованием параллельной версии описанного выше алгоритма Union-Find.&lt;br /&gt;
&lt;br /&gt;
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:&lt;br /&gt;
&lt;br /&gt;
1.	В начале итерации счётчик активных фрагментов обнуляется.&lt;br /&gt;
&lt;br /&gt;
2.	На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.&lt;br /&gt;
&lt;br /&gt;
3.	На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция &amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; вернула значение истина.&lt;br /&gt;
&lt;br /&gt;
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.&lt;br /&gt;
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Последовательная сложность алгоритма Борувки для графа с &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершинами и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбрами составляет &amp;lt;math&amp;gt;O(|E| \ln(|V|))&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний) и параллелизм в алгоритме обработки графа, не помещающегося в память.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Нижний уровень параллелизма&#039;&#039;&#039;: поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно с использованием описанного выше алгоритма Union-Find. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Верхний уровень параллелизма&#039;&#039;&#039;: построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. &lt;br /&gt;
&lt;br /&gt;
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим информационные графы и подробное описание каждого из них. Также можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующих операциям параллельного поиска минимальных исходящих ребер, а также на уровнях {6, 7, 8}, соответствующих операциям параллельного объединения деревьев. Также  различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе переход к следующей итерации. &lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального остовного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные операции compute mst. Затем, после параллельных вычислений mst, происходит вычисление итогового основного дерева, после чего полученный результат сохраняется (save results).&lt;br /&gt;
&lt;br /&gt;
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм Борувки обладает двумя уровнями параллелизма.&lt;br /&gt;
&lt;br /&gt;
На верхнем уровне минимальные остовные деревья могут искаться для отдельных частей списка ребер графа (параллельные compute_MST на рисунке 2). Однако затем должно последовать финальное объединение полученных ребер и вычисление минимального остовного дерева для полученного графа, которое будет производиться последовательно.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вычисление каждого из минимальных остовных деревьев (параллельные compute_MST на рисунке 2) обладает внутренним ресурсом параллелизма, описанным далее. Операции инициализации и копирования данных ([1], [2], [9]  на рисунке 1) могут производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Также параллельно могут производиться операции поиска минимальных исходящих ребер ([3],[4],[5]), при этом для каждой дуги и обратной к ней независимо, что даёт &amp;lt;math&amp;gt;2*O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Помимо этого, операции объединения деревьев [6], [7], [8] могут также производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
В результате для классического алгоритма Борувки ширина ярусно-параллельной формы равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, а высота ЯПФ зависит от числа шагов алгоритма и ограничена сверху &amp;lt;math&amp;gt;O(ln(|V|))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j},&lt;br /&gt;
v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.&lt;br /&gt;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &amp;lt;math&amp;gt;\log_2 n&amp;lt;/math&amp;gt;. Отсюда следует и оценка сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
На этапе поиска минимального ребра происходят следующие обращения к памяти:&lt;br /&gt;
# Чтение информации о рёбрах. Может производится последовательно.&lt;br /&gt;
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; с вероятным промахой по кэшу.&lt;br /&gt;
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.&lt;br /&gt;
&lt;br /&gt;
На этапе схлопывания фрагментов требуется атомарно обновить массив &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.&lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_1.png|thumb|center|700px|Рисунок 3. Алгоритм Борувки. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.&lt;br /&gt;
&lt;br /&gt;
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 3 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.&lt;br /&gt;
&lt;br /&gt;
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 3), которая показана на рис. 4. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 4 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее перейдем к изучению фрагмента 2 (рис. 5). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_3.png|thumb|center|700px|Рисунок 5. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 6 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_4.png|thumb|center|700px|Рисунок 6. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
Отдельное рассмотрение фрагмента 4 (рис. 7) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_5.png|thumb|center|500px|Рисунок 7. Профиль обращений, фрагмент 4]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 8 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 8. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм Борувки, состоит из двух частей: &lt;br /&gt;
&lt;br /&gt;
1. части, отвечающей за общую координацию вычислений&lt;br /&gt;
&lt;br /&gt;
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации &amp;lt;math&amp;gt;MERGE&amp;lt;/math&amp;gt; результаты операций &amp;lt;math&amp;gt;FIND(u)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.&lt;br /&gt;
&lt;br /&gt;
1.	Каждой вершине v соответствует запись &amp;lt;math&amp;gt;A[v] = { parent, rank }&amp;lt;/math&amp;gt;. Изначально &amp;lt;math&amp;gt;A[v] := { v, 0 }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Вспомогательная операция &amp;lt;math&amp;gt;UPDATE(v, rank_v, u, rank_u)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
old := A[v]&lt;br /&gt;
&lt;br /&gt;
if old.parent != v or old.rank != rank_v then return false&lt;br /&gt;
&lt;br /&gt;
new := { u, rank_u }&lt;br /&gt;
&lt;br /&gt;
return CAS(A[v], old, new)&lt;br /&gt;
&lt;br /&gt;
3.	Операция &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    while v != A[v].parent do&lt;br /&gt;
        u := A[v].parent&lt;br /&gt;
        CAS(A[v].parent, u, A[u].parent)&lt;br /&gt;
        v := A[u].parent&lt;br /&gt;
    return v&lt;br /&gt;
&lt;br /&gt;
4.	Операция UNION(u, v):&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        (u, v) := (FIND(u), FIND(v))&lt;br /&gt;
        if u = v then return false&lt;br /&gt;
        (rank_u, rank_v) := (A[u].rank, A[v].rank)&lt;br /&gt;
        if (A[u].rank, u) &amp;gt; (A[v].rank, v) then&lt;br /&gt;
            swap((u, rank_u), (v, rank_v))&lt;br /&gt;
        if UPDATE(u, rank_u, v, rank_u) then&lt;br /&gt;
            if rank_u = rank_v then&lt;br /&gt;
                UPDATE(v, rank_v, v, rank_v + 1)&lt;br /&gt;
            return true&lt;br /&gt;
&lt;br /&gt;
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются&lt;br /&gt;
# пропускная способность памяти при чтении данных графа&lt;br /&gt;
# соперничество потоков при выполнении атомарных операций с памятью&lt;br /&gt;
# барьерная синхронизация после каждого подшага алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Борувки, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Борувки, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Boruvka cpu.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 10%. Это нормальная картина для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Loadavg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 2. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 2 процессами, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 40 млн/сек . Интересен факт увеличения числа промахов к концу итерации до уровня в 70 млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже высокое и находится на уровне 30 млн/сек . На графике промахов второго уровня факт увеличения числа промахов к концу итерации проявляется более явно и увеличивается до значения в 50млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов L3 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Эт указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Inf data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka inf pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают алгоритм Борувки и [[алгоритм Крускала]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Boruvka&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24346</id>
		<title>Алгоритм Борувки</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24346"/>
		<updated>2017-11-27T13:45:13Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Борувки&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;max O(ln(|V|)) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Борувки&#039;&#039;&#039;&amp;lt;ref&amp;gt;Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.&amp;lt;/ref&amp;gt; предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный неориентированный граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt;. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).&lt;br /&gt;
&lt;br /&gt;
Алгоритм Борувки основан на следующих двух свойствах задачи:&lt;br /&gt;
* &#039;&#039;&#039;Минимальное ребро фрагмента&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева и &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; – ребро наименьшего веса, исходящее из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; (т.е. ровно один его конец является вершиной из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;). Если ребро &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; единственно, то оно принадлежит минимальному остовному дереву.&lt;br /&gt;
* &#039;&#039;&#039;Схлопывание фрагментов&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а граф &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; получен из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; склеиванием вершин, принадлежащих &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Тогда объединение &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и минимального остовного дерева графа &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; даёт минимальное остовное дерево исходного графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма каждая вершина графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основными операциями являются:&lt;br /&gt;
# Поиск минимального по весу исходящего ребра в каждом фрагменте.&lt;br /&gt;
# Объединение фрагментов.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. &lt;br /&gt;
&lt;br /&gt;
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный граф &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;V = ( v_{1}, v_{2}, ..., v_{n} )&amp;lt;/math&amp;gt; и рёбрами &amp;lt;math&amp;gt;E = ( e_{1}, e_{2}, ..., e_{m} )&amp;lt;/math&amp;gt;. Каждому ребру &amp;lt;math&amp;gt;e \in E&amp;lt;/math&amp;gt; приписан вес &amp;lt;math&amp;gt;w(e)&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Требуется построить дерево &amp;lt;math&amp;gt;T^* \subseteq E&amp;lt;/math&amp;gt;, связывающее все вершины и имеющее наименьший возможный вес среди всех таких деревьев:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; w(T^* )= \min_T( w(T)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
где вес множества рёбер есть сумма их весов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;w(T)=\sum_{e \in T} (w(T))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если граф &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; не является связным, то дерева, связывающего все вершины, не существует. &lt;br /&gt;
&lt;br /&gt;
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).&lt;br /&gt;
&lt;br /&gt;
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====&lt;br /&gt;
&lt;br /&gt;
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:&lt;br /&gt;
&lt;br /&gt;
1.	&amp;lt;math&amp;gt;FIND(v) = w&amp;lt;/math&amp;gt; – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда &amp;lt;math&amp;gt;FIND(u) = FIND(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; – объединяет два фрагмента, которым принадлежат вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v.&amp;lt;/math&amp;gt; (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.&lt;br /&gt;
&lt;br /&gt;
==== Последовательная версия====&lt;br /&gt;
&lt;br /&gt;
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя &amp;lt;math&amp;gt;parent(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
1.	Изначально &amp;lt;math&amp;gt;parent(v) := v&amp;lt;/math&amp;gt; для всех вершин.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; выполняется следующим образом: полагаем &amp;lt;math&amp;gt;u := v&amp;lt;/math&amp;gt;, и далее следуем по указателям &amp;lt;math&amp;gt;u := parent(u)&amp;lt;/math&amp;gt; до тех пор, пока не станет &amp;lt;math&amp;gt;u = parent(u)&amp;lt;/math&amp;gt;. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: &amp;lt;math&amp;gt;parent(u_i) := u&amp;lt;/math&amp;gt;, либо производить схлопывание по пути: &amp;lt;math&amp;gt;parent(u) := parent(parent(u)))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; выполняется следующим образом: вначале находим корневые вершины &amp;lt;math&amp;gt;u := FIND(u), v := FIND(v)&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;u = v&amp;lt;/math&amp;gt;, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из &amp;lt;math&amp;gt;parent(u) := v&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;parent(v) := u&amp;lt;/math&amp;gt;. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.&lt;br /&gt;
&lt;br /&gt;
1.	В самом начале каждая вершина является отдельным фрагментом.&lt;br /&gt;
&lt;br /&gt;
2.	На каждом шаге:&lt;br /&gt;
&lt;br /&gt;
*	Для каждого фрагмента определяется минимальное по весу исходящее ребро.&lt;br /&gt;
&lt;br /&gt;
*	Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм останавливается, когда остаётся только один фрагмент либо когда ни у одного из фрагментов нет исходящих рёбер.&lt;br /&gt;
&lt;br /&gt;
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).&lt;br /&gt;
&lt;br /&gt;
Объединение фрагментов также может быть реализовано параллельно с использованием параллельной версии описанного выше алгоритма Union-Find.&lt;br /&gt;
&lt;br /&gt;
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:&lt;br /&gt;
&lt;br /&gt;
1.	В начале итерации счётчик активных фрагментов обнуляется.&lt;br /&gt;
&lt;br /&gt;
2.	На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.&lt;br /&gt;
&lt;br /&gt;
3.	На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция &amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; вернула значение истина.&lt;br /&gt;
&lt;br /&gt;
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.&lt;br /&gt;
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Последовательная сложность алгоритма Борувки для графа с &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершинами и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбрами составляет &amp;lt;math&amp;gt;O(|E| \ln(|V|))&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний) и параллелизм в алгоритме обработки графа, не помещающегося в память.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Нижний уровень параллелизма&#039;&#039;&#039;: поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно с использованием описанного выше алгоритма Union-Find. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Верхний уровень параллелизма&#039;&#039;&#039;: построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. &lt;br /&gt;
&lt;br /&gt;
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим информационные графы и подробное описание каждого из них. Также можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующих операциям параллельного поиска минимальных исходящих ребер, а также на уровнях {6, 7, 8}, соответствующих операциям параллельного объединения деревьев. Также  различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе переход к следующей итерации. &lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального остовного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные операции compute mst. Затем, после параллельных вычислений mst, происходит вычисление итогового основного дерева, после чего полученный результат сохраняется (save results).&lt;br /&gt;
&lt;br /&gt;
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм Борувки обладает двумя уровнями параллелизма.&lt;br /&gt;
&lt;br /&gt;
На верхнем уровне минимальные остовные деревья могут искаться для отдельных частей списка ребер графа (параллельные compute_MST на рисунке 2). Однако, затем должно последовать финальное объединение полученных ребер и вычисление минимального остовного дерева для полученного графа, которое будет производиться последовательно.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вычисление каждого из минимальных остовных деревьев (параллельные compute_MST на рисунке 2) обладает внутренним ресурсом параллелизма, описанным далее. Операции инициализации и копирования данных ([1], [2], [9]  на рисунке 1) могут производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Так же параллельно могут производиться операции поиска минимальных исходящих ребер ([3],[4],[5]), при том для каждой дуги и обратной к ней независимо, что даёт &amp;lt;math&amp;gt;2*O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Помимо этого, операции объединения деревьев [6], [7], [8] могут так же производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
В результате, для классического алгоритма Борувки ширина ярусно-параллельной формы равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, а высота ЯПФ зависит от числа шагов алгоритма, и ограничена сверху &amp;lt;math&amp;gt;O(ln(|V|))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j},&lt;br /&gt;
v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.&lt;br /&gt;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &amp;lt;math&amp;gt;\log_2 n&amp;lt;/math&amp;gt;. Отсюда следует и оценка сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
На этапе поиска минимального ребра происходят следующие обращения к памяти:&lt;br /&gt;
# Чтение информации о рёбрах. Может производится последовательно.&lt;br /&gt;
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; с вероятным промахой по кэшу.&lt;br /&gt;
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.&lt;br /&gt;
&lt;br /&gt;
На этапе схлопывания фрагментов требуется атомарно обновить массив &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.&lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_1.png|thumb|center|700px|Рисунок 3. Алгоритм Борувки. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.&lt;br /&gt;
&lt;br /&gt;
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 3 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.&lt;br /&gt;
&lt;br /&gt;
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 3), которая показана на рис. 4. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 4 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее перейдем к изучению фрагмента 2 (рис. 5). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_3.png|thumb|center|700px|Рисунок 5. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 6 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_4.png|thumb|center|700px|Рисунок 6. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
Отдельное рассмотрение фрагмента 4 (рис. 7) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_5.png|thumb|center|500px|Рисунок 7. Профиль обращений, фрагмент 4]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 8 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 8. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм Борувки, состоит из двух частей: &lt;br /&gt;
&lt;br /&gt;
1. части, отвечающей за общую координацию вычислений&lt;br /&gt;
&lt;br /&gt;
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации &amp;lt;math&amp;gt;MERGE&amp;lt;/math&amp;gt; результаты операций &amp;lt;math&amp;gt;FIND(u)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.&lt;br /&gt;
&lt;br /&gt;
1.	Каждой вершине v соответствует запись &amp;lt;math&amp;gt;A[v] = { parent, rank }&amp;lt;/math&amp;gt;. Изначально &amp;lt;math&amp;gt;A[v] := { v, 0 }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Вспомогательная операция &amp;lt;math&amp;gt;UPDATE(v, rank_v, u, rank_u)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
old := A[v]&lt;br /&gt;
&lt;br /&gt;
if old.parent != v or old.rank != rank_v then return false&lt;br /&gt;
&lt;br /&gt;
new := { u, rank_u }&lt;br /&gt;
&lt;br /&gt;
return CAS(A[v], old, new)&lt;br /&gt;
&lt;br /&gt;
3.	Операция &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    while v != A[v].parent do&lt;br /&gt;
        u := A[v].parent&lt;br /&gt;
        CAS(A[v].parent, u, A[u].parent)&lt;br /&gt;
        v := A[u].parent&lt;br /&gt;
    return v&lt;br /&gt;
&lt;br /&gt;
4.	Операция UNION(u, v):&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        (u, v) := (FIND(u), FIND(v))&lt;br /&gt;
        if u = v then return false&lt;br /&gt;
        (rank_u, rank_v) := (A[u].rank, A[v].rank)&lt;br /&gt;
        if (A[u].rank, u) &amp;gt; (A[v].rank, v) then&lt;br /&gt;
            swap((u, rank_u), (v, rank_v))&lt;br /&gt;
        if UPDATE(u, rank_u, v, rank_u) then&lt;br /&gt;
            if rank_u = rank_v then&lt;br /&gt;
                UPDATE(v, rank_v, v, rank_v + 1)&lt;br /&gt;
            return true&lt;br /&gt;
&lt;br /&gt;
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются&lt;br /&gt;
# пропускная способность памяти при чтении данных графа&lt;br /&gt;
# соперничество потоков при выполнении атомарных операций с памятью&lt;br /&gt;
# барьерная синхронизация после каждого подшага алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Борувки, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Борувки, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Boruvka cpu.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 10%. Это нормальная картина для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Loadavg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 2. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 2 процессами, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 40 млн/сек . Интересен факт увеличения числа промахов к концу итерации до уровня в 70 млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже высокое и находится на уровне 30 млн/сек . На графике промахов второго уровня факт увеличения числа промахов к концу итерации проявляется более явно и увеличивается до значения в 50млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов L3 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Эт указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Inf data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka inf pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают алгоритм Борувки и [[алгоритм Крускала]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Boruvka&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24333</id>
		<title>Алгоритм Борувки</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24333"/>
		<updated>2017-11-27T12:37:44Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Схема реализации последовательного алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Борувки&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;max O(ln(|V|)) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Борувки&#039;&#039;&#039;&amp;lt;ref&amp;gt;Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.&amp;lt;/ref&amp;gt; предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный неориентированный граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt;. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).&lt;br /&gt;
&lt;br /&gt;
Алгоритм Борувки основан на следующих двух свойствах задачи:&lt;br /&gt;
* &#039;&#039;&#039;Минимальное ребро фрагмента&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева и &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; – ребро наименьшего веса, исходящее из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; (т.е. ровно один его конец является вершиной из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;). Если ребро &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; единственно, то оно принадлежит минимальному остовному дереву.&lt;br /&gt;
* &#039;&#039;&#039;Схлопывание фрагментов&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а граф &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; получен из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; склеиванием вершин, принадлежащих &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Тогда объединение &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и минимального остовного дерева графа &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; даёт минимальное остовное дерево исходного графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма каждая вершина графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основными операциями являются:&lt;br /&gt;
# Поиск минимального по весу исходящего ребра в каждом фрагменте.&lt;br /&gt;
# Объединение фрагментов.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. &lt;br /&gt;
&lt;br /&gt;
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный граф &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;V = ( v_{1}, v_{2}, ..., v_{n} )&amp;lt;/math&amp;gt; и рёбрами &amp;lt;math&amp;gt;E = ( e_{1}, e_{2}, ..., e_{m} )&amp;lt;/math&amp;gt;. Каждому ребру &amp;lt;math&amp;gt;e \in E&amp;lt;/math&amp;gt; приписан вес &amp;lt;math&amp;gt;w(e)&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Требуется построить дерево &amp;lt;math&amp;gt;T^* \subseteq E&amp;lt;/math&amp;gt;, связывающее все вершины и имеющее наименьший возможный вес среди всех таких деревьев:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; w(T^* )= \min_T( w(T)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
где вес множества рёбер есть сумма их весов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;w(T)=\sum_{e \in T} (w(T))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если граф &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; не является связным, то дерева, связывающего все вершины, не существует. &lt;br /&gt;
&lt;br /&gt;
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).&lt;br /&gt;
&lt;br /&gt;
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====&lt;br /&gt;
&lt;br /&gt;
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:&lt;br /&gt;
&lt;br /&gt;
1.	&amp;lt;math&amp;gt;FIND(v) = w&amp;lt;/math&amp;gt; – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда &amp;lt;math&amp;gt;FIND(u) = FIND(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; – объединяет два фрагмента, которым принадлежат вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v.&amp;lt;/math&amp;gt; (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.&lt;br /&gt;
&lt;br /&gt;
==== Последовательная версия====&lt;br /&gt;
&lt;br /&gt;
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя &amp;lt;math&amp;gt;parent(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
1.	Изначально &amp;lt;math&amp;gt;parent(v) := v&amp;lt;/math&amp;gt; для всех вершин.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; выполняется следующим образом: полагаем &amp;lt;math&amp;gt;u := v&amp;lt;/math&amp;gt;, и далее следуем по указателям &amp;lt;math&amp;gt;u := parent(u)&amp;lt;/math&amp;gt; до тех пор, пока не станет &amp;lt;math&amp;gt;u = parent(u)&amp;lt;/math&amp;gt;. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: &amp;lt;math&amp;gt;parent(u_i) := u&amp;lt;/math&amp;gt;, либо производить схлопывание по пути: &amp;lt;math&amp;gt;parent(u) := parent(parent(u)))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; выполняется следующим образом: вначале находим корневые вершины &amp;lt;math&amp;gt;u := FIND(u), v := FIND(v)&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;u = v&amp;lt;/math&amp;gt;, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из &amp;lt;math&amp;gt;parent(u) := v&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;parent(v) := u&amp;lt;/math&amp;gt;. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.&lt;br /&gt;
&lt;br /&gt;
1.	В самом начале каждая вершина является отдельным фрагментом.&lt;br /&gt;
&lt;br /&gt;
2.	На каждом шаге:&lt;br /&gt;
&lt;br /&gt;
*	Для каждого фрагмента определяется минимальное по весу исходящее ребро.&lt;br /&gt;
&lt;br /&gt;
*	Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм останавливается, когда остаётся только один фрагмент либо когда ни у одного из фрагментов нет исходящих рёбер.&lt;br /&gt;
&lt;br /&gt;
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).&lt;br /&gt;
&lt;br /&gt;
Объединение фрагментов также может быть реализовано параллельно с использованием параллельной версии описанного выше алгоритма Union-Find.&lt;br /&gt;
&lt;br /&gt;
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:&lt;br /&gt;
&lt;br /&gt;
1.	В начале итерации счётчик активных фрагментов обнуляется.&lt;br /&gt;
&lt;br /&gt;
2.	На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.&lt;br /&gt;
&lt;br /&gt;
3.	На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция &amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; вернула значение истина.&lt;br /&gt;
&lt;br /&gt;
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.&lt;br /&gt;
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Последовательная сложность алгоритма Борувки для графа с &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершинами и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбрами составляет &amp;lt;math&amp;gt;O(|E| \ln(|V|))&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний), и параллелизм в алгоритме обработка графа, не помещающегося в память.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Нижний уровень параллелизма&#039;&#039;&#039;: поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно, с использованием описанной структуры Union-Find. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Верхний уровень параллелизма&#039;&#039;&#039;: построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. &lt;br /&gt;
&lt;br /&gt;
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим информационные графы и подробное описание каждого из них. Так же можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующим операциям параллельного поиска минимальных исходящих ребер, а так же уровнях {6, 7, 8}, соответствующим операциям параллельного объединения деревьев. Так же, различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла, производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе аналогичная следующая итерация. &lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального основного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные compute mst. Затем, после параллельных вычислений mst, происходит вычисление итого основного дерево, после чего после чего полученный результат сохраняется (save results).&lt;br /&gt;
&lt;br /&gt;
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм Борувки обладает двумя уровнями параллелизма.&lt;br /&gt;
&lt;br /&gt;
На верхнем уровне минимальные остовные деревья могут искаться для отдельных частей списка ребер графа (параллельные compute_MST на рисунке 2). Однако, затем должно последовать финальное объединение полученных ребер и вычисление минимального остовного дерева для полученного графа, которое будет производиться последовательно.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вычисление каждого из минимальных остовных деревьев (параллельные compute_MST на рисунке 2) обладает внутренним ресурсом параллелизма, описанным далее. Операции инициализации и копирования данных ([1], [2], [9]  на рисунке 1) могут производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Так же параллельно могут производиться операции поиска минимальных исходящих ребер ([3],[4],[5]), при том для каждой дуги и обратной к ней независимо, что даёт &amp;lt;math&amp;gt;2*O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Помимо этого, операции объединения деревьев [6], [7], [8] могут так же производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
В результате, для классического алгоритма Борувки ширина ярусно-параллельной формы равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, а высота ЯПФ зависит от числа шагов алгоритма, и ограничена сверху &amp;lt;math&amp;gt;O(ln(|V|))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j},&lt;br /&gt;
v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.&lt;br /&gt;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &amp;lt;math&amp;gt;\log_2 n&amp;lt;/math&amp;gt;. Отсюда следует и оценка сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
На этапе поиска минимального ребра происходят следующие обращения к памяти:&lt;br /&gt;
# Чтение информации о рёбрах. Может производится последовательно.&lt;br /&gt;
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; с вероятным промахой по кэшу.&lt;br /&gt;
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.&lt;br /&gt;
&lt;br /&gt;
На этапе схлопывания фрагментов требуется атомарно обновить массив &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.&lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_1.png|thumb|center|700px|Рисунок 3. Алгоритм Борувки. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.&lt;br /&gt;
&lt;br /&gt;
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 3 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.&lt;br /&gt;
&lt;br /&gt;
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 3), которая показана на рис. 4. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 4 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее перейдем к изучению фрагмента 2 (рис. 5). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_3.png|thumb|center|700px|Рисунок 5. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 6 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_4.png|thumb|center|700px|Рисунок 6. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
Отдельное рассмотрение фрагмента 4 (рис. 7) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_5.png|thumb|center|500px|Рисунок 7. Профиль обращений, фрагмент 4]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 8 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 8. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм Борувки, состоит из двух частей: &lt;br /&gt;
&lt;br /&gt;
1. части, отвечающей за общую координацию вычислений&lt;br /&gt;
&lt;br /&gt;
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации &amp;lt;math&amp;gt;MERGE&amp;lt;/math&amp;gt; результаты операций &amp;lt;math&amp;gt;FIND(u)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.&lt;br /&gt;
&lt;br /&gt;
1.	Каждой вершине v соответствует запись &amp;lt;math&amp;gt;A[v] = { parent, rank }&amp;lt;/math&amp;gt;. Изначально &amp;lt;math&amp;gt;A[v] := { v, 0 }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Вспомогательная операция &amp;lt;math&amp;gt;UPDATE(v, rank_v, u, rank_u)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
old := A[v]&lt;br /&gt;
&lt;br /&gt;
if old.parent != v or old.rank != rank_v then return false&lt;br /&gt;
&lt;br /&gt;
new := { u, rank_u }&lt;br /&gt;
&lt;br /&gt;
return CAS(A[v], old, new)&lt;br /&gt;
&lt;br /&gt;
3.	Операция &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    while v != A[v].parent do&lt;br /&gt;
        u := A[v].parent&lt;br /&gt;
        CAS(A[v].parent, u, A[u].parent)&lt;br /&gt;
        v := A[u].parent&lt;br /&gt;
    return v&lt;br /&gt;
&lt;br /&gt;
4.	Операция UNION(u, v):&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        (u, v) := (FIND(u), FIND(v))&lt;br /&gt;
        if u = v then return false&lt;br /&gt;
        (rank_u, rank_v) := (A[u].rank, A[v].rank)&lt;br /&gt;
        if (A[u].rank, u) &amp;gt; (A[v].rank, v) then&lt;br /&gt;
            swap((u, rank_u), (v, rank_v))&lt;br /&gt;
        if UPDATE(u, rank_u, v, rank_u) then&lt;br /&gt;
            if rank_u = rank_v then&lt;br /&gt;
                UPDATE(v, rank_v, v, rank_v + 1)&lt;br /&gt;
            return true&lt;br /&gt;
&lt;br /&gt;
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются&lt;br /&gt;
# пропускная способность памяти при чтении данных графа&lt;br /&gt;
# соперничество потоков при выполнении атомарных операций с памятью&lt;br /&gt;
# барьерная синхронизация после каждого подшага алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Борувки, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Борувки, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Boruvka cpu.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 10%. Это нормальная картина для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Loadavg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 2. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 2 процессами, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 40 млн/сек . Интересен факт увеличения числа промахов к концу итерации до уровня в 70 млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже высокое и находится на уровне 30 млн/сек . На графике промахов второго уровня факт увеличения числа промахов к концу итерации проявляется более явно и увеличивается до значения в 50млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов L3 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Эт указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Inf data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka inf pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают алгоритм Борувки и [[алгоритм Крускала]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Boruvka&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24326</id>
		<title>Алгоритм Борувки</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&amp;diff=24326"/>
		<updated>2017-11-27T07:56:47Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Макроструктура алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Борувки&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;max O(ln(|V|)) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Борувки&#039;&#039;&#039;&amp;lt;ref&amp;gt;Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.&amp;lt;/ref&amp;gt; предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный неориентированный граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt;. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).&lt;br /&gt;
&lt;br /&gt;
Алгоритм Борувки основан на следующих двух свойствах задачи:&lt;br /&gt;
* &#039;&#039;&#039;Минимальное ребро фрагмента&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева и &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; – ребро наименьшего веса, исходящее из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; (т.е. ровно один его конец является вершиной из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;). Если ребро &amp;lt;math&amp;gt;e_F&amp;lt;/math&amp;gt; единственно, то оно принадлежит минимальному остовному дереву.&lt;br /&gt;
* &#039;&#039;&#039;Схлопывание фрагментов&#039;&#039;&#039;. Пусть &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; – фрагмент минимального остовного дерева графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а граф &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; получен из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; склеиванием вершин, принадлежащих &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. Тогда объединение &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и минимального остовного дерева графа &amp;lt;math&amp;gt;G&#039;&amp;lt;/math&amp;gt; даёт минимальное остовное дерево исходного графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма каждая вершина графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основными операциями являются:&lt;br /&gt;
# Поиск минимального по весу исходящего ребра в каждом фрагменте.&lt;br /&gt;
# Объединение фрагментов.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. &lt;br /&gt;
&lt;br /&gt;
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.&lt;br /&gt;
&lt;br /&gt;
Пусть задан связный граф &amp;lt;math&amp;gt;G=(V,E)&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;V = ( v_{1}, v_{2}, ..., v_{n} )&amp;lt;/math&amp;gt; и рёбрами &amp;lt;math&amp;gt;E = ( e_{1}, e_{2}, ..., e_{m} )&amp;lt;/math&amp;gt;. Каждому ребру &amp;lt;math&amp;gt;e \in E&amp;lt;/math&amp;gt; приписан вес &amp;lt;math&amp;gt;w(e)&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Требуется построить дерево &amp;lt;math&amp;gt;T^* \subseteq E&amp;lt;/math&amp;gt;, связывающее все вершины и имеющее наименьший возможный вес среди всех таких деревьев:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; w(T^* )= \min_T( w(T)) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
где вес множества рёбер есть сумма их весов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;w(T)=\sum_{e \in T} (w(T))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если граф &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; не является связным, то дерева, связывающего все вершины, не существует. &lt;br /&gt;
&lt;br /&gt;
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).&lt;br /&gt;
&lt;br /&gt;
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====&lt;br /&gt;
&lt;br /&gt;
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:&lt;br /&gt;
&lt;br /&gt;
1.	&amp;lt;math&amp;gt;FIND(v) = w&amp;lt;/math&amp;gt; – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда &amp;lt;math&amp;gt;FIND(u) = FIND(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; – объединяет два фрагмента, которым принадлежат вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v.&amp;lt;/math&amp;gt; (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.&lt;br /&gt;
&lt;br /&gt;
==== Последовательная версия====&lt;br /&gt;
&lt;br /&gt;
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя &amp;lt;math&amp;gt;parent(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
1.	Изначально &amp;lt;math&amp;gt;parent(v) := v&amp;lt;/math&amp;gt; для всех вершин.&lt;br /&gt;
&lt;br /&gt;
2.	&amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; выполняется следующим образом: полагаем &amp;lt;math&amp;gt;u := v&amp;lt;/math&amp;gt;, и далее следуем по указателям &amp;lt;math&amp;gt;u := parent(u)&amp;lt;/math&amp;gt; до тех пор, пока не станет &amp;lt;math&amp;gt;u = parent(u)&amp;lt;/math&amp;gt;. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: &amp;lt;math&amp;gt;parent(u_i) := u&amp;lt;/math&amp;gt;, либо производить схлопывание по пути: &amp;lt;math&amp;gt;parent(u) := parent(parent(u)))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	&amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; выполняется следующим образом: вначале находим корневые вершины &amp;lt;math&amp;gt;u := FIND(u), v := FIND(v)&amp;lt;/math&amp;gt;. Если &amp;lt;math&amp;gt;u = v&amp;lt;/math&amp;gt;, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из &amp;lt;math&amp;gt;parent(u) := v&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;parent(v) := u&amp;lt;/math&amp;gt;. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.&lt;br /&gt;
&lt;br /&gt;
1.	В самом начале каждая вершина является отдельным фрагментом.&lt;br /&gt;
&lt;br /&gt;
2.	На каждом шаге:&lt;br /&gt;
&lt;br /&gt;
*	Для каждого фрагмента определяется минимальное по весу исходящее ребро.&lt;br /&gt;
&lt;br /&gt;
*	Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм останавливается, когда остаётся только один фрагмент, либо когда ни у одного из фрагментов нет исходящих рёбер.&lt;br /&gt;
&lt;br /&gt;
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).&lt;br /&gt;
&lt;br /&gt;
Объединение фрагментов также может быть реализовано параллельно, с использованием описанной выше параллельной версии структуры Union-Find.&lt;br /&gt;
&lt;br /&gt;
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:&lt;br /&gt;
&lt;br /&gt;
1.	В начале итерации счётчик активных фрагментов обнуляется.&lt;br /&gt;
&lt;br /&gt;
2.	На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.&lt;br /&gt;
&lt;br /&gt;
3.	На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция &amp;lt;math&amp;gt;MERGE(u, v)&amp;lt;/math&amp;gt; вернула значение истина.&lt;br /&gt;
&lt;br /&gt;
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.&lt;br /&gt;
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Последовательная сложность алгоритма Борувки для графа с &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершинами и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбрами составляет &amp;lt;math&amp;gt;O(|E| \ln(|V|))&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний), и параллелизм в алгоритме обработка графа, не помещающегося в память.&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Нижний уровень параллелизма&#039;&#039;&#039;: поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно, с использованием описанной структуры Union-Find. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Верхний уровень параллелизма&#039;&#039;&#039;: построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. &lt;br /&gt;
&lt;br /&gt;
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим информационные графы и подробное описание каждого из них. Так же можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующим операциям параллельного поиска минимальных исходящих ребер, а так же уровнях {6, 7, 8}, соответствующим операциям параллельного объединения деревьев. Так же, различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла, производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе аналогичная следующая итерация. &lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального основного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные compute mst. Затем, после параллельных вычислений mst, происходит вычисление итого основного дерево, после чего после чего полученный результат сохраняется (save results).&lt;br /&gt;
&lt;br /&gt;
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм Борувки обладает двумя уровнями параллелизма.&lt;br /&gt;
&lt;br /&gt;
На верхнем уровне минимальные остовные деревья могут искаться для отдельных частей списка ребер графа (параллельные compute_MST на рисунке 2). Однако, затем должно последовать финальное объединение полученных ребер и вычисление минимального остовного дерева для полученного графа, которое будет производиться последовательно.&lt;br /&gt;
&lt;br /&gt;
Кроме того, вычисление каждого из минимальных остовных деревьев (параллельные compute_MST на рисунке 2) обладает внутренним ресурсом параллелизма, описанным далее. Операции инициализации и копирования данных ([1], [2], [9]  на рисунке 1) могут производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Так же параллельно могут производиться операции поиска минимальных исходящих ребер ([3],[4],[5]), при том для каждой дуги и обратной к ней независимо, что даёт &amp;lt;math&amp;gt;2*O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Помимо этого, операции объединения деревьев [6], [7], [8] могут так же производиться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
В результате, для классического алгоритма Борувки ширина ярусно-параллельной формы равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, а высота ЯПФ зависит от числа шагов алгоритма, и ограничена сверху &amp;lt;math&amp;gt;O(ln(|V|))&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j},&lt;br /&gt;
v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.&lt;br /&gt;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &amp;lt;math&amp;gt;\log_2 n&amp;lt;/math&amp;gt;. Отсюда следует и оценка сложности алгоритма.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
На этапе поиска минимального ребра происходят следующие обращения к памяти:&lt;br /&gt;
# Чтение информации о рёбрах. Может производится последовательно.&lt;br /&gt;
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; с вероятным промахой по кэшу.&lt;br /&gt;
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.&lt;br /&gt;
&lt;br /&gt;
На этапе схлопывания фрагментов требуется атомарно обновить массив &amp;lt;math&amp;gt;parent(u)&amp;lt;/math&amp;gt; для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.&lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_1.png|thumb|center|700px|Рисунок 3. Алгоритм Борувки. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.&lt;br /&gt;
&lt;br /&gt;
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 3 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.&lt;br /&gt;
&lt;br /&gt;
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 3), которая показана на рис. 4. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 4 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее перейдем к изучению фрагмента 2 (рис. 5). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_3.png|thumb|center|700px|Рисунок 5. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 6 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_4.png|thumb|center|700px|Рисунок 6. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
Отдельное рассмотрение фрагмента 4 (рис. 7) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_5.png|thumb|center|500px|Рисунок 7. Профиль обращений, фрагмент 4]]&lt;br /&gt;
&lt;br /&gt;
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 8 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.&lt;br /&gt;
&lt;br /&gt;
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 8. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм Борувки, состоит из двух частей: &lt;br /&gt;
&lt;br /&gt;
1. части, отвечающей за общую координацию вычислений&lt;br /&gt;
&lt;br /&gt;
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации &amp;lt;math&amp;gt;MERGE&amp;lt;/math&amp;gt; результаты операций &amp;lt;math&amp;gt;FIND(u)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt; могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.&lt;br /&gt;
&lt;br /&gt;
1.	Каждой вершине v соответствует запись &amp;lt;math&amp;gt;A[v] = { parent, rank }&amp;lt;/math&amp;gt;. Изначально &amp;lt;math&amp;gt;A[v] := { v, 0 }&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Вспомогательная операция &amp;lt;math&amp;gt;UPDATE(v, rank_v, u, rank_u)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
old := A[v]&lt;br /&gt;
&lt;br /&gt;
if old.parent != v or old.rank != rank_v then return false&lt;br /&gt;
&lt;br /&gt;
new := { u, rank_u }&lt;br /&gt;
&lt;br /&gt;
return CAS(A[v], old, new)&lt;br /&gt;
&lt;br /&gt;
3.	Операция &amp;lt;math&amp;gt;FIND(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
    while v != A[v].parent do&lt;br /&gt;
        u := A[v].parent&lt;br /&gt;
        CAS(A[v].parent, u, A[u].parent)&lt;br /&gt;
        v := A[u].parent&lt;br /&gt;
    return v&lt;br /&gt;
&lt;br /&gt;
4.	Операция UNION(u, v):&lt;br /&gt;
&lt;br /&gt;
    while true do&lt;br /&gt;
        (u, v) := (FIND(u), FIND(v))&lt;br /&gt;
        if u = v then return false&lt;br /&gt;
        (rank_u, rank_v) := (A[u].rank, A[v].rank)&lt;br /&gt;
        if (A[u].rank, u) &amp;gt; (A[v].rank, v) then&lt;br /&gt;
            swap((u, rank_u), (v, rank_v))&lt;br /&gt;
        if UPDATE(u, rank_u, v, rank_u) then&lt;br /&gt;
            if rank_u = rank_v then&lt;br /&gt;
                UPDATE(v, rank_v, v, rank_v + 1)&lt;br /&gt;
            return true&lt;br /&gt;
&lt;br /&gt;
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются&lt;br /&gt;
# пропускная способность памяти при чтении данных графа&lt;br /&gt;
# соперничество потоков при выполнении атомарных операций с памятью&lt;br /&gt;
# барьерная синхронизация после каждого подшага алгоритма.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]&lt;br /&gt;
&lt;br /&gt;
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Борувки, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Борувки, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Boruvka cpu.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 10%. Это нормальная картина для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Loadavg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 2. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 2 процессами, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 40 млн/сек . Интересен факт увеличения числа промахов к концу итерации до уровня в 70 млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже высокое и находится на уровне 30 млн/сек . На графике промахов второго уровня факт увеличения числа промахов к концу итерации проявляется более явно и увеличивается до значения в 50млн/сек.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов L3 в секунду при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Эт указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Boruvka Inf data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Boruvka inf pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Борувки]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]&amp;lt;/code&amp;gt; сочетают алгоритм Борувки и [[алгоритм Крускала]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Boruvka&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24275</id>
		<title>Алгоритм Беллмана-Форда</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24275"/>
		<updated>2017-11-26T17:13:52Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Беллмана-Форда&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Беллмана-Форда&#039;&#039;&#039;&amp;lt;ref&amp;gt;Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(|E| + |V|\ln(|V|))&amp;lt;/math&amp;gt; у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Беллмана-Форда ищет функцию &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; как единственное решение уравнения&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
с начальным условием &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основной операцией алгоритма является релаксация ребра: если &amp;lt;math&amp;gt;e = (w, v) \in E&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d(v) &amp;gt; d(w) + f(e)&amp;lt;/math&amp;gt;, то производится присваивание &amp;lt;math&amp;gt;d(v) \leftarrow d(w) + f(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* В самом начале производится присваивание &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;d(v) = \infty&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\forall v \ne u&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Далее происходит &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерация, в ходе каждой из которых производится релаксация всех рёбер графа.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;t(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;t(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Релаксация множества рёбер &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
а)	Для каждого ребра &amp;lt;math&amp;gt;e=(v,z) \in E&amp;lt;/math&amp;gt; вычисляется новое предполагаемое расстояние &amp;lt;math&amp;gt;t^&#039; (z)=t(v)+ w(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Если &amp;lt;math&amp;gt;t^&#039; (z)&amp;lt; t(z)&amp;lt;/math&amp;gt;, то происходит присваивание &amp;lt;math&amp;gt;t(z) := t&#039; (z)&amp;lt;/math&amp;gt; (релаксация ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; ).&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.&lt;br /&gt;
&lt;br /&gt;
Если на &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро &amp;lt;math&amp;gt;e=(v,z)&amp;lt;/math&amp;gt;, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): &amp;lt;math&amp;gt;t(v)+w(e)&amp;lt;d(z)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞&lt;br /&gt;
 &#039;&#039;d&#039;&#039;(&#039;&#039;u&#039;&#039;) = 0&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;i&#039;&#039; &#039;&#039;&#039;from&#039;&#039;&#039; 1 &#039;&#039;&#039;to&#039;&#039;&#039; |&#039;&#039;V&#039;&#039;| - 1:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;w&#039;&#039;, &#039;&#039;v&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Алгоритм выполняет &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерацию, на каждой из которых происходит релаксация &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер. Таким образом, общий объём работы составляет &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.&lt;br /&gt;
&lt;br /&gt;
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.&lt;br /&gt;
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]&lt;br /&gt;
&lt;br /&gt;
На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях [2] и [3], соответствующим операциям инициализации массива дистанций [2] и обновления массива c использованием данных массива ребер [3]. Операция [4] - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным ресурсом параллелизма. Во-первых, поиск кратчайших путей от различных вершин может производиться независимо для каждой из вершин (параллельные вертикальные плоскости на рисунке 1). Во-вторых, поиск кратчайших путей от фиксированной вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; также может выполняться параллельно: инициализация начальных путей [2] требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; параллельных операции, релаксация каждого ребра требует &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операции. &lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; процессоров алгоритм завершит работу максимум за &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; шагов. В реальности, шагов обычно требуется меньше, а именно &amp;lt;math&amp;gt;O(r)&amp;lt;/math&amp;gt; -(максимальная длина среди всех кратчайших путей от выбранной вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
Таким образом, ширина ярусно-параллельной формы алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, высота ЯПФ - &amp;lt;math&amp;gt;O(r) | r &amp;lt; |V|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма===&lt;br /&gt;
&lt;br /&gt;
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро &amp;lt;math&amp;gt;e = (v, w)&amp;lt;/math&amp;gt; лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; удовлетворяют условию&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) + f(e) &amp;lt; d(w),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; – вес ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt;. Условие может быть проверено для всех рёбер графа за время &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практически идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. &lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 2, выделенной на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 2]]&lt;br /&gt;
&lt;br /&gt;
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а также параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_Беллмана-Форда.png|thumb|center|700px|Рисунок 7. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Беллмана-Форда, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Беллмана-Форда, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Bellman-Ford cpu.png|thumb|center|700px|Рисунок 8. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 9. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако это число для узла слишком мало, что может указывать на не очень рациональное использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 10. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек. Это очень высокий показатель, показывающий потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 11. График кэш-промахов L2 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число таких промахов тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford Inf Data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на первом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford Inf Pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Bellman-Ford algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24258</id>
		<title>Алгоритм Беллмана-Форда</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24258"/>
		<updated>2017-11-26T14:52:54Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Беллмана-Форда&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Беллмана-Форда&#039;&#039;&#039;&amp;lt;ref&amp;gt;Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(|E| + |V|\ln(|V|))&amp;lt;/math&amp;gt; у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Беллмана-Форда ищет функцию &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; как единственное решение уравнения&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
с начальным условием &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основной операцией алгоритма является релаксация ребра: если &amp;lt;math&amp;gt;e = (w, v) \in E&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d(v) &amp;gt; d(w) + f(e)&amp;lt;/math&amp;gt;, то производится присваивание &amp;lt;math&amp;gt;d(v) \leftarrow d(w) + f(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* В самом начале производится присваивание &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;d(v) = \infty&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\forall v \ne u&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Далее происходит &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерация, в ходе каждой из которых производится релаксация всех рёбер графа.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;t(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;t(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Релаксация множества рёбер &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
а)	Для каждого ребра &amp;lt;math&amp;gt;e=(v,z) \in E&amp;lt;/math&amp;gt; вычисляется новое предполагаемое расстояние &amp;lt;math&amp;gt;t^&#039; (z)=t(v)+ w(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Если &amp;lt;math&amp;gt;t^&#039; (z)&amp;lt; t(z)&amp;lt;/math&amp;gt;, то происходит присваивание &amp;lt;math&amp;gt;t(z) := t&#039; (z)&amp;lt;/math&amp;gt; (релаксация ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; ).&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.&lt;br /&gt;
&lt;br /&gt;
Если на &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро &amp;lt;math&amp;gt;e=(v,z)&amp;lt;/math&amp;gt;, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): &amp;lt;math&amp;gt;t(v)+w(e)&amp;lt;d(z)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞&lt;br /&gt;
 &#039;&#039;d&#039;&#039;(&#039;&#039;u&#039;&#039;) = 0&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;i&#039;&#039; &#039;&#039;&#039;from&#039;&#039;&#039; 1 &#039;&#039;&#039;to&#039;&#039;&#039; |&#039;&#039;V&#039;&#039;| - 1:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;w&#039;&#039;, &#039;&#039;v&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Алгоритм выполняет &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерацию, на каждой из которых происходит релаксация &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер. Таким образом, общий объём работы составляет &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.&lt;br /&gt;
&lt;br /&gt;
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.&lt;br /&gt;
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]&lt;br /&gt;
&lt;br /&gt;
На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях [2] и [3], соответствующим операциям инициализации массива дистанций [2] и обновления массива c использованием данных массива ребер [3]. Операция [4] - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным ресурсом параллелизма. Во-первых, поиск кратчайших путей от различных вершин может производиться независимо для каждой из вершин (параллельные вертикальные плоскости на рисунке 1). Во-вторых, поиск кратчайших путей от фиксированной вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; также может выполняться параллельно: инициализация начальных путей [2] требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; параллельных операции, релаксация каждого ребра требует &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операции. &lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; процессоров алгоритм завершит работу максимум за &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; шагов. В реальности, шагов обычно требуется меньше, а именно &amp;lt;math&amp;gt;O(r)&amp;lt;/math&amp;gt; -(максимальная длина среди всех кратчайших путей от выбранной вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
Таким образом, ширина ярусно-параллельной формы алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, высота ЯПФ - &amp;lt;math&amp;gt;O(r) | r &amp;lt; |V|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма===&lt;br /&gt;
&lt;br /&gt;
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро &amp;lt;math&amp;gt;e = (v, w)&amp;lt;/math&amp;gt; лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; удовлетворяют условию&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) + f(e) &amp;lt; d(w),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; – вес ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt;. Условие может быть проверено для всех рёбер графа за время &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практически идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. &lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 2, выделенной на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 2]]&lt;br /&gt;
&lt;br /&gt;
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а также параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_Беллмана-Форда.png|thumb|center|700px|Рисунок 7. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Беллмана-Форда, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Беллмана-Форда, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Bellman-Ford cpu.png|thumb|center|700px|Рисунок 8. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 9. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако это число для узла слишком мало, что может указывать на не очень рациональное использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 10. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек. Это очень высокий показатель, показывающий потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 11. График кэш-промахов L2 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число таких промахов тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford Inf Data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford Inf Pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Bellman-Ford algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24255</id>
		<title>Алгоритм Беллмана-Форда</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24255"/>
		<updated>2017-11-26T14:39:11Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Беллмана-Форда&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Беллмана-Форда&#039;&#039;&#039;&amp;lt;ref&amp;gt;Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(|E| + |V|\ln(|V|))&amp;lt;/math&amp;gt; у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Беллмана-Форда ищет функцию &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; как единственное решение уравнения&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
с начальным условием &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основной операцией алгоритма является релаксация ребра: если &amp;lt;math&amp;gt;e = (w, v) \in E&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d(v) &amp;gt; d(w) + f(e)&amp;lt;/math&amp;gt;, то производится присваивание &amp;lt;math&amp;gt;d(v) \leftarrow d(w) + f(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* В самом начале производится присваивание &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;d(v) = \infty&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\forall v \ne u&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Далее происходит &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерация, в ходе каждой из которых производится релаксация всех рёбер графа.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;t(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;t(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Релаксация множества рёбер &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
а)	Для каждого ребра &amp;lt;math&amp;gt;e=(v,z) \in E&amp;lt;/math&amp;gt; вычисляется новое предполагаемое расстояние &amp;lt;math&amp;gt;t^&#039; (z)=t(v)+ w(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Если &amp;lt;math&amp;gt;t^&#039; (z)&amp;lt; t(z)&amp;lt;/math&amp;gt;, то происходит присваивание &amp;lt;math&amp;gt;t(z) := t&#039; (z)&amp;lt;/math&amp;gt; (релаксация ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; ).&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.&lt;br /&gt;
&lt;br /&gt;
Если на &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро &amp;lt;math&amp;gt;e=(v,z)&amp;lt;/math&amp;gt;, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): &amp;lt;math&amp;gt;t(v)+w(e)&amp;lt;d(z)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞&lt;br /&gt;
 &#039;&#039;d&#039;&#039;(&#039;&#039;u&#039;&#039;) = 0&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;i&#039;&#039; &#039;&#039;&#039;from&#039;&#039;&#039; 1 &#039;&#039;&#039;to&#039;&#039;&#039; |&#039;&#039;V&#039;&#039;| - 1:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;w&#039;&#039;, &#039;&#039;v&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Алгоритм выполняет &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерацию, на каждой из которых происходит релаксация &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер. Таким образом, общий объём работы составляет &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.&lt;br /&gt;
&lt;br /&gt;
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.&lt;br /&gt;
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]&lt;br /&gt;
&lt;br /&gt;
На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях [2] и [3], соответствующим операциям инициализации массива дистанций [2] и обновления массива c использованием данных массива ребер [3]. Операция [4] - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным ресурсом параллелизма. Во-первых, поиск кратчайших путей от различных вершин может производиться независимо для каждой из вершин (параллельные вертикальные плоскости на рисунке 1). Во-вторых, поиск кратчайших путей от фиксированной вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; также может выполняться параллельно: инициализация начальных путей [2] требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; параллельных операции, релаксация каждого ребра требует &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операции. &lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; процессоров алгоритм завершит работу максимум за &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; шагов. В реальности, шагов обычно требуется меньше, а именно &amp;lt;math&amp;gt;O(r)&amp;lt;/math&amp;gt; -(максимальная длина среди всех кратчайших путей от выбранной вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
Таким образом, ширина ярусно-параллельной формы алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, высота ЯПФ - &amp;lt;math&amp;gt;O(r) | r &amp;lt; |V|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма===&lt;br /&gt;
&lt;br /&gt;
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро &amp;lt;math&amp;gt;e = (v, w)&amp;lt;/math&amp;gt; лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; удовлетворяют условию&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) + f(e) &amp;lt; d(w),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; – вес ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt;. Условие может быть проверено для всех рёбер графа за время &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практически идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. &lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 2, выделенной на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 2]]&lt;br /&gt;
&lt;br /&gt;
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а также параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_Беллмана-Форда.png|thumb|center|700px|Рисунок 7. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Беллмана-Форда, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Беллмана-Форда, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Bellman-Ford cpu.png|thumb|center|700px|Рисунок 8. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 9. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако это число для узла слишком мало, что может указывать на не очень рациональное использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 10. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек. Это очень высокий показатель, показывающий потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford Inf Data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford Inf Pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Bellman-Ford algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24252</id>
		<title>Алгоритм Беллмана-Форда</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24252"/>
		<updated>2017-11-26T14:16:55Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Беллмана-Форда&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Беллмана-Форда&#039;&#039;&#039;&amp;lt;ref&amp;gt;Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(|E| + |V|\ln(|V|))&amp;lt;/math&amp;gt; у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Беллмана-Форда ищет функцию &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; как единственное решение уравнения&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
с начальным условием &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основной операцией алгоритма является релаксация ребра: если &amp;lt;math&amp;gt;e = (w, v) \in E&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d(v) &amp;gt; d(w) + f(e)&amp;lt;/math&amp;gt;, то производится присваивание &amp;lt;math&amp;gt;d(v) \leftarrow d(w) + f(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* В самом начале производится присваивание &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;d(v) = \infty&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\forall v \ne u&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Далее происходит &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерация, в ходе каждой из которых производится релаксация всех рёбер графа.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;t(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;t(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Релаксация множества рёбер &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
а)	Для каждого ребра &amp;lt;math&amp;gt;e=(v,z) \in E&amp;lt;/math&amp;gt; вычисляется новое предполагаемое расстояние &amp;lt;math&amp;gt;t^&#039; (z)=t(v)+ w(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Если &amp;lt;math&amp;gt;t^&#039; (z)&amp;lt; t(z)&amp;lt;/math&amp;gt;, то происходит присваивание &amp;lt;math&amp;gt;t(z) := t&#039; (z)&amp;lt;/math&amp;gt; (релаксация ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; ).&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.&lt;br /&gt;
&lt;br /&gt;
Если на &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро &amp;lt;math&amp;gt;e=(v,z)&amp;lt;/math&amp;gt;, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): &amp;lt;math&amp;gt;t(v)+w(e)&amp;lt;d(z)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞&lt;br /&gt;
 &#039;&#039;d&#039;&#039;(&#039;&#039;u&#039;&#039;) = 0&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;i&#039;&#039; &#039;&#039;&#039;from&#039;&#039;&#039; 1 &#039;&#039;&#039;to&#039;&#039;&#039; |&#039;&#039;V&#039;&#039;| - 1:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;w&#039;&#039;, &#039;&#039;v&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Алгоритм выполняет &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерацию, на каждой из которых происходит релаксация &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер. Таким образом, общий объём работы составляет &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.&lt;br /&gt;
&lt;br /&gt;
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.&lt;br /&gt;
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]&lt;br /&gt;
&lt;br /&gt;
На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях [2] и [3], соответствующим операциям инициализации массива дистанций [2] и обновления массива c использованием данных массива ребер [3]. Операция [4] - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным ресурсом параллелизма. Во-первых, поиск кратчайших путей от различных вершин может производиться независимо для каждой из вершин (параллельные вертикальные плоскости на рисунке 1). Во-вторых, поиск кратчайших путей от фиксированной вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; также может выполняться параллельно: инициализация начальных путей [2] требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; параллельных операции, релаксация каждого ребра требует &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операции. &lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; процессоров алгоритм завершит работу максимум за &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; шагов. В реальности, шагов обычно требуется меньше, а именно &amp;lt;math&amp;gt;O(r)&amp;lt;/math&amp;gt; -(максимальная длина среди всех кратчайших путей от выбранной вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
Таким образом, ширина ярусно-параллельной формы алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, высота ЯПФ - &amp;lt;math&amp;gt;O(r) | r &amp;lt; |V|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма===&lt;br /&gt;
&lt;br /&gt;
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро &amp;lt;math&amp;gt;e = (v, w)&amp;lt;/math&amp;gt; лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; удовлетворяют условию&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) + f(e) &amp;lt; d(w),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; – вес ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt;. Условие может быть проверено для всех рёбер графа за время &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практически идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. &lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 2, выделенной на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 2]]&lt;br /&gt;
&lt;br /&gt;
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а также параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_Беллмана-Форда.png|thumb|center|700px|Рисунок 7. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Беллмана-Форда, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Беллмана-Форда, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Bellman-Ford cpu.png|thumb|center|700px|Рисунок 8. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 9. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако это число для узла слишком мало, что может указывать на не очень рациональное использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 10. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford Inf Data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford Inf Pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Bellman-Ford algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24235</id>
		<title>Алгоритм Беллмана-Форда</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24235"/>
		<updated>2017-11-26T10:45:02Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Возможные способы и особенности параллельной реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Беллмана-Форда&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Беллмана-Форда&#039;&#039;&#039;&amp;lt;ref&amp;gt;Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(|E| + |V|\ln(|V|))&amp;lt;/math&amp;gt; у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Беллмана-Форда ищет функцию &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; как единственное решение уравнения&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
с начальным условием &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основной операцией алгоритма является релаксация ребра: если &amp;lt;math&amp;gt;e = (w, v) \in E&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d(v) &amp;gt; d(w) + f(e)&amp;lt;/math&amp;gt;, то производится присваивание &amp;lt;math&amp;gt;d(v) \leftarrow d(w) + f(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* В самом начале производится присваивание &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;d(v) = \infty&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\forall v \ne u&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Далее происходит &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерация, в ходе каждой из которых производится релаксация всех рёбер графа.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;t(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;t(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Релаксация множества рёбер &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
а)	Для каждого ребра &amp;lt;math&amp;gt;e=(v,z) \in E&amp;lt;/math&amp;gt; вычисляется новое предполагаемое расстояние &amp;lt;math&amp;gt;t^&#039; (z)=t(v)+ w(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Если &amp;lt;math&amp;gt;t^&#039; (z)&amp;lt; t(z)&amp;lt;/math&amp;gt;, то происходит присваивание &amp;lt;math&amp;gt;t(z) := t&#039; (z)&amp;lt;/math&amp;gt; (релаксация ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; ).&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.&lt;br /&gt;
&lt;br /&gt;
Если на &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро &amp;lt;math&amp;gt;e=(v,z)&amp;lt;/math&amp;gt;, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): &amp;lt;math&amp;gt;t(v)+w(e)&amp;lt;d(z)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞&lt;br /&gt;
 &#039;&#039;d&#039;&#039;(&#039;&#039;u&#039;&#039;) = 0&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;i&#039;&#039; &#039;&#039;&#039;from&#039;&#039;&#039; 1 &#039;&#039;&#039;to&#039;&#039;&#039; |&#039;&#039;V&#039;&#039;| - 1:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;w&#039;&#039;, &#039;&#039;v&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Алгоритм выполняет &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерацию, на каждой из которых происходит релаксация &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер. Таким образом, общий объём работы составляет &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.&lt;br /&gt;
&lt;br /&gt;
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.&lt;br /&gt;
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]&lt;br /&gt;
&lt;br /&gt;
На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях [2] и [3], соответствующим операциям инициализации массива дистанций [2] и обновления массива c использованием данных массива ребер [3]. Операция [4] - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным ресурсом параллелизма. Во-первых, поиск кратчайших путей от различных вершин может производиться независимо для каждой из вершин (параллельные вертикальные плоскости на рисунке 1). Во-вторых, поиск кратчайших путей от фиксированной вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; также может выполняться параллельно: инициализация начальных путей [2] требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; параллельных операции, релаксация каждого ребра требует &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операции. &lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; процессоров алгоритм завершит работу максимум за &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; шагов. В реальности, шагов обычно требуется меньше, а именно &amp;lt;math&amp;gt;O(r)&amp;lt;/math&amp;gt; -(максимальная длина среди всех кратчайших путей от выбранной вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
Таким образом, ширина ярусно-параллельной формы алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, высота ЯПФ - &amp;lt;math&amp;gt;O(r) | r &amp;lt; |V|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма===&lt;br /&gt;
&lt;br /&gt;
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро &amp;lt;math&amp;gt;e = (v, w)&amp;lt;/math&amp;gt; лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; удовлетворяют условию&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) + f(e) &amp;lt; d(w),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; – вес ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt;. Условие может быть проверено для всех рёбер графа за время &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практически идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. &lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 2, выделенной на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 2]]&lt;br /&gt;
&lt;br /&gt;
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а также параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_Беллмана-Форда.png|thumb|center|700px|Рисунок 7. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Беллмана-Форда, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Беллмана-Форда, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Bellman-Ford cpu.png|thumb|center|700px|Рисунок 8. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 9. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 10. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford Inf Data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford Inf Pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Bellman-Ford algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24233</id>
		<title>Алгоритм Беллмана-Форда</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24233"/>
		<updated>2017-11-26T10:08:29Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Количественная оценка локальности */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Беллмана-Форда&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Беллмана-Форда&#039;&#039;&#039;&amp;lt;ref&amp;gt;Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(|E| + |V|\ln(|V|))&amp;lt;/math&amp;gt; у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Беллмана-Форда ищет функцию &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; как единственное решение уравнения&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
с начальным условием &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основной операцией алгоритма является релаксация ребра: если &amp;lt;math&amp;gt;e = (w, v) \in E&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d(v) &amp;gt; d(w) + f(e)&amp;lt;/math&amp;gt;, то производится присваивание &amp;lt;math&amp;gt;d(v) \leftarrow d(w) + f(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* В самом начале производится присваивание &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;d(v) = \infty&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\forall v \ne u&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Далее происходит &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерация, в ходе каждой из которых производится релаксация всех рёбер графа.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;t(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;t(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Релаксация множества рёбер &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
а)	Для каждого ребра &amp;lt;math&amp;gt;e=(v,z) \in E&amp;lt;/math&amp;gt; вычисляется новое предполагаемое расстояние &amp;lt;math&amp;gt;t^&#039; (z)=t(v)+ w(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Если &amp;lt;math&amp;gt;t^&#039; (z)&amp;lt; t(z)&amp;lt;/math&amp;gt;, то происходит присваивание &amp;lt;math&amp;gt;t(z) := t&#039; (z)&amp;lt;/math&amp;gt; (релаксация ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; ).&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.&lt;br /&gt;
&lt;br /&gt;
Если на &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро &amp;lt;math&amp;gt;e=(v,z)&amp;lt;/math&amp;gt;, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): &amp;lt;math&amp;gt;t(v)+w(e)&amp;lt;d(z)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞&lt;br /&gt;
 &#039;&#039;d&#039;&#039;(&#039;&#039;u&#039;&#039;) = 0&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;i&#039;&#039; &#039;&#039;&#039;from&#039;&#039;&#039; 1 &#039;&#039;&#039;to&#039;&#039;&#039; |&#039;&#039;V&#039;&#039;| - 1:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;w&#039;&#039;, &#039;&#039;v&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Алгоритм выполняет &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерацию, на каждой из которых происходит релаксация &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер. Таким образом, общий объём работы составляет &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.&lt;br /&gt;
&lt;br /&gt;
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.&lt;br /&gt;
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]&lt;br /&gt;
&lt;br /&gt;
На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях [2] и [3], соответствующим операциям инициализации массива дистанций [2] и обновления массива c использованием данных массива ребер [3]. Операция [4] - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным ресурсом параллелизма. Во-первых, поиск кратчайших путей от различных вершин может производиться независимо для каждой из вершин (параллельные вертикальные плоскости на рисунке 1). Во-вторых, поиск кратчайших путей от фиксированной вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; также может выполняться параллельно: инициализация начальных путей [2] требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; параллельных операции, релаксация каждого ребра требует &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операции. &lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; процессоров алгоритм завершит работу максимум за &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; шагов. В реальности, шагов обычно требуется меньше, а именно &amp;lt;math&amp;gt;O(r)&amp;lt;/math&amp;gt; -(максимальная длина среди всех кратчайших путей от выбранной вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
Таким образом, ширина ярусно-параллельной формы алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, высота ЯПФ - &amp;lt;math&amp;gt;O(r) | r &amp;lt; |V|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма===&lt;br /&gt;
&lt;br /&gt;
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро &amp;lt;math&amp;gt;e = (v, w)&amp;lt;/math&amp;gt; лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; удовлетворяют условию&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) + f(e) &amp;lt; d(w),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; – вес ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt;. Условие может быть проверено для всех рёбер графа за время &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практически идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. &lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 2, выделенной на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 2]]&lt;br /&gt;
&lt;br /&gt;
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а так же параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_Беллмана-Форда.png|thumb|center|700px|Рисунок 7. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Беллмана-Форда, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Беллмана-Форда, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Bellman-Ford cpu.png|thumb|center|700px|Рисунок 8. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 9. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 10. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford Inf Data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford Inf Pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Bellman-Ford algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24232</id>
		<title>Алгоритм Беллмана-Форда</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24232"/>
		<updated>2017-11-26T09:55:15Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Структура обращений в память и качественная оценка локальности */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Беллмана-Форда&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Беллмана-Форда&#039;&#039;&#039;&amp;lt;ref&amp;gt;Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(|E| + |V|\ln(|V|))&amp;lt;/math&amp;gt; у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Беллмана-Форда ищет функцию &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; как единственное решение уравнения&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
с начальным условием &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основной операцией алгоритма является релаксация ребра: если &amp;lt;math&amp;gt;e = (w, v) \in E&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d(v) &amp;gt; d(w) + f(e)&amp;lt;/math&amp;gt;, то производится присваивание &amp;lt;math&amp;gt;d(v) \leftarrow d(w) + f(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* В самом начале производится присваивание &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;d(v) = \infty&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\forall v \ne u&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Далее происходит &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерация, в ходе каждой из которых производится релаксация всех рёбер графа.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;t(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;t(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Релаксация множества рёбер &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
а)	Для каждого ребра &amp;lt;math&amp;gt;e=(v,z) \in E&amp;lt;/math&amp;gt; вычисляется новое предполагаемое расстояние &amp;lt;math&amp;gt;t^&#039; (z)=t(v)+ w(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Если &amp;lt;math&amp;gt;t^&#039; (z)&amp;lt; t(z)&amp;lt;/math&amp;gt;, то происходит присваивание &amp;lt;math&amp;gt;t(z) := t&#039; (z)&amp;lt;/math&amp;gt; (релаксация ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; ).&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.&lt;br /&gt;
&lt;br /&gt;
Если на &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро &amp;lt;math&amp;gt;e=(v,z)&amp;lt;/math&amp;gt;, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): &amp;lt;math&amp;gt;t(v)+w(e)&amp;lt;d(z)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞&lt;br /&gt;
 &#039;&#039;d&#039;&#039;(&#039;&#039;u&#039;&#039;) = 0&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;i&#039;&#039; &#039;&#039;&#039;from&#039;&#039;&#039; 1 &#039;&#039;&#039;to&#039;&#039;&#039; |&#039;&#039;V&#039;&#039;| - 1:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;w&#039;&#039;, &#039;&#039;v&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Алгоритм выполняет &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерацию, на каждой из которых происходит релаксация &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер. Таким образом, общий объём работы составляет &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.&lt;br /&gt;
&lt;br /&gt;
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.&lt;br /&gt;
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]&lt;br /&gt;
&lt;br /&gt;
На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях [2] и [3], соответствующим операциям инициализации массива дистанций [2] и обновления массива c использованием данных массива ребер [3]. Операция [4] - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным ресурсом параллелизма. Во-первых, поиск кратчайших путей от различных вершин может производиться независимо для каждой из вершин (параллельные вертикальные плоскости на рисунке 1). Во-вторых, поиск кратчайших путей от фиксированной вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; также может выполняться параллельно: инициализация начальных путей [2] требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; параллельных операции, релаксация каждого ребра требует &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операции. &lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; процессоров алгоритм завершит работу максимум за &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; шагов. В реальности, шагов обычно требуется меньше, а именно &amp;lt;math&amp;gt;O(r)&amp;lt;/math&amp;gt; -(максимальная длина среди всех кратчайших путей от выбранной вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
Таким образом, ширина ярусно-параллельной формы алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, высота ЯПФ - &amp;lt;math&amp;gt;O(r) | r &amp;lt; |V|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма===&lt;br /&gt;
&lt;br /&gt;
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро &amp;lt;math&amp;gt;e = (v, w)&amp;lt;/math&amp;gt; лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; удовлетворяют условию&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) + f(e) &amp;lt; d(w),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; – вес ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt;. Условие может быть проверено для всех рёбер графа за время &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практически идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. &lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 2, выделенной на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 2]]&lt;br /&gt;
&lt;br /&gt;
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а так же параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_Беллмана-Форда.png|thumb|center|700px|Рисунок 7. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Беллмана-Форда, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Беллмана-Форда, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Bellman-Ford cpu.png|thumb|center|700px|Рисунок 8. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 9. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 10. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford Inf Data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford Inf Pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Bellman-Ford algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24230</id>
		<title>Алгоритм Беллмана-Форда</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24230"/>
		<updated>2017-11-25T21:20:35Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Структура обращений в память и качественная оценка локальности */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Беллмана-Форда&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Беллмана-Форда&#039;&#039;&#039;&amp;lt;ref&amp;gt;Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(|E| + |V|\ln(|V|))&amp;lt;/math&amp;gt; у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Беллмана-Форда ищет функцию &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; как единственное решение уравнения&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
с начальным условием &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основной операцией алгоритма является релаксация ребра: если &amp;lt;math&amp;gt;e = (w, v) \in E&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d(v) &amp;gt; d(w) + f(e)&amp;lt;/math&amp;gt;, то производится присваивание &amp;lt;math&amp;gt;d(v) \leftarrow d(w) + f(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* В самом начале производится присваивание &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;d(v) = \infty&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\forall v \ne u&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Далее происходит &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерация, в ходе каждой из которых производится релаксация всех рёбер графа.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;t(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;t(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Релаксация множества рёбер &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
а)	Для каждого ребра &amp;lt;math&amp;gt;e=(v,z) \in E&amp;lt;/math&amp;gt; вычисляется новое предполагаемое расстояние &amp;lt;math&amp;gt;t^&#039; (z)=t(v)+ w(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Если &amp;lt;math&amp;gt;t^&#039; (z)&amp;lt; t(z)&amp;lt;/math&amp;gt;, то происходит присваивание &amp;lt;math&amp;gt;t(z) := t&#039; (z)&amp;lt;/math&amp;gt; (релаксация ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; ).&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.&lt;br /&gt;
&lt;br /&gt;
Если на &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро &amp;lt;math&amp;gt;e=(v,z)&amp;lt;/math&amp;gt;, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): &amp;lt;math&amp;gt;t(v)+w(e)&amp;lt;d(z)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞&lt;br /&gt;
 &#039;&#039;d&#039;&#039;(&#039;&#039;u&#039;&#039;) = 0&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;i&#039;&#039; &#039;&#039;&#039;from&#039;&#039;&#039; 1 &#039;&#039;&#039;to&#039;&#039;&#039; |&#039;&#039;V&#039;&#039;| - 1:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;w&#039;&#039;, &#039;&#039;v&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Алгоритм выполняет &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерацию, на каждой из которых происходит релаксация &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер. Таким образом, общий объём работы составляет &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.&lt;br /&gt;
&lt;br /&gt;
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.&lt;br /&gt;
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]&lt;br /&gt;
&lt;br /&gt;
На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях [2] и [3], соответствующим операциям инициализации массива дистанций [2] и обновления массива c использованием данных массива ребер [3]. Операция [4] - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным ресурсом параллелизма. Во-первых, поиск кратчайших путей от различных вершин может производиться независимо для каждой из вершин (параллельные вертикальные плоскости на рисунке 1). Во-вторых, поиск кратчайших путей от фиксированной вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; также может выполняться параллельно: инициализация начальных путей [2] требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; параллельных операции, релаксация каждого ребра требует &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операции. &lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; процессоров алгоритм завершит работу максимум за &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; шагов. В реальности, шагов обычно требуется меньше, а именно &amp;lt;math&amp;gt;O(r)&amp;lt;/math&amp;gt; -(максимальная длина среди всех кратчайших путей от выбранной вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
Таким образом, ширина ярусно-параллельной формы алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, высота ЯПФ - &amp;lt;math&amp;gt;O(r) | r &amp;lt; |V|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма===&lt;br /&gt;
&lt;br /&gt;
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро &amp;lt;math&amp;gt;e = (v, w)&amp;lt;/math&amp;gt; лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; удовлетворяют условию&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) + f(e) &amp;lt; d(w),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; – вес ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt;. Условие может быть проверено для всех рёбер графа за время &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практически идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. &lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 1, выделенную на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 1]]&lt;br /&gt;
&lt;br /&gt;
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а так же параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_Беллмана-Форда.png|thumb|center|700px|Рисунок 7. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Беллмана-Форда, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Беллмана-Форда, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Bellman-Ford cpu.png|thumb|center|700px|Рисунок 8. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 9. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 10. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford Inf Data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford Inf Pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Bellman-Ford algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24164</id>
		<title>Алгоритм Беллмана-Форда</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&amp;diff=24164"/>
		<updated>2017-11-25T16:26:41Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Беллмана-Форда&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Беллмана-Форда&#039;&#039;&#039;&amp;lt;ref&amp;gt;Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; против &amp;lt;math&amp;gt;O(|E| + |V|\ln(|V|))&amp;lt;/math&amp;gt; у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Беллмана-Форда ищет функцию &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; как единственное решение уравнения&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
с начальным условием &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основной операцией алгоритма является релаксация ребра: если &amp;lt;math&amp;gt;e = (w, v) \in E&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;d(v) &amp;gt; d(w) + f(e)&amp;lt;/math&amp;gt;, то производится присваивание &amp;lt;math&amp;gt;d(v) \leftarrow d(w) + f(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* В самом начале производится присваивание &amp;lt;math&amp;gt;d(u) = 0&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;d(v) = \infty&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\forall v \ne u&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Далее происходит &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерация, в ходе каждой из которых производится релаксация всех рёбер графа.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;t(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;t(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Релаксация множества рёбер &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
а)	Для каждого ребра &amp;lt;math&amp;gt;e=(v,z) \in E&amp;lt;/math&amp;gt; вычисляется новое предполагаемое расстояние &amp;lt;math&amp;gt;t^&#039; (z)=t(v)+ w(e)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Если &amp;lt;math&amp;gt;t^&#039; (z)&amp;lt; t(z)&amp;lt;/math&amp;gt;, то происходит присваивание &amp;lt;math&amp;gt;t(z) := t&#039; (z)&amp;lt;/math&amp;gt; (релаксация ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; ).&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.&lt;br /&gt;
&lt;br /&gt;
Если на &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро &amp;lt;math&amp;gt;e=(v,z)&amp;lt;/math&amp;gt;, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): &amp;lt;math&amp;gt;t(v)+w(e)&amp;lt;d(z)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞&lt;br /&gt;
 &#039;&#039;d&#039;&#039;(&#039;&#039;u&#039;&#039;) = 0&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;i&#039;&#039; &#039;&#039;&#039;from&#039;&#039;&#039; 1 &#039;&#039;&#039;to&#039;&#039;&#039; |&#039;&#039;V&#039;&#039;| - 1:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;w&#039;&#039;, &#039;&#039;v&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
Алгоритм выполняет &amp;lt;math&amp;gt;|V|-1&amp;lt;/math&amp;gt; итерацию, на каждой из которых происходит релаксация &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер. Таким образом, общий объём работы составляет &amp;lt;math&amp;gt;O(|V||E|)&amp;lt;/math&amp;gt; операций.&lt;br /&gt;
&lt;br /&gt;
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.&lt;br /&gt;
&lt;br /&gt;
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.&lt;br /&gt;
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]&lt;br /&gt;
&lt;br /&gt;
На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).&lt;br /&gt;
&lt;br /&gt;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях [2] и [3], соответствующим операциям инициализации массива дистанций [2] и обновления массива c использованием данных массива ребер [3]. Операция [4] - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&lt;br /&gt;
&lt;br /&gt;
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным ресурсом параллелизма. Во-первых, поиск кратчайших путей от различных вершин может производиться независимо для каждой из вершин (параллельные вертикальные плоскости на рисунке 1). Во-вторых, поиск кратчайших путей от фиксированной вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; также может выполняться параллельно: инициализация начальных путей [2] требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; параллельных операции, релаксация каждого ребра требует &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операции. &lt;br /&gt;
&lt;br /&gt;
Таким образом, при наличии &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; процессоров алгоритм завершит работу максимум за &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; шагов. В реальности, шагов обычно требуется меньше, а именно &amp;lt;math&amp;gt;O(r)&amp;lt;/math&amp;gt; -(максимальная длина среди всех кратчайших путей от выбранной вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
Таким образом, ширина ярусно-параллельной формы алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;, высота ЯПФ - &amp;lt;math&amp;gt;O(r) | r &amp;lt; |V|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма===&lt;br /&gt;
&lt;br /&gt;
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро &amp;lt;math&amp;gt;e = (v, w)&amp;lt;/math&amp;gt; лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; удовлетворяют условию&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        d(v) + f(e) &amp;lt; d(w),&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
где &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; – вес ребра &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt;. Условие может быть проверено для всех рёбер графа за время &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.&lt;br /&gt;
&lt;br /&gt;
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практические идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. &lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 1, выделенную на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 1]]&lt;br /&gt;
&lt;br /&gt;
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а так же параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
*	размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_Беллмана-Форда.png|thumb|center|700px|Рисунок 7. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Беллмана-Форда, реализованная для CPU.  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, задача решалась для графа большого размера на одном узле. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Беллмана-Форда, запуск проводился на 1 узле для графа 2^27, выполнялась 1 итерация.&lt;br /&gt;
[[file:Bellman-Ford cpu.png|thumb|center|700px|Рисунок 8. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 9. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 10. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов тоже достаточно большое и составляет около 30 млн/сек по всем узлам. Это указывает на то, что задача очень плохо укладывается в кэш-память, и программа постоянно работает с оперативной памятью, что объясняется очень большим размером использованного графа.&lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford Inf Data.png|thumb|center|700px|Рисунок 13. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая интенсивность использования сети на кпервом этапе. Это объясняется программной логикой, которая предполагает чтение графа из файла с диска, коммуникации с которым происходят на Ломоносов-2 через выделенную для этих задач сеть Infiniband.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford Inf Pckts.png|thumb|center|700px|Рисунок 14. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается аналогичная картина очень высокой интенсивности на первом этапе выполнения задачи. Далее сеть почти не используется.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно интенсивно,  однако очень неэффективно использовала память из-за очень большого размера графа.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://jgrapht.org JGraphT] (класс &amp;lt;code&amp;gt;[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Bellman-Ford algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24148</id>
		<title>Алгоритм Дейкстры</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24148"/>
		<updated>2017-11-24T08:51:23Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Основные авторы описания: [[Участник:Daryin|А.Н.Дарьин]], [[Участник:VadimVV|Вад.В.Воеводин]] ([[#Описание локальности данных и вычислений|раздел 2.2]])&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Дейкстры&#039;&#039;&#039;&amp;lt;ref&amp;gt;Dijkstra, E W. “A Note on Two Problems in Connexion with Graphs.” Numerische Mathematik 1, no. 1 (December 1959): 269–71. doi:10.1007/BF01386390.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа с неотрицательными весами алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа.&lt;br /&gt;
Алгоритм Дейкстры (с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap&amp;gt;Fredman, Michael L, and Robert Endre Tarjan. “Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms.” Journal of the ACM 34, no. 3 (July 1987): 596–615. doi:10.1145/28869.28874.&amp;lt;/ref&amp;gt;) выполняется за время &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt; и является асимптотически быстрейшим из известных последовательных алгоритмов для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть уже вычислены все расстояния, не превосходящие некоторого числа &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt;, то есть расстояния до вершин из множества &amp;lt;math&amp;gt;V_r = \{ v \in V \mid d(v) \le r \}&amp;lt;/math&amp;gt;. Пусть&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        (v, w) \in \arg\min \{ d(v) + f(e) \mid  v \in V, e = (v, w) \in E \}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Тогда &amp;lt;math&amp;gt;d(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; лежит на кратчайшем пути от &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Величины &amp;lt;math&amp;gt;d^+(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v \in V_r&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;e = (v, w) \in E&amp;lt;/math&amp;gt;, называются &#039;&#039;предполагаемыми расстояниями&#039;&#039; и являются оценкой сверху для настоящих расстояний: &amp;lt;math&amp;gt;d(w) \le d^+(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры на каждом шаге находит вершину с наименьшим предполагаемым расстоянием, помечает её как посещённую и обновляет предполагаемые расстояния для всех концов рёбер, исходящих из неё.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основные вычисления связаны с операциями над очередью с приоритетом:&lt;br /&gt;
* извлечение минимального элемента (&amp;lt;code&amp;gt;delete_min&amp;lt;/code&amp;gt;);&lt;br /&gt;
* уменьшение приоритета элемента (&amp;lt;code&amp;gt;decrease_key&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Псевдокод алгоритма:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;Q&#039;&#039; := &#039;&#039;&#039;new&#039;&#039;&#039; priority queue&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;v&#039;&#039; = &#039;&#039;u&#039;&#039; &#039;&#039;&#039;then&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := 0 &#039;&#039;&#039;else&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞ &lt;br /&gt;
     &#039;&#039;Q&#039;&#039;.insert(&#039;&#039;v&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;))&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;while&#039;&#039;&#039; &#039;&#039;Q&#039;&#039; ≠ ∅:&lt;br /&gt;
     &#039;&#039;v&#039;&#039; := &#039;&#039;Q&#039;&#039;.delete_min()&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
             &#039;&#039;Q&#039;&#039;.decrease_key(&#039;&#039;w&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;))&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Конкретная реализация алгоритма Дейкстры определяется выбором используемого алгоритма очереди с приоритетом. В простейшем случае это может быть массив или список, поиск минимума в котором требует просмотра всех вершин. Более эффективным является использование кучи; наилучшую известную оценку сложности имеет вариант с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Возможен вариант реализации, когда вершины добавляются в очередь не на этапе инициализации, а в момент первого посещения.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма равна &amp;lt;math&amp;gt;O(C_1 m + C_2n)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; – количество операций уменьшения расстояния до вершины;&lt;br /&gt;
* &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt; – количество операций вычисления минимума.&lt;br /&gt;
&lt;br /&gt;
Оригинальный алгоритм Дейкстры использовал в качестве внутренней структуры данных списки, для которых &amp;lt;math&amp;gt;C_1 = O(1)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;C_2 = O(n)&amp;lt;/math&amp;gt;, так что общая сложность составляла &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
При использовании фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt; время вычисления минимума сокращается до &amp;lt;math&amp;gt;C_2 = O(\ln n)&amp;lt;/math&amp;gt;, так что общая сложность равна &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;, что является асимптотически наилучшим известным результатом для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Приводится граф алгоритма для базовой реализации алгоритма Дейкстры на списках или массивах.&lt;br /&gt;
[[file:Deikstra.png|thumb|center|600px|Рисунок 1. Граф алгоритма без отображения входных и выходных данных. n=3. Желтым цветом обозначены операции сравнения, зеленым - операции изменения меток вершин, синим - пометка вершины.]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры допускает эффективную параллелизацию&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;, среднее время работы &amp;lt;math&amp;gt;O(n^{1/3}\ln n)&amp;lt;/math&amp;gt; с объёмом вычислений &amp;lt;math&amp;gt;O(n \ln n + m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Дейкстры.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_1.png|thumb|center|700px|Рисунок 1. Реализация алгоритма Дейкстры. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 1 представлен профиль обращений в память для реализации алгоритма Дейкстры. Первое, что бросается в глаза, – большая разрозненность обращений. В частности, значительные области выше и ниже фрагмента 2 остаются пустыми, при этом сами обращения объединены лишь в небольшие группы. Это говорит о низкой эффективности, поскольку: а) повторные обращения практически отсутствуют либо происходят через значительный промежуток времени; б) расстояния между идущими подряд обращениями может быть очень большим.&lt;br /&gt;
&lt;br /&gt;
Однако при ближайшем рассмотрении может оказаться, что такие участки обладают высокой локальностью и состоят из значительного числа обращений. Более того, на общем профиле есть несколько областей (фрагменты 1 и 2), в которых обращения хорошо локализованы. Необходимо исследовать отдельные участки более подробно.&lt;br /&gt;
&lt;br /&gt;
Перейдем к изучению фрагмента 1 (рис. 2), в рамках которого выполняются обращения к двум небольшим массивам. Можно увидеть, что здесь задействованы только примерно 500 элементов, при этом к ним выполняется около 100 тысяч обращений. Весь профиль составляет около 120 тысяч обращений, поэтому получается, что подавляющая их часть выполняется именно к этим элементам. &lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Поскольку число элементов невелико, локальность в данном случае заведомо будет достаточно высокой, независимо от структуры самого фрагмента.  Однако на рис. 3, где представлены два участка из фрагмента 1, можно увидеть, что фрагмент в основном состоит из последовательных переборов, при этом данные зачастую задействованы повторно через не очень большие промежутки. Все это позволяет говорить, что данный фрагмент обладает высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_3.png|thumb|center|700px|Рисунок 3. Профили двух участков фрагмента 1 (выделены зеленым на рис. 2)]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь более подробно фрагмент 2 (рис. 4), в рамках которого выполняются обращения к еще служебному массиву. Здесь профиль состоит из двух этапов. На первом заметен достаточно хаотичный разброс обращений, напоминающий случайный доступ. На втором обращения образуют подобие последовательного перебора.  В целом такой профиль характеризуется очень низкой временной локальностью (повторные обращения практически или полностью отсутствуют) и достаточно низкой пространственной локальностью (из-за случайного доступа на первом этапе).&lt;br /&gt;
&lt;br /&gt;
Заметим, что при этом число задействованных элементов здесь больше, чем во фрагменте 1, однако число обращений гораздо меньше.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Далее остаются для рассмотрения два массива (область между фрагментами 1 и 2 и область ниже фрагмента 2). Характер обращений к этим массивам во многом похож, поэтому достаточно изучить более подробно только один из них. &lt;br /&gt;
&lt;br /&gt;
Фрагмент 3 рассмотрен на рис. 5. Этот участок отражаeт достаточно большую область, что не позволяет проанализировать профиль вплоть до отдельных обращений, однако здесь этого и не требуется. Из данного фрагмента видно, что основу профиля составляют участки с последовательным (или с небольшим шагом) перебором, состоящие из небольшого числа элементов – выделенный желтым самый большой участок фрагмента состоит всего из пары сотен обращений. При этом между разными участками расстояние может быть существенным. Все это говорит об очень низкой локальности (как пространственной, так и временной) в случае двух рассматриваемых массивов.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
В целом, несмотря на положительный вклад массивов из фрагмента 1, локальность общего профиля должна быть достаточно низкой, вследствие неэффективного использования данных в остальной части профиля.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Основной фрагмент реализации, на основе которого были получены количественные оценки, приведен [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/benchmarks/dijkstra/dijkstra.h здесь] (функция Kernel). Условия запуска описаны [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/README.md здесь].&lt;br /&gt;
&lt;br /&gt;
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью достаточно низка. Это неудивительно, поскольку реализации алгоритмов над графами почти всегда обладают низкой эффективностью вследствие нерегулярности доступа к данным, что мы и увидели при анализе профиля обращений.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
Вторая характеристика – cvg – предназначена для получения более машинно-независимой оценки локальности. Она определяет, насколько часто в программе необходимо подтягивать данные в кэш-память. Соответственно, чем меньше значение cvg, тем реже это нужно делать, тем лучше локальность. &lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения cvg для того же набора реализаций, отсортированные по убыванию (чем меньше cvg, тем в общем случае выше локальность). Можно увидеть, что в данном случае значение cvg хорошо коррелирует с оценкой производительности и отражает низкую локальность, что соответствует выводам, сделанным при качественной оценке локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_cvg.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки cvg]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов&amp;quot;&amp;lt;ref name=&amp;quot;Lom&amp;quot;&amp;gt;Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.&amp;lt;/ref&amp;gt; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [4, 8 : 128] со значениями квадрата целого числа;&lt;br /&gt;
*	размер графа [16000:64000] с шагом 16000.&lt;br /&gt;
&lt;br /&gt;
На следующем рисунке приведен график [[Глоссарий#Производительность|производительности]] выбранной реализации алгоритма в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация алгоритма. Изменение производительности в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
&lt;br /&gt;
В силу особенностей параллельной реализации алгоритма производительность в целом достаточно низкая и с ростом числа процессов увеличивается медленно, а при приближении к числу процессов 128 начинает уменьшаться.&lt;br /&gt;
Это объясняется использованием коллективных операций на каждой итерации алгоритма и тем, что затраты на коммуникационные обмены существенно возрастают с ростом числа использованных процессов. Вычисления на каждом процессе проходят достаточно быстро и потому декомпозиция графа слабо компенсирует эффект от затрат на коммуникационные обмены. &lt;br /&gt;
&lt;br /&gt;
[https://code.google.com/p/prir-dijkstra-mpi/source/browse/trunk/project/dijkstra_mpi.c Исследованная параллельная реализация на языке C]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Дейкстры.  Все результаты получены на суперкомпьютере &amp;quot;Ломоносов&amp;quot;. Использовались процессоры Intel Xeon X5570 с пиковой производительностью в 94 Гфлопс, а также компилятор intel 13.1.0. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Дейкстры  на 32 процессах.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra CPU User.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы уровень загрузки составляет около 50%. Это указывает на равномерную загруженность вычислениями процессоров, при использовании 8 процессов на вычислительный узел и без использования Hyper Threading. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Flops.png|thumb|center|700px|Рисунок 10. График операций с плавающей точкой в секунду при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На Рисунке 10 показан график количества операций с плавающей точкой в секунду. На графике видна общая очень низкая производительность вычислений около 250 Kфлопс в пике и около 150 Кфлопс в среднем по всем узлам. Это указывает то, что в программе почти все вычисления производятся с целыми числами. &lt;br /&gt;
 [[file:Dijkstra L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень большое для нескольких ядер и находится на уровне 15 млн/сек (в пике до 60 млн/сек), что указывает на интенсивные вычисления в части процессов. В среднем по всем узлам значения значительно ниже (около 9 млн/сек). Это указывает на неравномерное распределение вычислений.&lt;br /&gt;
 [[file:Dijkstra L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов третьего уровня видно, что число промахов очень невелико и находится на уровне 1,2 млн/сек, однако в среднем по всем узлам значения около 0,5 млн/сек. Соотношение кэш промахов L1|L3 для процессов с высокой производительностью доходит до 60, однако в среднем около 30. Это указывает на очень хорошую локальность вычислений как у части процессов, так и для всех в среднем, и это является признаком высокой производительности. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Mem Load.png|thumb|center|700px|Рисунок 13. График количества чтений из оперативной памяти при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике обращений в память видна достаточно типичная картина для таких приложений. Активность чтения достаточно низкая, что в совокупности с низкими значениями кэш-промахов L3 указывает на хорошую локальность. Хорошая локальность приложения также указывает на то, что значение около 1 млн/сек для задачи является результатом высокой производительности вычислений, хотя и присутствует неравномерность между процессами.&lt;br /&gt;
 [[file:Dijkstra Mem Store.png|thumb|center|700px|Рисунок 14. График количества записей в оперативную память при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике записей в память видна похожая картина неравномерности вычислений, при которой одновременно активно выполняют запись только несколько процессов. Это коррелирует с другими графиками выполнения. Стоит отметить достаточно низкое число обращений на запись в память. Это указывает на хорошую организацию вычислений и достаточно эффективную работу с памятью.&lt;br /&gt;
 [[file:Dijkstra Inf DATA.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая скорость передачи данных в байтах в секунду. Это говорит о том, что процессы между собой обмениваются интенсивно и вероятно достаточно малыми порциями данных, потому как производительность вычислений высока. Стоит отметить, что скорость передачи отличается между процессами, что указывает на дисбаланс вычислений.&lt;br /&gt;
 [[file:Dijkstra Inf PCKTS.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается крайне высокая интенсивность передачи данных. Это говорит о том, что, вероятно, процессы обмениваются не очень существенными объемами данных, но очень интенсивно. Используются коллективные операции на каждом шаге с небольшими порциями данных, что объясняет такую картину.  Также наблюдается меньший дизбаланс между процессами, чем наблюдаемый в графиках использования памяти, вычислений и передачи данных в байтах/сек. Это указывает на то, что процессы обмениваются по алгоритму одинаковым числом пакетов, однако получают разные объемы данных и ведут неравномерные вычисления. &lt;br /&gt;
 [[file:Dijkstra Load ABG.png|thumb|center|700px|Рисунок 17. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Дейкстры]]&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 8. Это свидетельствует о стабильной работе программы с загруженными вычислениями всеми узлами. Это указывает на очень рациональную и статичную загрузку аппаратных ресурсов процессами. И показывает достаточно хорошую эффективность выполняемой реализации.&lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала достаточно эффективно, и стабильно. Использование памяти  очень интенсивное, а использование коммуникационной среды крайне интенсивное, при этом объемы передаваемых данных не являются высокими. Это указывает на требовательность к латентности коммуникационной среды алгоритмической части программы. &lt;br /&gt;
Низкая эффективность связана, судя по всему, с достаточно высоким объемом пересылок на каждом процессе и с интенсивными обменами сообщениями.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html dijkstra_shortest_paths]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths_no_color_map.html dijkstra_shortest_paths_no_color_map]&amp;lt;/code&amp;gt;), сложность &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;. &lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]:&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#eager-dijkstra-s-algorithm eager_dijkstra_shortest_paths]&amp;lt;/code&amp;gt; – непосредственная реализация алгоритма Дейкстры;&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#crauser-et-al-s-algorithm crauser_et_al_shortest_paths]&amp;lt;/code&amp;gt; – реализация алгоритма Дейкстры в виде алгоритма из статьи Краузера и др.&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.single_source_dijkstra.html single_source_dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.Dijkstra networkit.graph.Dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Dijkstra&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24143</id>
		<title>Алгоритм Дейкстры</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24143"/>
		<updated>2017-11-23T20:23:22Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Основные авторы описания: [[Участник:Daryin|А.Н.Дарьин]], [[Участник:VadimVV|Вад.В.Воеводин]] ([[#Описание локальности данных и вычислений|раздел 2.2]])&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Дейкстры&#039;&#039;&#039;&amp;lt;ref&amp;gt;Dijkstra, E W. “A Note on Two Problems in Connexion with Graphs.” Numerische Mathematik 1, no. 1 (December 1959): 269–71. doi:10.1007/BF01386390.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа с неотрицательными весами алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа.&lt;br /&gt;
Алгоритм Дейкстры (с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap&amp;gt;Fredman, Michael L, and Robert Endre Tarjan. “Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms.” Journal of the ACM 34, no. 3 (July 1987): 596–615. doi:10.1145/28869.28874.&amp;lt;/ref&amp;gt;) выполняется за время &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt; и является асимптотически быстрейшим из известных последовательных алгоритмов для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть уже вычислены все расстояния, не превосходящие некоторого числа &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt;, то есть расстояния до вершин из множества &amp;lt;math&amp;gt;V_r = \{ v \in V \mid d(v) \le r \}&amp;lt;/math&amp;gt;. Пусть&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        (v, w) \in \arg\min \{ d(v) + f(e) \mid  v \in V, e = (v, w) \in E \}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Тогда &amp;lt;math&amp;gt;d(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; лежит на кратчайшем пути от &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Величины &amp;lt;math&amp;gt;d^+(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v \in V_r&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;e = (v, w) \in E&amp;lt;/math&amp;gt;, называются &#039;&#039;предполагаемыми расстояниями&#039;&#039; и являются оценкой сверху для настоящих расстояний: &amp;lt;math&amp;gt;d(w) \le d^+(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры на каждом шаге находит вершину с наименьшим предполагаемым расстоянием, помечает её как посещённую и обновляет предполагаемые расстояния для всех концов рёбер, исходящих из неё.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основные вычисления связаны с операциями над очередью с приоритетом:&lt;br /&gt;
* извлечение минимального элемента (&amp;lt;code&amp;gt;delete_min&amp;lt;/code&amp;gt;);&lt;br /&gt;
* уменьшение приоритета элемента (&amp;lt;code&amp;gt;decrease_key&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Псевдокод алгоритма:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;Q&#039;&#039; := &#039;&#039;&#039;new&#039;&#039;&#039; priority queue&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;v&#039;&#039; = &#039;&#039;u&#039;&#039; &#039;&#039;&#039;then&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := 0 &#039;&#039;&#039;else&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞ &lt;br /&gt;
     &#039;&#039;Q&#039;&#039;.insert(&#039;&#039;v&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;))&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;while&#039;&#039;&#039; &#039;&#039;Q&#039;&#039; ≠ ∅:&lt;br /&gt;
     &#039;&#039;v&#039;&#039; := &#039;&#039;Q&#039;&#039;.delete_min()&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
             &#039;&#039;Q&#039;&#039;.decrease_key(&#039;&#039;w&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;))&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Конкретная реализация алгоритма Дейкстры определяется выбором используемого алгоритма очереди с приоритетом. В простейшем случае это может быть массив или список, поиск минимума в котором требует просмотра всех вершин. Более эффективным является использование кучи; наилучшую известную оценку сложности имеет вариант с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Возможен вариант реализации, когда вершины добавляются в очередь не на этапе инициализации, а в момент первого посещения.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма равна &amp;lt;math&amp;gt;O(C_1 m + C_2n)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; – количество операций уменьшения расстояния до вершины;&lt;br /&gt;
* &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt; – количество операций вычисления минимума.&lt;br /&gt;
&lt;br /&gt;
Оригинальный алгоритм Дейкстры использовал в качестве внутренней структуры данных списки, для которых &amp;lt;math&amp;gt;C_1 = O(1)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;C_2 = O(n)&amp;lt;/math&amp;gt;, так что общая сложность составляла &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
При использовании фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt; время вычисления минимума сокращается до &amp;lt;math&amp;gt;C_2 = O(\ln n)&amp;lt;/math&amp;gt;, так что общая сложность равна &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;, что является асимптотически наилучшим известным результатом для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Приводится граф алгоритма для базовой реализации алгоритма Дейкстры на списках или массивах.&lt;br /&gt;
[[file:Deikstra.png|thumb|center|600px|Рисунок 1. Граф алгоритма без отображения входных и выходных данных. n=3. Желтым цветом обозначены операции сравнения, зеленым - операции изменения меток вершин, синим - пометка вершины.]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры допускает эффективную параллелизацию&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;, среднее время работы &amp;lt;math&amp;gt;O(n^{1/3}\ln n)&amp;lt;/math&amp;gt; с объёмом вычислений &amp;lt;math&amp;gt;O(n \ln n + m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Дейкстры.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_1.png|thumb|center|700px|Рисунок 1. Реализация алгоритма Дейкстры. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 1 представлен профиль обращений в память для реализации алгоритма Дейкстры. Первое, что бросается в глаза, – большая разрозненность обращений. В частности, значительные области выше и ниже фрагмента 2 остаются пустыми, при этом сами обращения объединены лишь в небольшие группы. Это говорит о низкой эффективности, поскольку: а) повторные обращения практически отсутствуют либо происходят через значительный промежуток времени; б) расстояния между идущими подряд обращениями может быть очень большим.&lt;br /&gt;
&lt;br /&gt;
Однако при ближайшем рассмотрении может оказаться, что такие участки обладают высокой локальностью и состоят из значительного числа обращений. Более того, на общем профиле есть несколько областей (фрагменты 1 и 2), в которых обращения хорошо локализованы. Необходимо исследовать отдельные участки более подробно.&lt;br /&gt;
&lt;br /&gt;
Перейдем к изучению фрагмента 1 (рис. 2), в рамках которого выполняются обращения к двум небольшим массивам. Можно увидеть, что здесь задействованы только примерно 500 элементов, при этом к ним выполняется около 100 тысяч обращений. Весь профиль составляет около 120 тысяч обращений, поэтому получается, что подавляющая их часть выполняется именно к этим элементам. &lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Поскольку число элементов невелико, локальность в данном случае заведомо будет достаточно высокой, независимо от структуры самого фрагмента.  Однако на рис. 3, где представлены два участка из фрагмента 1, можно увидеть, что фрагмент в основном состоит из последовательных переборов, при этом данные зачастую задействованы повторно через не очень большие промежутки. Все это позволяет говорить, что данный фрагмент обладает высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_3.png|thumb|center|700px|Рисунок 3. Профили двух участков фрагмента 1 (выделены зеленым на рис. 2)]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь более подробно фрагмент 2 (рис. 4), в рамках которого выполняются обращения к еще служебному массиву. Здесь профиль состоит из двух этапов. На первом заметен достаточно хаотичный разброс обращений, напоминающий случайный доступ. На втором обращения образуют подобие последовательного перебора.  В целом такой профиль характеризуется очень низкой временной локальностью (повторные обращения практически или полностью отсутствуют) и достаточно низкой пространственной локальностью (из-за случайного доступа на первом этапе).&lt;br /&gt;
&lt;br /&gt;
Заметим, что при этом число задействованных элементов здесь больше, чем во фрагменте 1, однако число обращений гораздо меньше.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Далее остаются для рассмотрения два массива (область между фрагментами 1 и 2 и область ниже фрагмента 2). Характер обращений к этим массивам во многом похож, поэтому достаточно изучить более подробно только один из них. &lt;br /&gt;
&lt;br /&gt;
Фрагмент 3 рассмотрен на рис. 5. Этот участок отражаeт достаточно большую область, что не позволяет проанализировать профиль вплоть до отдельных обращений, однако здесь этого и не требуется. Из данного фрагмента видно, что основу профиля составляют участки с последовательным (или с небольшим шагом) перебором, состоящие из небольшого числа элементов – выделенный желтым самый большой участок фрагмента состоит всего из пары сотен обращений. При этом между разными участками расстояние может быть существенным. Все это говорит об очень низкой локальности (как пространственной, так и временной) в случае двух рассматриваемых массивов.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
В целом, несмотря на положительный вклад массивов из фрагмента 1, локальность общего профиля должна быть достаточно низкой, вследствие неэффективного использования данных в остальной части профиля.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Основной фрагмент реализации, на основе которого были получены количественные оценки, приведен [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/benchmarks/dijkstra/dijkstra.h здесь] (функция Kernel). Условия запуска описаны [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/README.md здесь].&lt;br /&gt;
&lt;br /&gt;
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью достаточно низка. Это неудивительно, поскольку реализации алгоритмов над графами почти всегда обладают низкой эффективностью вследствие нерегулярности доступа к данным, что мы и увидели при анализе профиля обращений.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
Вторая характеристика – cvg – предназначена для получения более машинно-независимой оценки локальности. Она определяет, насколько часто в программе необходимо подтягивать данные в кэш-память. Соответственно, чем меньше значение cvg, тем реже это нужно делать, тем лучше локальность. &lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения cvg для того же набора реализаций, отсортированные по убыванию (чем меньше cvg, тем в общем случае выше локальность). Можно увидеть, что в данном случае значение cvg хорошо коррелирует с оценкой производительности и отражает низкую локальность, что соответствует выводам, сделанным при качественной оценке локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_cvg.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки cvg]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов&amp;quot;&amp;lt;ref name=&amp;quot;Lom&amp;quot;&amp;gt;Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.&amp;lt;/ref&amp;gt; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [4, 8 : 128] со значениями квадрата целого числа;&lt;br /&gt;
*	размер графа [16000:64000] с шагом 16000.&lt;br /&gt;
&lt;br /&gt;
На следующем рисунке приведен график [[Глоссарий#Производительность|производительности]] выбранной реализации алгоритма в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация алгоритма. Изменение производительности в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
&lt;br /&gt;
В силу особенностей параллельной реализации алгоритма производительность в целом достаточно низкая и с ростом числа процессов увеличивается медленно, а при приближении к числу процессов 128 начинает уменьшаться.&lt;br /&gt;
Это объясняется использованием коллективных операций на каждой итерации алгоритма и тем, что затраты на коммуникационные обмены существенно возрастают с ростом числа использованных процессов. Вычисления на каждом процессе проходят достаточно быстро и потому декомпозиция графа слабо компенсирует эффект от затрат на коммуникационные обмены. &lt;br /&gt;
&lt;br /&gt;
[https://code.google.com/p/prir-dijkstra-mpi/source/browse/trunk/project/dijkstra_mpi.c Исследованная параллельная реализация на языке C]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Дейкстры.  Все результаты получены на суперкомпьютере &amp;quot;Ломоносов&amp;quot;. Использовались процессоры Intel Xeon X5570 с пиковой производительностью в 94 Гфлопс, а также компилятор intel 13.1.0. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Дейкстры  на 32 процессах.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra CPU User.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы уровень загрузки составляет около 50%. Это указывает на равномерную загруженность вычислениями процессоров, при использовании 8 процессов на вычислительный узел и без использования Hyper Threading. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Flops.png|thumb|center|700px|Рисунок 10. График операций с плавающей точкой в секунду при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На Рисунке 10 показан график количества операций с плавающей точкой в секунду. На графике видна общая очень низкая производительность вычислений около 250 Kфлопс в пике и около 150 Кфлопс в среднем по всем узлам. Это указывает то, что в программе почти все вычисления производятся с целыми числами. &lt;br /&gt;
 [[file:Dijkstra L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень большое для нескольких ядер и находится на уровне 15 млн/сек (в пике до 60 млн/сек), что указывает на интенсивные вычисления в части процессов. В среднем по всем узлам значения значительно ниже (около 9 млн/сек). Это указывает на неравномерное распределение вычислений.&lt;br /&gt;
 [[file:Dijkstra L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов третьего уровня видно, что число промахов очень невелико и находится на уровне 1,2 млн/сек, однако в среднем по всем узлам значения около 0,5 млн/сек. Соотношение кэш промахов L1|L3 для процессов с высокой производительностью доходит до 60, однако в среднем около 30. Это указывает на очень хорошую локальность вычислений как у части процессов, так и для всех в среднем, и это является признаком высокой производительности. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Mem Load.png|thumb|center|700px|Рисунок 13. График количества чтений из оперативной памяти при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике обращений в память видна достаточно типичная картина для таких приложений. Активность чтения достаточно низкая, что в совокупности с низкими значениями кэш-промахов L3 указывает на хорошую локальность. Хорошая локальность приложения также указывает на то, что значение около 1 млн/сек для задачи является результатом высокой производительности вычислений, хотя и присутствует неравномерность между процессами.&lt;br /&gt;
 [[file:Dijkstra Mem Store.png|thumb|center|700px|Рисунок 14. График количества записей в оперативную память при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике записей в память видна похожая картина неравномерности вычислений, при которой одновременно активно выполняют запись только несколько процессов. Это коррелирует с другими графиками выполнения. Стоит отметить достаточно низкое число обращений на запись в память. Это указывает на хорошую организацию вычислений и достаточно эффективную работу с памятью.&lt;br /&gt;
 [[file:Dijkstra Inf DATA.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая скорость передачи данных в байтах в секунду. Это говорит о том, что процессы между собой обмениваются интенсивно и вероятно достаточно малыми порциями данных, потому как производительность вычислений высока. Стоит отметить, что скорость передачи отличается между процессами, что указывает на дисбаланс вычислений.&lt;br /&gt;
 [[file:Dijkstra Inf PCKTS.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается крайне высокая интенсивность передачи данных. Это говорит о том, что, вероятно, процессы обмениваются не очень существенными объемами данных, но очень интенсивно. Используются коллективные операции на каждом шаге с небольшими порциями данных, что объясняет такую картину.  Также наблюдается меньший дизбаланс между процессами, чем наблюдаемый в графиках использования памяти, вычислений и передачи данных в байтах/сек. Это указывает на то, что процессы обмениваются по алгоритму одинаковым числом пакетов, однако получают разные объемы данных и ведут неравномерные вычисления. &lt;br /&gt;
 [[file:Dijkstra Load ABG.png|thumb|center|700px|Рисунок 17. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Дейкстры]]&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 8. Это свидетельствует о стабильной работе программы с загруженными вычислениями всеми узлами. Это указывает на очень рациональную и статичную загрузку аппаратных ресурсов процессами. И показывает достаточно хорошую эффективность выполняемой реализации.&lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала достаточно эффективно, и стабильно. Использование памяти  очень интенсивное, а использование коммуникационной среды крайне интенсивное, при этом объемы передаваемых данных не являются высокими. Это указывает на требовательность к латентности коммуникационной среды алгоритмической части программы. &lt;br /&gt;
Низкая эффективность связана судя по всему с достаточно высоким объемом пересылок на каждом процессе, интенсивными обменами сообщениями.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html dijkstra_shortest_paths]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths_no_color_map.html dijkstra_shortest_paths_no_color_map]&amp;lt;/code&amp;gt;), сложность &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;. &lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]:&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#eager-dijkstra-s-algorithm eager_dijkstra_shortest_paths]&amp;lt;/code&amp;gt; – непосредственная реализация алгоритма Дейкстры;&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#crauser-et-al-s-algorithm crauser_et_al_shortest_paths]&amp;lt;/code&amp;gt; – реализация алгоритма Дейкстры в виде алгоритма из статьи Краузера и др.&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.single_source_dijkstra.html single_source_dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.Dijkstra networkit.graph.Dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Dijkstra&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24141</id>
		<title>Алгоритм Дейкстры</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24141"/>
		<updated>2017-11-23T19:15:43Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Основные авторы описания: [[Участник:Daryin|А.Н.Дарьин]], [[Участник:VadimVV|Вад.В.Воеводин]] ([[#Описание локальности данных и вычислений|раздел 2.2]])&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Дейкстры&#039;&#039;&#039;&amp;lt;ref&amp;gt;Dijkstra, E W. “A Note on Two Problems in Connexion with Graphs.” Numerische Mathematik 1, no. 1 (December 1959): 269–71. doi:10.1007/BF01386390.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа с неотрицательными весами алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа.&lt;br /&gt;
Алгоритм Дейкстры (с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap&amp;gt;Fredman, Michael L, and Robert Endre Tarjan. “Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms.” Journal of the ACM 34, no. 3 (July 1987): 596–615. doi:10.1145/28869.28874.&amp;lt;/ref&amp;gt;) выполняется за время &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt; и является асимптотически быстрейшим из известных последовательных алгоритмов для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть уже вычислены все расстояния, не превосходящие некоторого числа &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt;, то есть расстояния до вершин из множества &amp;lt;math&amp;gt;V_r = \{ v \in V \mid d(v) \le r \}&amp;lt;/math&amp;gt;. Пусть&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        (v, w) \in \arg\min \{ d(v) + f(e) \mid  v \in V, e = (v, w) \in E \}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Тогда &amp;lt;math&amp;gt;d(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; лежит на кратчайшем пути от &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Величины &amp;lt;math&amp;gt;d^+(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v \in V_r&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;e = (v, w) \in E&amp;lt;/math&amp;gt;, называются &#039;&#039;предполагаемыми расстояниями&#039;&#039; и являются оценкой сверху для настоящих расстояний: &amp;lt;math&amp;gt;d(w) \le d^+(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры на каждом шаге находит вершину с наименьшим предполагаемым расстоянием, помечает её как посещённую и обновляет предполагаемые расстояния для всех концов рёбер, исходящих из неё.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основные вычисления связаны с операциями над очередью с приоритетом:&lt;br /&gt;
* извлечение минимального элемента (&amp;lt;code&amp;gt;delete_min&amp;lt;/code&amp;gt;);&lt;br /&gt;
* уменьшение приоритета элемента (&amp;lt;code&amp;gt;decrease_key&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Псевдокод алгоритма:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;Q&#039;&#039; := &#039;&#039;&#039;new&#039;&#039;&#039; priority queue&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;v&#039;&#039; = &#039;&#039;u&#039;&#039; &#039;&#039;&#039;then&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := 0 &#039;&#039;&#039;else&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞ &lt;br /&gt;
     &#039;&#039;Q&#039;&#039;.insert(&#039;&#039;v&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;))&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;while&#039;&#039;&#039; &#039;&#039;Q&#039;&#039; ≠ ∅:&lt;br /&gt;
     &#039;&#039;v&#039;&#039; := &#039;&#039;Q&#039;&#039;.delete_min()&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
             &#039;&#039;Q&#039;&#039;.decrease_key(&#039;&#039;w&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;))&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Конкретная реализация алгоритма Дейкстры определяется выбором используемого алгоритма очереди с приоритетом. В простейшем случае это может быть массив или список, поиск минимума в котором требует просмотра всех вершин. Более эффективным является использование кучи; наилучшую известную оценку сложности имеет вариант с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Возможен вариант реализации, когда вершины добавляются в очередь не на этапе инициализации, а в момент первого посещения.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма равна &amp;lt;math&amp;gt;O(C_1 m + C_2n)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; – количество операций уменьшения расстояния до вершины;&lt;br /&gt;
* &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt; – количество операций вычисления минимума.&lt;br /&gt;
&lt;br /&gt;
Оригинальный алгоритм Дейкстры использовал в качестве внутренней структуры данных списки, для которых &amp;lt;math&amp;gt;C_1 = O(1)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;C_2 = O(n)&amp;lt;/math&amp;gt;, так что общая сложность составляла &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
При использовании фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt; время вычисления минимума сокращается до &amp;lt;math&amp;gt;C_2 = O(\ln n)&amp;lt;/math&amp;gt;, так что общая сложность равна &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;, что является асимптотически наилучшим известным результатом для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Приводится граф алгоритма для базовой реализации алгоритма Дейкстры на списках или массивах.&lt;br /&gt;
[[file:Deikstra.png|thumb|center|600px|Рисунок 1. Граф алгоритма без отображения входных и выходных данных. n=3. Желтым цветом обозначены операции сравнения, зеленым - операции изменения меток вершин, синим - пометка вершины.]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры допускает эффективную параллелизацию&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;, среднее время работы &amp;lt;math&amp;gt;O(n^{1/3}\ln n)&amp;lt;/math&amp;gt; с объёмом вычислений &amp;lt;math&amp;gt;O(n \ln n + m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Дейкстры.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_1.png|thumb|center|700px|Рисунок 1. Реализация алгоритма Дейкстры. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 1 представлен профиль обращений в память для реализации алгоритма Дейкстры. Первое, что бросается в глаза, – большая разрозненность обращений. В частности, значительные области выше и ниже фрагмента 2 остаются пустыми, при этом сами обращения объединены лишь в небольшие группы. Это говорит о низкой эффективности, поскольку: а) повторные обращения практически отсутствуют либо происходят через значительный промежуток времени; б) расстояния между идущими подряд обращениями может быть очень большим.&lt;br /&gt;
&lt;br /&gt;
Однако при ближайшем рассмотрении может оказаться, что такие участки обладают высокой локальностью и состоят из значительного числа обращений. Более того, на общем профиле есть несколько областей (фрагменты 1 и 2), в которых обращения хорошо локализованы. Необходимо исследовать отдельные участки более подробно.&lt;br /&gt;
&lt;br /&gt;
Перейдем к изучению фрагмента 1 (рис. 2), в рамках которого выполняются обращения к двум небольшим массивам. Можно увидеть, что здесь задействованы только примерно 500 элементов, при этом к ним выполняется около 100 тысяч обращений. Весь профиль составляет около 120 тысяч обращений, поэтому получается, что подавляющая их часть выполняется именно к этим элементам. &lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Поскольку число элементов невелико, локальность в данном случае заведомо будет достаточно высокой, независимо от структуры самого фрагмента.  Однако на рис. 3, где представлены два участка из фрагмента 1, можно увидеть, что фрагмент в основном состоит из последовательных переборов, при этом данные зачастую задействованы повторно через не очень большие промежутки. Все это позволяет говорить, что данный фрагмент обладает высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_3.png|thumb|center|700px|Рисунок 3. Профили двух участков фрагмента 1 (выделены зеленым на рис. 2)]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь более подробно фрагмент 2 (рис. 4), в рамках которого выполняются обращения к еще служебному массиву. Здесь профиль состоит из двух этапов. На первом заметен достаточно хаотичный разброс обращений, напоминающий случайный доступ. На втором обращения образуют подобие последовательного перебора.  В целом такой профиль характеризуется очень низкой временной локальностью (повторные обращения практически или полностью отсутствуют) и достаточно низкой пространственной локальностью (из-за случайного доступа на первом этапе).&lt;br /&gt;
&lt;br /&gt;
Заметим, что при этом число задействованных элементов здесь больше, чем во фрагменте 1, однако число обращений гораздо меньше.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Далее остаются для рассмотрения два массива (область между фрагментами 1 и 2 и область ниже фрагмента 2). Характер обращений к этим массивам во многом похож, поэтому достаточно изучить более подробно только один из них. &lt;br /&gt;
&lt;br /&gt;
Фрагмент 3 рассмотрен на рис. 5. Этот участок отражаeт достаточно большую область, что не позволяет проанализировать профиль вплоть до отдельных обращений, однако здесь этого и не требуется. Из данного фрагмента видно, что основу профиля составляют участки с последовательным (или с небольшим шагом) перебором, состоящие из небольшого числа элементов – выделенный желтым самый большой участок фрагмента состоит всего из пары сотен обращений. При этом между разными участками расстояние может быть существенным. Все это говорит об очень низкой локальности (как пространственной, так и временной) в случае двух рассматриваемых массивов.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
В целом, несмотря на положительный вклад массивов из фрагмента 1, локальность общего профиля должна быть достаточно низкой, вследствие неэффективного использования данных в остальной части профиля.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Основной фрагмент реализации, на основе которого были получены количественные оценки, приведен [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/benchmarks/dijkstra/dijkstra.h здесь] (функция Kernel). Условия запуска описаны [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/README.md здесь].&lt;br /&gt;
&lt;br /&gt;
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью достаточно низка. Это неудивительно, поскольку реализации алгоритмов над графами почти всегда обладают низкой эффективностью вследствие нерегулярности доступа к данным, что мы и увидели при анализе профиля обращений.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
Вторая характеристика – cvg – предназначена для получения более машинно-независимой оценки локальности. Она определяет, насколько часто в программе необходимо подтягивать данные в кэш-память. Соответственно, чем меньше значение cvg, тем реже это нужно делать, тем лучше локальность. &lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения cvg для того же набора реализаций, отсортированные по убыванию (чем меньше cvg, тем в общем случае выше локальность). Можно увидеть, что в данном случае значение cvg хорошо коррелирует с оценкой производительности и отражает низкую локальность, что соответствует выводам, сделанным при качественной оценке локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_cvg.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки cvg]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов&amp;quot;&amp;lt;ref name=&amp;quot;Lom&amp;quot;&amp;gt;Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.&amp;lt;/ref&amp;gt; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [4, 8 : 128] со значениями квадрата целого числа;&lt;br /&gt;
*	размер графа [16000:64000] с шагом 16000.&lt;br /&gt;
&lt;br /&gt;
На следующем рисунке приведен график [[Глоссарий#Производительность|производительности]] выбранной реализации алгоритма в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация алгоритма. Изменение производительности в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
&lt;br /&gt;
В силу особенностей параллельной реализации алгоритма производительность в целом достаточно низкая и с ростом числа процессов увеличивается медленно, а при приближении к числу процессов 128 начинает уменьшаться.&lt;br /&gt;
Это объясняется использованием коллективных операций на каждой итерации алгоритма и тем, что затраты на коммуникационные обмены существенно возрастают с ростом числа использованных процессов. Вычисления на каждом процессе проходят достаточно быстро и потому декомпозиция графа слабо компенсирует эффект от затрат на коммуникационные обмены. &lt;br /&gt;
&lt;br /&gt;
[https://code.google.com/p/prir-dijkstra-mpi/source/browse/trunk/project/dijkstra_mpi.c Исследованная параллельная реализация на языке C]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Дейкстры.  Все результаты получены на суперкомпьютере &amp;quot;Ломоносов&amp;quot;. Использовались процессоры Intel Xeon X5570 с пиковой производительностью в 94 Гфлопс, а также компилятор intel 13.1.0. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Дейкстры  на 32 процессах.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra CPU User.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы уровень загрузки составляет около 50%. Это указывает на равномерную загруженность вычислениями процессоров, при использовании 8 процессов на вычислительный узел и без использования Hyper Threading. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Flops.png|thumb|center|700px|Рисунок 10. График операций с плавающей точкой в секунду при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На Рисунке 10 показан график количества операций с плавающей точкой в секунду. На графике видна общая очень низкая производительность вычислений около 250 Kфлопс в пике и около 150 Кфлопс в среднем по всем узлам. Это указывает то, что в программе почти все вычисления производятся с целыми числами. &lt;br /&gt;
 [[file:Dijkstra L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень большое для нескольких ядер и находится на уровне 15 млн/сек (в пике до 60 млн/сек), что указывает на интенсивные вычисления в части процессов. В среднем по всем узлам значения значительно ниже (около 9 млн/сек). Это указывает на неравномерное распределение вычислений.&lt;br /&gt;
 [[file:Dijkstra L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов третьего уровня видно, что число промахов очень невелико и находится на уровне 1,2 млн/сек, однако в среднем по всем узлам значения около 0,5 млн/сек. Соотношение кэш промахов L1|L3 для процессов с высокой производительностью доходит до 60, однако в среднем около 30. Это указывает на очень хорошую локальность вычислений как у части процессов, так и для всех в среднем, и это является признаком высокой производительности. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Mem Load.png|thumb|center|700px|Рисунок 13. График количества чтений из оперативной памяти при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике обращений в память видна достаточно типичная картина для таких приложений. Активность чтения достаточно низкая, что в совокупности с низкими значениями кэш-промахов L3 указывает на хорошую локальность. Хорошая локальность приложения также указывает на то, что значение около 1 млн/сек для задачи является результатом высокой производительности вычислений, хотя и присутствует неравномерность между процессами.&lt;br /&gt;
 [[file:Dijkstra Mem Store.png|thumb|center|700px|Рисунок 14. График количества записей в оперативную память при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике записей в память видна похожая картина неравномерности вычислений, при которой одновременно активно выполняют запись только несколько процессов. Это коррелирует с другими графиками выполнения. Стоит отметить достаточно низкое число обращений на запись в память. Это указывает на хорошую организацию вычислений и достаточно эффективную работу с памятью.&lt;br /&gt;
 [[file:Dijkstra Inf DATA.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая скорость передачи данных в байтах в секунду. Это говорит о том, что процессы между собой обмениваются интенсивно и вероятно достаточно малыми порциями данных, потому как производительность вычислений высока. Стоит отметить, что скорость передачи отличается между процессами, что указывает на дисбаланс вычислений.&lt;br /&gt;
 [[file:Dijkstra Inf PCKTS.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается крайне высокая интенсивность передачи данных выраженная в пакетах в секунду. Это говорит о том, что, вероятно, процессы обмениваются не очень существенными объемами данных, но очень интенсивно. Используются коллективные операции на каждом шаге с небольшими порциями данных, что объясняет такую картину.  Так же наблюдается почти меньший дизбаланс между процессами чем наблюдаемый в графиках использования памяти и вычислений и передачи данных в байтах/сек. Это указывает на то, что процессы обмениваются по алгоритму одинаковым числом пакетов, однако получают разные объемы данных и ведут неравномерные вычисления. &lt;br /&gt;
 [[file:Dijkstra Load ABG.png|thumb|center|700px|Рисунок 17. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Дейкстры]]&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 8. Это свидетельствует о стабильной работе программы с загруженными вычислениями всеми узлами. Это указывает на очень рациональную и статичную загрузку аппаратных ресурсов процессами. И показывает достаточно хорошую эффективность выполняемой реализации.&lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала достаточно эффективно, и стабильно. Использование памяти  очень интенсивное, а использование коммуникационной среды крайне интенсивное, при этом объемы передаваемых данных не являются высокими. Это указывает на требовательность к латентности коммуникационной среды алгоритмической части программы. &lt;br /&gt;
Низкая эффективность связана судя по всему с достаточно высоким объемом пересылок на каждом процессе, интенсивными обменами сообщениями.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html dijkstra_shortest_paths]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths_no_color_map.html dijkstra_shortest_paths_no_color_map]&amp;lt;/code&amp;gt;), сложность &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;. &lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]:&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#eager-dijkstra-s-algorithm eager_dijkstra_shortest_paths]&amp;lt;/code&amp;gt; – непосредственная реализация алгоритма Дейкстры;&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#crauser-et-al-s-algorithm crauser_et_al_shortest_paths]&amp;lt;/code&amp;gt; – реализация алгоритма Дейкстры в виде алгоритма из статьи Краузера и др.&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.single_source_dijkstra.html single_source_dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.Dijkstra networkit.graph.Dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Dijkstra&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24140</id>
		<title>Алгоритм Дейкстры</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24140"/>
		<updated>2017-11-23T16:55:54Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Основные авторы описания: [[Участник:Daryin|А.Н.Дарьин]], [[Участник:VadimVV|Вад.В.Воеводин]] ([[#Описание локальности данных и вычислений|раздел 2.2]])&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Дейкстры&#039;&#039;&#039;&amp;lt;ref&amp;gt;Dijkstra, E W. “A Note on Two Problems in Connexion with Graphs.” Numerische Mathematik 1, no. 1 (December 1959): 269–71. doi:10.1007/BF01386390.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа с неотрицательными весами алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа.&lt;br /&gt;
Алгоритм Дейкстры (с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap&amp;gt;Fredman, Michael L, and Robert Endre Tarjan. “Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms.” Journal of the ACM 34, no. 3 (July 1987): 596–615. doi:10.1145/28869.28874.&amp;lt;/ref&amp;gt;) выполняется за время &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt; и является асимптотически быстрейшим из известных последовательных алгоритмов для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть уже вычислены все расстояния, не превосходящие некоторого числа &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt;, то есть расстояния до вершин из множества &amp;lt;math&amp;gt;V_r = \{ v \in V \mid d(v) \le r \}&amp;lt;/math&amp;gt;. Пусть&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        (v, w) \in \arg\min \{ d(v) + f(e) \mid  v \in V, e = (v, w) \in E \}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Тогда &amp;lt;math&amp;gt;d(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; лежит на кратчайшем пути от &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Величины &amp;lt;math&amp;gt;d^+(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v \in V_r&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;e = (v, w) \in E&amp;lt;/math&amp;gt;, называются &#039;&#039;предполагаемыми расстояниями&#039;&#039; и являются оценкой сверху для настоящих расстояний: &amp;lt;math&amp;gt;d(w) \le d^+(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры на каждом шаге находит вершину с наименьшим предполагаемым расстоянием, помечает её как посещённую и обновляет предполагаемые расстояния для всех концов рёбер, исходящих из неё.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основные вычисления связаны с операциями над очередью с приоритетом:&lt;br /&gt;
* извлечение минимального элемента (&amp;lt;code&amp;gt;delete_min&amp;lt;/code&amp;gt;);&lt;br /&gt;
* уменьшение приоритета элемента (&amp;lt;code&amp;gt;decrease_key&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Псевдокод алгоритма:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;Q&#039;&#039; := &#039;&#039;&#039;new&#039;&#039;&#039; priority queue&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;v&#039;&#039; = &#039;&#039;u&#039;&#039; &#039;&#039;&#039;then&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := 0 &#039;&#039;&#039;else&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞ &lt;br /&gt;
     &#039;&#039;Q&#039;&#039;.insert(&#039;&#039;v&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;))&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;while&#039;&#039;&#039; &#039;&#039;Q&#039;&#039; ≠ ∅:&lt;br /&gt;
     &#039;&#039;v&#039;&#039; := &#039;&#039;Q&#039;&#039;.delete_min()&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
             &#039;&#039;Q&#039;&#039;.decrease_key(&#039;&#039;w&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;))&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Конкретная реализация алгоритма Дейкстры определяется выбором используемого алгоритма очереди с приоритетом. В простейшем случае это может быть массив или список, поиск минимума в котором требует просмотра всех вершин. Более эффективным является использование кучи; наилучшую известную оценку сложности имеет вариант с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Возможен вариант реализации, когда вершины добавляются в очередь не на этапе инициализации, а в момент первого посещения.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма равна &amp;lt;math&amp;gt;O(C_1 m + C_2n)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; – количество операций уменьшения расстояния до вершины;&lt;br /&gt;
* &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt; – количество операций вычисления минимума.&lt;br /&gt;
&lt;br /&gt;
Оригинальный алгоритм Дейкстры использовал в качестве внутренней структуры данных списки, для которых &amp;lt;math&amp;gt;C_1 = O(1)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;C_2 = O(n)&amp;lt;/math&amp;gt;, так что общая сложность составляла &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
При использовании фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt; время вычисления минимума сокращается до &amp;lt;math&amp;gt;C_2 = O(\ln n)&amp;lt;/math&amp;gt;, так что общая сложность равна &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;, что является асимптотически наилучшим известным результатом для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Приводится граф алгоритма для базовой реализации алгоритма Дейкстры на списках или массивах.&lt;br /&gt;
[[file:Deikstra.png|thumb|center|600px|Рисунок 1. Граф алгоритма без отображения входных и выходных данных. n=3. Желтым цветом обозначены операции сравнения, зеленым - операции изменения меток вершин, синим - пометка вершины.]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры допускает эффективную параллелизацию&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;, среднее время работы &amp;lt;math&amp;gt;O(n^{1/3}\ln n)&amp;lt;/math&amp;gt; с объёмом вычислений &amp;lt;math&amp;gt;O(n \ln n + m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Дейкстры.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_1.png|thumb|center|700px|Рисунок 1. Реализация алгоритма Дейкстры. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 1 представлен профиль обращений в память для реализации алгоритма Дейкстры. Первое, что бросается в глаза, – большая разрозненность обращений. В частности, значительные области выше и ниже фрагмента 2 остаются пустыми, при этом сами обращения объединены лишь в небольшие группы. Это говорит о низкой эффективности, поскольку: а) повторные обращения практически отсутствуют либо происходят через значительный промежуток времени; б) расстояния между идущими подряд обращениями может быть очень большим.&lt;br /&gt;
&lt;br /&gt;
Однако при ближайшем рассмотрении может оказаться, что такие участки обладают высокой локальностью и состоят из значительного числа обращений. Более того, на общем профиле есть несколько областей (фрагменты 1 и 2), в которых обращения хорошо локализованы. Необходимо исследовать отдельные участки более подробно.&lt;br /&gt;
&lt;br /&gt;
Перейдем к изучению фрагмента 1 (рис. 2), в рамках которого выполняются обращения к двум небольшим массивам. Можно увидеть, что здесь задействованы только примерно 500 элементов, при этом к ним выполняется около 100 тысяч обращений. Весь профиль составляет около 120 тысяч обращений, поэтому получается, что подавляющая их часть выполняется именно к этим элементам. &lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Поскольку число элементов невелико, локальность в данном случае заведомо будет достаточно высокой, независимо от структуры самого фрагмента.  Однако на рис. 3, где представлены два участка из фрагмента 1, можно увидеть, что фрагмент в основном состоит из последовательных переборов, при этом данные зачастую задействованы повторно через не очень большие промежутки. Все это позволяет говорить, что данный фрагмент обладает высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_3.png|thumb|center|700px|Рисунок 3. Профили двух участков фрагмента 1 (выделены зеленым на рис. 2)]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь более подробно фрагмент 2 (рис. 4), в рамках которого выполняются обращения к еще служебному массиву. Здесь профиль состоит из двух этапов. На первом заметен достаточно хаотичный разброс обращений, напоминающий случайный доступ. На втором обращения образуют подобие последовательного перебора.  В целом такой профиль характеризуется очень низкой временной локальностью (повторные обращения практически или полностью отсутствуют) и достаточно низкой пространственной локальностью (из-за случайного доступа на первом этапе).&lt;br /&gt;
&lt;br /&gt;
Заметим, что при этом число задействованных элементов здесь больше, чем во фрагменте 1, однако число обращений гораздо меньше.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Далее остаются для рассмотрения два массива (область между фрагментами 1 и 2 и область ниже фрагмента 2). Характер обращений к этим массивам во многом похож, поэтому достаточно изучить более подробно только один из них. &lt;br /&gt;
&lt;br /&gt;
Фрагмент 3 рассмотрен на рис. 5. Этот участок отражаeт достаточно большую область, что не позволяет проанализировать профиль вплоть до отдельных обращений, однако здесь этого и не требуется. Из данного фрагмента видно, что основу профиля составляют участки с последовательным (или с небольшим шагом) перебором, состоящие из небольшого числа элементов – выделенный желтым самый большой участок фрагмента состоит всего из пары сотен обращений. При этом между разными участками расстояние может быть существенным. Все это говорит об очень низкой локальности (как пространственной, так и временной) в случае двух рассматриваемых массивов.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
В целом, несмотря на положительный вклад массивов из фрагмента 1, локальность общего профиля должна быть достаточно низкой, вследствие неэффективного использования данных в остальной части профиля.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Основной фрагмент реализации, на основе которого были получены количественные оценки, приведен [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/benchmarks/dijkstra/dijkstra.h здесь] (функция Kernel). Условия запуска описаны [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/README.md здесь].&lt;br /&gt;
&lt;br /&gt;
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью достаточно низка. Это неудивительно, поскольку реализации алгоритмов над графами почти всегда обладают низкой эффективностью вследствие нерегулярности доступа к данным, что мы и увидели при анализе профиля обращений.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
Вторая характеристика – cvg – предназначена для получения более машинно-независимой оценки локальности. Она определяет, насколько часто в программе необходимо подтягивать данные в кэш-память. Соответственно, чем меньше значение cvg, тем реже это нужно делать, тем лучше локальность. &lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения cvg для того же набора реализаций, отсортированные по убыванию (чем меньше cvg, тем в общем случае выше локальность). Можно увидеть, что в данном случае значение cvg хорошо коррелирует с оценкой производительности и отражает низкую локальность, что соответствует выводам, сделанным при качественной оценке локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_cvg.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки cvg]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов&amp;quot;&amp;lt;ref name=&amp;quot;Lom&amp;quot;&amp;gt;Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.&amp;lt;/ref&amp;gt; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [4, 8 : 128] со значениями квадрата целого числа;&lt;br /&gt;
*	размер графа [16000:64000] с шагом 16000.&lt;br /&gt;
&lt;br /&gt;
На следующем рисунке приведен график [[Глоссарий#Производительность|производительности]] выбранной реализации алгоритма в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация алгоритма. Изменение производительности в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
&lt;br /&gt;
В силу особенностей параллельной реализации алгоритма производительность в целом достаточно низкая и с ростом числа процессов увеличивается медленно, а при приближении к числу процессов 128 начинает уменьшаться.&lt;br /&gt;
Это объясняется использованием коллективных операций на каждой итерации алгоритма и тем, что затраты на коммуникационные обмены существенно возрастают с ростом числа использованных процессов. Вычисления на каждом процессе проходят достаточно быстро и потому декомпозиция графа слабо компенсирует эффект от затрат на коммуникационные обмены. &lt;br /&gt;
&lt;br /&gt;
[https://code.google.com/p/prir-dijkstra-mpi/source/browse/trunk/project/dijkstra_mpi.c Исследованная параллельная реализация на языке C]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Дейкстры.  Все результаты получены на суперкомпьютере &amp;quot;Ломоносов&amp;quot;. Использовались процессоры Intel Xeon X5570 с пиковой производительностью в 94 Гфлопс, а также компилятор intel 13.1.0. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Дейкстры  на 32 процессах.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra CPU User.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы уровень загрузки составляет около 50%. Это указывает на равномерную загруженность вычислениями процессоров, при использовании 8 процессов на вычислительный узел и без использования Hyper Threading. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Flops.png|thumb|center|700px|Рисунок 10. График операций с плавающей точкой в секунду при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На Рисунке 10 показан график количества операций с плавающей точкой в секунду. На графике видна общая очень низкая производительность вычислений около 250 Kфлопс в пике и около 150 Кфлопс в среднем по всем узлам. Это указывает то, что в программе почти все вычисления производятся с целыми числами. &lt;br /&gt;
 [[file:Dijkstra L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень большое для нескольких ядер и находится на уровне 15 млн/сек (в пике до 60 млн/сек), что указывает на интенсивные вычисления в части процессов. В среднем по всем узлам значения значительно ниже (около 9 млн/сек). Это указывает на неравномерное распределение вычислений.&lt;br /&gt;
 [[file:Dijkstra L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов третьего уровня видно, что число промахов очень невелико и находится на уровне 1,2 млн/сек, однако в среднем по всем узлам значения около 0,5 млн/сек. Соотношение кэш промахов L1|L3 для процессов с высокой производительностью доходит до 60, однако в среднем около 30. Это указывает на очень хорошую локальность вычислений как у части процессов, так и для всех в среднем, и это является признаком высокой производительности. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Mem Load.png|thumb|center|700px|Рисунок 13. График количества чтений из оперативной памяти при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике обращений в память видна достаточно типичная картина для таких приложений. Активность чтения достаточно низкая, что в совокупности с низкими значениями кэш-промахов L3 указывает на хорошую локальность. Хорошая локальность приложения также указывает на то, что значение около 1 млн/сек для задачи является результатом высокой производительности вычислений, хотя и присутствует неравномерность между процессами.&lt;br /&gt;
 [[file:Dijkstra Mem Store.png|thumb|center|700px|Рисунок 14. График количества записей в оперативную память при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике записей в память видна похожая картина неравномерности вычислений, при которой одновременно активно выполняют запись только несколько процессов. Это коррелирует с другими графиками выполнения. Стоит отметить, достаточно низкое число обращений на запись в память. Это указывает на хорошую организацию вычислений, и достаточно эффективную работу с памятью.&lt;br /&gt;
 [[file:Dijkstra Inf DATA.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая скорость передачи данных в байтах в секунду. Это говорит о том, что процессы между собой обмениваются интенсивно и вероятно достаточно малыми порциями данных, потому как производительность вычислений высока. Стоит отметить, что скорость передачи отличается между процессами, что указывает на дисбаланс вычислений.&lt;br /&gt;
 [[file:Dijkstra Inf PCKTS.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается крайне высокая интенсивность передачи данных выраженная в пакетах в секунду. Это говорит о том, что, вероятно, процессы обмениваются не очень существенными объемами данных, но очень интенсивно. Используются коллективные операции на каждом шаге с небольшими порциями данных, что объясняет такую картину.  Так же наблюдается почти меньший дизбаланс между процессами чем наблюдаемый в графиках использования памяти и вычислений и передачи данных в байтах/сек. Это указывает на то, что процессы обмениваются по алгоритму одинаковым числом пакетов, однако получают разные объемы данных и ведут неравномерные вычисления. &lt;br /&gt;
 [[file:Dijkstra Load ABG.png|thumb|center|700px|Рисунок 17. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Дейкстры]]&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 8. Это свидетельствует о стабильной работе программы с загруженными вычислениями всеми узлами. Это указывает на очень рациональную и статичную загрузку аппаратных ресурсов процессами. И показывает достаточно хорошую эффективность выполняемой реализации.&lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала достаточно эффективно, и стабильно. Использование памяти  очень интенсивное, а использование коммуникационной среды крайне интенсивное, при этом объемы передаваемых данных не являются высокими. Это указывает на требовательность к латентности коммуникационной среды алгоритмической части программы. &lt;br /&gt;
Низкая эффективность связана судя по всему с достаточно высоким объемом пересылок на каждом процессе, интенсивными обменами сообщениями.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html dijkstra_shortest_paths]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths_no_color_map.html dijkstra_shortest_paths_no_color_map]&amp;lt;/code&amp;gt;), сложность &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;. &lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]:&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#eager-dijkstra-s-algorithm eager_dijkstra_shortest_paths]&amp;lt;/code&amp;gt; – непосредственная реализация алгоритма Дейкстры;&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#crauser-et-al-s-algorithm crauser_et_al_shortest_paths]&amp;lt;/code&amp;gt; – реализация алгоритма Дейкстры в виде алгоритма из статьи Краузера и др.&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.single_source_dijkstra.html single_source_dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.Dijkstra networkit.graph.Dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Dijkstra&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24139</id>
		<title>Алгоритм Дейкстры</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24139"/>
		<updated>2017-11-23T15:48:49Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Основные авторы описания: [[Участник:Daryin|А.Н.Дарьин]], [[Участник:VadimVV|Вад.В.Воеводин]] ([[#Описание локальности данных и вычислений|раздел 2.2]])&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Дейкстры&#039;&#039;&#039;&amp;lt;ref&amp;gt;Dijkstra, E W. “A Note on Two Problems in Connexion with Graphs.” Numerische Mathematik 1, no. 1 (December 1959): 269–71. doi:10.1007/BF01386390.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа с неотрицательными весами алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа.&lt;br /&gt;
Алгоритм Дейкстры (с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap&amp;gt;Fredman, Michael L, and Robert Endre Tarjan. “Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms.” Journal of the ACM 34, no. 3 (July 1987): 596–615. doi:10.1145/28869.28874.&amp;lt;/ref&amp;gt;) выполняется за время &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt; и является асимптотически быстрейшим из известных последовательных алгоритмов для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть уже вычислены все расстояния, не превосходящие некоторого числа &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt;, то есть расстояния до вершин из множества &amp;lt;math&amp;gt;V_r = \{ v \in V \mid d(v) \le r \}&amp;lt;/math&amp;gt;. Пусть&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        (v, w) \in \arg\min \{ d(v) + f(e) \mid  v \in V, e = (v, w) \in E \}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Тогда &amp;lt;math&amp;gt;d(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; лежит на кратчайшем пути от &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Величины &amp;lt;math&amp;gt;d^+(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v \in V_r&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;e = (v, w) \in E&amp;lt;/math&amp;gt;, называются &#039;&#039;предполагаемыми расстояниями&#039;&#039; и являются оценкой сверху для настоящих расстояний: &amp;lt;math&amp;gt;d(w) \le d^+(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры на каждом шаге находит вершину с наименьшим предполагаемым расстоянием, помечает её как посещённую и обновляет предполагаемые расстояния для всех концов рёбер, исходящих из неё.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основные вычисления связаны с операциями над очередью с приоритетом:&lt;br /&gt;
* извлечение минимального элемента (&amp;lt;code&amp;gt;delete_min&amp;lt;/code&amp;gt;);&lt;br /&gt;
* уменьшение приоритета элемента (&amp;lt;code&amp;gt;decrease_key&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Псевдокод алгоритма:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;Q&#039;&#039; := &#039;&#039;&#039;new&#039;&#039;&#039; priority queue&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;v&#039;&#039; = &#039;&#039;u&#039;&#039; &#039;&#039;&#039;then&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := 0 &#039;&#039;&#039;else&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞ &lt;br /&gt;
     &#039;&#039;Q&#039;&#039;.insert(&#039;&#039;v&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;))&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;while&#039;&#039;&#039; &#039;&#039;Q&#039;&#039; ≠ ∅:&lt;br /&gt;
     &#039;&#039;v&#039;&#039; := &#039;&#039;Q&#039;&#039;.delete_min()&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
             &#039;&#039;Q&#039;&#039;.decrease_key(&#039;&#039;w&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;))&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Конкретная реализация алгоритма Дейкстры определяется выбором используемого алгоритма очереди с приоритетом. В простейшем случае это может быть массив или список, поиск минимума в котором требует просмотра всех вершин. Более эффективным является использование кучи; наилучшую известную оценку сложности имеет вариант с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Возможен вариант реализации, когда вершины добавляются в очередь не на этапе инициализации, а в момент первого посещения.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма равна &amp;lt;math&amp;gt;O(C_1 m + C_2n)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; – количество операций уменьшения расстояния до вершины;&lt;br /&gt;
* &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt; – количество операций вычисления минимума.&lt;br /&gt;
&lt;br /&gt;
Оригинальный алгоритм Дейкстры использовал в качестве внутренней структуры данных списки, для которых &amp;lt;math&amp;gt;C_1 = O(1)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;C_2 = O(n)&amp;lt;/math&amp;gt;, так что общая сложность составляла &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
При использовании фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt; время вычисления минимума сокращается до &amp;lt;math&amp;gt;C_2 = O(\ln n)&amp;lt;/math&amp;gt;, так что общая сложность равна &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;, что является асимптотически наилучшим известным результатом для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Приводится граф алгоритма для базовой реализации алгоритма Дейкстры на списках или массивах.&lt;br /&gt;
[[file:Deikstra.png|thumb|center|600px|Рисунок 1. Граф алгоритма без отображения входных и выходных данных. n=3. Желтым цветом обозначены операции сравнения, зеленым - операции изменения меток вершин, синим - пометка вершины.]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры допускает эффективную параллелизацию&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;, среднее время работы &amp;lt;math&amp;gt;O(n^{1/3}\ln n)&amp;lt;/math&amp;gt; с объёмом вычислений &amp;lt;math&amp;gt;O(n \ln n + m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Дейкстры.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_1.png|thumb|center|700px|Рисунок 1. Реализация алгоритма Дейкстры. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 1 представлен профиль обращений в память для реализации алгоритма Дейкстры. Первое, что бросается в глаза, – большая разрозненность обращений. В частности, значительные области выше и ниже фрагмента 2 остаются пустыми, при этом сами обращения объединены лишь в небольшие группы. Это говорит о низкой эффективности, поскольку: а) повторные обращения практически отсутствуют либо происходят через значительный промежуток времени; б) расстояния между идущими подряд обращениями может быть очень большим.&lt;br /&gt;
&lt;br /&gt;
Однако при ближайшем рассмотрении может оказаться, что такие участки обладают высокой локальностью и состоят из значительного числа обращений. Более того, на общем профиле есть несколько областей (фрагменты 1 и 2), в которых обращения хорошо локализованы. Необходимо исследовать отдельные участки более подробно.&lt;br /&gt;
&lt;br /&gt;
Перейдем к изучению фрагмента 1 (рис. 2), в рамках которого выполняются обращения к двум небольшим массивам. Можно увидеть, что здесь задействованы только примерно 500 элементов, при этом к ним выполняется около 100 тысяч обращений. Весь профиль составляет около 120 тысяч обращений, поэтому получается, что подавляющая их часть выполняется именно к этим элементам. &lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Поскольку число элементов невелико, локальность в данном случае заведомо будет достаточно высокой, независимо от структуры самого фрагмента.  Однако на рис. 3, где представлены два участка из фрагмента 1, можно увидеть, что фрагмент в основном состоит из последовательных переборов, при этом данные зачастую задействованы повторно через не очень большие промежутки. Все это позволяет говорить, что данный фрагмент обладает высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_3.png|thumb|center|700px|Рисунок 3. Профили двух участков фрагмента 1 (выделены зеленым на рис. 2)]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь более подробно фрагмент 2 (рис. 4), в рамках которого выполняются обращения к еще служебному массиву. Здесь профиль состоит из двух этапов. На первом заметен достаточно хаотичный разброс обращений, напоминающий случайный доступ. На втором обращения образуют подобие последовательного перебора.  В целом такой профиль характеризуется очень низкой временной локальностью (повторные обращения практически или полностью отсутствуют) и достаточно низкой пространственной локальностью (из-за случайного доступа на первом этапе).&lt;br /&gt;
&lt;br /&gt;
Заметим, что при этом число задействованных элементов здесь больше, чем во фрагменте 1, однако число обращений гораздо меньше.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Далее остаются для рассмотрения два массива (область между фрагментами 1 и 2 и область ниже фрагмента 2). Характер обращений к этим массивам во многом похож, поэтому достаточно изучить более подробно только один из них. &lt;br /&gt;
&lt;br /&gt;
Фрагмент 3 рассмотрен на рис. 5. Этот участок отражаeт достаточно большую область, что не позволяет проанализировать профиль вплоть до отдельных обращений, однако здесь этого и не требуется. Из данного фрагмента видно, что основу профиля составляют участки с последовательным (или с небольшим шагом) перебором, состоящие из небольшого числа элементов – выделенный желтым самый большой участок фрагмента состоит всего из пары сотен обращений. При этом между разными участками расстояние может быть существенным. Все это говорит об очень низкой локальности (как пространственной, так и временной) в случае двух рассматриваемых массивов.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
В целом, несмотря на положительный вклад массивов из фрагмента 1, локальность общего профиля должна быть достаточно низкой, вследствие неэффективного использования данных в остальной части профиля.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Основной фрагмент реализации, на основе которого были получены количественные оценки, приведен [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/benchmarks/dijkstra/dijkstra.h здесь] (функция Kernel). Условия запуска описаны [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/README.md здесь].&lt;br /&gt;
&lt;br /&gt;
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью достаточно низка. Это неудивительно, поскольку реализации алгоритмов над графами почти всегда обладают низкой эффективностью вследствие нерегулярности доступа к данным, что мы и увидели при анализе профиля обращений.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
Вторая характеристика – cvg – предназначена для получения более машинно-независимой оценки локальности. Она определяет, насколько часто в программе необходимо подтягивать данные в кэш-память. Соответственно, чем меньше значение cvg, тем реже это нужно делать, тем лучше локальность. &lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения cvg для того же набора реализаций, отсортированные по убыванию (чем меньше cvg, тем в общем случае выше локальность). Можно увидеть, что в данном случае значение cvg хорошо коррелирует с оценкой производительности и отражает низкую локальность, что соответствует выводам, сделанным при качественной оценке локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_cvg.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки cvg]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов&amp;quot;&amp;lt;ref name=&amp;quot;Lom&amp;quot;&amp;gt;Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.&amp;lt;/ref&amp;gt; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [4, 8 : 128] со значениями квадрата целого числа;&lt;br /&gt;
*	размер графа [16000:64000] с шагом 16000.&lt;br /&gt;
&lt;br /&gt;
На следующем рисунке приведен график [[Глоссарий#Производительность|производительности]] выбранной реализации алгоритма в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация алгоритма. Изменение производительности в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
&lt;br /&gt;
В силу особенностей параллельной реализации алгоритма производительность в целом достаточно низкая и с ростом числа процессов увеличивается медленно, а при приближении к числу процессов 128 начинает уменьшаться.&lt;br /&gt;
Это объясняется использованием коллективных операций на каждой итерации алгоритма и тем, что затраты на коммуникационные обмены существенно возрастают с ростом числа использованных процессов. Вычисления на каждом процессе проходят достаточно быстро и потому декомпозиция графа слабо компенсирует эффект от затрат на коммуникационные обмены. &lt;br /&gt;
&lt;br /&gt;
[https://code.google.com/p/prir-dijkstra-mpi/source/browse/trunk/project/dijkstra_mpi.c Исследованная параллельная реализация на языке C]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Дейкстры.  Все результаты получены на суперкомпьютере &amp;quot;Ломоносов&amp;quot;. Использовались процессоры Intel Xeon X5570 с пиковой производительностью в 94 Гфлопс, а также компилятор intel 13.1.0. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Дейкстры  на 32 процессах.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra CPU User.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы уровень загрузки составляет около 50%. Это указывает на равномерную загруженность вычислениями процессоров, при использовании 8 процессов на вычислительный узел и без использования Hyper Threading. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Flops.png|thumb|center|700px|Рисунок 10. График операций с плавающей точкой в секунду при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На Рисунке 10 показан график количества операций с плавающей точкой в секунду. На графике видна общая очень низкая производительность вычислений около 250 Kфлопс в пике и около 150 Кфлопс в среднем по всем узлам. Это указывает то, что в программе почти все вычисления производятся с целыми числами. &lt;br /&gt;
 [[file:Dijkstra L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень большое для нескольких ядер и находится на уровне 15 млн/сек (в пике до 60 млн/сек), что указывает на интенсивные вычисления в части процессов. В среднем по всем узлам значения значительно ниже (около 9 млн/сек). Это указывает на неравномерное распределение вычислений.&lt;br /&gt;
 [[file:Dijkstra L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов третьего уровня видно, что число промахов очень невелико и находится на уровне 1,2 млн/сек, однако в среднем по всем узлам значения около 0,5 млн/сек. Соотношение кэш промахов L1|L3 для процессов с высокой производительностью доходит до 60, однако в среднем около 30. Это указывает на очень хорошую локальность вычислений как у части процессов, так и для всех в среднем, и это является признаком высокой производительности. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Mem Load.png|thumb|center|700px|Рисунок 13. График количества чтений из оперативной памяти при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике обращений в память видна достаточно типичная картина для таких приложений. Активность чтения достаточно низкая, что в совокупности с низкими значениями кэш-промахов L3 указывает на хорошую локальность. Хорошая локальность приложения так же указывает на то, что значения около 1 млн/сек для задачи является результатом высокой производительности вычислений, хотя и присутствует неравномерность между процессами.&lt;br /&gt;
 [[file:Dijkstra Mem Store.png|thumb|center|700px|Рисунок 14. График количества записей в оперативную память при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике записей в память видна похожая картина неравномерности вычислений, при которой одновременно активно выполняют запись только несколько процессов. Это коррелирует с другими графиками выполнения. Стоит отметить, достаточно низкое число обращений на запись в память. Это указывает на хорошую организацию вычислений, и достаточно эффективную работу с памятью.&lt;br /&gt;
 [[file:Dijkstra Inf DATA.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая скорость передачи данных в байтах в секунду. Это говорит о том, что процессы между собой обмениваются интенсивно и вероятно достаточно малыми порциями данных, потому как производительность вычислений высока. Стоит отметить, что скорость передачи отличается между процессами, что указывает на дисбаланс вычислений.&lt;br /&gt;
 [[file:Dijkstra Inf PCKTS.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается крайне высокая интенсивность передачи данных выраженная в пакетах в секунду. Это говорит о том, что, вероятно, процессы обмениваются не очень существенными объемами данных, но очень интенсивно. Используются коллективные операции на каждом шаге с небольшими порциями данных, что объясняет такую картину.  Так же наблюдается почти меньший дизбаланс между процессами чем наблюдаемый в графиках использования памяти и вычислений и передачи данных в байтах/сек. Это указывает на то, что процессы обмениваются по алгоритму одинаковым числом пакетов, однако получают разные объемы данных и ведут неравномерные вычисления. &lt;br /&gt;
 [[file:Dijkstra Load ABG.png|thumb|center|700px|Рисунок 17. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Дейкстры]]&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 8. Это свидетельствует о стабильной работе программы с загруженными вычислениями всеми узлами. Это указывает на очень рациональную и статичную загрузку аппаратных ресурсов процессами. И показывает достаточно хорошую эффективность выполняемой реализации.&lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала достаточно эффективно, и стабильно. Использование памяти  очень интенсивное, а использование коммуникационной среды крайне интенсивное, при этом объемы передаваемых данных не являются высокими. Это указывает на требовательность к латентности коммуникационной среды алгоритмической части программы. &lt;br /&gt;
Низкая эффективность связана судя по всему с достаточно высоким объемом пересылок на каждом процессе, интенсивными обменами сообщениями.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html dijkstra_shortest_paths]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths_no_color_map.html dijkstra_shortest_paths_no_color_map]&amp;lt;/code&amp;gt;), сложность &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;. &lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]:&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#eager-dijkstra-s-algorithm eager_dijkstra_shortest_paths]&amp;lt;/code&amp;gt; – непосредственная реализация алгоритма Дейкстры;&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#crauser-et-al-s-algorithm crauser_et_al_shortest_paths]&amp;lt;/code&amp;gt; – реализация алгоритма Дейкстры в виде алгоритма из статьи Краузера и др.&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.single_source_dijkstra.html single_source_dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.Dijkstra networkit.graph.Dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Dijkstra&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24136</id>
		<title>Алгоритм Дейкстры</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24136"/>
		<updated>2017-11-22T19:40:13Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Структура обращений в память и качественная оценка локальности */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Основные авторы описания: [[Участник:Daryin|А.Н.Дарьин]], [[Участник:VadimVV|Вад.В.Воеводин]] ([[#Описание локальности данных и вычислений|раздел 2.2]])&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Дейкстры&#039;&#039;&#039;&amp;lt;ref&amp;gt;Dijkstra, E W. “A Note on Two Problems in Connexion with Graphs.” Numerische Mathematik 1, no. 1 (December 1959): 269–71. doi:10.1007/BF01386390.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа с неотрицательными весами алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа.&lt;br /&gt;
Алгоритм Дейкстры (с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap&amp;gt;Fredman, Michael L, and Robert Endre Tarjan. “Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms.” Journal of the ACM 34, no. 3 (July 1987): 596–615. doi:10.1145/28869.28874.&amp;lt;/ref&amp;gt;) выполняется за время &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt; и является асимптотически быстрейшим из известных последовательных алгоритмов для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть уже вычислены все расстояния, не превосходящие некоторого числа &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt;, то есть расстояния до вершин из множества &amp;lt;math&amp;gt;V_r = \{ v \in V \mid d(v) \le r \}&amp;lt;/math&amp;gt;. Пусть&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        (v, w) \in \arg\min \{ d(v) + f(e) \mid  v \in V, e = (v, w) \in E \}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Тогда &amp;lt;math&amp;gt;d(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; лежит на кратчайшем пути от &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Величины &amp;lt;math&amp;gt;d^+(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v \in V_r&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;e = (v, w) \in E&amp;lt;/math&amp;gt;, называются &#039;&#039;предполагаемыми расстояниями&#039;&#039; и являются оценкой сверху для настоящих расстояний: &amp;lt;math&amp;gt;d(w) \le d^+(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры на каждом шаге находит вершину с наименьшим предполагаемым расстоянием, помечает её как посещённую и обновляет предполагаемые расстояния для всех концов рёбер, исходящих из неё.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основные вычисления связаны с операциями над очередью с приоритетом:&lt;br /&gt;
* извлечение минимального элемента (&amp;lt;code&amp;gt;delete_min&amp;lt;/code&amp;gt;);&lt;br /&gt;
* уменьшение приоритета элемента (&amp;lt;code&amp;gt;decrease_key&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Псевдокод алгоритма:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;Q&#039;&#039; := &#039;&#039;&#039;new&#039;&#039;&#039; priority queue&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;v&#039;&#039; = &#039;&#039;u&#039;&#039; &#039;&#039;&#039;then&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := 0 &#039;&#039;&#039;else&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞ &lt;br /&gt;
     &#039;&#039;Q&#039;&#039;.insert(&#039;&#039;v&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;))&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;while&#039;&#039;&#039; &#039;&#039;Q&#039;&#039; ≠ ∅:&lt;br /&gt;
     &#039;&#039;v&#039;&#039; := &#039;&#039;Q&#039;&#039;.delete_min()&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
             &#039;&#039;Q&#039;&#039;.decrease_key(&#039;&#039;w&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;))&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Конкретная реализация алгоритма Дейкстры определяется выбором используемого алгоритма очереди с приоритетом. В простейшем случае это может быть массив или список, поиск минимума в котором требует просмотра всех вершин. Более эффективным является использование кучи; наилучшую известную оценку сложности имеет вариант с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Возможен вариант реализации, когда вершины добавляются в очередь не на этапе инициализации, а в момент первого посещения.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма равна &amp;lt;math&amp;gt;O(C_1 m + C_2n)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; – количество операций уменьшения расстояния до вершины;&lt;br /&gt;
* &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt; – количество операций вычисления минимума.&lt;br /&gt;
&lt;br /&gt;
Оригинальный алгоритм Дейкстры использовал в качестве внутренней структуры данных списки, для которых &amp;lt;math&amp;gt;C_1 = O(1)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;C_2 = O(n)&amp;lt;/math&amp;gt;, так что общая сложность составляла &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
При использовании фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt; время вычисления минимума сокращается до &amp;lt;math&amp;gt;C_2 = O(\ln n)&amp;lt;/math&amp;gt;, так что общая сложность равна &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;, что является асимптотически наилучшим известным результатом для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Приводится граф алгоритма для базовой реализации алгоритма Дейкстры на списках или массивах.&lt;br /&gt;
[[file:Deikstra.png|thumb|center|600px|Рисунок 1. Граф алгоритма без отображения входных и выходных данных. n=3. Желтым цветом обозначены операции сравнения, зеленым - операции изменения меток вершин, синим - пометка вершины.]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры допускает эффективную параллелизацию&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;, среднее время работы &amp;lt;math&amp;gt;O(n^{1/3}\ln n)&amp;lt;/math&amp;gt; с объёмом вычислений &amp;lt;math&amp;gt;O(n \ln n + m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Дейкстры.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_1.png|thumb|center|700px|Рисунок 1. Реализация алгоритма Дейкстры. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 1 представлен профиль обращений в память для реализации алгоритма Дейкстры. Первое, что бросается в глаза, – большая разрозненность обращений. В частности, значительные области выше и ниже фрагмента 2 остаются пустыми, при этом сами обращения объединены лишь в небольшие группы. Это говорит о низкой эффективности, поскольку: а) повторные обращения практически отсутствуют либо происходят через значительный промежуток времени; б) расстояния между идущими подряд обращениями может быть очень большим.&lt;br /&gt;
&lt;br /&gt;
Однако при ближайшем рассмотрении может оказаться, что такие участки обладают высокой локальностью и состоят из значительного числа обращений. Более того, на общем профиле есть несколько областей (фрагменты 1 и 2), в которых обращения хорошо локализованы. Необходимо исследовать отдельные участки более подробно.&lt;br /&gt;
&lt;br /&gt;
Перейдем к изучению фрагмента 1 (рис. 2), в рамках которого выполняются обращения к двум небольшим массивам. Можно увидеть, что здесь задействованы только примерно 500 элементов, при этом к ним выполняется около 100 тысяч обращений. Весь профиль составляет около 120 тысяч обращений, поэтому получается, что подавляющая их часть выполняется именно к этим элементам. &lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Поскольку число элементов невелико, локальность в данном случае заведомо будет достаточно высокой, независимо от структуры самого фрагмента.  Однако на рис. 3, где представлены два участка из фрагмента 1, можно увидеть, что фрагмент в основном состоит из последовательных переборов, при этом данные зачастую задействованы повторно через не очень большие промежутки. Все это позволяет говорить, что данный фрагмент обладает высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_3.png|thumb|center|700px|Рисунок 3. Профили двух участков фрагмента 1 (выделены зеленым на рис. 2)]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь более подробно фрагмент 2 (рис. 4), в рамках которого выполняются обращения к еще служебному массиву. Здесь профиль состоит из двух этапов. На первом заметен достаточно хаотичный разброс обращений, напоминающий случайный доступ. На втором обращения образуют подобие последовательного перебора.  В целом такой профиль характеризуется очень низкой временной локальностью (повторные обращения практически или полностью отсутствуют) и достаточно низкой пространственной локальностью (из-за случайного доступа на первом этапе).&lt;br /&gt;
&lt;br /&gt;
Заметим, что при этом число задействованных элементов здесь больше, чем во фрагменте 1, однако число обращений гораздо меньше.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Далее остаются для рассмотрения два массива (область между фрагментами 1 и 2 и область ниже фрагмента 2). Характер обращений к этим массивам во многом похож, поэтому достаточно изучить более подробно только один из них. &lt;br /&gt;
&lt;br /&gt;
Фрагмент 3 рассмотрен на рис. 5. Этот участок отражаeт достаточно большую область, что не позволяет проанализировать профиль вплоть до отдельных обращений, однако здесь этого и не требуется. Из данного фрагмента видно, что основу профиля составляют участки с последовательным (или с небольшим шагом) перебором, состоящие из небольшого числа элементов – выделенный желтым самый большой участок фрагмента состоит всего из пары сотен обращений. При этом между разными участками расстояние может быть существенным. Все это говорит об очень низкой локальности (как пространственной, так и временной) в случае двух рассматриваемых массивов.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
В целом, несмотря на положительный вклад массивов из фрагмента 1, локальность общего профиля должна быть достаточно низкой, вследствие неэффективного использования данных в остальной части профиля.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Основной фрагмент реализации, на основе которого были получены количественные оценки, приведен [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/benchmarks/dijkstra/dijkstra.h здесь] (функция Kernel). Условия запуска описаны [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/README.md здесь].&lt;br /&gt;
&lt;br /&gt;
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью достаточно низка. Это неудивительно, поскольку реализации алгоритмов над графами почти всегда обладают низкой эффективностью вследствие нерегулярности доступа к данным, что мы и увидели при анализе профиля обращений.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
Вторая характеристика – cvg – предназначена для получения более машинно-независимой оценки локальности. Она определяет, насколько часто в программе необходимо подтягивать данные в кэш-память. Соответственно, чем меньше значение cvg, тем реже это нужно делать, тем лучше локальность. &lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения cvg для того же набора реализаций, отсортированные по убыванию (чем меньше cvg, тем в общем случае выше локальность). Можно увидеть, что в данном случае значение cvg хорошо коррелирует с оценкой производительности и отражает низкую локальность, что соответствует выводам, сделанным при качественной оценке локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_cvg.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки cvg]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов&amp;quot;&amp;lt;ref name=&amp;quot;Lom&amp;quot;&amp;gt;Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.&amp;lt;/ref&amp;gt; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [4, 8 : 128] со значениями квадрата целого числа;&lt;br /&gt;
*	размер графа [16000:64000] с шагом 16000.&lt;br /&gt;
&lt;br /&gt;
На следующем рисунке приведен график [[Глоссарий#Производительность|производительности]] выбранной реализации алгоритма в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация алгоритма. Изменение производительности в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
&lt;br /&gt;
В силу особенностей параллельной реализации алгоритма производительность в целом достаточно низкая и с ростом числа процессов увеличивается медленно, а при приближении к числу процессов 128 начинает уменьшаться.&lt;br /&gt;
Это объясняется использованием коллективных операций на каждой итерации алгоритма и тем, что затраты на коммуникационные обмены существенно возрастают с ростом числа использованных процессов. Вычисления на каждом процессе проходят достаточно быстро и потому декомпозиция графа слабо компенсирует эффект от затрат на коммуникационные обмены. &lt;br /&gt;
&lt;br /&gt;
[https://code.google.com/p/prir-dijkstra-mpi/source/browse/trunk/project/dijkstra_mpi.c Исследованная параллельная реализация на языке C]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Дейкстры.  Все результаты получены на суперкомпьютере &amp;quot;Ломоносов&amp;quot;. Использовались процессоры Intel Xeon X5570 с пиковой производительностью в 94 Гфлопс, а также компилятор intel 13.1.0. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Дейкстры  на 32 процессах.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra CPU User.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы уровень загрузки составляет около 50%. Это указывает на равномерную загруженность вычислениями процессоров, при использовании 8 процессов на вычислительный узел и без использования Hyper Threading. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Flops.png|thumb|center|700px|Рисунок 10. График операций с плавающей точкой в секунду при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На Рисунке 10 показан график количества операций с плавающей точкой в секунду. На графике видна общая очень низкая производительность вычислений около 250 Kфлопс в пике и около 150 Кфлопс в среднем по всем узлам. Это указывает то, что в программе почти все вычисления производятся с целыми числами. &lt;br /&gt;
 [[file:Dijkstra L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень большое для нескольких ядер и находится на уровне 15 млн/сек (в пике до 60 млн/сек), что указывает на интенсивные вычисления в части процессов. В среднем по всем узлам значения значительно ниже (около 9 млн/сек). Это указывает на неравномерное распределение вычислений.&lt;br /&gt;
 [[file:Dijkstra L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов третьего уровня видно, что число промахов очень немного и находится на уровне 1,2 млн/сек, однако в среднем по всем узлам значения около 0,5 млн/сек. Соотношение кэш промахов L1|L3 для процессов с высокой производительностью доходит до 60, однако в среднем около 30. Это указывает на очень хорошую локальность вычислений как у части процессов, так и для всех в среднем, и это является признаком высокой производительности.&lt;br /&gt;
[[file:Dijkstra Mem Load.png|thumb|center|700px|Рисунок 13. График количества чтений из оперативной памяти при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике обращений в память видна достаточно типичная картина для таких приложений. Активность чтения достаточно низкая, что в совокупности с низкими значениями кэш-промахов L3 указывает на хорошую локальность. Хорошая локальность приложения так же указывает на то, что значения около 1 млн/сек для задачи является результатом высокой производительности вычислений, хотя и присутствует неравномерность между процессами.&lt;br /&gt;
 [[file:Dijkstra Mem Store.png|thumb|center|700px|Рисунок 14. График количества записей в оперативную память при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике записей в память видна похожая картина неравномерности вычислений, при которой одновременно активно выполняют запись только несколько процессов. Это коррелирует с другими графиками выполнения. Стоит отметить, достаточно низкое число обращений на запись в память. Это указывает на хорошую организацию вычислений, и достаточно эффективную работу с памятью.&lt;br /&gt;
 [[file:Dijkstra Inf DATA.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая скорость передачи данных в байтах в секунду. Это говорит о том, что процессы между собой обмениваются интенсивно и вероятно достаточно малыми порциями данных, потому как производительность вычислений высока. Стоит отметить, что скорость передачи отличается между процессами, что указывает на дисбаланс вычислений.&lt;br /&gt;
 [[file:Dijkstra Inf PCKTS.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается крайне высокая интенсивность передачи данных выраженная в пакетах в секунду. Это говорит о том, что, вероятно, процессы обмениваются не очень существенными объемами данных, но очень интенсивно. Используются коллективные операции на каждом шаге с небольшими порциями данных, что объясняет такую картину.  Так же наблюдается почти меньший дизбаланс между процессами чем наблюдаемый в графиках использования памяти и вычислений и передачи данных в байтах/сек. Это указывает на то, что процессы обмениваются по алгоритму одинаковым числом пакетов, однако получают разные объемы данных и ведут неравномерные вычисления. &lt;br /&gt;
 [[file:Dijkstra Load ABG.png|thumb|center|700px|Рисунок 17. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Дейкстры]]&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 8. Это свидетельствует о стабильной работе программы с загруженными вычислениями всеми узлами. Это указывает на очень рациональную и статичную загрузку аппаратных ресурсов процессами. И показывает достаточно хорошую эффективность выполняемой реализации.&lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала достаточно эффективно, и стабильно. Использование памяти  очень интенсивное, а использование коммуникационной среды крайне интенсивное, при этом объемы передаваемых данных не являются высокими. Это указывает на требовательность к латентности коммуникационной среды алгоритмической части программы. &lt;br /&gt;
Низкая эффективность связана судя по всему с достаточно высоким объемом пересылок на каждом процессе, интенсивными обменами сообщениями.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html dijkstra_shortest_paths]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths_no_color_map.html dijkstra_shortest_paths_no_color_map]&amp;lt;/code&amp;gt;), сложность &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;. &lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]:&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#eager-dijkstra-s-algorithm eager_dijkstra_shortest_paths]&amp;lt;/code&amp;gt; – непосредственная реализация алгоритма Дейкстры;&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#crauser-et-al-s-algorithm crauser_et_al_shortest_paths]&amp;lt;/code&amp;gt; – реализация алгоритма Дейкстры в виде алгоритма из статьи Краузера и др.&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.single_source_dijkstra.html single_source_dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.Dijkstra networkit.graph.Dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Dijkstra&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24135</id>
		<title>Алгоритм Дейкстры</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24135"/>
		<updated>2017-11-22T18:06:52Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Структура обращений в память и качественная оценка локальности */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Основные авторы описания: [[Участник:Daryin|А.Н.Дарьин]], [[Участник:VadimVV|Вад.В.Воеводин]] ([[#Описание локальности данных и вычислений|раздел 2.2]])&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Дейкстры&#039;&#039;&#039;&amp;lt;ref&amp;gt;Dijkstra, E W. “A Note on Two Problems in Connexion with Graphs.” Numerische Mathematik 1, no. 1 (December 1959): 269–71. doi:10.1007/BF01386390.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа с неотрицательными весами алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа.&lt;br /&gt;
Алгоритм Дейкстры (с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap&amp;gt;Fredman, Michael L, and Robert Endre Tarjan. “Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms.” Journal of the ACM 34, no. 3 (July 1987): 596–615. doi:10.1145/28869.28874.&amp;lt;/ref&amp;gt;) выполняется за время &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt; и является асимптотически быстрейшим из известных последовательных алгоритмов для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть уже вычислены все расстояния, не превосходящие некоторого числа &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt;, то есть расстояния до вершин из множества &amp;lt;math&amp;gt;V_r = \{ v \in V \mid d(v) \le r \}&amp;lt;/math&amp;gt;. Пусть&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        (v, w) \in \arg\min \{ d(v) + f(e) \mid  v \in V, e = (v, w) \in E \}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Тогда &amp;lt;math&amp;gt;d(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; лежит на кратчайшем пути от &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Величины &amp;lt;math&amp;gt;d^+(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v \in V_r&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;e = (v, w) \in E&amp;lt;/math&amp;gt;, называются &#039;&#039;предполагаемыми расстояниями&#039;&#039; и являются оценкой сверху для настоящих расстояний: &amp;lt;math&amp;gt;d(w) \le d^+(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры на каждом шаге находит вершину с наименьшим предполагаемым расстоянием, помечает её как посещённую и обновляет предполагаемые расстояния для всех концов рёбер, исходящих из неё.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основные вычисления связаны с операциями над очередью с приоритетом:&lt;br /&gt;
* извлечение минимального элемента (&amp;lt;code&amp;gt;delete_min&amp;lt;/code&amp;gt;);&lt;br /&gt;
* уменьшение приоритета элемента (&amp;lt;code&amp;gt;decrease_key&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Псевдокод алгоритма:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;Q&#039;&#039; := &#039;&#039;&#039;new&#039;&#039;&#039; priority queue&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;v&#039;&#039; = &#039;&#039;u&#039;&#039; &#039;&#039;&#039;then&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := 0 &#039;&#039;&#039;else&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞ &lt;br /&gt;
     &#039;&#039;Q&#039;&#039;.insert(&#039;&#039;v&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;))&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;while&#039;&#039;&#039; &#039;&#039;Q&#039;&#039; ≠ ∅:&lt;br /&gt;
     &#039;&#039;v&#039;&#039; := &#039;&#039;Q&#039;&#039;.delete_min()&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
             &#039;&#039;Q&#039;&#039;.decrease_key(&#039;&#039;w&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;))&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Конкретная реализация алгоритма Дейкстры определяется выбором используемого алгоритма очереди с приоритетом. В простейшем случае это может быть массив или список, поиск минимума в котором требует просмотра всех вершин. Более эффективным является использование кучи; наилучшую известную оценку сложности имеет вариант с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Возможен вариант реализации, когда вершины добавляются в очередь не на этапе инициализации, а в момент первого посещения.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма равна &amp;lt;math&amp;gt;O(C_1 m + C_2n)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; – количество операций уменьшения расстояния до вершины;&lt;br /&gt;
* &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt; – количество операций вычисления минимума.&lt;br /&gt;
&lt;br /&gt;
Оригинальный алгоритм Дейкстры использовал в качестве внутренней структуры данных списки, для которых &amp;lt;math&amp;gt;C_1 = O(1)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;C_2 = O(n)&amp;lt;/math&amp;gt;, так что общая сложность составляла &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
При использовании фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt; время вычисления минимума сокращается до &amp;lt;math&amp;gt;C_2 = O(\ln n)&amp;lt;/math&amp;gt;, так что общая сложность равна &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;, что является асимптотически наилучшим известным результатом для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Приводится граф алгоритма для базовой реализации алгоритма Дейкстры на списках или массивах.&lt;br /&gt;
[[file:Deikstra.png|thumb|center|600px|Рисунок 1. Граф алгоритма без отображения входных и выходных данных. n=3. Желтым цветом обозначены операции сравнения, зеленым - операции изменения меток вершин, синим - пометка вершины.]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры допускает эффективную параллелизацию&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;, среднее время работы &amp;lt;math&amp;gt;O(n^{1/3}\ln n)&amp;lt;/math&amp;gt; с объёмом вычислений &amp;lt;math&amp;gt;O(n \ln n + m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Дейкстры.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_1.png|thumb|center|700px|Рисунок 1. Реализация алгоритма Дейкстры. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 1 представлен профиль обращений в память для реализации алгоритма Дейкстры. Первое, что бросается в глаза, – большая разрозненность обращений. В частности, значительные области выше и ниже фрагмента 2 остаются пустыми, при этом сами обращения объединены лишь в небольшие группы. Это говорит о низкой эффективности, поскольку: а) повторные обращения практически отсутствуют либо происходят через значительный промежуток времени; б) расстояния между идущими подряд обращениями может быть очень большим.&lt;br /&gt;
&lt;br /&gt;
Однако при ближайшем рассмотрении может оказаться, что такие участки обладают высокой локальностью и состоят из значительного числа обращений. Более того, на общем профиле есть несколько областей (фрагменты 1 и 2), в которых обращения хорошо локализованы. Необходимо исследовать отдельные участки более подробно.&lt;br /&gt;
&lt;br /&gt;
Перейдем к изучению фрагмента 1 (рис. 2), в рамках которого выполняются обращения к двум небольшим массивам. Можно увидеть, что здесь задействованы только примерно 500 элементов, при этом к ним выполняется около 100 тысяч обращений. Весь профиль составляет около 120 тысяч обращений, поэтому получается, что подавляющая их часть выполняется именно к этим элементам. &lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Поскольку число элементов невелико, локальность в данном случае заведомо будет достаточно высокой, независимо от структуры самого фрагмента.  Однако на рис. 3, где представлены два участка из фрагмента 1, можно увидеть, что фрагмент в основном состоит из последовательных переборов, при этом данные зачастую задействованы повторно через не очень большие промежутки. Все это позволяет говорить, что данный фрагмент обладает высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_3.png|thumb|center|700px|Рисунок 3. Профили двух участков фрагмента 1 (выделены зеленым на рис. 2)]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь более подробно фрагмент 2 (рис. 4), в рамках которого выполняются обращения к еще служебному массиву. Здесь профиль состоит из двух этапов. На первом заметен достаточно хаотичный разброс обращений, напоминающий случайный доступ. На втором обращения образуют подобие последовательного перебора.  В целом такой профиль характеризуется очень низкой временной локальностью (повторные обращения практически или полностью отсутствуют) и достаточно низкой пространственной локальностью (из-за случайного доступа на первом этапе).&lt;br /&gt;
&lt;br /&gt;
Заметим, что при этом число задействованных элементов здесь больше, чем во фрагменте 1, однако число обращений гораздо меньше.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Далее остаются для рассмотрения два массива (область между фрагментами 1 и 2 и область ниже фрагмента 2). Характер обращений к этим массивам во многом похож, поэтому достаточно изучить более подробно только один из них. &lt;br /&gt;
&lt;br /&gt;
Фрагмент 3 рассмотрен на рис. 5. Этот участок отражают достаточно большую область, что не позволяет проанализировать профиль вплоть до отдельных обращений, однако здесь этого и не требуется. Из данного фрагмента видно, что основу профиля составляют участки с последовательным (или с небольшим шагом) перебором, состоящие из небольшого числа элементов – выделенный желтым самый большой фрагмента участок состоит всего из пары сотен обращений. При этом между разными участками расстояние может быть существенным. Все это говорит об очень низкой локальности (как пространственной, так и временной) в случае двух рассматриваемых массивов.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
В целом, несмотря на положительный вклад массивов из фрагмента 1, локальность общего профиля должна быть достаточно низкой, вследствие неэффективного использования данных в остальной части профиля.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Основной фрагмент реализации, на основе которого были получены количественные оценки, приведен [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/benchmarks/dijkstra/dijkstra.h здесь] (функция Kernel). Условия запуска описаны [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/README.md здесь].&lt;br /&gt;
&lt;br /&gt;
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью достаточно низка. Это неудивительно, поскольку реализации алгоритмов над графами почти всегда обладают низкой эффективностью вследствие нерегулярности доступа к данным, что мы и увидели при анализе профиля обращений.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
Вторая характеристика – cvg – предназначена для получения более машинно-независимой оценки локальности. Она определяет, насколько часто в программе необходимо подтягивать данные в кэш-память. Соответственно, чем меньше значение cvg, тем реже это нужно делать, тем лучше локальность. &lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения cvg для того же набора реализаций, отсортированные по убыванию (чем меньше cvg, тем в общем случае выше локальность). Можно увидеть, что в данном случае значение cvg хорошо коррелирует с оценкой производительности и отражает низкую локальность, что соответствует выводам, сделанным при качественной оценке локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_cvg.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки cvg]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов&amp;quot;&amp;lt;ref name=&amp;quot;Lom&amp;quot;&amp;gt;Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.&amp;lt;/ref&amp;gt; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [4, 8 : 128] со значениями квадрата целого числа;&lt;br /&gt;
*	размер графа [16000:64000] с шагом 16000.&lt;br /&gt;
&lt;br /&gt;
На следующем рисунке приведен график [[Глоссарий#Производительность|производительности]] выбранной реализации алгоритма в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация алгоритма. Изменение производительности в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
&lt;br /&gt;
В силу особенностей параллельной реализации алгоритма производительность в целом достаточно низкая и с ростом числа процессов увеличивается медленно, а при приближении к числу процессов 128 начинает уменьшаться.&lt;br /&gt;
Это объясняется использованием коллективных операций на каждой итерации алгоритма и тем, что затраты на коммуникационные обмены существенно возрастают с ростом числа использованных процессов. Вычисления на каждом процессе проходят достаточно быстро и потому декомпозиция графа слабо компенсирует эффект от затрат на коммуникационные обмены. &lt;br /&gt;
&lt;br /&gt;
[https://code.google.com/p/prir-dijkstra-mpi/source/browse/trunk/project/dijkstra_mpi.c Исследованная параллельная реализация на языке C]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Дейкстры.  Все результаты получены на суперкомпьютере &amp;quot;Ломоносов&amp;quot;. Использовались процессоры Intel Xeon X5570 с пиковой производительностью в 94 Гфлопс, а также компилятор intel 13.1.0. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Дейкстры  на 32 процессах.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra CPU User.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы уровень загрузки составляет около 50%. Это указывает на равномерную загруженность вычислениями процессоров, при использовании 8 процессов на вычислительный узел и без использования Hyper Threading. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Flops.png|thumb|center|700px|Рисунок 10. График операций с плавающей точкой в секунду при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На Рисунке 10 показан график количества операций с плавающей точкой в секунду. На графике видна общая очень низкая производительность вычислений около 250 Kфлопс в пике и около 150 Кфлопс в среднем по всем узлам. Это указывает то, что в программе почти все вычисления производятся с целыми числами. &lt;br /&gt;
 [[file:Dijkstra L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень большое для нескольких ядер и находится на уровне 15 млн/сек (в пике до 60 млн/сек), что указывает на интенсивные вычисления в части процессов. В среднем по всем узлам значения значительно ниже (около 9 млн/сек). Это указывает на неравномерное распределение вычислений.&lt;br /&gt;
 [[file:Dijkstra L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов третьего уровня видно, что число промахов очень немного и находится на уровне 1,2 млн/сек, однако в среднем по всем узлам значения около 0,5 млн/сек. Соотношение кэш промахов L1|L3 для процессов с высокой производительностью доходит до 60, однако в среднем около 30. Это указывает на очень хорошую локальность вычислений как у части процессов, так и для всех в среднем, и это является признаком высокой производительности.&lt;br /&gt;
[[file:Dijkstra Mem Load.png|thumb|center|700px|Рисунок 13. График количества чтений из оперативной памяти при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике обращений в память видна достаточно типичная картина для таких приложений. Активность чтения достаточно низкая, что в совокупности с низкими значениями кэш-промахов L3 указывает на хорошую локальность. Хорошая локальность приложения так же указывает на то, что значения около 1 млн/сек для задачи является результатом высокой производительности вычислений, хотя и присутствует неравномерность между процессами.&lt;br /&gt;
 [[file:Dijkstra Mem Store.png|thumb|center|700px|Рисунок 14. График количества записей в оперативную память при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике записей в память видна похожая картина неравномерности вычислений, при которой одновременно активно выполняют запись только несколько процессов. Это коррелирует с другими графиками выполнения. Стоит отметить, достаточно низкое число обращений на запись в память. Это указывает на хорошую организацию вычислений, и достаточно эффективную работу с памятью.&lt;br /&gt;
 [[file:Dijkstra Inf DATA.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая скорость передачи данных в байтах в секунду. Это говорит о том, что процессы между собой обмениваются интенсивно и вероятно достаточно малыми порциями данных, потому как производительность вычислений высока. Стоит отметить, что скорость передачи отличается между процессами, что указывает на дисбаланс вычислений.&lt;br /&gt;
 [[file:Dijkstra Inf PCKTS.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается крайне высокая интенсивность передачи данных выраженная в пакетах в секунду. Это говорит о том, что, вероятно, процессы обмениваются не очень существенными объемами данных, но очень интенсивно. Используются коллективные операции на каждом шаге с небольшими порциями данных, что объясняет такую картину.  Так же наблюдается почти меньший дизбаланс между процессами чем наблюдаемый в графиках использования памяти и вычислений и передачи данных в байтах/сек. Это указывает на то, что процессы обмениваются по алгоритму одинаковым числом пакетов, однако получают разные объемы данных и ведут неравномерные вычисления. &lt;br /&gt;
 [[file:Dijkstra Load ABG.png|thumb|center|700px|Рисунок 17. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Дейкстры]]&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 8. Это свидетельствует о стабильной работе программы с загруженными вычислениями всеми узлами. Это указывает на очень рациональную и статичную загрузку аппаратных ресурсов процессами. И показывает достаточно хорошую эффективность выполняемой реализации.&lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала достаточно эффективно, и стабильно. Использование памяти  очень интенсивное, а использование коммуникационной среды крайне интенсивное, при этом объемы передаваемых данных не являются высокими. Это указывает на требовательность к латентности коммуникационной среды алгоритмической части программы. &lt;br /&gt;
Низкая эффективность связана судя по всему с достаточно высоким объемом пересылок на каждом процессе, интенсивными обменами сообщениями.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html dijkstra_shortest_paths]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths_no_color_map.html dijkstra_shortest_paths_no_color_map]&amp;lt;/code&amp;gt;), сложность &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;. &lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]:&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#eager-dijkstra-s-algorithm eager_dijkstra_shortest_paths]&amp;lt;/code&amp;gt; – непосредственная реализация алгоритма Дейкстры;&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#crauser-et-al-s-algorithm crauser_et_al_shortest_paths]&amp;lt;/code&amp;gt; – реализация алгоритма Дейкстры в виде алгоритма из статьи Краузера и др.&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.single_source_dijkstra.html single_source_dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.Dijkstra networkit.graph.Dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Dijkstra&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24131</id>
		<title>Алгоритм Дейкстры</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&amp;diff=24131"/>
		<updated>2017-11-22T15:08:17Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Входные и выходные данные алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Основные авторы описания: [[Участник:Daryin|А.Н.Дарьин]], [[Участник:VadimVV|Вад.В.Воеводин]] ([[#Описание локальности данных и вычислений|раздел 2.2]])&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Дейкстры&#039;&#039;&#039;&amp;lt;ref&amp;gt;Dijkstra, E W. “A Note on Two Problems in Connexion with Graphs.” Numerische Mathematik 1, no. 1 (December 1959): 269–71. doi:10.1007/BF01386390.&amp;lt;/ref&amp;gt; предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа с неотрицательными весами алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа.&lt;br /&gt;
Алгоритм Дейкстры (с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap&amp;gt;Fredman, Michael L, and Robert Endre Tarjan. “Fibonacci Heaps and Their Uses in Improved Network Optimization Algorithms.” Journal of the ACM 34, no. 3 (July 1987): 596–615. doi:10.1145/28869.28874.&amp;lt;/ref&amp;gt;) выполняется за время &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt; и является асимптотически быстрейшим из известных последовательных алгоритмов для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; с весами рёбер &amp;lt;math&amp;gt;f(e)&amp;lt;/math&amp;gt; и выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Обозначим через &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt; кратчайшее расстояние от источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть уже вычислены все расстояния, не превосходящие некоторого числа &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt;, то есть расстояния до вершин из множества &amp;lt;math&amp;gt;V_r = \{ v \in V \mid d(v) \le r \}&amp;lt;/math&amp;gt;. Пусть&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
        (v, w) \in \arg\min \{ d(v) + f(e) \mid  v \in V, e = (v, w) \in E \}.&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
Тогда &amp;lt;math&amp;gt;d(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; лежит на кратчайшем пути от &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Величины &amp;lt;math&amp;gt;d^+(w) = d(v) + f(e)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v \in V_r&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;e = (v, w) \in E&amp;lt;/math&amp;gt;, называются &#039;&#039;предполагаемыми расстояниями&#039;&#039; и являются оценкой сверху для настоящих расстояний: &amp;lt;math&amp;gt;d(w) \le d^+(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры на каждом шаге находит вершину с наименьшим предполагаемым расстоянием, помечает её как посещённую и обновляет предполагаемые расстояния для всех концов рёбер, исходящих из неё.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
Основные вычисления связаны с операциями над очередью с приоритетом:&lt;br /&gt;
* извлечение минимального элемента (&amp;lt;code&amp;gt;delete_min&amp;lt;/code&amp;gt;);&lt;br /&gt;
* уменьшение приоритета элемента (&amp;lt;code&amp;gt;decrease_key&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
Псевдокод алгоритма:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039; с весами &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;);&lt;br /&gt;
   вершина-источник &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: расстояния &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; от вершины &#039;&#039;u&#039;&#039;.&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;Q&#039;&#039; := &#039;&#039;&#039;new&#039;&#039;&#039; priority queue&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;v&#039;&#039; = &#039;&#039;u&#039;&#039; &#039;&#039;&#039;then&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := 0 &#039;&#039;&#039;else&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) := ∞ &lt;br /&gt;
     &#039;&#039;Q&#039;&#039;.insert(&#039;&#039;v&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;))&lt;br /&gt;
 &lt;br /&gt;
 &#039;&#039;&#039;while&#039;&#039;&#039; &#039;&#039;Q&#039;&#039; ≠ ∅:&lt;br /&gt;
     &#039;&#039;v&#039;&#039; := &#039;&#039;Q&#039;&#039;.delete_min()&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;e&#039;&#039; = (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039;:&lt;br /&gt;
         &#039;&#039;&#039;if&#039;&#039;&#039; &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) &amp;gt; &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;):&lt;br /&gt;
             &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;) := &#039;&#039;d&#039;&#039;(&#039;&#039;v&#039;&#039;) + &#039;&#039;f&#039;&#039;(&#039;&#039;e&#039;&#039;)&lt;br /&gt;
             &#039;&#039;Q&#039;&#039;.decrease_key(&#039;&#039;w&#039;&#039;, &#039;&#039;d&#039;&#039;(&#039;&#039;w&#039;&#039;))&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
Конкретная реализация алгоритма Дейкстры определяется выбором используемого алгоритма очереди с приоритетом. В простейшем случае это может быть массив или список, поиск минимума в котором требует просмотра всех вершин. Более эффективным является использование кучи; наилучшую известную оценку сложности имеет вариант с использованием фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Возможен вариант реализации, когда вершины добавляются в очередь не на этапе инициализации, а в момент первого посещения.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательная сложность алгоритма равна &amp;lt;math&amp;gt;O(C_1 m + C_2n)&amp;lt;/math&amp;gt;, где&lt;br /&gt;
* &amp;lt;math&amp;gt;C_1&amp;lt;/math&amp;gt; – количество операций уменьшения расстояния до вершины;&lt;br /&gt;
* &amp;lt;math&amp;gt;C_2&amp;lt;/math&amp;gt; – количество операций вычисления минимума.&lt;br /&gt;
&lt;br /&gt;
Оригинальный алгоритм Дейкстры использовал в качестве внутренней структуры данных списки, для которых &amp;lt;math&amp;gt;C_1 = O(1)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;C_2 = O(n)&amp;lt;/math&amp;gt;, так что общая сложность составляла &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
При использовании фибоначчиевой кучи&amp;lt;ref name=FibHeap /&amp;gt; время вычисления минимума сокращается до &amp;lt;math&amp;gt;C_2 = O(\ln n)&amp;lt;/math&amp;gt;, так что общая сложность равна &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;, что является асимптотически наилучшим известным результатом для данного класса задач.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Приводится граф алгоритма для базовой реализации алгоритма Дейкстры на списках или массивах.&lt;br /&gt;
[[file:Deikstra.png|thumb|center|600px|Рисунок 1. Граф алгоритма без отображения входных и выходных данных. n=3. Желтым цветом обозначены операции сравнения, зеленым - операции изменения меток вершин, синим - пометка вершины.]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм Дейкстры допускает эффективную параллелизацию&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;, среднее время работы &amp;lt;math&amp;gt;O(n^{1/3}\ln n)&amp;lt;/math&amp;gt; с объёмом вычислений &amp;lt;math&amp;gt;O(n \ln n + m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Дейкстры.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: взвешенный граф &amp;lt;math&amp;gt;(V, E, W)&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt; с весами &amp;lt;math&amp;gt;f_j&amp;lt;/math&amp;gt;), вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – последнее ребро &amp;lt;math&amp;gt;e^*_v = (w, v)&amp;lt;/math&amp;gt;, лежащее на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, или соответствующая вершина &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;;&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – суммарный вес &amp;lt;math&amp;gt;f^*(v)&amp;lt;/math&amp;gt; кратчайшего пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_1.png|thumb|center|700px|Рисунок 1. Реализация алгоритма Дейкстры. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 1 представлен профиль обращений в память для реализации алгоритма Дейкстры. Первое, что бросается в глаза, – большая разрозненность обращений. В частности, значительные области выше и ниже фрагмента 2 остаются пустыми, при этом сами обращения объединены лишь в небольшие группы. Это говорит о низкой эффективности, поскольку: а) повторные обращения практически отсутствуют и либо происходят через значительный промежуток времени; 2) расстояния между идущими подряд обращениями может быть очень большим.&lt;br /&gt;
&lt;br /&gt;
Однако при ближайшем рассмотрении может оказаться, что такие участки обладают высокой локальностью и состоят из значительного числа обращений. Более того, на общем профиле есть несколько областей (фрагменты 1 и 2), в которых обращения хорошо локализованы. Необходимо исследовать отдельные участки более подробно.&lt;br /&gt;
&lt;br /&gt;
Перейдем к изучению фрагмента 1 (рис. 2), в рамках которого выполняются обращения к двум небольшим массивам. Можно увидеть, что здесь задействованы только примерно 500 элементов, при этом к ним выполняется около 100 тысяч обращений. Весь профиль составляет около 120 тысяч обращений, поэтому получается, что подавляющая их часть выполняется именно к этим элементам. &lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Поскольку число элементов невелико, локальность в данном случае заведомо будет достаточно высокой, независимо от структуры самого фрагмента.  Однако на рис. 2, где представлены два участка из фрагмента 1, можно увидеть, что фрагмент в основном состоит из последовательных переборов, при этом данные зачастую задействованы повторно через не очень большие промежутки. Все это позволяет говорить, что данный фрагмент обладает высокой как пространственной, так и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_3.png|thumb|center|700px|Рисунок 3. Профили двух участков фрагмента 1 (выделены зеленым на рис. 2)]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим теперь более подробно фрагмент 2 (рис. 4), в рамках которого выполняются обращения к еще служебному массиву. Здесь профиль состоит из двух этапов. На первом заметен достаточно хаотичный разброс обращений, напоминающий случайный доступ. На втором обращения образуют подобие последовательного перебора.  В целом такой профиль характеризуется очень низкой временной локальностью (повторные обращения практически или полностью отсутствуют) и достаточно низкой пространственной локальностью (из-за случайного доступа на первом этапе).&lt;br /&gt;
&lt;br /&gt;
Заметим, что при этом число задействованных элементов здесь больше, чем во фрагменте 1, однако число обращений гораздо меньше.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]&lt;br /&gt;
&lt;br /&gt;
Далее остаются для рассмотрения два массива (область между фрагментами 1 и 2 и область ниже фрагмента 2). Характер обращений к этим массивам во многом похож, поэтому достаточно изучить более подробно только один из них. &lt;br /&gt;
&lt;br /&gt;
Фрагмент 3 рассмотрен на рис. 5. Этот участок отражают достаточно большую область, что не позволяет проанализировать профиль вплоть до отдельных обращений, однако здесь этого и не требуется. Из данного фрагмента видно, что основу профиля составляют участки с последовательным (или с небольшим шагом) перебором, состоящие из небольшого числа элементов – выделенный желтым самый большой фрагмента участок состоит всего из пары сотен обращений. При этом между разными участками расстояние может быть существенным. Все это говорит об очень низкой локальности (как пространственной, так и временной) в случае двух рассматриваемых массивов.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 3]]&lt;br /&gt;
&lt;br /&gt;
В целом, несмотря на положительный вклад массивов из фрагмента 1, локальность общего профиля должна быть достаточно низкой, вследствие неэффективного использования данных в остальной части профиля.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Основной фрагмент реализации, на основе которого были получены количественные оценки, приведен [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/benchmarks/dijkstra/dijkstra.h здесь] (функция Kernel). Условия запуска описаны [http://git.parallel.ru/shvets.pavel.srcc/locality/blob/master/README.md здесь].&lt;br /&gt;
&lt;br /&gt;
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью достаточно низка. Это неудивительно, поскольку реализации алгоритмов над графами почти всегда обладают низкой эффективностью вследствие нерегулярности доступа к данным, что мы и увидели при анализе профиля обращений.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
Вторая характеристика – cvg – предназначена для получения более машинно-независимой оценки локальности. Она определяет, насколько часто в программе необходимо подтягивать данные в кэш-память. Соответственно, чем меньше значение cvg, тем реже это нужно делать, тем лучше локальность. &lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения cvg для того же набора реализаций, отсортированные по убыванию (чем меньше cvg, тем в общем случае выше локальность). Можно увидеть, что в данном случае значение cvg хорошо коррелирует с оценкой производительности и отражает низкую локальность, что соответствует выводам, сделанным при качественной оценке локальности.&lt;br /&gt;
&lt;br /&gt;
[[file:dijkstra_cvg.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки cvg]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов&amp;quot;&amp;lt;ref name=&amp;quot;Lom&amp;quot;&amp;gt;Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.&amp;lt;/ref&amp;gt; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [4, 8 : 128] со значениями квадрата целого числа;&lt;br /&gt;
*	размер графа [16000:64000] с шагом 16000.&lt;br /&gt;
&lt;br /&gt;
На следующем рисунке приведен график [[Глоссарий#Производительность|производительности]] выбранной реализации алгоритма в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация алгоритма. Изменение производительности в зависимости от числа процессоров и размера области.]]&lt;br /&gt;
&lt;br /&gt;
В силу особенностей параллельной реализации алгоритма производительность в целом достаточно низкая и с ростом числа процессов увеличивается медленно, а при приближении к числу процессов 128 начинает уменьшаться.&lt;br /&gt;
Это объясняется использованием коллективных операций на каждой итерации алгоритма и тем, что затраты на коммуникационные обмены существенно возрастают с ростом числа использованных процессов. Вычисления на каждом процессе проходят достаточно быстро и потому декомпозиция графа слабо компенсирует эффект от затрат на коммуникационные обмены. &lt;br /&gt;
&lt;br /&gt;
[https://code.google.com/p/prir-dijkstra-mpi/source/browse/trunk/project/dijkstra_mpi.c Исследованная параллельная реализация на языке C]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация алгоритма Дейкстры.  Все результаты получены на суперкомпьютере &amp;quot;Ломоносов&amp;quot;. Использовались процессоры Intel Xeon X5570 с пиковой производительностью в 94 Гфлопс, а также компилятор intel 13.1.0. &lt;br /&gt;
На рисунках показана эффективность реализации алгоритма Дейкстры  на 32 процессах.&lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra CPU User.png|thumb|center|700px|Рисунок 9. График загрузки CPU при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы уровень загрузки составляет около 50%. Это указывает на равномерную загруженность вычислениями процессоров, при использовании 8 процессов на вычислительный узел и без использования Hyper Threading. &lt;br /&gt;
&lt;br /&gt;
[[file:Dijkstra Flops.png|thumb|center|700px|Рисунок 10. График операций с плавающей точкой в секунду при выполнении алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На Рисунке 10 показан график количества операций с плавающей точкой в секунду. На графике видна общая очень низкая производительность вычислений около 250 Kфлопс в пике и около 150 Кфлопс в среднем по всем узлам. Это указывает то, что в программе почти все вычисления производятся с целыми числами. &lt;br /&gt;
 [[file:Dijkstra L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень большое для нескольких ядер и находится на уровне 15 млн/сек (в пике до 60 млн/сек), что указывает на интенсивные вычисления в части процессов. В среднем по всем узлам значения значительно ниже (около 9 млн/сек). Это указывает на неравномерное распределение вычислений.&lt;br /&gt;
 [[file:Dijkstra L3.png|thumb|center|700px|Рисунок 12. График кэш-промахов L3 в секунду при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов третьего уровня видно, что число промахов очень немного и находится на уровне 1,2 млн/сек, однако в среднем по всем узлам значения около 0,5 млн/сек. Соотношение кэш промахов L1|L3 для процессов с высокой производительностью доходит до 60, однако в среднем около 30. Это указывает на очень хорошую локальность вычислений как у части процессов, так и для всех в среднем, и это является признаком высокой производительности.&lt;br /&gt;
[[file:Dijkstra Mem Load.png|thumb|center|700px|Рисунок 13. График количества чтений из оперативной памяти при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике обращений в память видна достаточно типичная картина для таких приложений. Активность чтения достаточно низкая, что в совокупности с низкими значениями кэш-промахов L3 указывает на хорошую локальность. Хорошая локальность приложения так же указывает на то, что значения около 1 млн/сек для задачи является результатом высокой производительности вычислений, хотя и присутствует неравномерность между процессами.&lt;br /&gt;
 [[file:Dijkstra Mem Store.png|thumb|center|700px|Рисунок 14. График количества записей в оперативную память при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике записей в память видна похожая картина неравномерности вычислений, при которой одновременно активно выполняют запись только несколько процессов. Это коррелирует с другими графиками выполнения. Стоит отметить, достаточно низкое число обращений на запись в память. Это указывает на хорошую организацию вычислений, и достаточно эффективную работу с памятью.&lt;br /&gt;
 [[file:Dijkstra Inf DATA.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно высокая скорость передачи данных в байтах в секунду. Это говорит о том, что процессы между собой обмениваются интенсивно и вероятно достаточно малыми порциями данных, потому как производительность вычислений высока. Стоит отметить, что скорость передачи отличается между процессами, что указывает на дисбаланс вычислений.&lt;br /&gt;
 [[file:Dijkstra Inf PCKTS.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе алгоритма Дейкстры]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается крайне высокая интенсивность передачи данных выраженная в пакетах в секунду. Это говорит о том, что, вероятно, процессы обмениваются не очень существенными объемами данных, но очень интенсивно. Используются коллективные операции на каждом шаге с небольшими порциями данных, что объясняет такую картину.  Так же наблюдается почти меньший дизбаланс между процессами чем наблюдаемый в графиках использования памяти и вычислений и передачи данных в байтах/сек. Это указывает на то, что процессы обмениваются по алгоритму одинаковым числом пакетов, однако получают разные объемы данных и ведут неравномерные вычисления. &lt;br /&gt;
 [[file:Dijkstra Load ABG.png|thumb|center|700px|Рисунок 17. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Дейкстры]]&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 8. Это свидетельствует о стабильной работе программы с загруженными вычислениями всеми узлами. Это указывает на очень рациональную и статичную загрузку аппаратных ресурсов процессами. И показывает достаточно хорошую эффективность выполняемой реализации.&lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала достаточно эффективно, и стабильно. Использование памяти  очень интенсивное, а использование коммуникационной среды крайне интенсивное, при этом объемы передаваемых данных не являются высокими. Это указывает на требовательность к латентности коммуникационной среды алгоритмической части программы. &lt;br /&gt;
Низкая эффективность связана судя по всему с достаточно высоким объемом пересылок на каждом процессе, интенсивными обменами сообщениями.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths.html dijkstra_shortest_paths]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/dijkstra_shortest_paths_no_color_map.html dijkstra_shortest_paths_no_color_map]&amp;lt;/code&amp;gt;), сложность &amp;lt;math&amp;gt;O(m + n \ln n)&amp;lt;/math&amp;gt;. &lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]:&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#eager-dijkstra-s-algorithm eager_dijkstra_shortest_paths]&amp;lt;/code&amp;gt; – непосредственная реализация алгоритма Дейкстры;&lt;br /&gt;
** функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/dijkstra_shortest_paths.html#crauser-et-al-s-algorithm crauser_et_al_shortest_paths]&amp;lt;/code&amp;gt; – реализация алгоритма Дейкстры в виде алгоритма из статьи Краузера и др.&amp;lt;ref&amp;gt;Crauser, A, K Mehlhorn, U Meyer, and P Sanders. “A Parallelization of Dijkstra&#039;s Shortest Path Algorithm,” Proceedings of Mathematical Foundations of Computer Science / Lecture Notes in Computer Science, 1450:722–31, Berlin, Heidelberg: Springer, 1998. doi:10.1007/BFb0055823.&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.single_source_dijkstra.html single_source_dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.Dijkstra networkit.graph.Dijkstra]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Dijkstra&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24114</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24114"/>
		<updated>2017-11-22T09:16:38Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Масштабируемость алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма являются поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а также поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основными частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработки результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а также рекурсивных вызовов FB-шага. Далее будут приведены более подробные информационные графы для каждой из частей, а также дана подробная расшифровка соответствующих операций, а в следующем разделе мы оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а также активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а также массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонентам с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходили ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять FB-шаги, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированном графах независимы и также могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления FB-шагов одного уровня (а также всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в непересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) также имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а также проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могут производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) также могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при этом число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода на каждом шаге алгоритма поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и т.д. Максимальное (или даже среднее) число вершин на каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а также структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальность, масштабируемость) этих алгоритмов также схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичного подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно задействовать при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, используя механизмы tasks и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24106</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24106"/>
		<updated>2017-11-21T19:09:06Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Свойства алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма являются поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а также поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основными частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработки результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а также рекурсивных вызовов FB-шага. Далее будут приведены более подробные информационные графы для каждой из частей, а также дана подробная расшифровка соответствующих операций, а в следующем разделе мы оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а также активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а также массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонентам с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходили ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять FB-шаги, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированном графах независимы и также могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления FB-шагов одного уровня (а также всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в непересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) также имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а также проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могут производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) также могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при этом число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода на каждом шаге алгоритма поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и т.д. Максимальное (или даже среднее) число вершин на каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а также структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальность, масштабируемость) этих алгоритмов также схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24097</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24097"/>
		<updated>2017-11-21T14:23:20Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма являются поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а также поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основными частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработки результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а также рекурсивных вызовов FB-шага. Далее будут приведены более подробные информационные графы для каждой из частей, а также дана подробная расшифровка соответствующих операций, а в следующем разделе мы оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а также активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а также массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонентам с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходили ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять FB-шаги, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированном графах независимы и также могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления FB-шагов одного уровня (а также всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в непересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) также имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а также проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могут производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) также могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при этом число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода на каждом шаге алгоритма поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и т.д. Максимальное (или даже среднее) число вершин на каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а также структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24096</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24096"/>
		<updated>2017-11-21T13:02:53Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма являются поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а также поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основными частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработки результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а также рекурсивных вызовов FB-шага. Далее будут приведены более подробные информационные графы для каждой из частей, а также дана подробная расшифровка соответствующих операций, а в следующем разделе мы оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а также активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а также массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонентам с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходили ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять FB-шаги, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированном графах независимы и также могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления FB-шагов одного уровня (а также всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в непересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) также имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а также проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могут производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) также могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при этом число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода, на каждом алгоритма шаге поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и.т.д. Максимальное (или даже среднее) число вершин а каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а так же структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24095</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24095"/>
		<updated>2017-11-21T12:02:36Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма являются поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а также поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основными частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработки результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а также рекурсивных вызовов FB-шага. Далее будут приведены более подробные информационные графы для каждой из частей, а также дана подробная расшифровка соответствующих операций, а в следующем разделе мы оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а также активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а также массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонентам с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходили ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять FB-шаги, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированном графах независимы и также могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления FB-шагов одного уровня (а также всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в непересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) также имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а также проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могут производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) также могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при этом число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода, на каждом алгоритма шаге поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и.т.д. Максимальное (или даже среднее) число вершин а каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а так же структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24094</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24094"/>
		<updated>2017-11-21T10:16:39Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма являются поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а также поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основными частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработки результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а также рекурсивных вызовов FB-шага. Далее будут приведены более подробные информационные графы для каждой из частей, а также дана подробная расшифровка соответствующих операций, а в следующем разделе мы оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а также активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а также массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонентам с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходили ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять FB-шаги, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированном графах независимы и также могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления FB-шагов одного уровня (а также всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в непересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) также имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а так же проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могу производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) так же могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при том число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода, на каждом алгоритма шаге поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и.т.д. Максимальное (или даже среднее) число вершин а каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а так же структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24093</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24093"/>
		<updated>2017-11-21T08:08:48Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма являются поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а также поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основными частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработки результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а также рекурсивных вызовов FB-шага. Далее будут приведены более подробные информационные графы для каждой из частей, а также дана подробная расшифровка соответствующих операций, а в следующем разделе мы оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а также активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а также массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонентам с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходили ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответствующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять шаги FB-step, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированному нему графах независимы, и так же могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления шагов FB-step одного уровня (а так же всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в не пересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) так же имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а так же проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могу производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) так же могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при том число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода, на каждом алгоритма шаге поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и.т.д. Максимальное (или даже среднее) число вершин а каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а так же структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24090</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24090"/>
		<updated>2017-11-20T17:57:07Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Схема реализации последовательного алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма являются поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а также поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основным частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработке результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а так же рекурсивных вызовах FB-step. Далее будут приведены более подробные информационные графы для каждой из частей, а так же дана подробная расшифровка соотвесвующих операций, а в следующем разделе и оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а так же активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а так же массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменно наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонент с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходи ли ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять шаги FB-step, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированному нему графах независимы, и так же могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления шагов FB-step одного уровня (а так же всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в не пересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) так же имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а так же проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могу производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) так же могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при том число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода, на каждом алгоритма шаге поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и.т.д. Максимальное (или даже среднее) число вершин а каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а так же структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24063</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24063"/>
		<updated>2017-11-20T09:22:09Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Вычислительное ядро алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма являются поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а также поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основным частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработке результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а так же рекурсивных вызовах FB-step. Далее будут приведены более подробные информационные графы для каждой из частей, а так же дана подробная расшифровка соотвесвующих операций, а в следующем разделе и оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а так же активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а так же массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменно наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонент с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходи ли ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять шаги FB-step, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированному нему графах независимы, и так же могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления шагов FB-step одного уровня (а так же всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в не пересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) так же имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а так же проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могу производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) так же могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при том число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода, на каждом алгоритма шаге поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и.т.д. Максимальное (или даже среднее) число вершин а каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а так же структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24062</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24062"/>
		<updated>2017-11-20T09:19:58Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Вычислительное ядро алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма является поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а так же поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало очереди и помечается как посещенная. &lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основным частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработке результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а так же рекурсивных вызовах FB-step. Далее будут приведены более подробные информационные графы для каждой из частей, а так же дана подробная расшифровка соотвесвующих операций, а в следующем разделе и оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а так же активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а так же массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменно наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонент с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходи ли ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять шаги FB-step, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированному нему графах независимы, и так же могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления шагов FB-step одного уровня (а так же всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в не пересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) так же имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а так же проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могу производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) так же могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при том число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода, на каждом алгоритма шаге поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и.т.д. Максимальное (или даже среднее) число вершин а каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а так же структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24060</id>
		<title>Алгоритм DCSC поиска компонент сильной связности</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_DCSC_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D0%BA%D0%BE%D0%BC%D0%BF%D0%BE%D0%BD%D0%B5%D0%BD%D1%82_%D1%81%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24060"/>
		<updated>2017-11-20T00:11:43Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Общее описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм DCSC поиска компонент сильной связности&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;C * O(ln(u))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм DCSC&#039;&#039;&#039;&amp;lt;ref&amp;gt;Fleischer, Lisa K, Bruce Hendrickson, and Ali Pınar. “On Identifying Strongly Connected Components in Parallel.” In Lecture Notes in Computer Science, Volume 1800, Springer, 2000, pp. 505–11. doi:10.1007/3-540-45591-4_68.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;McLendon, William, III, Bruce Hendrickson, Steven J Plimpton, and Lawrence Rauchwerger. “Finding Strongly Connected Components in Distributed Graphs.” Journal of Parallel and Distributed Computing 65, no. 8 (August 2005): 901–10. doi:10.1016/j.jpdc.2005.03.007.&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Hong, Sungpack, Nicole C Rodia, and Kunle Olukotun. “On Fast Parallel Detection of Strongly Connected Components (SCC) in Small-World Graphs,” Proceeedings of SC&#039;13, 1–11, New York, New York, USA: ACM Press, 2013. doi:10.1145/2503210.2503246.&amp;lt;/ref&amp;gt; (англ. Divide and Conquer Strong Components – компоненты сильной связности по принципу «Разделяй и властвуй») находит [[Связность в графах|компоненты сильной связности]] ориентированного графа с ожидаемой работой &amp;lt;math&amp;gt;O(|V| \ln |V|)&amp;lt;/math&amp;gt; (при условии ограниченной константой степени вершин). &lt;br /&gt;
&lt;br /&gt;
Также алгоритм носит другое название - Forward-Backward (сокр. FB-algorithm), в основном в литературе, связанной с его GPU-реализациями. &amp;lt;ref&amp;gt; Jiˇr ́ı Barnat, Petr Bauch, Lubosˇ Brim, and Milan Cˇesˇka. Computing Strongly Connected Components in Parallel on CUDA. Faculty of Informatics, Masaryk University, Botanicka ́ 68a, 60200 Brno, Czech Republic. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации: на каждом шаге он находит одну компоненту сильной связности и выделяет до трёх подмножеств графа, которые содержат другие компоненты связности и могут обрабатываться параллельно. Кроме того, выделение данных подмножеств и сильно связанной компоненты на каждом шаге также может производиться параллельно (с использованием параллельных поисков в ширину). Следует отметить, что в данном случае не требуется синхронизации между итерациями поиска в ширину, поскольку требуется только определить достижимые вершины, но не расстояния до них.&lt;br /&gt;
&lt;br /&gt;
Алгоритм хорошо подходит для графов, имеющих небольшое число сильно-связанных компонент большого размера. При значительном увеличении числа сильно связанных компонент сложность данного алгоритма также значительно увеличивается (пропорционально числу компонент), из-за чего данный алгоритм может стать менее эффективным в сравнении с последовательным алгоритмом Тарьяна, выделяющим сильно-связанные компоненты за один проход по графу. &lt;br /&gt;
&lt;br /&gt;
Для увеличения эффективности работы алгоритма на графах с большим числом тривиальных сильно-связанных компонент (размера 1 или 2), предложена модификация алгоритма: перед началом работы классического алгоритма производится шаг Trim, описанный в следующих разделах, позволяющий выделять все тривиальные сильно-связанные компоненты. В результате, к примеру в RMAT графах, после шага Trim в графе остается всего лишь несколько компонент сильной связанности большого размера, на которых алгоритм будет иметь небольшую алгоритмическую сложность.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;  – некоторая вершина графа. Определим следующие множества вершин:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;  – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; – вершины, из которых достижима &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; (эквивалентно – вершины, достижимые из &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в графе, полученном из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; обращением всех рёбер).&lt;br /&gt;
&lt;br /&gt;
Используя эти множества, разобьём все вершины графа на четыре области:&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_1 =  Fwd(v) \cap Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_2 =  Fwd(v) \setminus Pred(v) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_3 =   Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
⎯ &amp;lt;math&amp;gt;V_4 =   V \setminus Pred(v) \setminus Fwd(v)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда можно утверждать следующее:&lt;br /&gt;
&lt;br /&gt;
1.	Область &amp;lt;math&amp;gt;V_1&amp;lt;/math&amp;gt; является компонентой сильной связности.&lt;br /&gt;
&lt;br /&gt;
2.	Любая другая компонента сильной связности полностью содержится в одной из областей &amp;lt;math&amp;gt;V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt;, или &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Основными вычислительными операциями алгоритма является поиск вершин, достижимых из выбранной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, а так же поиск вершин, из которых достижима выбранная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Обе данные операции могут быть реализованы через поиски в ширину, устроенные следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.    Вершина &amp;lt;math&amp;gt;v_0&amp;lt;/math&amp;gt; помещается в начало в очереди и помечается ее как посещенная&lt;br /&gt;
&lt;br /&gt;
2.    Верхняя вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; извлекается из очереди. Для всех ребер &amp;lt;math&amp;gt;(v, u)&amp;lt;/math&amp;gt;, исходящих из вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, проверяется, является ли посещенной вершина  &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. В случае, если является, вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; помещается в начало очереди.&lt;br /&gt;
&lt;br /&gt;
3.    Происходит переход на шаг 2 до тех пор, пока в очереди есть вершины.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм DCSC состоит в следующем:&lt;br /&gt;
&lt;br /&gt;
1.	Поместить в очередь множество &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Параллельно обрабатывать очередь. Для каждого элемента очереди &amp;lt;math&amp;gt;V&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
а)	Выбрать произвольную ведущую вершину &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б)	Вычислить множества &amp;lt;math&amp;gt;Fwd(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;Pred(v)&amp;lt;/math&amp;gt; (эти два вычисления можно производить параллельно, кроме того, как указано выше, сами эти вычисления хорошо параллелизуются).&lt;br /&gt;
&lt;br /&gt;
в)	Добавить множество &amp;lt;math&amp;gt; V_1&amp;lt;/math&amp;gt; в список компонент сильной связности.&lt;br /&gt;
&lt;br /&gt;
г)	Добавить множества &amp;lt;math&amp;gt; V_2&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;V_3&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;V_4&amp;lt;/math&amp;gt; в очередь.&lt;br /&gt;
&lt;br /&gt;
3.	Алгоритм завершает работу, когда очередь пуста и не осталось активных процессов-обработчиков.&lt;br /&gt;
&lt;br /&gt;
Для улучшения балансировки нагрузки на первых шагах можно выбирать не одну ведущую вершину, а сразу несколько. Тогда, если они принадлежат различным компонентам связности, граф будет сразу разбит на большое количество областей, которые будут далее обрабатываться параллельно.&lt;br /&gt;
&lt;br /&gt;
Важной модификацией алгоритма является шаг Trim, производимый перед основными вычислениями алгоритма DCSC, который может быть описан следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.     Пометить все вершины из  &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; активными.&lt;br /&gt;
&lt;br /&gt;
2.     Для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;вычислить число входящих (&amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt;) и исходящих (&amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt;) дуг &amp;lt;math&amp;gt;(v, u) \in E&amp;lt;/math&amp;gt;, таких, что вершина &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - активная.&lt;br /&gt;
&lt;br /&gt;
3.    Все вершины &amp;lt;math&amp;gt;v \in V&amp;lt;/math&amp;gt; , для которых &amp;lt;math&amp;gt;in(v)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;out(v)&amp;lt;/math&amp;gt; равно нулю, пометить как неактивные.&lt;br /&gt;
&lt;br /&gt;
4.    Переходить на шаг 2, до тех пор, пока число активных вершин не перестанет изменяться.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в зависимости от схемы хранения графа может потребоваться предварительное нахождение транспонированного к нему для более эффективной реализации как шага trim, так и поиска вершин, из которых достижима заданная вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в вычислительном ядре алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный алгоритм реализуется следующим псевдокодом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
   граф с вершинами &#039;&#039;V&#039;&#039;, рёбрами &#039;&#039;E&#039;&#039;;&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: номера компонент сильной связанности &#039;&#039;c(v)&#039;&#039; до каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
compute_scc_with_trim(G)&lt;br /&gt;
{&lt;br /&gt;
	G’ = transpose(G)&lt;br /&gt;
	G = trim(G)&lt;br /&gt;
	compute_scc()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
compute_scc(V, G, G’)&lt;br /&gt;
{&lt;br /&gt;
	p = pivot(V)&lt;br /&gt;
&lt;br /&gt;
	fwd = bfs(G, p);&lt;br /&gt;
	pred = bfs(G’, p);&lt;br /&gt;
&lt;br /&gt;
	compute_scc(fwd / pred, G, G’)&lt;br /&gt;
	compute_scc(pred / fwd, G, G’)&lt;br /&gt;
	compute_scc(V / pred / fwd, G, G’)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
pivot(V)&lt;br /&gt;
{&lt;br /&gt;
	return random v in V&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
trim(G)&lt;br /&gt;
{&lt;br /&gt;
	for each v in V(G)&lt;br /&gt;
		active[v] = true&lt;br /&gt;
&lt;br /&gt;
	changes = false&lt;br /&gt;
	do&lt;br /&gt;
	{&lt;br /&gt;
		for each v in V&lt;br /&gt;
			in_deg(v) = compute_in_degree(v)&lt;br /&gt;
			out_deg(v) = compute_out_degree(v)&lt;br /&gt;
		&lt;br /&gt;
		for each v in V&lt;br /&gt;
			if (in_deg[v] == 0 || out_deg[v] == 0)&lt;br /&gt;
				active[v] = false&lt;br /&gt;
				changes = true&lt;br /&gt;
	} while (!changes)&lt;br /&gt;
	return active&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
bfs здесь стандартный поиск в ширину, описанный в соответствующем разделе, результатом которого являются все узлы, достижимые из вершины &amp;lt;math&amp;gt; p &amp;lt;/math&amp;gt; в графе &amp;lt;math&amp;gt; G &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Ожидаемая последовательная сложность алгоритма составляет &amp;lt;math&amp;gt;O(|V| \ln(|V|))&amp;lt;/math&amp;gt; при условии, что степень вершин ограничена сверху константой.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 представлен информационный граф алгоритма DCSC, демонстрирующий связь между его основным частями.&lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_full_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма DCSC(Forward-Backward-Trim).]]&lt;br /&gt;
&lt;br /&gt;
Итак, алгоритм состоит из первоначального шага Trim, выбора pivot-вершины [1], поиска в ширину в прямом и транспонированном графах (forward и backward BFS), обработке результатов поиска [2], проверки необходимости проведения последующих рекурсивных шагов [3],  а так же рекурсивных вызовах FB-step. Далее будут приведены более подробные информационные графы для каждой из частей, а так же дана подробная расшифровка соотвесвующих операций, а в следующем разделе и оценим ресурс параллелизма для каждой части алгоритма. &lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф шага Trim. &lt;br /&gt;
&lt;br /&gt;
[[file:DCSC_trim_step.png|thumb|center|500px|Рисунок 2. Информационный граф шага Trim алгоритма DCSC.]]&lt;br /&gt;
&lt;br /&gt;
[1] - выделение памяти под массивы данных числа входящих и исходящих дуг, а так же активных вершин&lt;br /&gt;
&lt;br /&gt;
[2] - инциализация переменной наличия изменений&lt;br /&gt;
&lt;br /&gt;
[3] - инициализация массива сильно связанных компонент, а так же массива активных вершин.&lt;br /&gt;
&lt;br /&gt;
[4] - установка переменной наличия изменений в позицию &amp;quot;нет изменений&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[5] - обнуление массивов числа входящих и исходящих дуг&lt;br /&gt;
&lt;br /&gt;
[6] - проверка, активны ли вершины на концах каждого ребра, увеличение числа входящих/исходящих дуг для соответствующих вершин, изменение переменно наличия изменений&lt;br /&gt;
&lt;br /&gt;
[7] - проверка активности всех вершин графа&lt;br /&gt;
&lt;br /&gt;
[8] - присваивание номеров сильно связанных компонент с нулевой степенью входящих или исходящих вершин&lt;br /&gt;
&lt;br /&gt;
[9] - проверка, происходи ли ли изменения на шаге 6, переход к следующей итерации&lt;br /&gt;
&lt;br /&gt;
Информационный граф этапа поиска в ширину (Forward и Backward BFS) приведен в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]).&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально предназначен для параллельной реализации и имеет несколько уровней параллелизма. &lt;br /&gt;
&lt;br /&gt;
На верхнем уровне алгоритм DCSC может параллельно выполнять шаги FB-step, продемонстрированные на рисунке 1. Таким образом, на каждом рекурсивном шаге алгоритма могут рекурсивно порождаться до 3 новых параллельных процессов, общее число которых равно числу сильно связанных компонент графа. Кроме того, поиски в ширину в исходном и транспонированному нему графах независимы, и так же могут выполняться параллельно друг с другом. Таким образом, на верхнем уровне может выполняться до &amp;lt;math&amp;gt; 2*u/log|u|&amp;lt;/math&amp;gt; параллельных потоков, где &amp;lt;math&amp;gt; u &amp;lt;/math&amp;gt; - это число сильно связанных компонент графа. Важно заметить, что вычисления шагов FB-step одного уровня (а так же всех последующих рекурсивных) абсолютно независимы по данным, так как производятся в не пересекающихся множествах вершин графа, поэтому могут производиться в том числе и на различных узлах. Кроме того, составные шаги алгоритма (поиск в ширину и шаг Trim) так же имеют значительный потенциал параллелизма, описанный далее.&lt;br /&gt;
&lt;br /&gt;
Этап Trim алгоритма, информационный граф которого приведен на рисунке 2, обладает значительным ресурсом параллелизма: единственной последовательной частью является  инициализация массивов и переменных (1)(2), а так же проверка условия выхода из цикла (9). Операции (3),(5),(7) и (8) абсолютно независимы и могу производиться параллельно, их число составляет &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;. Проверки и обновления данных в операции (6) так же могут производиться параллельно, однако обновления данных должны производиться атомарно. Число операций (6) равно &amp;lt;math&amp;gt;O(E)&amp;lt;/math&amp;gt;. Таким образом, почти все операции шага Trim могут выполняться параллельно, при том число параллельно выполняемых операций составляет &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt;. Учитывая, что величины &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; для графов большого размера крайне велики, потенциал параллелизма данной части алгоритма очень значителен и достаточен для полной загрузки современных вычислительных узлов/сопроцессоров. Кроме того, операции (3),(5),(7),(8) могут быть успешно векторизованы. &lt;br /&gt;
&lt;br /&gt;
Ресурс параллелизма операции поиска в ширину подробно описан в соответвующем разделе ([[Поиск_в_ширину_(BFS)]]). В зависимости от выбора формата хранения графа, подход к параллельной реализации поиска в ширину может быть линейным или квадратичным (в худшем случае). &lt;br /&gt;
&lt;br /&gt;
В случае линейного подхода, на каждом алгоритма шаге поиска в ширину может выполняться &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; параллельных операций, где &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - это число вершин, добавленных для просмотра на предыдущем шаге. Перед началом работы алгоритма для просмотра добавляется вершина-источник, на первом шаге - все еще не посещенные смежные с ней, на втором - все еще не посещенные смежные с вершинами на первом шаге, и.т.д. Максимальное (или даже среднее) число вершин а каждом шаге оценить проблематично, так как число шагов алгоритма, число достижимых вершин, а так же структура связей между ними определены лишь структурой конкретного входного графа. В случае, если все вершины достижимы, а число шагов поиска в ширину составляет &amp;lt;math&amp;gt; r &amp;lt;/math&amp;gt;, то среднее число параллельно обрабатываемых вершин на каждом шаге составляет &amp;lt;math&amp;gt; O(|V|)/r &amp;lt;/math&amp;gt;. Важно заметить, что наибольший уровень параллелизма достигается на средних шагах алгоритма, в то время как число вершин для обработки на начальных и конечных шагах может быть небольшим.&lt;br /&gt;
&lt;br /&gt;
В случае квадратической параллелизации алгоритм поиска в ширину на каждом шаге обходит все ребра графа, таким образом выполняя &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций, что совпадает с аналогичной оценкой для шага trim. Кроме того, поиск в ширину при реализации с использованием формата списка ребер графа может быть векторизован.&lt;br /&gt;
&lt;br /&gt;
Таким образом, на начальном этапе алгоритма может выполняться &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; или &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций. Далее следует &amp;lt;math&amp;gt;O(log(u))&amp;lt;/math&amp;gt; шагов, в каждом из которых в среднем присутствует &amp;lt;math&amp;gt;2*u/O(log(u))&amp;lt;/math&amp;gt; поисков в ширину, которые так же обладают внутренним ресурсом параллелизма: каждый поиск в ширину имеет &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных операций на каждом шаге при квадратическом подходе к параллелизации.&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно связанных компонент), по причине аналогичной зависимости шагов FB_step от входных данных.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа – номер компоненты сильной связанности, в которую входит данная вершина &amp;lt;math&amp;gt; c(v) &amp;lt;/math&amp;gt;, вершины, принадлежащие одной компоненте сильно связанности имеют одинаковые номера&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительное ядро алгоритма основано на поисках в ширину, поэтому многие свойства (локальности, масштабируемости) этих алгоритмов так же схожи.&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичнго подхода, либо при помощи параллельных очередей (по очереди на процессор).&lt;br /&gt;
&lt;br /&gt;
Кроме того, дополнительный ресурс параллелизма можно получить при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, использую задачи и nested parallelism.&lt;br /&gt;
&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Проведём исследование масштабируемости параллельной реализации алгоритма Forward-Backward-Trim согласно методике. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2 Суперкомпьютерного комплекса Московского университета.&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых параметров запуска реализации алгоритма:&lt;br /&gt;
&lt;br /&gt;
число процессоров [1 : 28] с шагом 1;&lt;br /&gt;
размер графа [2^20 : 2^27].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования сильной масштабируемости вширь указанной реализации.&lt;br /&gt;
&lt;br /&gt;
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.&lt;br /&gt;
&lt;br /&gt;
[[file:Сильная_масштабируемость_вширь_алгоритма_DCSC.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Farward-Backward-Trim, масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/strong_components.html strong_components]&amp;lt;/code&amp;gt;), распределённый алгоритм DCSC сочетается с локальным поиском компонент сильной связности [[Алгоритм DCSC поиска компонент сильной связности|алгоритмом Тарьяна]].&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:DCSC algorithm for finding the strongly connected components]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24042</id>
		<title>Алгоритм Пурдома</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24042"/>
		<updated>2017-11-19T18:19:29Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Масштабируемость реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Пурдома&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Пурдома&#039;&#039;&#039;&amp;lt;ref name=Purdom&amp;gt;Purdom, Paul, Jr. “A Transitive Closure Algorithm.” Bit 10, no. 1 (March 1970): 76–94. doi:10.1007/BF01940892.&amp;lt;/ref&amp;gt; находит [[Поиск транзитивного замыкания орграфа|транзитивное замыкание ориентированного графа]] за время &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\mu \le |E|&amp;lt;/math&amp;gt; – число [[Связность в графах|компонент сильной связности]] этого графа. Алгоритм может быть модифицирован для проверки принадлежности к транзитивному замыканию множества заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на следующих свойствах:&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; принадлежат одной [[Связность в графах|компоненте сильной связности]] графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, то его транзитивное замыкание &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; содержит рёбра &amp;lt;math&amp;gt;(v, w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(w, v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; принадлежат одной компоненте сильной связности графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а вершины &amp;lt;math&amp;gt;z&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt; – другой, то рёбра &amp;lt;math&amp;gt;(x, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(x, t)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, t)&amp;lt;/math&amp;gt; принадлежат или не принадлежат транзитивному замыканию &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; одновременно.&lt;br /&gt;
&lt;br /&gt;
Таким образом, поиск транзитивного замыкания графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; сводится к поиску транзитивного замыкания ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, полученного из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; схлопыванием каждой компоненты сильной связности в одну вершину. Транзитивное замыкание ациклического графа вычисляется с использованием топологической сортировки вершин графа.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из трех важных вычислительных частей, для каждой из которых важно оценить относительное время выполнения относительно других частей.&lt;br /&gt;
# Поиск сильно связанных компонент в исходном графе&lt;br /&gt;
# Построение графа промежуточного представления&lt;br /&gt;
# Поиски в ширину в графе промежуточного представления для построения итогового транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
1. В случае решения задачи проверки принадлежности к транзитивному замыканию множества заданных пар вершин, соотношение объемов различных вычислительных частей алгоритма может быть очень различным и, очевидно, зависит от числа пар входных вершин. Кроме того, данное соотношение может также зависеть от типа входного графа, так как структура и количество сильно-связанных компонент сильно влияют как на время поиска сильно-связанных компонент, так и на время выполнения поисков в ширину. Далее представлена таблица, демонстрирующая процент времени выполнения данных частей для одной из реализаций алгоритма для различных типов графов с числом вершин &amp;lt;math&amp;gt;2^{23}&amp;lt;/math&amp;gt; и числом входных пар вершин для проверки, равным 10000.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_time_table.png|thumb|center|500px|Рисунок 1. Сравнение времени выполнения различных частей одной из реализаций алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
Важно заметить, что некоторые алгоритмы поиска сильно-связанных компонент (например, DCSC) также основываются на поисках в ширину. Таким образом, вычислительным ядром алгоритма можно считать поиск в ширину. В свою очередь вычислительным ядром алгоритма поиска в ширину является обход вершин, смежных с выбранной вершиной, с последующим добавлением еще не посещенных вершин в множество &amp;quot;передовых&amp;quot; вершин. &lt;br /&gt;
&lt;br /&gt;
2. В случае решения задачи поиска полного транзитивного замыкания наиболее вычислительно затратной частью являются поиски в ширину в графе промежуточного представления, так как их необходимо произвести от всех вершин графа промежуточного представления, число которых может быть значительным при большом числе сильно-связанных компонент в исходном графе.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычисления производятся в четыре этапа:&lt;br /&gt;
&lt;br /&gt;
# Найти [[Связность в графах|компоненты сильной связности]] исходного графа, заменить каждую компоненту на одну вершину и удалить образовавшиеся рёбра-петли.&lt;br /&gt;
# Выполнить топологическую сортировку полученного ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычислить транзитивное замыкание графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, двигаясь от вершин с бо́льшими номерами к меньшим.&lt;br /&gt;
# По найденному транзитивному замыканию &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; восстановить транзитивное замыкание исходного графа.&lt;br /&gt;
&lt;br /&gt;
Последний этап не является обязательным, если рассматривать транзитивное замыкание &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; как «упакованное» транзитивное замыкание &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1 этап&#039;&#039;&#039; (&#039;&#039;вычисление компонент сильной связности&#039;&#039;) может быть реализован [[Алгоритм Тарьяна поиска компонент сильной связности|алгоритмом Тарьяна]]&amp;lt;ref name=DFS&amp;gt;Tarjan, Robert. “Depth-First Search and Linear Graph Algorithms.” SIAM Journal on Computing 1, no. 2 (1972): 146–60.&amp;lt;/ref&amp;gt;, находящим компоненты сильной связности в ходе [[Поиск в глубину (DFS)|поиска в глубину]].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2 этап&#039;&#039;&#039; (&#039;&#039;топологическая сортировка&#039;&#039;) может быть реализован алгоритмом Кана&amp;lt;ref&amp;gt;Kahn, A B. “Topological Sorting of Large Networks.” Communications of the ACM 5, no. 11 (November 1962): 558–62. doi:10.1145/368996.369025.&amp;lt;/ref&amp;gt; либо последовательным применением [[Поиск в глубину (DFS)|поиска в глубину]]&amp;lt;ref name=DFS /&amp;gt; для нумерации вершин в предпорядке.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;) выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
     ациклический граф &#039;G&#039;&#039; с вершинами &#039;V&#039;&#039;, пронумерованными в топологическом порядке, и рёбрами &#039;&#039;E&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;in reverse order&#039;&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;T&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;) либо выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
         &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;x&#039;&#039; ∈ &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;x&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;)    &#039;&#039;/* присваивание по ссылке */&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
либо транзитивное замыкание может строиться по упакованным данным &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt; одной из следующих функций:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     отображение &#039;&#039;R&#039;&#039;(&#039;&#039;v&#039;&#039;) вершин графа &#039;&#039;G&#039;&#039; на вершины графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; transitive_closure(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
     &#039;&#039;T&#039;&#039; := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039; := &#039;&#039;T&#039;&#039; ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;T&#039;&#039;&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; is_in_transitive_closure(&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;):&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;R&#039;&#039;(&#039;&#039;w&#039;&#039;) ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Первый и второй этап выполняются с помощью поиска в глубину со сложностью &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
На третьем этапе основной операцией является объединение списков вершин. В случае хранения списков в отсортированном виде объединение выполняется за линейное время. Количество списков не превосходит &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;, а в каждом списке не более &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; вершин, где &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; – количество компонент сильной связности. Общая сложность тогда составляет &amp;lt;math&amp;gt;O(\mu^2)&amp;lt;/math&amp;gt;. Альтернативным форматом хранения списка вершин является битовая маска с такой же общей сложностью.&lt;br /&gt;
&lt;br /&gt;
Сложность четвёртого этапа составляет &amp;lt;math&amp;gt;O(\mu |V|)&amp;lt;/math&amp;gt;, так как для каждой из &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; компонент сильной связности за линейное время вычисляется список вершин из не более чем &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин. Поскольку компоненты сильной связности не пересекаются, на данном этапе уже не требуется хранить списки в отсортированном виде.&lt;br /&gt;
&lt;br /&gt;
Таким образом, общая сложность составляет &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;, если не требуется явное построение транзитивного замыкания, или &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt; в противном случае.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф алгоритма Пурдома, демонстрирующий связь между его основным частями. Алгоритм представлен для вариации постановки задачи, когда производится проверка принадлежности к транзитивному замыканию множества заранее заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_ig.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация массива номеров сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[Поиск сильно связанных компонент (Tarjan, DCSC, etc)] - независимый блок алгоритма, может быть реализован любым алгоритмом сильно связанных компонент: [[Алгоритм_DCSC_поиска_компонент_сильной_связности|DCSC]], [[Алгоритм_Тарьяна_поиска_компонент_сильной_связности|Тарьяна]] и др. Информационный графы данных алгоритмов могут быть найдены в соответствующих разделах.&lt;br /&gt;
&lt;br /&gt;
[2] - выделение памяти под вершины графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[3] - добавление вершин к графу промежуточного представления, каждая вершина соответствует одной сильно-связанной компоненте&lt;br /&gt;
&lt;br /&gt;
[4] - подсчет числа вершин в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[5] - выделение памяти под ребра графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[6] - добавление ребер к графу промежуточного представления, связывающих различные сильно связанные компоненты&lt;br /&gt;
&lt;br /&gt;
[7] - подсчет числа ребер в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[8] - проверка номеров сильно связанных компонент для каждой пары сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[BFS] - поиски в ширину от тех вершин, которые находятся в одних сильно связанных компонентах&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В данном разделе будет описан ресурс параллелизма, основываясь на информационном графе алгоритма, приведенном на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
Инициализация сильно связанных компонент [1] может производиться параллельно и требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; операций. &lt;br /&gt;
&lt;br /&gt;
Поиск сильно связанных компонент может иметь различный ресурс параллелизма в зависимости от выбора алгоритма. &lt;br /&gt;
&lt;br /&gt;
Добавление вершин и ребер к графу промежуточного представления [2]-[7] требует &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|u|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - число сильно связанных компонент исходного графа), которые также могут выполняться параллельно. При этом вершины и ребра добавляются к графу независимо друг от друга, поэтому добавление вершин может производиться параллельно с добавлением ребер.&lt;br /&gt;
&lt;br /&gt;
Проверка номеров сильно связанных компонент [8] также может выполняться параллельно и требует &amp;lt;math&amp;gt;|n|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число пар вершин, подаваемых на выход), которые также могут выполняться параллельно. &lt;br /&gt;
&lt;br /&gt;
В случае принадлежности вершин одной пары к одной сильно связанной компоненте производится поиск в ширину [BFS] для каждой соответствующей пары. Данные поиски в ширину также могут выполняться параллельно друг другу, при этом каждый поиск в ширину имеет определенный ресурс параллелизма, описанный в соответствующем [[Поиск_в_ширину_(BFS)|разделе]].&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно-связанных компонент), так как в свою очередь от нее зависит ширина и высота ЯПФ алгоритма поиска сильно-связанных компонент.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Объем входных и выходных данных зависит от вариации постановки проблемы. Далее будут рассмотрены два случая.&lt;br /&gt;
&lt;br /&gt;
1. Поиск полного транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой пары вершин графа её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Проверка принадлежности к транзитивному замыканию для &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; заранее заданных пар вершин &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; пар вершин  &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E| + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой входной пары вершин её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Для демонстрации эффективности представлены графики времени выполнения различных режимов вычислений в зависимости от размера входного графа. &lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определенная как TEPS, то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальной ситуации. &lt;br /&gt;
&lt;br /&gt;
Дело в том, что сложность выбранного алгоритма Пурдома зависит от количества ребер в графе нелинейно и, кроме того, зависит еще и от структуры графа (количества сильно связанных компонент). &lt;br /&gt;
&lt;br /&gt;
[[file:Замыкание scaling wide.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Пурдома: масштабируемость различных версий: время выполнения в зависимости от размера графа.]]&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения, можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). На представленных графиках по оси X граф с каждым делением увеличивается в два раза, и по такому графику можно оценивать, насколько увеличилось время выполнения.&lt;br /&gt;
Также время выполнения можно сравнивать для различных версий, например, последовательной CPU, параллельной CPU или же GPU.&lt;br /&gt;
На приведенной диаграмме (рисунок 1) для различных размеров графа (от 2^20 до 2^26) представлено время выполнения трех различных версий: последовательной, параллельной CPU и GPU. Шкала на данной диаграмме логарифмическая.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/transitive_closure.html transitive_closure]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Purdom&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24041</id>
		<title>Алгоритм Пурдома</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24041"/>
		<updated>2017-11-19T17:53:07Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Масштабируемость реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Пурдома&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Пурдома&#039;&#039;&#039;&amp;lt;ref name=Purdom&amp;gt;Purdom, Paul, Jr. “A Transitive Closure Algorithm.” Bit 10, no. 1 (March 1970): 76–94. doi:10.1007/BF01940892.&amp;lt;/ref&amp;gt; находит [[Поиск транзитивного замыкания орграфа|транзитивное замыкание ориентированного графа]] за время &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\mu \le |E|&amp;lt;/math&amp;gt; – число [[Связность в графах|компонент сильной связности]] этого графа. Алгоритм может быть модифицирован для проверки принадлежности к транзитивному замыканию множества заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на следующих свойствах:&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; принадлежат одной [[Связность в графах|компоненте сильной связности]] графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, то его транзитивное замыкание &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; содержит рёбра &amp;lt;math&amp;gt;(v, w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(w, v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; принадлежат одной компоненте сильной связности графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а вершины &amp;lt;math&amp;gt;z&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt; – другой, то рёбра &amp;lt;math&amp;gt;(x, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(x, t)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, t)&amp;lt;/math&amp;gt; принадлежат или не принадлежат транзитивному замыканию &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; одновременно.&lt;br /&gt;
&lt;br /&gt;
Таким образом, поиск транзитивного замыкания графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; сводится к поиску транзитивного замыкания ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, полученного из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; схлопыванием каждой компоненты сильной связности в одну вершину. Транзитивное замыкание ациклического графа вычисляется с использованием топологической сортировки вершин графа.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из трех важных вычислительных частей, для каждой из которых важно оценить относительное время выполнения относительно других частей.&lt;br /&gt;
# Поиск сильно связанных компонент в исходном графе&lt;br /&gt;
# Построение графа промежуточного представления&lt;br /&gt;
# Поиски в ширину в графе промежуточного представления для построения итогового транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
1. В случае решения задачи проверки принадлежности к транзитивному замыканию множества заданных пар вершин, соотношение объемов различных вычислительных частей алгоритма может быть очень различным и, очевидно, зависит от числа пар входных вершин. Кроме того, данное соотношение может также зависеть от типа входного графа, так как структура и количество сильно-связанных компонент сильно влияют как на время поиска сильно-связанных компонент, так и на время выполнения поисков в ширину. Далее представлена таблица, демонстрирующая процент времени выполнения данных частей для одной из реализаций алгоритма для различных типов графов с числом вершин &amp;lt;math&amp;gt;2^{23}&amp;lt;/math&amp;gt; и числом входных пар вершин для проверки, равным 10000.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_time_table.png|thumb|center|500px|Рисунок 1. Сравнение времени выполнения различных частей одной из реализаций алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
Важно заметить, что некоторые алгоритмы поиска сильно-связанных компонент (например, DCSC) также основываются на поисках в ширину. Таким образом, вычислительным ядром алгоритма можно считать поиск в ширину. В свою очередь вычислительным ядром алгоритма поиска в ширину является обход вершин, смежных с выбранной вершиной, с последующим добавлением еще не посещенных вершин в множество &amp;quot;передовых&amp;quot; вершин. &lt;br /&gt;
&lt;br /&gt;
2. В случае решения задачи поиска полного транзитивного замыкания наиболее вычислительно затратной частью являются поиски в ширину в графе промежуточного представления, так как их необходимо произвести от всех вершин графа промежуточного представления, число которых может быть значительным при большом числе сильно-связанных компонент в исходном графе.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычисления производятся в четыре этапа:&lt;br /&gt;
&lt;br /&gt;
# Найти [[Связность в графах|компоненты сильной связности]] исходного графа, заменить каждую компоненту на одну вершину и удалить образовавшиеся рёбра-петли.&lt;br /&gt;
# Выполнить топологическую сортировку полученного ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычислить транзитивное замыкание графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, двигаясь от вершин с бо́льшими номерами к меньшим.&lt;br /&gt;
# По найденному транзитивному замыканию &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; восстановить транзитивное замыкание исходного графа.&lt;br /&gt;
&lt;br /&gt;
Последний этап не является обязательным, если рассматривать транзитивное замыкание &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; как «упакованное» транзитивное замыкание &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1 этап&#039;&#039;&#039; (&#039;&#039;вычисление компонент сильной связности&#039;&#039;) может быть реализован [[Алгоритм Тарьяна поиска компонент сильной связности|алгоритмом Тарьяна]]&amp;lt;ref name=DFS&amp;gt;Tarjan, Robert. “Depth-First Search and Linear Graph Algorithms.” SIAM Journal on Computing 1, no. 2 (1972): 146–60.&amp;lt;/ref&amp;gt;, находящим компоненты сильной связности в ходе [[Поиск в глубину (DFS)|поиска в глубину]].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2 этап&#039;&#039;&#039; (&#039;&#039;топологическая сортировка&#039;&#039;) может быть реализован алгоритмом Кана&amp;lt;ref&amp;gt;Kahn, A B. “Topological Sorting of Large Networks.” Communications of the ACM 5, no. 11 (November 1962): 558–62. doi:10.1145/368996.369025.&amp;lt;/ref&amp;gt; либо последовательным применением [[Поиск в глубину (DFS)|поиска в глубину]]&amp;lt;ref name=DFS /&amp;gt; для нумерации вершин в предпорядке.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;) выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
     ациклический граф &#039;G&#039;&#039; с вершинами &#039;V&#039;&#039;, пронумерованными в топологическом порядке, и рёбрами &#039;&#039;E&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;in reverse order&#039;&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;T&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;) либо выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
         &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;x&#039;&#039; ∈ &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;x&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;)    &#039;&#039;/* присваивание по ссылке */&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
либо транзитивное замыкание может строиться по упакованным данным &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt; одной из следующих функций:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     отображение &#039;&#039;R&#039;&#039;(&#039;&#039;v&#039;&#039;) вершин графа &#039;&#039;G&#039;&#039; на вершины графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; transitive_closure(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
     &#039;&#039;T&#039;&#039; := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039; := &#039;&#039;T&#039;&#039; ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;T&#039;&#039;&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; is_in_transitive_closure(&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;):&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;R&#039;&#039;(&#039;&#039;w&#039;&#039;) ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Первый и второй этап выполняются с помощью поиска в глубину со сложностью &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
На третьем этапе основной операцией является объединение списков вершин. В случае хранения списков в отсортированном виде объединение выполняется за линейное время. Количество списков не превосходит &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;, а в каждом списке не более &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; вершин, где &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; – количество компонент сильной связности. Общая сложность тогда составляет &amp;lt;math&amp;gt;O(\mu^2)&amp;lt;/math&amp;gt;. Альтернативным форматом хранения списка вершин является битовая маска с такой же общей сложностью.&lt;br /&gt;
&lt;br /&gt;
Сложность четвёртого этапа составляет &amp;lt;math&amp;gt;O(\mu |V|)&amp;lt;/math&amp;gt;, так как для каждой из &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; компонент сильной связности за линейное время вычисляется список вершин из не более чем &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин. Поскольку компоненты сильной связности не пересекаются, на данном этапе уже не требуется хранить списки в отсортированном виде.&lt;br /&gt;
&lt;br /&gt;
Таким образом, общая сложность составляет &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;, если не требуется явное построение транзитивного замыкания, или &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt; в противном случае.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф алгоритма Пурдома, демонстрирующий связь между его основным частями. Алгоритм представлен для вариации постановки задачи, когда производится проверка принадлежности к транзитивному замыканию множества заранее заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_ig.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация массива номеров сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[Поиск сильно связанных компонент (Tarjan, DCSC, etc)] - независимый блок алгоритма, может быть реализован любым алгоритмом сильно связанных компонент: [[Алгоритм_DCSC_поиска_компонент_сильной_связности|DCSC]], [[Алгоритм_Тарьяна_поиска_компонент_сильной_связности|Тарьяна]] и др. Информационный графы данных алгоритмов могут быть найдены в соответствующих разделах.&lt;br /&gt;
&lt;br /&gt;
[2] - выделение памяти под вершины графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[3] - добавление вершин к графу промежуточного представления, каждая вершина соответствует одной сильно-связанной компоненте&lt;br /&gt;
&lt;br /&gt;
[4] - подсчет числа вершин в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[5] - выделение памяти под ребра графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[6] - добавление ребер к графу промежуточного представления, связывающих различные сильно связанные компоненты&lt;br /&gt;
&lt;br /&gt;
[7] - подсчет числа ребер в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[8] - проверка номеров сильно связанных компонент для каждой пары сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[BFS] - поиски в ширину от тех вершин, которые находятся в одних сильно связанных компонентах&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В данном разделе будет описан ресурс параллелизма, основываясь на информационном графе алгоритма, приведенном на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
Инициализация сильно связанных компонент [1] может производиться параллельно и требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; операций. &lt;br /&gt;
&lt;br /&gt;
Поиск сильно связанных компонент может иметь различный ресурс параллелизма в зависимости от выбора алгоритма. &lt;br /&gt;
&lt;br /&gt;
Добавление вершин и ребер к графу промежуточного представления [2]-[7] требует &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|u|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - число сильно связанных компонент исходного графа), которые также могут выполняться параллельно. При этом вершины и ребра добавляются к графу независимо друг от друга, поэтому добавление вершин может производиться параллельно с добавлением ребер.&lt;br /&gt;
&lt;br /&gt;
Проверка номеров сильно связанных компонент [8] также может выполняться параллельно и требует &amp;lt;math&amp;gt;|n|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число пар вершин, подаваемых на выход), которые также могут выполняться параллельно. &lt;br /&gt;
&lt;br /&gt;
В случае принадлежности вершин одной пары к одной сильно связанной компоненте производится поиск в ширину [BFS] для каждой соответствующей пары. Данные поиски в ширину также могут выполняться параллельно друг другу, при этом каждый поиск в ширину имеет определенный ресурс параллелизма, описанный в соответствующем [[Поиск_в_ширину_(BFS)|разделе]].&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно-связанных компонент), так как в свою очередь от нее зависит ширина и высота ЯПФ алгоритма поиска сильно-связанных компонент.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Объем входных и выходных данных зависит от вариации постановки проблемы. Далее будут рассмотрены два случая.&lt;br /&gt;
&lt;br /&gt;
1. Поиск полного транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой пары вершин графа её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Проверка принадлежности к транзитивному замыканию для &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; заранее заданных пар вершин &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; пар вершин  &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E| + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой входной пары вершин её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Для демонстрации эффективности представлены графики времени выполнения различных режимов вычислений в зависимости от размера входного графа. &lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определенная как TEPS, то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальной ситуации. &lt;br /&gt;
&lt;br /&gt;
Дело в том, что сложность выбранного алгоритма Пурдома зависит от количества ребер в графе нелинейно и, кроме того, зависит еще и от структуры графа (количества сильно связанных компонент). &lt;br /&gt;
&lt;br /&gt;
[[file:Замыкание scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Пурдома: масштабируемость различных версий: производительность в зависимости от размера графа.]]&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения, можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). На представленных графиках по оси X граф с каждым делением увеличивается в два раза, и по такому графику можно оценивать, насколько увеличилось время выполнения.&lt;br /&gt;
Также время выполнения можно сравнивать для различных версий, например, последовательной CPU, параллельной CPU или же GPU.&lt;br /&gt;
На приведенной диаграмме (рисунок 1) для различных размеров графа (от 2^20 до 2^26) представлено время выполнения трех различных версий: последовательной, параллельной CPU и GPU. Шкала на данной диаграмме логарифмическая.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/transitive_closure.html transitive_closure]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Purdom&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24034</id>
		<title>Алгоритм Пурдома</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24034"/>
		<updated>2017-11-19T11:56:27Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Пурдома&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Пурдома&#039;&#039;&#039;&amp;lt;ref name=Purdom&amp;gt;Purdom, Paul, Jr. “A Transitive Closure Algorithm.” Bit 10, no. 1 (March 1970): 76–94. doi:10.1007/BF01940892.&amp;lt;/ref&amp;gt; находит [[Поиск транзитивного замыкания орграфа|транзитивное замыкание ориентированного графа]] за время &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\mu \le |E|&amp;lt;/math&amp;gt; – число [[Связность в графах|компонент сильной связности]] этого графа. Алгоритм может быть модифицирован для проверки принадлежности к транзитивному замыканию множества заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на следующих свойствах:&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; принадлежат одной [[Связность в графах|компоненте сильной связности]] графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, то его транзитивное замыкание &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; содержит рёбра &amp;lt;math&amp;gt;(v, w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(w, v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; принадлежат одной компоненте сильной связности графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а вершины &amp;lt;math&amp;gt;z&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt; – другой, то рёбра &amp;lt;math&amp;gt;(x, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(x, t)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, t)&amp;lt;/math&amp;gt; принадлежат или не принадлежат транзитивному замыканию &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; одновременно.&lt;br /&gt;
&lt;br /&gt;
Таким образом, поиск транзитивного замыкания графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; сводится к поиску транзитивного замыкания ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, полученного из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; схлопыванием каждой компоненты сильной связности в одну вершину. Транзитивное замыкание ациклического графа вычисляется с использованием топологической сортировки вершин графа.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из трех важных вычислительных частей, для каждой из которых важно оценить относительное время выполнения относительно других частей.&lt;br /&gt;
# Поиск сильно связанных компонент в исходном графе&lt;br /&gt;
# Построение графа промежуточного представления&lt;br /&gt;
# Поиски в ширину в графе промежуточного представления для построения итогового транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
1. В случае решения задачи проверки принадлежности к транзитивному замыканию множества заданных пар вершин, соотношение объемов различных вычислительных частей алгоритма может быть очень различным и, очевидно, зависит от числа пар входных вершин. Кроме того, данное соотношение может также зависеть от типа входного графа, так как структура и количество сильно-связанных компонент сильно влияют как на время поиска сильно-связанных компонент, так и на время выполнения поисков в ширину. Далее представлена таблица, демонстрирующая процент времени выполнения данных частей для одной из реализаций алгоритма для различных типов графов с числом вершин &amp;lt;math&amp;gt;2^{23}&amp;lt;/math&amp;gt; и числом входных пар вершин для проверки, равным 10000.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_time_table.png|thumb|center|500px|Рисунок 1. Сравнение времени выполнения различных частей одной из реализаций алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
Важно заметить, что некоторые алгоритмы поиска сильно-связанных компонент (например, DCSC) также основываются на поисках в ширину. Таким образом, вычислительным ядром алгоритма можно считать поиск в ширину. В свою очередь вычислительным ядром алгоритма поиска в ширину является обход вершин, смежных с выбранной вершиной, с последующим добавлением еще не посещенных вершин в множество &amp;quot;передовых&amp;quot; вершин. &lt;br /&gt;
&lt;br /&gt;
2. В случае решения задачи поиска полного транзитивного замыкания наиболее вычислительно затратной частью являются поиски в ширину в графе промежуточного представления, так как его необходимо произвести от всех вершин данного графа, число которых может быть значительным при большом числе сильно-связанных компонент в исходном графе.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычисления производятся в четыре этапа:&lt;br /&gt;
&lt;br /&gt;
# Найти [[Связность в графах|компоненты сильной связности]] исходного графа, заменить каждую компоненту на одну вершину и удалить образовавшиеся рёбра-петли.&lt;br /&gt;
# Выполнить топологическую сортировку полученного ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычислить транзитивное замыкание графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, двигаясь от вершин с бо́льшими номерами к меньшим.&lt;br /&gt;
# По найденному транзитивному замыканию &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; восстановить транзитивное замыкание исходного графа.&lt;br /&gt;
&lt;br /&gt;
Последний этап не является обязательным, если рассматривать транзитивное замыкание &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; как «упакованное» транзитивное замыкание &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1 этап&#039;&#039;&#039; (&#039;&#039;вычисление компонент сильной связности&#039;&#039;) может быть реализован [[Алгоритм Тарьяна поиска компонент сильной связности|алгоритмом Тарьяна]]&amp;lt;ref name=DFS&amp;gt;Tarjan, Robert. “Depth-First Search and Linear Graph Algorithms.” SIAM Journal on Computing 1, no. 2 (1972): 146–60.&amp;lt;/ref&amp;gt;, находящим компоненты сильной связности в ходе [[Поиск в глубину (DFS)|поиска в глубину]].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2 этап&#039;&#039;&#039; (&#039;&#039;топологическая сортировка&#039;&#039;) может быть реализован алгоритмом Кана&amp;lt;ref&amp;gt;Kahn, A B. “Topological Sorting of Large Networks.” Communications of the ACM 5, no. 11 (November 1962): 558–62. doi:10.1145/368996.369025.&amp;lt;/ref&amp;gt; либо последовательным применением [[Поиск в глубину (DFS)|поиска в глубину]]&amp;lt;ref name=DFS /&amp;gt; для нумерации вершин в предпорядке.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;) выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
     ациклический граф &#039;G&#039;&#039; с вершинами &#039;V&#039;&#039;, пронумерованными в топологическом порядке, и рёбрами &#039;&#039;E&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;in reverse order&#039;&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;T&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;) либо выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
         &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;x&#039;&#039; ∈ &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;x&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;)    &#039;&#039;/* присваивание по ссылке */&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
либо транзитивное замыкание может строиться по упакованным данным &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt; одной из следующих функций:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     отображение &#039;&#039;R&#039;&#039;(&#039;&#039;v&#039;&#039;) вершин графа &#039;&#039;G&#039;&#039; на вершины графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; transitive_closure(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
     &#039;&#039;T&#039;&#039; := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039; := &#039;&#039;T&#039;&#039; ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;T&#039;&#039;&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; is_in_transitive_closure(&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;):&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;R&#039;&#039;(&#039;&#039;w&#039;&#039;) ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Первый и второй этап выполняются с помощью поиска в глубину со сложностью &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
На третьем этапе основной операцией является объединение списков вершин. В случае хранения списков в отсортированном виде объединение выполняется за линейное время. Количество списков не превосходит &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;, а в каждом списке не более &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; вершин, где &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; – количество компонент сильной связности. Общая сложность тогда составляет &amp;lt;math&amp;gt;O(\mu^2)&amp;lt;/math&amp;gt;. Альтернативным форматом хранения списка вершин является битовая маска с такой же общей сложностью.&lt;br /&gt;
&lt;br /&gt;
Сложность четвёртого этапа составляет &amp;lt;math&amp;gt;O(\mu |V|)&amp;lt;/math&amp;gt;, так как для каждой из &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; компонент сильной связности за линейное время вычисляется список вершин из не более чем &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин. Поскольку компоненты сильной связности не пересекаются, на данном этапе уже не требуется хранить списки в отсортированном виде.&lt;br /&gt;
&lt;br /&gt;
Таким образом, общая сложность составляет &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;, если не требуется явное построение транзитивного замыкания, или &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt; в противном случае.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф алгоритма Пурдома, демонстрирующий связь между его основным частями. Алгоритм представлен для вариации постановки задачи, когда производится проверка принадлежности к транзитивному замыканию множества заранее заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_ig.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация массива номеров сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[Поиск сильно связанных компонент (Tarjan, DCSC, etc)] - независимый блок алгоритма, может быть реализован любым алгоритмом сильно связанных компонент: [[Алгоритм_DCSC_поиска_компонент_сильной_связности|DCSC]], [[Алгоритм_Тарьяна_поиска_компонент_сильной_связности|Тарьяна]] и др. Информационный графы данных алгоритмов могут быть найдены в соответствующих разделах.&lt;br /&gt;
&lt;br /&gt;
[2] - выделение памяти под вершины графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[3] - добавление вершин к графу промежуточного представления, каждая вершина соответствует одной сильно-связанной компоненте&lt;br /&gt;
&lt;br /&gt;
[4] - подсчет числа вершин в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[5] - выделение памяти под ребра графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[6] - добавление ребер к графу промежуточного представления, связывающих различные сильно связанные компоненты&lt;br /&gt;
&lt;br /&gt;
[7] - подсчет числа ребер в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[8] - проверка номеров сильно связанных компонент для каждой пары сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[BFS] - поиски в ширину от тех вершин, которые находятся в одних сильно связанных компонентах&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В данном разделе будет описан ресурс параллелизма, основываясь на информационном графе алгоритма, приведенном на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
Инициализация сильно связанных компонент [1] может производиться параллельно и требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; операций. &lt;br /&gt;
&lt;br /&gt;
Поиск сильно связанных компонент может иметь различный ресурс параллелизма в зависимости от выбора алгоритма. &lt;br /&gt;
&lt;br /&gt;
Добавление вершин и ребер к графу промежуточного представления [2]-[7] требует &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|u|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - число сильно связанных компонент исходного графа), которые также могут выполняться параллельно. При этом вершины и ребра добавляются к графу независимо друг от друга, поэтому добавление вершин может производиться параллельно с добавлением ребер.&lt;br /&gt;
&lt;br /&gt;
Проверка номеров сильно связанных компонент [8] также может выполняться параллельно и требует &amp;lt;math&amp;gt;|n|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число пар вершин, подаваемых на выход), которые также могут выполняться параллельно. &lt;br /&gt;
&lt;br /&gt;
В случае принадлежности вершин одной пары к одной сильно связанной компоненте производится поиск в ширину [BFS] для каждой соответствующей пары. Данные поиски в ширину также могут выполняться параллельно друг другу, при этом каждый поиск в ширину имеет определенный ресурс параллелизма, описанный в соответствующем [[Поиск_в_ширину_(BFS)|разделе]].&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно-связанных компонент), так как в свою очередь от нее зависит ширина и высота ЯПФ алгоритма поиска сильно-связанных компонент.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Объем входных и выходных данных зависит от вариации постановки проблемы. Далее будут рассмотрены два случая.&lt;br /&gt;
&lt;br /&gt;
1. Поиск полного транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой пары вершин графа её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Проверка принадлежности к транзитивному замыканию для &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; заранее заданных пар вершин &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; пар вершин  &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E| + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой входной пары вершин её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая поиск транзитного замыкания, состоит CPU части, отвечающей за общую координацию вычислений, использующей вспомогательные классы.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Для демонстрации эффективности представлены графики времени выполнения различных режимов вычислений в зависимости от размера входного графа. &lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальной ситуации. &lt;br /&gt;
&lt;br /&gt;
Дело в том, что сложность выбранного алгоритма Пурдома зависит от количества ребер в графе не линейно, и, кроме того, зависит еще и от структуры графа количества сильно связанных компонент). &lt;br /&gt;
&lt;br /&gt;
[[file:Замыкание scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Пурдома масштабируемость различных версий: производительность в зависимости от размера графа.]]&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). На представленных графиках по оси X граф с каждым делением увеличивается в два раза, и по представленному графику можно оценивать, на сколько увеличилось время выполнения.&lt;br /&gt;
Так же время выполнения можно сравнивать для различных версий, например, последовательной CPU, параллельной CPU или же GPU.&lt;br /&gt;
На приведенной диаграмме (рисунок 1) для различных размеров графа (от 2^20 до 2^26) представлено время выполнения трех различных версий: последовательной, параллельной CPU и GPU. Шкала на данной диаграмме логарифмическая.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/transitive_closure.html transitive_closure]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Purdom&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24030</id>
		<title>Алгоритм Пурдома</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24030"/>
		<updated>2017-11-19T09:19:05Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Последовательная сложность алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Пурдома&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Пурдома&#039;&#039;&#039;&amp;lt;ref name=Purdom&amp;gt;Purdom, Paul, Jr. “A Transitive Closure Algorithm.” Bit 10, no. 1 (March 1970): 76–94. doi:10.1007/BF01940892.&amp;lt;/ref&amp;gt; находит [[Поиск транзитивного замыкания орграфа|транзитивное замыкание ориентированного графа]] за время &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\mu \le |E|&amp;lt;/math&amp;gt; – число [[Связность в графах|компонент сильной связности]] этого графа. Алгоритм может быть модифицирован для проверки принадлежности к транзитивному замыканию множества заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на следующих свойствах:&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; принадлежат одной [[Связность в графах|компоненте сильной связности]] графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, то его транзитивное замыкание &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; содержит рёбра &amp;lt;math&amp;gt;(v, w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(w, v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; принадлежат одной компоненте сильной связности графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а вершины &amp;lt;math&amp;gt;z&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt; – другой, то рёбра &amp;lt;math&amp;gt;(x, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(x, t)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, t)&amp;lt;/math&amp;gt; принадлежат или не принадлежат транзитивному замыканию &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; одновременно.&lt;br /&gt;
&lt;br /&gt;
Таким образом, поиск транзитивного замыкания графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; сводится к поиску транзитивного замыкания ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, полученного из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; схлопыванием каждой компоненты сильной связности в одну вершину. Транзитивное замыкание ациклического графа вычисляется с использованием топологической сортировки вершин графа.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из трех важных вычислительных частей, для каждой из которых важно оценить относительное время выполнения относительно других частей.&lt;br /&gt;
# Поиск сильно связанных компонент в исходном графе&lt;br /&gt;
# Построение графа промежуточного представления&lt;br /&gt;
# Поиски в ширину в графе промежуточного представления для построения итогового транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
1. В случае решения задачи проверки принадлежности к транзитивному замыканию множества заданных пар вершин, соотношение объемов различных вычислительных частей алгоритма может быть очень различным и, очевидно, зависит от числа пар входных вершин. Кроме того, данное соотношение может также зависеть от типа входного графа, так как структура и количество сильно-связанных компонент сильно влияют как на время поиска сильно-связанных компонент, так и на время выполнения поисков в ширину. Далее представлена таблица, демонстрирующая процент времени выполнения данных частей для одной из реализаций алгоритма для различных типов графов с числом вершин &amp;lt;math&amp;gt;2^{23}&amp;lt;/math&amp;gt; и числом входных пар вершин для проверки, равным 10000.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_time_table.png|thumb|center|500px|Рисунок 1. Сравнение времени выполнения различных частей одной из реализаций алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
Важно заметить, что некоторые алгоритмы поиска сильно-связанных компонент (например, DCSC) также основываются на поисках в ширину. Таким образом, вычислительным ядром алгоритма можно считать поиск в ширину. В свою очередь вычислительным ядром алгоритма поиска в ширину является обход вершин, смежных с выбранной вершиной, с последующим добавлением еще не посещенных вершин в множество &amp;quot;передовых&amp;quot; вершин. &lt;br /&gt;
&lt;br /&gt;
2. В случае решения задачи поиска полного транзитивного замыкания наиболее вычислительно затратной частью являются поиски в ширину в графе промежуточного представления, так как его необходимо произвести от всех вершин данного графа, число которых может быть значительным при большом числе сильно-связанных компонент в исходном графе.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычисления производятся в четыре этапа:&lt;br /&gt;
&lt;br /&gt;
# Найти [[Связность в графах|компоненты сильной связности]] исходного графа, заменить каждую компоненту на одну вершину и удалить образовавшиеся рёбра-петли.&lt;br /&gt;
# Выполнить топологическую сортировку полученного ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычислить транзитивное замыкание графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, двигаясь от вершин с бо́льшими номерами к меньшим.&lt;br /&gt;
# По найденному транзитивному замыканию &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; восстановить транзитивное замыкание исходного графа.&lt;br /&gt;
&lt;br /&gt;
Последний этап не является обязательным, если рассматривать транзитивное замыкание &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; как «упакованное» транзитивное замыкание &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1 этап&#039;&#039;&#039; (&#039;&#039;вычисление компонент сильной связности&#039;&#039;) может быть реализован [[Алгоритм Тарьяна поиска компонент сильной связности|алгоритмом Тарьяна]]&amp;lt;ref name=DFS&amp;gt;Tarjan, Robert. “Depth-First Search and Linear Graph Algorithms.” SIAM Journal on Computing 1, no. 2 (1972): 146–60.&amp;lt;/ref&amp;gt;, находящим компоненты сильной связности в ходе [[Поиск в глубину (DFS)|поиска в глубину]].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2 этап&#039;&#039;&#039; (&#039;&#039;топологическая сортировка&#039;&#039;) может быть реализован алгоритмом Кана&amp;lt;ref&amp;gt;Kahn, A B. “Topological Sorting of Large Networks.” Communications of the ACM 5, no. 11 (November 1962): 558–62. doi:10.1145/368996.369025.&amp;lt;/ref&amp;gt; либо последовательным применением [[Поиск в глубину (DFS)|поиска в глубину]]&amp;lt;ref name=DFS /&amp;gt; для нумерации вершин в предпорядке.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;) выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
     ациклический граф &#039;G&#039;&#039; с вершинами &#039;V&#039;&#039;, пронумерованными в топологическом порядке, и рёбрами &#039;&#039;E&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;in reverse order&#039;&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;T&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;) либо выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
         &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;x&#039;&#039; ∈ &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;x&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;)    &#039;&#039;/* присваивание по ссылке */&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
либо транзитивное замыкание может строиться по упакованным данным &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt; одной из следующих функций:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     отображение &#039;&#039;R&#039;&#039;(&#039;&#039;v&#039;&#039;) вершин графа &#039;&#039;G&#039;&#039; на вершины графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; transitive_closure(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
     &#039;&#039;T&#039;&#039; := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039; := &#039;&#039;T&#039;&#039; ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;T&#039;&#039;&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; is_in_transitive_closure(&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;):&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;R&#039;&#039;(&#039;&#039;w&#039;&#039;) ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Первый и второй этап выполняются с помощью поиска в глубину со сложностью &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
На третьем этапе основной операцией является объединение списков вершин. В случае хранения списков в отсортированном виде объединение выполняется за линейное время. Количество списков не превосходит &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;, а в каждом списке не более &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; вершин, где &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; – количество компонент сильной связности. Общая сложность тогда составляет &amp;lt;math&amp;gt;O(\mu^2)&amp;lt;/math&amp;gt;. Альтернативным форматом хранения списка вершин является битовая маска с такой же общей сложностью.&lt;br /&gt;
&lt;br /&gt;
Сложность четвёртого этапа составляет &amp;lt;math&amp;gt;O(\mu |V|)&amp;lt;/math&amp;gt;, так как для каждой из &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; компонент сильной связности за линейное время вычисляется список вершин из не более чем &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин. Поскольку компоненты сильной связности не пересекаются, на данном этапе уже не требуется хранить списки в отсортированном виде.&lt;br /&gt;
&lt;br /&gt;
Таким образом, общая сложность составляет &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;, если не требуется явное построение транзитивного замыкания, или &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt; в противном случае.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф алгоритма Пурдома, демонстрирующий связь между его основным частями. Алгоритм представлен для вариации постановки задачи, когда производится проверка принадлежности к транзитивному замыканию множества заранее заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_ig.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация массива номеров сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[Поиск сильно связанных компонент (Tarjan, DCSC, etc)] - независимый блок алгоритма, может быть реализован любым алгоритмом сильно связанных компонент: [[Алгоритм_DCSC_поиска_компонент_сильной_связности|DCSC]], [[Алгоритм_Тарьяна_поиска_компонент_сильной_связности|Тарьяна]] и др. Информационный графы данных алгоритмов могут быть найдены в соответствующих разделах.&lt;br /&gt;
&lt;br /&gt;
[2] - выделение памяти под вершины графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[3] - добавление вершин к графу промежуточного представления, каждая вершина соответствует одной сильно-связанной компоненте&lt;br /&gt;
&lt;br /&gt;
[4] - подсчет числа вершин в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[5] - выделение памяти под ребра графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[6] - добавление ребер к графу промежуточного представления, связывающих различные сильно связанные компоненты&lt;br /&gt;
&lt;br /&gt;
[7] - подсчет числа ребер в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[8] - проверка номеров сильно связанных компонент для каждой пары сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[BFS] - поиски в ширину от тех вершин, которые находятся в одних сильно связанных компонентах&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В данном разделе будет описан ресурс параллелизма, основываясь на информационном графе алгоритма, приведенном на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
Инициализация сильно связанных компонент [1] может производиться параллельно и требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; операций. &lt;br /&gt;
&lt;br /&gt;
Поиск сильно связанных компонент может иметь различный ресурс параллелизма, в зависимости от выбора алгоритма. &lt;br /&gt;
&lt;br /&gt;
Добавление вершин и ребер к графу промежуточного представления [2]-[7] требует &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|u|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - число сильно связанных компонент исходного графа), которые так же могут выполняться параллельно. При этом, вершины и ребра добавляются к графу независимо друг от друга, поэтому добавление вершин может производиться параллельно с добавлением ребер.&lt;br /&gt;
&lt;br /&gt;
Проверка номеров сильно связанных компонент [8] так же может выполняться параллельно и требует &amp;lt;math&amp;gt;|n|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число пар вершин, подаваемых на выход), которые так же могут выполняться параллельно. &lt;br /&gt;
&lt;br /&gt;
В случае принаделжности вершин одной пары к одной сильно связанный компоненте, производится поиск в ширину [BFS], для каждой соответствующей пары. Данные поиски в ширину так же могут выполняться параллельно друг другу, при том каждый поиск в ширину имеет определенный ресурс параллелизма, описанный в соответствующем [[Поиск_в_ширину_(BFS)|разделе]].&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно-связанных компонент), так как в свою очередь от нее зависит ширина и высота ЯПФ алгоритма поиска сильно-связанных компонент.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Объем входных и выходных данных зависит от вариации постановки проблемы. Далее будут рассмотрены два случая.&lt;br /&gt;
&lt;br /&gt;
1. Поиск полного транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой пары вершин графа её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Проверка принадлежности к транзитивному замыканию для &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; заранее заданных пар вершин &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; пар вершин  &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E| + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой входной пары вершин её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая поиск транзитного замыкания, состоит CPU части, отвечающей за общую координацию вычислений, использующей вспомогательные классы.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Для демонстрации эффективности представлены графики времени выполнения различных режимов вычислений в зависимости от размера входного графа. &lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальной ситуации. &lt;br /&gt;
&lt;br /&gt;
Дело в том, что сложность выбранного алгоритма Пурдома зависит от количества ребер в графе не линейно, и, кроме того, зависит еще и от структуры графа количества сильно связанных компонент). &lt;br /&gt;
&lt;br /&gt;
[[file:Замыкание scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Пурдома масштабируемость различных версий: производительность в зависимости от размера графа.]]&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). На представленных графиках по оси X граф с каждым делением увеличивается в два раза, и по представленному графику можно оценивать, на сколько увеличилось время выполнения.&lt;br /&gt;
Так же время выполнения можно сравнивать для различных версий, например, последовательной CPU, параллельной CPU или же GPU.&lt;br /&gt;
На приведенной диаграмме (рисунок 1) для различных размеров графа (от 2^20 до 2^26) представлено время выполнения трех различных версий: последовательной, параллельной CPU и GPU. Шкала на данной диаграмме логарифмическая.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/transitive_closure.html transitive_closure]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Purdom&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24004</id>
		<title>Алгоритм Пурдома</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24004"/>
		<updated>2017-11-18T00:09:51Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Вычислительное ядро алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Пурдома&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Пурдома&#039;&#039;&#039;&amp;lt;ref name=Purdom&amp;gt;Purdom, Paul, Jr. “A Transitive Closure Algorithm.” Bit 10, no. 1 (March 1970): 76–94. doi:10.1007/BF01940892.&amp;lt;/ref&amp;gt; находит [[Поиск транзитивного замыкания орграфа|транзитивное замыкание ориентированного графа]] за время &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\mu \le |E|&amp;lt;/math&amp;gt; – число [[Связность в графах|компонент сильной связности]] этого графа. Алгоритм может быть модифицирован для проверки принадлежности к транзитивному замыканию множества заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на следующих свойствах:&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; принадлежат одной [[Связность в графах|компоненте сильной связности]] графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, то его транзитивное замыкание &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; содержит рёбра &amp;lt;math&amp;gt;(v, w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(w, v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; принадлежат одной компоненте сильной связности графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а вершины &amp;lt;math&amp;gt;z&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt; – другой, то рёбра &amp;lt;math&amp;gt;(x, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(x, t)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, t)&amp;lt;/math&amp;gt; принадлежат или не принадлежат транзитивному замыканию &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; одновременно.&lt;br /&gt;
&lt;br /&gt;
Таким образом, поиск транзитивного замыкания графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; сводится к поиску транзитивного замыкания ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, полученного из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; схлопыванием каждой компоненты сильной связности в одну вершину. Транзитивное замыкание ациклического графа вычисляется с использованием топологической сортировки вершин графа.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из трех важных вычислительных частей, для каждой из которых важно оценить относительное время выполнения относительно других частей.&lt;br /&gt;
# Поиск сильно связанных компонент в исходном графе&lt;br /&gt;
# Построение графа промежуточного представления&lt;br /&gt;
# Поиски в ширину в графе промежуточного представления для построения итогового транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
1. В случае решения задачи проверки принадлежности к транзитивному замыканию множества заданных пар вершин, соотношение объемов различных вычислительных частей алгоритма может быть очень различным и, очевидно, зависит от числа пар входных вершин. Кроме того, данное соотношение может также зависеть от типа входного графа, так как структура и количество сильно-связанных компонент сильно влияют как на время поиска сильно-связанных компонент, так и на время выполнения поисков в ширину. Далее представлена таблица, демонстрирующая процент времени выполнения данных частей для одной из реализаций алгоритма для различных типов графов с числом вершин &amp;lt;math&amp;gt;2^{23}&amp;lt;/math&amp;gt; и числом входных пар вершин для проверки, равным 10000.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_time_table.png|thumb|center|500px|Рисунок 1. Сравнение времени выполнения различных частей одной из реализаций алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
Важно заметить, что некоторые алгоритмы поиска сильно-связанных компонент (например, DCSC) также основываются на поисках в ширину. Таким образом, вычислительным ядром алгоритма можно считать поиск в ширину. В свою очередь вычислительным ядром алгоритма поиска в ширину является обход вершин, смежных с выбранной вершиной, с последующим добавлением еще не посещенных вершин в множество &amp;quot;передовых&amp;quot; вершин. &lt;br /&gt;
&lt;br /&gt;
2. В случае решения задачи поиска полного транзитивного замыкания наиболее вычислительно затратной частью являются поиски в ширину в графе промежуточного представления, так как его необходимо произвести от всех вершин данного графа, число которых может быть значительным при большом числе сильно-связанных компонент в исходном графе.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычисления производятся в четыре этапа:&lt;br /&gt;
&lt;br /&gt;
# Найти [[Связность в графах|компоненты сильной связности]] исходного графа, заменить каждую компоненту на одну вершину и удалить образовавшиеся рёбра-петли.&lt;br /&gt;
# Выполнить топологическую сортировку полученного ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычислить транзитивное замыкание графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, двигаясь от вершин с бо́льшими номерами к меньшим.&lt;br /&gt;
# По найденному транзитивному замыканию &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; восстановить транзитивное замыкание исходного графа.&lt;br /&gt;
&lt;br /&gt;
Последний этап не является обязательным, если рассматривать транзитивное замыкание &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; как «упакованное» транзитивное замыкание &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1 этап&#039;&#039;&#039; (&#039;&#039;вычисление компонент сильной связности&#039;&#039;) может быть реализован [[Алгоритм Тарьяна поиска компонент сильной связности|алгоритмом Тарьяна]]&amp;lt;ref name=DFS&amp;gt;Tarjan, Robert. “Depth-First Search and Linear Graph Algorithms.” SIAM Journal on Computing 1, no. 2 (1972): 146–60.&amp;lt;/ref&amp;gt;, находящим компоненты сильной связности в ходе [[Поиск в глубину (DFS)|поиска в глубину]].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2 этап&#039;&#039;&#039; (&#039;&#039;топологическая сортировка&#039;&#039;) может быть реализован алгоритмом Кана&amp;lt;ref&amp;gt;Kahn, A B. “Topological Sorting of Large Networks.” Communications of the ACM 5, no. 11 (November 1962): 558–62. doi:10.1145/368996.369025.&amp;lt;/ref&amp;gt; либо последовательным применением [[Поиск в глубину (DFS)|поиска в глубину]]&amp;lt;ref name=DFS /&amp;gt; для нумерации вершин в предпорядке.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;) выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
     ациклический граф &#039;G&#039;&#039; с вершинами &#039;V&#039;&#039;, пронумерованными в топологическом порядке, и рёбрами &#039;&#039;E&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;in reverse order&#039;&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;T&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;) либо выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
         &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;x&#039;&#039; ∈ &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;x&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;)    &#039;&#039;/* присваивание по ссылке */&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
либо транзитивное замыкание может строиться по упакованным данным &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt; одной из следующих функций:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     отображение &#039;&#039;R&#039;&#039;(&#039;&#039;v&#039;&#039;) вершин графа &#039;&#039;G&#039;&#039; на вершины графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; transitive_closure(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
     &#039;&#039;T&#039;&#039; := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039; := &#039;&#039;T&#039;&#039; ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;T&#039;&#039;&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; is_in_transitive_closure(&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;):&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;R&#039;&#039;(&#039;&#039;w&#039;&#039;) ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Первый и второй этап выполняются с помощью поиска в глубину со сложностью &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
На третьем этапе основой операцией является объединение списков вершин. В случае хранения списков в отсортированном виде объединение выполняется за линейное время. Количество списков не превосходит &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;, а в каждом списке не более &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; вершин, где &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; – количество компонент сильной связности. Общая сложность тогда составляет &amp;lt;math&amp;gt;O(\mu^2)&amp;lt;/math&amp;gt;. Альтернативным форматов хранения списка вершин является битовая маска с такой же общей сложностью.&lt;br /&gt;
&lt;br /&gt;
Сложность четвёртого этапа составляет &amp;lt;math&amp;gt;O(\mu |V|)&amp;lt;/math&amp;gt;, так как для каждой из &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; компонент сильной связности за линейное время вычисляется список вершин из не более чем &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин. Поскольку компоненты сильной связности не пересекаются, на данном этапе уже не требуется хранить списки в отсортированном виде.&lt;br /&gt;
&lt;br /&gt;
Таким образом, общая сложность составляет &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;, если не требуется явное построение транзитивного замыкания, или &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt; в противном случае.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф алгоритма Пурдома, демонстрирующий связь между его основным частями. Алгоритм представлен для вариации постановки задачи, когда производится проверка принадлежности к транзитивному замыканию множества заранее заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_ig.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация массива номеров сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[Поиск сильно связанных компонент (Tarjan, DCSC, etc)] - независимый блок алгоритма, может быть реализован любым алгоритмом сильно связанных компонент: [[Алгоритм_DCSC_поиска_компонент_сильной_связности|DCSC]], [[Алгоритм_Тарьяна_поиска_компонент_сильной_связности|Тарьяна]] и др. Информационный графы данных алгоритмов могут быть найдены в соответствующих разделах.&lt;br /&gt;
&lt;br /&gt;
[2] - выделение памяти под вершины графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[3] - добавление вершин к графу промежуточного представления, каждая вершина соответствует одной сильно-связанной компоненте&lt;br /&gt;
&lt;br /&gt;
[4] - подсчет числа вершин в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[5] - выделение памяти под ребра графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[6] - добавление ребер к графу промежуточного представления, связывающих различные сильно связанные компоненты&lt;br /&gt;
&lt;br /&gt;
[7] - подсчет числа ребер в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[8] - проверка номеров сильно связанных компонент для каждой пары сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[BFS] - поиски в ширину от тех вершин, которые находятся в одних сильно связанных компонентах&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В данном разделе будет описан ресурс параллелизма, основываясь на информационном графе алгоритма, приведенном на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
Инициализация сильно связанных компонент [1] может производиться параллельно и требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; операций. &lt;br /&gt;
&lt;br /&gt;
Поиск сильно связанных компонент может иметь различный ресурс параллелизма, в зависимости от выбора алгоритма. &lt;br /&gt;
&lt;br /&gt;
Добавление вершин и ребер к графу промежуточного представления [2]-[7] требует &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|u|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - число сильно связанных компонент исходного графа), которые так же могут выполняться параллельно. При этом, вершины и ребра добавляются к графу независимо друг от друга, поэтому добавление вершин может производиться параллельно с добавлением ребер.&lt;br /&gt;
&lt;br /&gt;
Проверка номеров сильно связанных компонент [8] так же может выполняться параллельно и требует &amp;lt;math&amp;gt;|n|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число пар вершин, подаваемых на выход), которые так же могут выполняться параллельно. &lt;br /&gt;
&lt;br /&gt;
В случае принаделжности вершин одной пары к одной сильно связанный компоненте, производится поиск в ширину [BFS], для каждой соответствующей пары. Данные поиски в ширину так же могут выполняться параллельно друг другу, при том каждый поиск в ширину имеет определенный ресурс параллелизма, описанный в соответствующем [[Поиск_в_ширину_(BFS)|разделе]].&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно-связанных компонент), так как в свою очередь от нее зависит ширина и высота ЯПФ алгоритма поиска сильно-связанных компонент.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Объем входных и выходных данных зависит от вариации постановки проблемы. Далее будут рассмотрены два случая.&lt;br /&gt;
&lt;br /&gt;
1. Поиск полного транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой пары вершин графа её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Проверка принадлежности к транзитивному замыканию для &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; заранее заданных пар вершин &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; пар вершин  &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E| + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой входной пары вершин её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая поиск транзитного замыкания, состоит CPU части, отвечающей за общую координацию вычислений, использующей вспомогательные классы.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Для демонстрации эффективности представлены графики времени выполнения различных режимов вычислений в зависимости от размера входного графа. &lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальной ситуации. &lt;br /&gt;
&lt;br /&gt;
Дело в том, что сложность выбранного алгоритма Пурдома зависит от количества ребер в графе не линейно, и, кроме того, зависит еще и от структуры графа количества сильно связанных компонент). &lt;br /&gt;
&lt;br /&gt;
[[file:Замыкание scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Пурдома масштабируемость различных версий: производительность в зависимости от размера графа.]]&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). На представленных графиках по оси X граф с каждым делением увеличивается в два раза, и по представленному графику можно оценивать, на сколько увеличилось время выполнения.&lt;br /&gt;
Так же время выполнения можно сравнивать для различных версий, например, последовательной CPU, параллельной CPU или же GPU.&lt;br /&gt;
На приведенной диаграмме (рисунок 1) для различных размеров графа (от 2^20 до 2^26) представлено время выполнения трех различных версий: последовательной, параллельной CPU и GPU. Шкала на данной диаграмме логарифмическая.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/transitive_closure.html transitive_closure]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Purdom&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24003</id>
		<title>Алгоритм Пурдома</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D1%83%D1%80%D0%B4%D0%BE%D0%BC%D0%B0&amp;diff=24003"/>
		<updated>2017-11-18T00:06:50Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Вычислительное ядро алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм Пурдома&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Пурдома&#039;&#039;&#039;&amp;lt;ref name=Purdom&amp;gt;Purdom, Paul, Jr. “A Transitive Closure Algorithm.” Bit 10, no. 1 (March 1970): 76–94. doi:10.1007/BF01940892.&amp;lt;/ref&amp;gt; находит [[Поиск транзитивного замыкания орграфа|транзитивное замыкание ориентированного графа]] за время &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\mu \le |E|&amp;lt;/math&amp;gt; – число [[Связность в графах|компонент сильной связности]] этого графа. Алгоритм может быть модифицирован для проверки принадлежности к транзитивному замыканию множества заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на следующих свойствах:&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt; принадлежат одной [[Связность в графах|компоненте сильной связности]] графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, то его транзитивное замыкание &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; содержит рёбра &amp;lt;math&amp;gt;(v, w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(w, v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Если вершины &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; принадлежат одной компоненте сильной связности графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;, а вершины &amp;lt;math&amp;gt;z&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;t&amp;lt;/math&amp;gt; – другой, то рёбра &amp;lt;math&amp;gt;(x, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(x, t)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, z)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;(y, t)&amp;lt;/math&amp;gt; принадлежат или не принадлежат транзитивному замыканию &amp;lt;math&amp;gt;G^+&amp;lt;/math&amp;gt; одновременно.&lt;br /&gt;
&lt;br /&gt;
Таким образом, поиск транзитивного замыкания графа &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; сводится к поиску транзитивного замыкания ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, полученного из &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; схлопыванием каждой компоненты сильной связности в одну вершину. Транзитивное замыкание ациклического графа вычисляется с использованием топологической сортировки вершин графа.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм состоит из трех важных вычислительных частей, для каждой из которых важно оценить относительное время выполнения относительно других частей.&lt;br /&gt;
# Поиск сильно связанных компонент в исходном графе&lt;br /&gt;
# Построение графа промежуточного представления&lt;br /&gt;
# Поиски в ширину в графе промежуточного представления для построения итогового транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
1. В случае решения задачи проверки принадлежности к транзитивному замыканию множества заданных пар вершин, соотношение объемов различных вычислительных частей алгоритма может быть очень различным и, очевидно, зависит от числа пар входных вершин. Кроме того, данное соотношение может также зависеть от типа входного графа, так как структура и количество сильно-связанных компонент сильно влияют как на время поиска сильно-связанных компонент, так и на время выполнения поисков в ширину. Далее представлена таблица, демонстрирующая процент времени выполнения данных частей для одной из реализаций алгоритма для различных типов графов с числом вершин &amp;lt;math&amp;gt;2^{23}&amp;lt;/math&amp;gt;, и числом входных пар вершин для проверки равным 10000.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_time_table.png|thumb|center|500px|Рисунок 1. Сравнение времени выполнения различных частей одной из реализаций алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
Важно заметить, что некоторые алгоритмы поиска сильно-связанных компонент (например, DCSC) также основываются на поисках в ширину. Таким образом, вычислительным ядром алгоритма можно считать поиск в ширину. В свою очередь вычислительным ядром алгоритма поиска в ширину является обход вершин, смежных с выбранной вершиной, с последующим добавлением еще не посещенных вершин в множество &amp;quot;передовых&amp;quot; вершин. &lt;br /&gt;
&lt;br /&gt;
2. В случае решения задачи поиска полного транзитивного замыкания наиболее вычислительно затратной частью являются поиски в ширину в графе промежуточного представления, так как его необходимо произвести от всех вершин данного графа, число которых может быть значительным при большом числе сильно-связанных компонент в исходном графе.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычисления производятся в четыре этапа:&lt;br /&gt;
&lt;br /&gt;
# Найти [[Связность в графах|компоненты сильной связности]] исходного графа, заменить каждую компоненту на одну вершину и удалить образовавшиеся рёбра-петли.&lt;br /&gt;
# Выполнить топологическую сортировку полученного ациклического графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Вычислить транзитивное замыкание графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;, двигаясь от вершин с бо́льшими номерами к меньшим.&lt;br /&gt;
# По найденному транзитивному замыканию &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; восстановить транзитивное замыкание исходного графа.&lt;br /&gt;
&lt;br /&gt;
Последний этап не является обязательным, если рассматривать транзитивное замыкание &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; как «упакованное» транзитивное замыкание &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1 этап&#039;&#039;&#039; (&#039;&#039;вычисление компонент сильной связности&#039;&#039;) может быть реализован [[Алгоритм Тарьяна поиска компонент сильной связности|алгоритмом Тарьяна]]&amp;lt;ref name=DFS&amp;gt;Tarjan, Robert. “Depth-First Search and Linear Graph Algorithms.” SIAM Journal on Computing 1, no. 2 (1972): 146–60.&amp;lt;/ref&amp;gt;, находящим компоненты сильной связности в ходе [[Поиск в глубину (DFS)|поиска в глубину]].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2 этап&#039;&#039;&#039; (&#039;&#039;топологическая сортировка&#039;&#039;) может быть реализован алгоритмом Кана&amp;lt;ref&amp;gt;Kahn, A B. “Topological Sorting of Large Networks.” Communications of the ACM 5, no. 11 (November 1962): 558–62. doi:10.1145/368996.369025.&amp;lt;/ref&amp;gt; либо последовательным применением [[Поиск в глубину (DFS)|поиска в глубину]]&amp;lt;ref name=DFS /&amp;gt; для нумерации вершин в предпорядке.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;) выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;:&lt;br /&gt;
     ациклический граф &#039;G&#039;&#039; с вершинами &#039;V&#039;&#039;, пронумерованными в топологическом порядке, и рёбрами &#039;&#039;E&#039;&#039;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;in reverse order&#039;&#039;&#039;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;) ∈ &#039;&#039;E&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;T&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4 этап&#039;&#039;&#039; (&#039;&#039;транзитивное замыкание&#039;&#039; &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt;) либо выполняется следующим алгоритмом:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
 &#039;&#039;&#039;Выходные данные&#039;&#039;&#039;:&lt;br /&gt;
     для каждой вершины &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; – набор вершин &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;),&lt;br /&gt;
         так что транзитивное замыкание &#039;&#039;G&#039;&#039; состоит из рёбер (&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;), &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039;, &#039;&#039;w&#039;&#039; ∈ &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;).&lt;br /&gt;
         &lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &#039;&#039;V&#039;&#039; &#039;&#039;&#039;do&#039;&#039;&#039; &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
 &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;v&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;:&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;) ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;x&#039;&#039; ∈ &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
         &#039;&#039;T&#039;&#039;(&#039;&#039;x&#039;&#039;) := &#039;&#039;T&#039;&#039;(&#039;&#039;v&#039;&#039;)    &#039;&#039;/* присваивание по ссылке */&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
либо транзитивное замыкание может строиться по упакованным данным &amp;lt;math&amp;gt;\tilde T(v)&amp;lt;/math&amp;gt; одной из следующих функций:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;Входные данные&#039;&#039;&#039;&lt;br /&gt;
     граф &#039;&#039;G&#039;&#039; с вершинами &#039;&#039;V&#039;&#039;;&lt;br /&gt;
     компоненты сильной связности &#039;&#039;SCC&#039;&#039;(&#039;&#039;v&#039;&#039;) графа &#039;&#039;G&#039;&#039;;&lt;br /&gt;
     отображение &#039;&#039;R&#039;&#039;(&#039;&#039;v&#039;&#039;) вершин графа &#039;&#039;G&#039;&#039; на вершины графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;;&lt;br /&gt;
     граф &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt; с вершинами &amp;lt;math&amp;gt;\tilde V&amp;lt;/math&amp;gt;;&lt;br /&gt;
     транзитивное замыкание &amp;lt;math&amp;gt;\tilde T&amp;lt;/math&amp;gt; графа &amp;lt;math&amp;gt;\tilde G&amp;lt;/math&amp;gt;.&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; transitive_closure(&#039;&#039;v&#039;&#039;):&lt;br /&gt;
     &#039;&#039;T&#039;&#039; := { &#039;&#039;v&#039;&#039; }&lt;br /&gt;
     &#039;&#039;&#039;for each&#039;&#039;&#039; &#039;&#039;w&#039;&#039; ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;:&lt;br /&gt;
         &#039;&#039;T&#039;&#039; := &#039;&#039;T&#039;&#039; ∪ &#039;&#039;SCC&#039;&#039;(&#039;&#039;w&#039;&#039;)&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;T&#039;&#039;&lt;br /&gt;
     &lt;br /&gt;
 &#039;&#039;&#039;function&#039;&#039;&#039; is_in_transitive_closure(&#039;&#039;v&#039;&#039;, &#039;&#039;w&#039;&#039;):&lt;br /&gt;
     &#039;&#039;&#039;return&#039;&#039;&#039; &#039;&#039;R&#039;&#039;(&#039;&#039;w&#039;&#039;) ∈ &amp;lt;math&amp;gt;\tilde T(R(v))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Первый и второй этап выполняются с помощью поиска в глубину со сложностью &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
На третьем этапе основой операцией является объединение списков вершин. В случае хранения списков в отсортированном виде объединение выполняется за линейное время. Количество списков не превосходит &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;, а в каждом списке не более &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; вершин, где &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; – количество компонент сильной связности. Общая сложность тогда составляет &amp;lt;math&amp;gt;O(\mu^2)&amp;lt;/math&amp;gt;. Альтернативным форматов хранения списка вершин является битовая маска с такой же общей сложностью.&lt;br /&gt;
&lt;br /&gt;
Сложность четвёртого этапа составляет &amp;lt;math&amp;gt;O(\mu |V|)&amp;lt;/math&amp;gt;, так как для каждой из &amp;lt;math&amp;gt;\mu&amp;lt;/math&amp;gt; компонент сильной связности за линейное время вычисляется список вершин из не более чем &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин. Поскольку компоненты сильной связности не пересекаются, на данном этапе уже не требуется хранить списки в отсортированном виде.&lt;br /&gt;
&lt;br /&gt;
Таким образом, общая сложность составляет &amp;lt;math&amp;gt;O(|E| + \mu^2)&amp;lt;/math&amp;gt;, если не требуется явное построение транзитивного замыкания, или &amp;lt;math&amp;gt;O(|E| + \mu |V|)&amp;lt;/math&amp;gt; в противном случае.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунке 2 представлен информационный граф алгоритма Пурдома, демонстрирующий связь между его основным частями. Алгоритм представлен для вариации постановки задачи, когда производится проверка принадлежности к транзитивному замыканию множества заранее заданных пар вершин.&lt;br /&gt;
&lt;br /&gt;
[[file:Purdom_ig.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма Пурдома.]]&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация массива номеров сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[Поиск сильно связанных компонент (Tarjan, DCSC, etc)] - независимый блок алгоритма, может быть реализован любым алгоритмом сильно связанных компонент: [[Алгоритм_DCSC_поиска_компонент_сильной_связности|DCSC]], [[Алгоритм_Тарьяна_поиска_компонент_сильной_связности|Тарьяна]] и др. Информационный графы данных алгоритмов могут быть найдены в соответствующих разделах.&lt;br /&gt;
&lt;br /&gt;
[2] - выделение памяти под вершины графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[3] - добавление вершин к графу промежуточного представления, каждая вершина соответствует одной сильно-связанной компоненте&lt;br /&gt;
&lt;br /&gt;
[4] - подсчет числа вершин в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[5] - выделение памяти под ребра графа промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[6] - добавление ребер к графу промежуточного представления, связывающих различные сильно связанные компоненты&lt;br /&gt;
&lt;br /&gt;
[7] - подсчет числа ребер в графе промежуточного представления&lt;br /&gt;
&lt;br /&gt;
[8] - проверка номеров сильно связанных компонент для каждой пары сильно связанных компонент&lt;br /&gt;
&lt;br /&gt;
[BFS] - поиски в ширину от тех вершин, которые находятся в одних сильно связанных компонентах&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В данном разделе будет описан ресурс параллелизма, основываясь на информационном графе алгоритма, приведенном на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
Инициализация сильно связанных компонент [1] может производиться параллельно и требует &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; операций. &lt;br /&gt;
&lt;br /&gt;
Поиск сильно связанных компонент может иметь различный ресурс параллелизма, в зависимости от выбора алгоритма. &lt;br /&gt;
&lt;br /&gt;
Добавление вершин и ребер к графу промежуточного представления [2]-[7] требует &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|u|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; - число сильно связанных компонент исходного графа), которые так же могут выполняться параллельно. При этом, вершины и ребра добавляются к графу независимо друг от друга, поэтому добавление вершин может производиться параллельно с добавлением ребер.&lt;br /&gt;
&lt;br /&gt;
Проверка номеров сильно связанных компонент [8] так же может выполняться параллельно и требует &amp;lt;math&amp;gt;|n|&amp;lt;/math&amp;gt; операций (&amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; - число пар вершин, подаваемых на выход), которые так же могут выполняться параллельно. &lt;br /&gt;
&lt;br /&gt;
В случае принаделжности вершин одной пары к одной сильно связанный компоненте, производится поиск в ширину [BFS], для каждой соответствующей пары. Данные поиски в ширину так же могут выполняться параллельно друг другу, при том каждый поиск в ширину имеет определенный ресурс параллелизма, описанный в соответствующем [[Поиск_в_ширину_(BFS)|разделе]].&lt;br /&gt;
&lt;br /&gt;
Высота и ширина ярусно-параллельной формы зависит от структуры графа (числа и расположения сильно-связанных компонент), так как в свою очередь от нее зависит ширина и высота ЯПФ алгоритма поиска сильно-связанных компонент.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Объем входных и выходных данных зависит от вариации постановки проблемы. Далее будут рассмотрены два случая.&lt;br /&gt;
&lt;br /&gt;
1. Поиск полного транзитивного замыкания&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой пары вершин графа её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2. Проверка принадлежности к транзитивному замыканию для &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; заранее заданных пар вершин &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; пар вершин  &amp;lt;math&amp;gt;(v_{i}, v_{j})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E| + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: для каждой входной пары вершин её принадлежность к транзитивному замыканию.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
Программа, реализующая поиск транзитного замыкания, состоит CPU части, отвечающей за общую координацию вычислений, использующей вспомогательные классы.&lt;br /&gt;
&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
&lt;br /&gt;
Для демонстрации эффективности представлены графики времени выполнения различных режимов вычислений в зависимости от размера входного графа. &lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальной ситуации. &lt;br /&gt;
&lt;br /&gt;
Дело в том, что сложность выбранного алгоритма Пурдома зависит от количества ребер в графе не линейно, и, кроме того, зависит еще и от структуры графа количества сильно связанных компонент). &lt;br /&gt;
&lt;br /&gt;
[[file:Замыкание scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Пурдома масштабируемость различных версий: производительность в зависимости от размера графа.]]&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). На представленных графиках по оси X граф с каждым делением увеличивается в два раза, и по представленному графику можно оценивать, на сколько увеличилось время выполнения.&lt;br /&gt;
Так же время выполнения можно сравнивать для различных версий, например, последовательной CPU, параллельной CPU или же GPU.&lt;br /&gt;
На приведенной диаграмме (рисунок 1) для различных размеров графа (от 2^20 до 2^26) представлено время выполнения трех различных версий: последовательной, параллельной CPU и GPU. Шкала на данной диаграмме логарифмическая.&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/transitive_closure.html transitive_closure]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Purdom&#039;s algorithm]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23975</id>
		<title>Поиск в ширину (BFS)</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23975"/>
		<updated>2017-11-16T12:54:29Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Ресурс параллелизма алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм поиска в ширину (BFS)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, \max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A, \max O(|E|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Поиск в ширину&#039;&#039;&#039; (англ. Breadth-First Search, BFS) позволяет вычислить кратчайшие расстояния (в терминах количества рёбер) от выделенной вершины ориентированного графа до всех остальных вершин, и/или построить корневое направленное дерево, расстояния в котором совпадают с расстояниями в исходном графе. Кроме того, поиск в ширину позволяет решать задачу проверки достижимости (существуют ли пути между вершиной источником и остальными вершинами графа). Впервые алгоритм поиска в ширину описан в работах Мура&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; и Ли&amp;lt;ref&amp;gt;Lee, C Y. “An Algorithm for Path Connections and Its Applications.” IEEE Transactions on Electronic Computers 10, no. 3 (September 1961): 346–65. doi:10.1109/TEC.1961.5219222.&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на обходе вершин графа &amp;quot;по слоям&amp;quot;. На каждом шаге есть множество &amp;quot;передовых&amp;quot; вершин, для смежных к которым производится проверка, относятся ли они к еще не посещенным. Все еще не посещенные вершины добавляются в новое множество &amp;quot;передовых&amp;quot; вершин, обрабатываемых на следующем шаге. Изначально в множество &amp;quot;передовых&amp;quot; вершин входит только вершина-источник, от которой и начинается обход.&lt;br /&gt;
&lt;br /&gt;
В последовательном случае алгоритм имеет алгоритмическую сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; - число вершин в графе, &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число ребер в графе.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; без весов, и с выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Путем &amp;lt;math&amp;gt;P(u,v)&amp;lt;/math&amp;gt; между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; называется множество ребер &amp;lt;math&amp;gt;(u, v_1), (v_1, v_2), ... (v_{n-1}, v)&amp;lt;/math&amp;gt;. Длиной пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; обозначим число ребер в данном пути между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Поиск в ширину находит кратчайшие пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до всех остальных вершин графа описанным далее образом.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма расстояние до вершины-источника &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt;, до остальных вершин &amp;lt;math&amp;gt;d(v) = \infty, \forall v \neq u &amp;lt;/math&amp;gt;. Также в начале работы алгоритма инициализируется множество &amp;lt;math&amp;gt;F = \{u\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Далее на каждом шаге алгоритма строится множество вершин &amp;lt;math&amp;gt;P = {w} &amp;lt;/math&amp;gt;, таких, что для &amp;lt;math&amp;gt;\forall v \in F \exists (v, w) \in E | d(w) = \infty &amp;lt;/math&amp;gt;, при этом обновляются расстояния &amp;lt;math&amp;gt;d(w)=d(v)+1&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;\forall w \in P &amp;lt;/math&amp;gt;. Затем производится переход на следующий шаг до тех пор, пока &amp;lt;math&amp;gt;P \neq \emptyset&amp;lt;/math&amp;gt;; при этом в начале каждого шага множество F заменяется на P.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром алгоритма является обход вершин, смежных с выбранной вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, с последующим добавлением еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;. Данная операция выполняется на каждом шаге для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;d(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Помещение вершины источника &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в множество &amp;quot;передовых&amp;quot; вершин &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.     Обход вершин множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
а) Инициализация множества &amp;lt;math&amp;gt;P=\emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б) Для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt; обход всех вершин &amp;lt;math&amp;gt;w | \exists (v, w)&amp;lt;/math&amp;gt; (смежных с ней), c помещением в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; таких вершин &amp;lt;math&amp;gt;w |    d(w)=\infty&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
в) Замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и переход на шаг 3 в случае, если множество &amp;lt;math&amp;gt;F \neq \emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Простейшая версия алгоритма поиск в ширину может быть реализована при помощи очередей на языке C++ следующим образом.&lt;br /&gt;
Код приведен в предположении, что граф хранится в формате сжатого списка смежности: для каждой вершины в массиве vertices_to_edges_ptrs хранятся индекс начала и индекс конца списка смежных с ней вершин из массива dst_ids.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// init distances&lt;br /&gt;
for(int i = 0; i &amp;lt; vertices_count; i++)&lt;br /&gt;
    _result[i] = MAX_INT;&lt;br /&gt;
    &lt;br /&gt;
// init queue and first vertex&lt;br /&gt;
std::queue&amp;lt;int&amp;gt; vertex_queue;&lt;br /&gt;
vertex_queue.push(_source_vertex);&lt;br /&gt;
_result[_source_vertex] = 1;&lt;br /&gt;
    &lt;br /&gt;
// do bfs&lt;br /&gt;
while(vertex_queue.size() &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    int cur_vertex = vertex_queue.front();&lt;br /&gt;
    vertex_queue.pop();&lt;br /&gt;
        &lt;br /&gt;
    long long first = vertices_to_edges_ptrs[cur_vertex];&lt;br /&gt;
    long long last = vertices_to_edges_ptrs[cur_vertex + 1];&lt;br /&gt;
    for(long long i = first; i &amp;lt; last; i++)&lt;br /&gt;
    {&lt;br /&gt;
        int dst_id = dst_ids[i];&lt;br /&gt;
        if(_result[dst_id] == MAX_INT)&lt;br /&gt;
        {&lt;br /&gt;
            _result[dst_id] = _result[src_id] + 1;&lt;br /&gt;
            vertex_queue.push(dst_id);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет последовательную сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число вершин и ребер графа соответственно: алгоритм инициализирует начальный массив расстояний - &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, а затем обходит каждую вершину один единственный раз -  &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций. Данная оценка верна в случае, если формат хранения графа позволяет обходить вершины, смежные к выбранной (к примеру форматы списка смежности, сжатого списка смежности). При использовании других форматов оценка сложности может быть большей.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
Информационный граф классического алгоритма поиска в ширину приведен на рисунке 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Classical_BFS_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма BFS.]]&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 используются следующие обозначения: &lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к множеству &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
[2] - извлечение добавленной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; из множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[3] - проверка расстояний до вершин, смежных с вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[4] - добавление еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[5] - замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на  &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и проверка его пустоты. В случае непустого множества - переход на следующую итерацию, иначе завершение работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм имеет один важный недостаток при реализации: операция [4] требует бесконфликтной возможности добавления элементов в множество P, что, на практике, всегда будет сводиться к сериализации обращений к структуре данных, моделирующей данное множество.&lt;br /&gt;
&lt;br /&gt;
В результате часто используется модификация алгоритма (далее алгоритм-2), использующая набор независимых структур данных для каждого из параллельных процессов. Информационный граф данного подхода приведен на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
[[Файл:BFS_ig_parallel_queues.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма BFS (независимые структуры данных).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 2:&lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[2] - разделение данных множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; между процессами&lt;br /&gt;
&lt;br /&gt;
[3] - помещение в множества &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt; соответствующих данных из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; каждым процессом с номером i.&lt;br /&gt;
&lt;br /&gt;
[4] - извлечение очередной вершины из множеств &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt;, обход её соседей и добавление их в множество &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; в случае, если они еще не посещены&lt;br /&gt;
&lt;br /&gt;
[5] - попарное слияние множеств &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; для различных процессов, итоговое преобразование их в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[6] - проверка условия выхода из цикла&lt;br /&gt;
&lt;br /&gt;
Кроме того, в случае, если реализация структур данных, моделирующих множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;, невозможна, может использоваться квадратичный по сложности алгоритм, схожий с алгоритм Беллмана-Форда. Основная идея заключается в том, что на каждом шаге производится обход всех ребер графа с обновлением текущего массива дистанций. Информационный граф данной модификации алгоритма приведен на рисунке 3.&lt;br /&gt;
&lt;br /&gt;
[[Файл:quadratic_BFS_ig.png|thumb|center|500px|Рисунок 3. Информационный граф алгоритма BFS (квадратичный подход к распараллеливанию).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 3:&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация расстояний до вершины-источника&lt;br /&gt;
&lt;br /&gt;
[2] - инициализация расстояний до остальных вершин графа&lt;br /&gt;
&lt;br /&gt;
[3] - загрузка информации об очередном ребре и обновление дистанций до соответствующих вершин. &lt;br /&gt;
&lt;br /&gt;
[4] - проверка условия выхода из цикла&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В ходе работы классический вариант алгоритма обходит граф по слоям. В каждый слой добавляются еще не посещенные вершины, достижимые из вершин предыдущего слоя. Обход вершин каждого слоя, как и их соседей, может производиться параллельно. Точно оценить число вершин в каждом слое невозможно в силу того, что их количество зависит от структуры связанности входного графа. Аналогично невозможно оценить число шагов алгоритма, за которое будут найдены все кратчайшие пути.&lt;br /&gt;
&lt;br /&gt;
Произведем оценку ширины ярусно-параллельной формы алгоритма через максимальное число вершин p в слое среди всех шагов алгоритма. Тогда число параллельных операций на данном слое будет равно сумме числа смежных вершин для каждой вершины слоя: &amp;lt;math&amp;gt;\sum_{n=1}^{p} degree(v_i)&amp;lt;/math&amp;gt;, при этом для каждого слоя данное значение будет различным. Высота ярусно-параллельной формы будет равна числу шагов в алгоритме и может быть оценена только сверху (не более &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
При квадратичном подходе к параллельной реализации алгоритма на каждом шаге выполняется &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций, которые могут быть выполнены параллельно; таким образом, ширина ЯПФ данной модификации алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;. Число шагов алгоритма, как и в классическом случае, зависит от структуры графа и может быть оценено сверху как &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа расстояние &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;, определенное как число ребер, лежащих на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа значение достижимости (достижима или нет) от вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* Распределённый алгоритм поиска вширь является вычислительным ядром бенчмарка [http://www.graph500.org Graph500].&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_visit.html breadth_first_visit]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://webgraph.di.unimi.it WebGraph] (класс &amp;lt;code&amp;gt;[http://webgraph.di.unimi.it/docs/it/unimi/dsi/webgraph/algo/ParallelBreadthFirstVisit.html ParallelBreadthFirstVisit]&amp;lt;/code&amp;gt;), многопоточная реализация.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.traversal.breadth_first_search.bfs_edges.html bfs_edges]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.BFS networkit.graph.BFS]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Breadth-first search (BFS)]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23973</id>
		<title>Поиск в ширину (BFS)</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23973"/>
		<updated>2017-11-16T10:05:11Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм поиска в ширину (BFS)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, \max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A, \max O(|E|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Поиск в ширину&#039;&#039;&#039; (англ. Breadth-First Search, BFS) позволяет вычислить кратчайшие расстояния (в терминах количества рёбер) от выделенной вершины ориентированного графа до всех остальных вершин, и/или построить корневое направленное дерево, расстояния в котором совпадают с расстояниями в исходном графе. Кроме того, поиск в ширину позволяет решать задачу проверки достижимости (существуют ли пути между вершиной источником и остальными вершинами графа). Впервые алгоритм поиска в ширину описан в работах Мура&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; и Ли&amp;lt;ref&amp;gt;Lee, C Y. “An Algorithm for Path Connections and Its Applications.” IEEE Transactions on Electronic Computers 10, no. 3 (September 1961): 346–65. doi:10.1109/TEC.1961.5219222.&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на обходе вершин графа &amp;quot;по слоям&amp;quot;. На каждом шаге есть множество &amp;quot;передовых&amp;quot; вершин, для смежных к которым производится проверка, относятся ли они к еще не посещенным. Все еще не посещенные вершины добавляются в новое множество &amp;quot;передовых&amp;quot; вершин, обрабатываемых на следующем шаге. Изначально в множество &amp;quot;передовых&amp;quot; вершин входит только вершина-источник, от которой и начинается обход.&lt;br /&gt;
&lt;br /&gt;
В последовательном случае алгоритм имеет алгоритмическую сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; - число вершин в графе, &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число ребер в графе.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; без весов, и с выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Путем &amp;lt;math&amp;gt;P(u,v)&amp;lt;/math&amp;gt; между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; называется множество ребер &amp;lt;math&amp;gt;(u, v_1), (v_1, v_2), ... (v_{n-1}, v)&amp;lt;/math&amp;gt;. Длиной пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; обозначим число ребер в данном пути между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Поиск в ширину находит кратчайшие пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до всех остальных вершин графа описанным далее образом.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма расстояние до вершины-источника &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt;, до остальных вершин &amp;lt;math&amp;gt;d(v) = \infty, \forall v \neq u &amp;lt;/math&amp;gt;. Также в начале работы алгоритма инициализируется множество &amp;lt;math&amp;gt;F = \{u\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Далее на каждом шаге алгоритма строится множество вершин &amp;lt;math&amp;gt;P = {w} &amp;lt;/math&amp;gt;, таких, что для &amp;lt;math&amp;gt;\forall v \in F \exists (v, w) \in E | d(w) = \infty &amp;lt;/math&amp;gt;, при этом обновляются расстояния &amp;lt;math&amp;gt;d(w)=d(v)+1&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;\forall w \in P &amp;lt;/math&amp;gt;. Затем производится переход на следующий шаг до тех пор, пока &amp;lt;math&amp;gt;P \neq \emptyset&amp;lt;/math&amp;gt;; при этом в начале каждого шага множество F заменяется на P.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром алгоритма является обход вершин, смежных с выбранной вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, с последующим добавлением еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;. Данная операция выполняется на каждом шаге для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;d(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Помещение вершины источника &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в множество &amp;quot;передовых&amp;quot; вершин &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.     Обход вершин множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
а) Инициализация множества &amp;lt;math&amp;gt;P=\emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б) Для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt; обход всех вершин &amp;lt;math&amp;gt;w | \exists (v, w)&amp;lt;/math&amp;gt; (смежных с ней), c помещением в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; таких вершин &amp;lt;math&amp;gt;w |    d(w)=\infty&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
в) Замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и переход на шаг 3 в случае, если множество &amp;lt;math&amp;gt;F \neq \emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Простейшая версия алгоритма поиск в ширину может быть реализована при помощи очередей на языке C++ следующим образом.&lt;br /&gt;
Код приведен в предположении, что граф хранится в формате сжатого списка смежности: для каждой вершины в массиве vertices_to_edges_ptrs хранятся индекс начала и индекс конца списка смежных с ней вершин из массива dst_ids.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// init distances&lt;br /&gt;
for(int i = 0; i &amp;lt; vertices_count; i++)&lt;br /&gt;
    _result[i] = MAX_INT;&lt;br /&gt;
    &lt;br /&gt;
// init queue and first vertex&lt;br /&gt;
std::queue&amp;lt;int&amp;gt; vertex_queue;&lt;br /&gt;
vertex_queue.push(_source_vertex);&lt;br /&gt;
_result[_source_vertex] = 1;&lt;br /&gt;
    &lt;br /&gt;
// do bfs&lt;br /&gt;
while(vertex_queue.size() &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    int cur_vertex = vertex_queue.front();&lt;br /&gt;
    vertex_queue.pop();&lt;br /&gt;
        &lt;br /&gt;
    long long first = vertices_to_edges_ptrs[cur_vertex];&lt;br /&gt;
    long long last = vertices_to_edges_ptrs[cur_vertex + 1];&lt;br /&gt;
    for(long long i = first; i &amp;lt; last; i++)&lt;br /&gt;
    {&lt;br /&gt;
        int dst_id = dst_ids[i];&lt;br /&gt;
        if(_result[dst_id] == MAX_INT)&lt;br /&gt;
        {&lt;br /&gt;
            _result[dst_id] = _result[src_id] + 1;&lt;br /&gt;
            vertex_queue.push(dst_id);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет последовательную сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число вершин и ребер графа соответственно: алгоритм инициализирует начальный массив расстояний - &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, а затем обходит каждую вершину один единственный раз -  &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций. Данная оценка верна в случае, если формат хранения графа позволяет обходить вершины, смежные к выбранной (к примеру форматы списка смежности, сжатого списка смежности). При использовании других форматов оценка сложности может быть большей.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
Информационный граф классического алгоритма поиска в ширину приведен на рисунке 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Classical_BFS_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма BFS.]]&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 используются следующие обозначения: &lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к множеству &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
[2] - извлечение добавленной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; из множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[3] - проверка расстояний до вершин, смежных с вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[4] - добавление еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[5] - замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на  &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и проверка его пустоты. В случае непустого множества - переход на следующую итерацию, иначе завершение работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм имеет один важный недостаток при реализации: операция [4] требует бесконфликтной возможности добавления элементов в множество P, что, на практике, всегда будет сводиться к сериализации обращений к структуре данных, моделирующей данное множество.&lt;br /&gt;
&lt;br /&gt;
В результате часто используется модификация алгоритма (далее алгоритм-2), использующая набор независимых структур данных для каждого из параллельных процессов. Информационный граф данного подхода приведен на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
[[Файл:BFS_ig_parallel_queues.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма BFS (независимые структуры данных).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 2:&lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[2] - разделение данных множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; между процессами&lt;br /&gt;
&lt;br /&gt;
[3] - помещение в множества &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt; соответствующих данных из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; каждым процессом с номером i.&lt;br /&gt;
&lt;br /&gt;
[4] - извлечение очередной вершины из множеств &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt;, обход её соседей и добавление их в множество &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; в случае, если они еще не посещены&lt;br /&gt;
&lt;br /&gt;
[5] - попарное слияние множеств &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; для различных процессов, итоговое преобразование их в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[6] - проверка условия выхода из цикла&lt;br /&gt;
&lt;br /&gt;
Кроме того, в случае, если реализация структур данных, моделирующих множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;, невозможна, может использоваться квадратичный по сложности алгоритм, схожий с алгоритм Беллмана-Форда. Основная идея заключается в том, что на каждом шаге производится обход всех ребер графа с обновлением текущего массива дистанций. Информационный граф данной модификации алгоритма приведен на рисунке 3.&lt;br /&gt;
&lt;br /&gt;
[[Файл:quadratic_BFS_ig.png|thumb|center|500px|Рисунок 3. Информационный граф алгоритма BFS (квадратичный подход к распараллеливанию).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 3:&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация расстояний до вершины-источника&lt;br /&gt;
&lt;br /&gt;
[2] - инициализация расстояний до остальных вершин графа&lt;br /&gt;
&lt;br /&gt;
[3] - загрузка информации об очередном ребре и обновление дистанций до соответствующих вершин. &lt;br /&gt;
&lt;br /&gt;
[4] - проверка условия выхода из цикла&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В ходе работы классический вариант алгоритма обходит граф по слоям. В каждый слой добавляются еще не посещенные вершины, достижимые из вершин предыдущего слоя. Обход вершин каждого слоя, как и их соседей, может производиться параллельно. Точно оценить число вершин в каждом слое невозможно в силу того, что их количество зависит от структуры связанности входного графа. Аналогично невозможно оценить число шагов алгоритма, за которое будут найдены все кратчайшие пути.&lt;br /&gt;
&lt;br /&gt;
Произведем оценку ширины ярусно-параллельной формы алгоритма через максимальное число вершин p в слое среди всех шагов алгоритма. Тогда число параллельных операций на данном слое будет равно сумме числа смежных вершин для каждой вершины слоя: &amp;lt;math&amp;gt;\sum_{n=1}^{p} degree(v_i)&amp;lt;/math&amp;gt;, при том для каждого слоя данное значение будет различным. Высота ярусно-параллельной формы будет равна числу шагов в алгоритме, и может быть оценена только сверху (не более &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
При квадратичном подходе к параллельной реализации алгоритма на каждом шаге алгоритма выполняется &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций, которые могут быть выполнены параллельно; таким образом, ширина ЯПФ данной модификации алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;. Число шагов алгоритма как и в классическом случае зависит от структуры графа, и может быть оценено сверху как &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа расстояние &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;, определенное как число ребер, лежащих на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа значение достижимости (достижима или нет) от вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* Распределённый алгоритм поиска вширь является вычислительным ядром бенчмарка [http://www.graph500.org Graph500].&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_visit.html breadth_first_visit]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://webgraph.di.unimi.it WebGraph] (класс &amp;lt;code&amp;gt;[http://webgraph.di.unimi.it/docs/it/unimi/dsi/webgraph/algo/ParallelBreadthFirstVisit.html ParallelBreadthFirstVisit]&amp;lt;/code&amp;gt;), многопоточная реализация.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.traversal.breadth_first_search.bfs_edges.html bfs_edges]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.BFS networkit.graph.BFS]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Breadth-first search (BFS)]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23972</id>
		<title>Поиск в ширину (BFS)</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23972"/>
		<updated>2017-11-16T10:02:45Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм поиска в ширину (BFS)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, \max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A, \max O(|E|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Поиск в ширину&#039;&#039;&#039; (англ. Breadth-First Search, BFS) позволяет вычислить кратчайшие расстояния (в терминах количества рёбер) от выделенной вершины ориентированного графа до всех остальных вершин, и/или построить корневое направленное дерево, расстояния в котором совпадают с расстояниями в исходном графе. Кроме того, поиск в ширину позволяет решать задачу проверки достижимости (существуют ли пути между вершиной источником и остальными вершинами графа). Впервые алгоритм поиска в ширину описан в работах Мура&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; и Ли&amp;lt;ref&amp;gt;Lee, C Y. “An Algorithm for Path Connections and Its Applications.” IEEE Transactions on Electronic Computers 10, no. 3 (September 1961): 346–65. doi:10.1109/TEC.1961.5219222.&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на обходе вершин графа &amp;quot;по слоям&amp;quot;. На каждом шаге есть множество &amp;quot;передовых&amp;quot; вершин, для смежных к которым производится проверка, относятся ли они к еще не посещенным. Все еще не посещенные вершины добавляются в новое множество &amp;quot;передовых&amp;quot; вершин, обрабатываемых на следующем шаге. Изначально в множество &amp;quot;передовых&amp;quot; вершин входит только вершина-источник, от которой и начинается обход.&lt;br /&gt;
&lt;br /&gt;
В последовательном случае алгоритм имеет алгоритмическую сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; - число вершин в графе, &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число ребер в графе.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; без весов, и с выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Путем &amp;lt;math&amp;gt;P(u,v)&amp;lt;/math&amp;gt; между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; называется множество ребер &amp;lt;math&amp;gt;(u, v_1), (v_1, v_2), ... (v_{n-1}, v)&amp;lt;/math&amp;gt;. Длиной пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; обозначим число ребер в данном пути между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Поиск в ширину находит кратчайшие пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до всех остальных вершин графа описанным далее образом.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма расстояние до вершины-источника &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt;, до остальных вершин &amp;lt;math&amp;gt;d(v) = \infty, \forall v \neq u &amp;lt;/math&amp;gt;. Также в начале работы алгоритма инициализируется множество &amp;lt;math&amp;gt;F = \{u\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Далее на каждом шаге алгоритма строится множество вершин &amp;lt;math&amp;gt;P = {w} &amp;lt;/math&amp;gt;, таких, что для &amp;lt;math&amp;gt;\forall v \in F \exists (v, w) \in E | d(w) = \infty &amp;lt;/math&amp;gt;, при этом обновляются расстояния &amp;lt;math&amp;gt;d(w)=d(v)+1&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;\forall w \in P &amp;lt;/math&amp;gt;. Затем производится переход на следующий шаг до тех пор, пока &amp;lt;math&amp;gt;P \neq \emptyset&amp;lt;/math&amp;gt;; при этом в начале каждого шага множество F заменяется на P.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром алгоритма является обход вершин, смежных с выбранной вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, с последующим добавлением еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;. Данная операция выполняется на каждом шаге для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;d(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Помещение вершины источника &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в множество &amp;quot;передовых&amp;quot; вершин &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.     Обход вершин множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
а) Инициализация множества &amp;lt;math&amp;gt;P=\emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б) Для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt; обход всех вершин &amp;lt;math&amp;gt;w | \exists (v, w)&amp;lt;/math&amp;gt; (смежных с ней), c помещением в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; таких вершин &amp;lt;math&amp;gt;w |    d(w)=\infty&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
в) Замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и переход на шаг 3 в случае, если множество &amp;lt;math&amp;gt;F \neq \emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Простейшая версия алгоритма поиск в ширину может быть реализована при помощи очередей на языке C++ следующим образом.&lt;br /&gt;
Код приведен в предположении, что граф хранится в формате сжатого списка смежности: для каждой вершины в массиве vertices_to_edges_ptrs хранятся индекс начала и индекс конца списка смежных с ней вершин из массива dst_ids.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// init distances&lt;br /&gt;
for(int i = 0; i &amp;lt; vertices_count; i++)&lt;br /&gt;
    _result[i] = MAX_INT;&lt;br /&gt;
    &lt;br /&gt;
// init queue and first vertex&lt;br /&gt;
std::queue&amp;lt;int&amp;gt; vertex_queue;&lt;br /&gt;
vertex_queue.push(_source_vertex);&lt;br /&gt;
_result[_source_vertex] = 1;&lt;br /&gt;
    &lt;br /&gt;
// do bfs&lt;br /&gt;
while(vertex_queue.size() &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    int cur_vertex = vertex_queue.front();&lt;br /&gt;
    vertex_queue.pop();&lt;br /&gt;
        &lt;br /&gt;
    long long first = vertices_to_edges_ptrs[cur_vertex];&lt;br /&gt;
    long long last = vertices_to_edges_ptrs[cur_vertex + 1];&lt;br /&gt;
    for(long long i = first; i &amp;lt; last; i++)&lt;br /&gt;
    {&lt;br /&gt;
        int dst_id = dst_ids[i];&lt;br /&gt;
        if(_result[dst_id] == MAX_INT)&lt;br /&gt;
        {&lt;br /&gt;
            _result[dst_id] = _result[src_id] + 1;&lt;br /&gt;
            vertex_queue.push(dst_id);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет последовательную сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число вершин и ребер графа соответственно: алгоритм инициализирует начальный массив расстояний - &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, а затем обходит каждую вершину один единственный раз -  &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций. Данная оценка верна в случае, если формат хранения графа позволяет обходить вершины, смежные к выбранной (к примеру форматы списка смежности, сжатого списка смежности). При использовании других форматов оценка сложности может быть большей.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
Информационный граф классического алгоритма поиска в ширину приведен на рисунке 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Classical_BFS_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма BFS.]]&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 используются следующие обозначения: &lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к множеству &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
[2] - извлечение добавленной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; из множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[3] - проверка расстояний до вершин, смежных с вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[4] - добавление еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[5] - замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на  &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и проверка его пустоты. В случае непустого множества - переход на следующую итерацию, иначе завершение работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм имеет один важный недостаток при реализации: операция [4] требует бесконфликтной возможности добавления элементов в множество P, что, на практике, всегда будет сводиться к сериализации обращений к структуре данных, моделирующей данное множество.&lt;br /&gt;
&lt;br /&gt;
В результате часто используется модификация алгоритма (далее алгоритм-2), использующая набор независимых структур данных для каждого из параллельных процессов. Информационный граф данного подхода приведен на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
[[Файл:BFS_ig_parallel_queues.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма BFS (независимые структуры данных).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 2:&lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[2] - разделение данных множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; между процессами&lt;br /&gt;
&lt;br /&gt;
[3] - помещение в множества &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt; соответствующих данных из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; каждым процессом с номером i.&lt;br /&gt;
&lt;br /&gt;
[4] - извлечение очередной вершины из множеств &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt;, обход её соседей и добавление их в множество &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; в случае, если они еще не посещены&lt;br /&gt;
&lt;br /&gt;
[5] - попарное слияние множеств &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; для различных процессов, итоговое преобразование их в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[6] - проверка условия выхода из цикла&lt;br /&gt;
&lt;br /&gt;
Кроме того, в случае, если реализация структур данных, моделирующих множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;, невозможна, может использоваться квадратичный по сложности алгоритм, схожий с алгоритм Беллмана-Форда. Основная идея заключается в том, что на каждом шаге производится обход всех ребер графа с обновлением текущего массива дистанций. Информационный граф данной модификации алгоритма приведен на рисунке 3.&lt;br /&gt;
&lt;br /&gt;
[[Файл:quadratic_BFS_ig.png|thumb|center|500px|Рисунок 3. Информационный граф алгоритма BFS (квадратичный подход к распараллеливанию).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 3:&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация расстояний до вершины-источника&lt;br /&gt;
&lt;br /&gt;
[2] - инициалищация расстояний до остальных вершин графа&lt;br /&gt;
&lt;br /&gt;
[3] - загрузка информации об очередном ребре и обновления дистанций до соответствующих вершин. &lt;br /&gt;
&lt;br /&gt;
[4] - проверка условий выхода из цикла&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В ходе работы классический вариант алгоритма обходит граф по слоям. В каждый слой добавляются еще не посещенные вершины, достижимые из вершин предыдущего слоя. Обход вершин каждого слоя, как и их соседей, может производиться параллельно. Точно оценить число вершин в каждом слое невозможно в силу того, что их количество зависит от структуры связанности входного графа. Аналогично невозможно оценить число шагов алгоритма, за которое будут найдены все кратчайшие пути.&lt;br /&gt;
&lt;br /&gt;
Произведем оценку ширины ярусно-параллельной формы алгоритма через максимальное число вершин p в слое среди всех шагов алгоритма. Тогда число параллельных операций на данном слое будет равно сумме числа смежных вершин для каждой вершины слоя: &amp;lt;math&amp;gt;\sum_{n=1}^{p} degree(v_i)&amp;lt;/math&amp;gt;, при том для каждого слоя данное значение будет различным. Высота ярусно-параллельной формы будет равна числу шагов в алгоритме, и может быть оценена только сверху (не более &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
При квадратичном подходе к параллельной реализации алгоритма на каждом шаге алгоритма выполняется &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций, которые могут быть выполнены параллельно; таким образом, ширина ЯПФ данной модификации алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;. Число шагов алгоритма как и в классическом случае зависит от структуры графа, и может быть оценено сверху как &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа расстояние &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;, определенное как число ребер, лежащих на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа значение достижимости (достижима или нет) от вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* Распределённый алгоритм поиска вширь является вычислительным ядром бенчмарка [http://www.graph500.org Graph500].&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_visit.html breadth_first_visit]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://webgraph.di.unimi.it WebGraph] (класс &amp;lt;code&amp;gt;[http://webgraph.di.unimi.it/docs/it/unimi/dsi/webgraph/algo/ParallelBreadthFirstVisit.html ParallelBreadthFirstVisit]&amp;lt;/code&amp;gt;), многопоточная реализация.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.traversal.breadth_first_search.bfs_edges.html bfs_edges]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.BFS networkit.graph.BFS]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Breadth-first search (BFS)]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23971</id>
		<title>Поиск в ширину (BFS)</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23971"/>
		<updated>2017-11-16T09:11:33Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм поиска в ширину (BFS)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, \max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A, \max O(|E|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Поиск в ширину&#039;&#039;&#039; (англ. Breadth-First Search, BFS) позволяет вычислить кратчайшие расстояния (в терминах количества рёбер) от выделенной вершины ориентированного графа до всех остальных вершин, и/или построить корневое направленное дерево, расстояния в котором совпадают с расстояниями в исходном графе. Кроме того, поиск в ширину позволяет решать задачу проверки достижимости (существуют ли пути между вершиной источником и остальными вершинами графа). Впервые алгоритм поиска в ширину описан в работах Мура&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; и Ли&amp;lt;ref&amp;gt;Lee, C Y. “An Algorithm for Path Connections and Its Applications.” IEEE Transactions on Electronic Computers 10, no. 3 (September 1961): 346–65. doi:10.1109/TEC.1961.5219222.&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на обходе вершин графа &amp;quot;по слоям&amp;quot;. На каждом шаге есть множество &amp;quot;передовых&amp;quot; вершин, для смежных к которым производится проверка, относятся ли они к еще не посещенным. Все еще не посещенные вершины добавляются в новое множество &amp;quot;передовых&amp;quot; вершин, обрабатываемых на следующем шаге. Изначально в множество &amp;quot;передовых&amp;quot; вершин входит только вершина-источник, от которой и начинается обход.&lt;br /&gt;
&lt;br /&gt;
В последовательном случае алгоритм имеет алгоритмическую сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; - число вершин в графе, &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число ребер в графе.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; без весов, и с выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Путем &amp;lt;math&amp;gt;P(u,v)&amp;lt;/math&amp;gt; между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; называется множество ребер &amp;lt;math&amp;gt;(u, v_1), (v_1, v_2), ... (v_{n-1}, v)&amp;lt;/math&amp;gt;. Длиной пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; обозначим число ребер в данном пути между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Поиск в ширину находит кратчайшие пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до всех остальных вершин графа описанным далее образом.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма расстояние до вершины-источника &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt;, до остальных вершин &amp;lt;math&amp;gt;d(v) = \infty, \forall v \neq u &amp;lt;/math&amp;gt;. Также в начале работы алгоритма инициализируется множество &amp;lt;math&amp;gt;F = \{u\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Далее на каждом шаге алгоритма строится множество вершин &amp;lt;math&amp;gt;P = {w} &amp;lt;/math&amp;gt;, таких, что для &amp;lt;math&amp;gt;\forall v \in F \exists (v, w) \in E | d(w) = \infty &amp;lt;/math&amp;gt;, при этом обновляются расстояния &amp;lt;math&amp;gt;d(w)=d(v)+1&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;\forall w \in P &amp;lt;/math&amp;gt;. Затем производится переход на следующий шаг до тех пор, пока &amp;lt;math&amp;gt;P \neq \emptyset&amp;lt;/math&amp;gt;; при этом в начале каждого шага множество F заменяется на P.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром алгоритма является обход вершин, смежных с выбранной вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, с последующим добавлением еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;. Данная операция выполняется на каждом шаге для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;d(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Помещение вершины источника &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в множество &amp;quot;передовых&amp;quot; вершин &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.     Обход вершин множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
а) Инициализация множества &amp;lt;math&amp;gt;P=\emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б) Для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt; обход всех вершин &amp;lt;math&amp;gt;w | \exists (v, w)&amp;lt;/math&amp;gt; (смежных с ней), c помещением в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; таких вершин &amp;lt;math&amp;gt;w |    d(w)=\infty&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
в) Замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и переход на шаг 3 в случае, если множество &amp;lt;math&amp;gt;F \neq \emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Простейшая версия алгоритма поиск в ширину может быть реализована при помощи очередей на языке C++ следующим образом.&lt;br /&gt;
Код приведен в предположении, что граф хранится в формате сжатого списка смежности: для каждой вершины в массиве vertices_to_edges_ptrs хранятся индекс начала и индекс конца списка смежных с ней вершин из массива dst_ids.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// init distances&lt;br /&gt;
for(int i = 0; i &amp;lt; vertices_count; i++)&lt;br /&gt;
    _result[i] = MAX_INT;&lt;br /&gt;
    &lt;br /&gt;
// init queue and first vertex&lt;br /&gt;
std::queue&amp;lt;int&amp;gt; vertex_queue;&lt;br /&gt;
vertex_queue.push(_source_vertex);&lt;br /&gt;
_result[_source_vertex] = 1;&lt;br /&gt;
    &lt;br /&gt;
// do bfs&lt;br /&gt;
while(vertex_queue.size() &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    int cur_vertex = vertex_queue.front();&lt;br /&gt;
    vertex_queue.pop();&lt;br /&gt;
        &lt;br /&gt;
    long long first = vertices_to_edges_ptrs[cur_vertex];&lt;br /&gt;
    long long last = vertices_to_edges_ptrs[cur_vertex + 1];&lt;br /&gt;
    for(long long i = first; i &amp;lt; last; i++)&lt;br /&gt;
    {&lt;br /&gt;
        int dst_id = dst_ids[i];&lt;br /&gt;
        if(_result[dst_id] == MAX_INT)&lt;br /&gt;
        {&lt;br /&gt;
            _result[dst_id] = _result[src_id] + 1;&lt;br /&gt;
            vertex_queue.push(dst_id);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет последовательную сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число вершин и ребер графа соответственно: алгоритм инициализирует начальный массив расстояний - &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, а затем обходит каждую вершину один единственный раз -  &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций. Данная оценка верна в случае, если формат хранения графа позволяет обходить вершины, смежные к выбранной (к примеру форматы списка смежности, сжатого списка смежности). При использовании других форматов оценка сложности может быть большей.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
Информационный граф классического алгоритма поиска в ширину приведен на рисунке 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Classical_BFS_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма BFS.]]&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 используются следующие обозначения: &lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к множеству &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
[2] - извлечение добавленной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; из множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[3] - проверка расстояний до вершин, смежных с вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[4] - добавление еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[5] - замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на  &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и проверка его пустоты. В случае непустого множества - переход на следующую итерацию, иначе завершение работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм имеет один важный недостаток при реализации: операция [4] требует бесконфликтной возможности добавления элементов в множество P, что, на практике, всегда будет сводиться к сериализации обращений к структуре данных, моделирующей данное множество.&lt;br /&gt;
&lt;br /&gt;
В результате часто используется модификация алгоритма (далее алгоритм-2), использующая набор независимых структур данных для каждого из параллельных процессов. Информационный граф данного подхода приведен на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
[[Файл:BFS_ig_parallel_queues.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма BFS (независимые структуры данных).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 2:&lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[2] - разделение данных множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; между процессами&lt;br /&gt;
&lt;br /&gt;
[3] - помещение в множества &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt; соответствующих данных из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; каждым процессом с номером i.&lt;br /&gt;
&lt;br /&gt;
[4] - извлечение очередной вершины из множеств &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt;, обход её соседей и добавление их в множество &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; в случае, если они еще не посещены&lt;br /&gt;
&lt;br /&gt;
[5] - попарное слияние множеств &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; для различных процессов, итоговое преобразование их в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[6] - проверка условия выхода из цикла&lt;br /&gt;
&lt;br /&gt;
Кроме того, в случае, если реализация структур данных, моделирующих множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; невозможна, может использоваться квадратичный по сложности алгоритм, схожий с алгоритм Беллмана-Форда. Основная идея заключается в том, что на каждом шаге производится обход всех ребер графа с обновлением текущего массива дистанций. Информационный граф данной модификации алгоритма приведен на рисунке 3.&lt;br /&gt;
&lt;br /&gt;
[[Файл:quadratic_BFS_ig.png|thumb|center|500px|Рисунок 3. Информационный граф алгоритма BFS (квадратичный подход к распараллеливанию).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 3:&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация расстояний до вершины-источника&lt;br /&gt;
&lt;br /&gt;
[2] - инициалищация расстояний до остальных вершин графа&lt;br /&gt;
&lt;br /&gt;
[3] - загрузка информации об очередном ребре и обновления дистанций до соответствующих вершин. &lt;br /&gt;
&lt;br /&gt;
[4] - проверка условий выхода из цикла&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В ходе работы классический вариант алгоритма обходит граф по слоям. В каждый слой добавляются еще не посещенные вершины, достижимые из вершин предыдущего слоя. Обход вершин каждого слоя, как и их соседей, может производиться параллельно. Точно оценить число вершин в каждом слое невозможно в силу того, что их количество зависит от структуры связанности входного графа. Аналогично невозможно оценить число шагов алгоритма, за которое будут найдены все кратчайшие пути.&lt;br /&gt;
&lt;br /&gt;
Произведем оценку ширины ярусно-параллельной формы алгоритма через максимальное число вершин p в слое среди всех шагов алгоритма. Тогда число параллельных операций на данном слое будет равно сумме числа смежных вершин для каждой вершины слоя: &amp;lt;math&amp;gt;\sum_{n=1}^{p} degree(v_i)&amp;lt;/math&amp;gt;, при том для каждого слоя данное значение будет различным. Высота ярусно-параллельной формы будет равна числу шагов в алгоритме, и может быть оценена только сверху (не более &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
При квадратичном подходе к параллельной реализации алгоритма на каждом шаге алгоритма выполняется &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций, которые могут быть выполнены параллельно; таким образом, ширина ЯПФ данной модификации алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;. Число шагов алгоритма как и в классическом случае зависит от структуры графа, и может быть оценено сверху как &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа расстояние &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;, определенное как число ребер, лежащих на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа значение достижимости (достижима или нет) от вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* Распределённый алгоритм поиска вширь является вычислительным ядром бенчмарка [http://www.graph500.org Graph500].&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_visit.html breadth_first_visit]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://webgraph.di.unimi.it WebGraph] (класс &amp;lt;code&amp;gt;[http://webgraph.di.unimi.it/docs/it/unimi/dsi/webgraph/algo/ParallelBreadthFirstVisit.html ParallelBreadthFirstVisit]&amp;lt;/code&amp;gt;), многопоточная реализация.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.traversal.breadth_first_search.bfs_edges.html bfs_edges]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.BFS networkit.graph.BFS]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Breadth-first search (BFS)]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23960</id>
		<title>Поиск в ширину (BFS)</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23960"/>
		<updated>2017-11-15T16:32:50Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Вычислительное ядро алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм поиска в ширину (BFS)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, \max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A, \max O(|E|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Поиск в ширину&#039;&#039;&#039; (англ. Breadth-First Search, BFS) позволяет вычислить кратчайшие расстояния (в терминах количества рёбер) от выделенной вершины ориентированного графа до всех остальных вершин, и/или построить корневое направленное дерево, расстояния в котором совпадают с расстояниями в исходном графе. Кроме того, поиск в ширину позволяет решать задачу проверки достижимости (существуют ли пути между вершиной источником и остальными вершинами графа). Впервые алгоритм поиска в ширину описан в работах Мура&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; и Ли&amp;lt;ref&amp;gt;Lee, C Y. “An Algorithm for Path Connections and Its Applications.” IEEE Transactions on Electronic Computers 10, no. 3 (September 1961): 346–65. doi:10.1109/TEC.1961.5219222.&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на обходе вершин графа &amp;quot;по слоям&amp;quot;. На каждом шаге есть множество &amp;quot;передовых&amp;quot; вершин, для смежных к которым производится проверка, относятся ли они к еще не посещенным. Все еще не посещенные вершины добавляются в новое множество &amp;quot;передовых&amp;quot; вершин, обрабатываемых на следующем шаге. Изначально в множество &amp;quot;передовых&amp;quot; вершин входит только вершина-источник, от которой и начинается обход.&lt;br /&gt;
&lt;br /&gt;
В последовательном случае алгоритм имеет алгоритмическую сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; - число вершин в графе, &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число ребер в графе.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; без весов, и с выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Путем &amp;lt;math&amp;gt;P(u,v)&amp;lt;/math&amp;gt; между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; называется множество ребер &amp;lt;math&amp;gt;(u, v_1), (v_1, v_2), ... (v_{n-1}, v)&amp;lt;/math&amp;gt;. Длиной пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; обозначим число ребер в данном пути между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Поиск в ширину находит кратчайшие пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до всех остальных вершин графа описанным далее образом.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма расстояние до вершины-источника &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt;, до остальных вершин &amp;lt;math&amp;gt;d(v) = \infty, \forall v \neq u &amp;lt;/math&amp;gt;. Также в начале работы алгоритма инициализируется множество &amp;lt;math&amp;gt;F = \{u\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Далее на каждом шаге алгоритма строится множество вершин &amp;lt;math&amp;gt;P = {w} &amp;lt;/math&amp;gt;, таких, что для &amp;lt;math&amp;gt;\forall v \in F \exists (v, w) \in E | d(w) = \infty &amp;lt;/math&amp;gt;, при этом обновляются расстояния &amp;lt;math&amp;gt;d(w)=d(v)+1&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;\forall w \in P &amp;lt;/math&amp;gt;. Затем производится переход на следующий шаг до тех пор, пока &amp;lt;math&amp;gt;P \neq \emptyset&amp;lt;/math&amp;gt;; при этом в начале каждого шага множество F заменяется на P.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром алгоритма является обход вершин, смежных с выбранной вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;, с последующим добавлением еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;. Данная операция выполняется на каждом шаге для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;d(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Помещение вершины источника &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в множество &amp;quot;передовых&amp;quot; вершин &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.     Обход вершин множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
а) Инициализация множества &amp;lt;math&amp;gt;P=\emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б) Для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt; обход всех вершин &amp;lt;math&amp;gt;w | \exists (v, w)&amp;lt;/math&amp;gt; (смежных с ней), c помещением в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; таких вершин &amp;lt;math&amp;gt;w |    d(w)=\infty&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
в) Замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и переход на шаг 3 в случае, если множество &amp;lt;math&amp;gt;F \neq \emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Простейшая версия алгоритма поиск в ширину может быть реализована при помощи очередей на языке C++ следующим образом.&lt;br /&gt;
Код приведен в предположении, что граф хранится в формате сжатого списка смежности: для каждой вершины в массиве vertices_to_edges_ptrs хранятся индекс начала и индекс конца списка смежных с ней вершин из массива dst_ids.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// init distances&lt;br /&gt;
for(int i = 0; i &amp;lt; vertices_count; i++)&lt;br /&gt;
    _result[i] = MAX_INT;&lt;br /&gt;
    &lt;br /&gt;
// init queue and first vertex&lt;br /&gt;
std::queue&amp;lt;int&amp;gt; vertex_queue;&lt;br /&gt;
vertex_queue.push(_source_vertex);&lt;br /&gt;
_result[_source_vertex] = 1;&lt;br /&gt;
    &lt;br /&gt;
// do bfs&lt;br /&gt;
while(vertex_queue.size() &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    int cur_vertex = vertex_queue.front();&lt;br /&gt;
    vertex_queue.pop();&lt;br /&gt;
        &lt;br /&gt;
    long long first = vertices_to_edges_ptrs[cur_vertex];&lt;br /&gt;
    long long last = vertices_to_edges_ptrs[cur_vertex + 1];&lt;br /&gt;
    for(long long i = first; i &amp;lt; last; i++)&lt;br /&gt;
    {&lt;br /&gt;
        int dst_id = dst_ids[i];&lt;br /&gt;
        if(_result[dst_id] == MAX_INT)&lt;br /&gt;
        {&lt;br /&gt;
            _result[dst_id] = _result[src_id] + 1;&lt;br /&gt;
            vertex_queue.push(dst_id);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет последовательную сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число вершин и ребер графа соответственно: алгоритм инициализирует начальный массив расстояний - &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, а затем обходит каждую вершину один единственный раз -  &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций. Данная оценка верна в случае, если формат хранения графа позволяет обходить вершины, смежные к выбранной (к примеру форматы списка смежности, сжатого списка смежности). При использовании других форматов оценка сложности может быть большей.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
Информационный граф классического алгоритма поиска в ширину приведен на рисунке 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Classical_BFS_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма BFS.]]&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 используются следующие обозначения: &lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к множеству &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
[2] - извлечение добавленной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; из множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[3] - проверка расстояний до вершин, смежных с вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[4] - добавление еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[5] - замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на  &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и проверка его пустоты. В случае негустого множества - переход на следующую итерацию, иначе завершение работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм имеет один важный недостаток при реализации: операция [4] требует бесконфликтной возможности добавления элементов в множество P, что, на практике, всегда будет сводиться к сериализации обращений к структуре данных, моделирующих данное множество.&lt;br /&gt;
&lt;br /&gt;
В результате часто используется модификация алгоритма (далее алгоритм-2), использующая набор независимых структур данных для каждого из параллельных процессов. Информационный граф данного подхода приведен на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
[[Файл:BFS_ig_parallel_queues.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма BFS (независимые структуры данных).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 2:&lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[2] - разделение данных множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; между процессами&lt;br /&gt;
&lt;br /&gt;
[3] - помещение в множества &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt; соответствующих данных из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; каждым процессом с номером i.&lt;br /&gt;
&lt;br /&gt;
[4] - извлечение очередной вершины из множеств &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt;, обход её соседей и добавление их в множество &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; в случае, если они еще не посещены&lt;br /&gt;
&lt;br /&gt;
[5] - попарное слияние множеств &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; для различных процессов, итоговое преобразование их в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[6] - проверка условия выхода из цикла&lt;br /&gt;
&lt;br /&gt;
Кроме того, в случае, если реализация структур данных, моделирующих множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; невозможна, может использоваться квадратичный по сложности алгоритм, схожий с алгоритм Беллмана-Форда. Основная идея заключается в том, что на каждом шаге производится обход всех ребер графа с обновлением текущего массива дистанций. Информационный граф данной модификации алгоритма приведен на рисунке 3.&lt;br /&gt;
&lt;br /&gt;
[[Файл:quadratic_BFS_ig.png|thumb|center|500px|Рисунок 3. Информационный граф алгоритма BFS (квадратичный подход к распараллеливанию).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 3:&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация расстояний до вершины-источника&lt;br /&gt;
&lt;br /&gt;
[2] - инициалищация расстояний до остальных вершин графа&lt;br /&gt;
&lt;br /&gt;
[3] - загрузка информации об очередном ребре и обновления дистанций до соответствующих вершин. &lt;br /&gt;
&lt;br /&gt;
[4] - проверка условий выхода из цикла&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В ходе работы классический вариант алгоритма обходит граф по слоям. В каждый слой добавляются еще не посещенные вершины, достижимые из вершин предыдущего слоя. Обход вершин каждого слоя, как и их соседей, может производиться параллельно. Точно оценить число вершин в каждом слое невозможно в силу того, что их количество зависит от структуры связанности входного графа. Аналогично невозможно оценить число шагов алгоритма, за которое будут найдены все кратчайшие пути.&lt;br /&gt;
&lt;br /&gt;
Произведем оценку ширины ярусно-параллельной формы алгоритма через максимальное число вершин p в слое среди всех шагов алгоритма. Тогда число параллельных операций на данном слое будет равно сумме числа смежных вершин для каждой вершины слоя: &amp;lt;math&amp;gt;\sum_{n=1}^{p} degree(v_i)&amp;lt;/math&amp;gt;, при том для каждого слоя данное значение будет различным. Высота ярусно-параллельной формы будет равна числу шагов в алгоритме, и может быть оценена только сверху (не более &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
При квадратичном подходе к параллельной реализации алгоритма на каждом шаге алгоритма выполняется &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций, которые могут быть выполнены параллельно; таким образом, ширина ЯПФ данной модификации алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;. Число шагов алгоритма как и в классическом случае зависит от структуры графа, и может быть оценено сверху как &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа расстояние &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;, определенное как число ребер, лежащих на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа значение достижимости (достижима или нет) от вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* Распределённый алгоритм поиска вширь является вычислительным ядром бенчмарка [http://www.graph500.org Graph500].&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_visit.html breadth_first_visit]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://webgraph.di.unimi.it WebGraph] (класс &amp;lt;code&amp;gt;[http://webgraph.di.unimi.it/docs/it/unimi/dsi/webgraph/algo/ParallelBreadthFirstVisit.html ParallelBreadthFirstVisit]&amp;lt;/code&amp;gt;), многопоточная реализация.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.traversal.breadth_first_search.bfs_edges.html bfs_edges]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.BFS networkit.graph.BFS]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Breadth-first search (BFS)]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23959</id>
		<title>Поиск в ширину (BFS)</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%B2_%D1%88%D0%B8%D1%80%D0%B8%D0%BD%D1%83_(BFS)&amp;diff=23959"/>
		<updated>2017-11-15T15:56:54Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Математическое описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Алгоритм поиска в ширину (BFS)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;N/A, \max O(|V|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;N/A, \max O(|E|) &amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Основные авторы описания: [[Участник:Elijah|И.В.Афанасьев]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Поиск в ширину&#039;&#039;&#039; (англ. Breadth-First Search, BFS) позволяет вычислить кратчайшие расстояния (в терминах количества рёбер) от выделенной вершины ориентированного графа до всех остальных вершин, и/или построить корневое направленное дерево, расстояния в котором совпадают с расстояниями в исходном графе. Кроме того, поиск в ширину позволяет решать задачу проверки достижимости (существуют ли пути между вершиной источником и остальными вершинами графа). Впервые алгоритм поиска в ширину описан в работах Мура&amp;lt;ref&amp;gt;Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.&amp;lt;/ref&amp;gt; и Ли&amp;lt;ref&amp;gt;Lee, C Y. “An Algorithm for Path Connections and Its Applications.” IEEE Transactions on Electronic Computers 10, no. 3 (September 1961): 346–65. doi:10.1109/TEC.1961.5219222.&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм основан на обходе вершин графа &amp;quot;по слоям&amp;quot;. На каждом шаге есть множество &amp;quot;передовых&amp;quot; вершин, для смежных к которым производится проверка, относятся ли они к еще не посещенным. Все еще не посещенные вершины добавляются в новое множество &amp;quot;передовых&amp;quot; вершин, обрабатываемых на следующем шаге. Изначально в множество &amp;quot;передовых&amp;quot; вершин входит только вершина-источник, от которой и начинается обход.&lt;br /&gt;
&lt;br /&gt;
В последовательном случае алгоритм имеет алгоритмическую сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; - число вершин в графе, &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число ребер в графе.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Пусть задан граф &amp;lt;math&amp;gt;G = (V, E)&amp;lt;/math&amp;gt; без весов, и с выделенной вершиной-источником &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;. Путем &amp;lt;math&amp;gt;P(u,v)&amp;lt;/math&amp;gt; между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; называется множество ребер &amp;lt;math&amp;gt;(u, v_1), (v_1, v_2), ... (v_{n-1}, v)&amp;lt;/math&amp;gt;. Длиной пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; обозначим число ребер в данном пути между вершинами &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Поиск в ширину находит кратчайшие пути &amp;lt;math&amp;gt;d(u,v)&amp;lt;/math&amp;gt; от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; до всех остальных вершин графа описанным далее образом.&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритма расстояние до вершины-источника &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt;, до остальных вершин &amp;lt;math&amp;gt;d(v) = \infty, \forall v \neq u &amp;lt;/math&amp;gt;. Также в начале работы алгоритма инициализируется множество &amp;lt;math&amp;gt;F = \{u\}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Далее на каждом шаге алгоритма строится множество вершин &amp;lt;math&amp;gt;P = {w} &amp;lt;/math&amp;gt;, таких, что для &amp;lt;math&amp;gt;\forall v \in F \exists (v, w) \in E | d(w) = \infty &amp;lt;/math&amp;gt;, при этом обновляются расстояния &amp;lt;math&amp;gt;d(w)=d(v)+1&amp;lt;/math&amp;gt; для &amp;lt;math&amp;gt;\forall w \in P &amp;lt;/math&amp;gt;. Затем производится переход на следующий шаг до тех пор, пока &amp;lt;math&amp;gt;P \neq \emptyset&amp;lt;/math&amp;gt;; при этом в начале каждого шага множество F заменяется на P.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительным ядром алгоритма является обход вершин, смежной к выбранной вершине &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; с последующем добавлением еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;. Данная операция выполняться на каждом шаге для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм последовательно уточняет значения функции &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Структуру можно описать следующим образом:&lt;br /&gt;
&lt;br /&gt;
1.	Инициализация: всем вершинам присваивается предполагаемое расстояние &amp;lt;math&amp;gt;d(v)=\infty&amp;lt;/math&amp;gt;, кроме вершины-источника, для которой &amp;lt;math&amp;gt;d(u)=0&amp;lt;/math&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
2.	Помещение вершины источника &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в множество &amp;quot;передовых&amp;quot; вершин &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.     Обход вершин множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
а) Инициализация множества &amp;lt;math&amp;gt;P=\emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
б) Для каждой вершины &amp;lt;math&amp;gt;v \in F&amp;lt;/math&amp;gt; обход всех вершин &amp;lt;math&amp;gt;w | \exists (v, w)&amp;lt;/math&amp;gt; (смежных с ней), c помещением в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; таких вершин &amp;lt;math&amp;gt;w |    d(w)=\infty&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
в) Замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и переход на шаг 3 в случае, если множество &amp;lt;math&amp;gt;F \neq \emptyset&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Простейшая версия алгоритма поиск в ширину может быть реализована при помощи очередей на языке C++ следующим образом.&lt;br /&gt;
Код приведен в предположении, что граф хранится в формате сжатого списка смежности: для каждой вершины в массиве vertices_to_edges_ptrs хранятся индекс начала и индекс конца списка смежных с ней вершин из массива dst_ids.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C++&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// init distances&lt;br /&gt;
for(int i = 0; i &amp;lt; vertices_count; i++)&lt;br /&gt;
    _result[i] = MAX_INT;&lt;br /&gt;
    &lt;br /&gt;
// init queue and first vertex&lt;br /&gt;
std::queue&amp;lt;int&amp;gt; vertex_queue;&lt;br /&gt;
vertex_queue.push(_source_vertex);&lt;br /&gt;
_result[_source_vertex] = 1;&lt;br /&gt;
    &lt;br /&gt;
// do bfs&lt;br /&gt;
while(vertex_queue.size() &amp;gt; 0)&lt;br /&gt;
{&lt;br /&gt;
    int cur_vertex = vertex_queue.front();&lt;br /&gt;
    vertex_queue.pop();&lt;br /&gt;
        &lt;br /&gt;
    long long first = vertices_to_edges_ptrs[cur_vertex];&lt;br /&gt;
    long long last = vertices_to_edges_ptrs[cur_vertex + 1];&lt;br /&gt;
    for(long long i = first; i &amp;lt; last; i++)&lt;br /&gt;
    {&lt;br /&gt;
        int dst_id = dst_ids[i];&lt;br /&gt;
        if(_result[dst_id] == MAX_INT)&lt;br /&gt;
        {&lt;br /&gt;
            _result[dst_id] = _result[src_id] + 1;&lt;br /&gt;
            vertex_queue.push(dst_id);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм имеет последовательную сложность &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; - число вершин и ребер графа соответственно: алгоритм инициализирует начальный массив расстояний - &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, а затем обходит каждую вершину один единственный раз -  &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций. Данная оценка верна в случае, если формат хранения графа позволяет обходить вершины, смежные к выбранной (к примеру форматы списка смежности, сжатого списка смежности). При использовании других форматов оценка сложности может быть большей.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
Информационный граф классического алгоритма поиска в ширину приведен на рисунке 1.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Classical_BFS_ig.png|thumb|center|500px|Рисунок 1. Информационный граф алгоритма BFS.]]&lt;br /&gt;
&lt;br /&gt;
На рисунке 1 используются следующие обозначения: &lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к множеству &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
[2] - извлечение добавленной вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; из множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[3] - проверка расстояний до вершин, смежных с вершиной &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[4] - добавление еще не посещенных вершин в множество &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[5] - замена множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; на  &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; и проверка его пустоты. В случае негустого множества - переход на следующую итерацию, иначе завершение работы алгоритма.&lt;br /&gt;
&lt;br /&gt;
Данный алгоритм имеет один важный недостаток при реализации: операция [4] требует бесконфликтной возможности добавления элементов в множество P, что, на практике, всегда будет сводиться к сериализации обращений к структуре данных, моделирующих данное множество.&lt;br /&gt;
&lt;br /&gt;
В результате часто используется модификация алгоритма (далее алгоритм-2), использующая набор независимых структур данных для каждого из параллельных процессов. Информационный граф данного подхода приведен на рисунке 2.&lt;br /&gt;
&lt;br /&gt;
[[Файл:BFS_ig_parallel_queues.png|thumb|center|500px|Рисунок 2. Информационный граф алгоритма BFS (независимые структуры данных).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 2:&lt;br /&gt;
&lt;br /&gt;
[1] - добавление вершины-источника в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[2] - разделение данных множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; между процессами&lt;br /&gt;
&lt;br /&gt;
[3] - помещение в множества &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt; соответствующих данных из &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; каждым процессом с номером i.&lt;br /&gt;
&lt;br /&gt;
[4] - извлечение очередной вершины из множеств &amp;lt;math&amp;gt;F_i&amp;lt;/math&amp;gt;, обход её соседей и добавление их в множество &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; в случае, если они еще не посещены&lt;br /&gt;
&lt;br /&gt;
[5] - попарное слияние множеств &amp;lt;math&amp;gt;P_i&amp;lt;/math&amp;gt; для различных процессов, итоговое преобразование их в множество &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[6] - проверка условия выхода из цикла&lt;br /&gt;
&lt;br /&gt;
Кроме того, в случае, если реализация структур данных, моделирующих множества &amp;lt;math&amp;gt;F&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;P&amp;lt;/math&amp;gt; невозможна, может использоваться квадратичный по сложности алгоритм, схожий с алгоритм Беллмана-Форда. Основная идея заключается в том, что на каждом шаге производится обход всех ребер графа с обновлением текущего массива дистанций. Информационный граф данной модификации алгоритма приведен на рисунке 3.&lt;br /&gt;
&lt;br /&gt;
[[Файл:quadratic_BFS_ig.png|thumb|center|500px|Рисунок 3. Информационный граф алгоритма BFS (квадратичный подход к распараллеливанию).]]&lt;br /&gt;
&lt;br /&gt;
Обозначения для рисунка 3:&lt;br /&gt;
&lt;br /&gt;
[1] - инициализация расстояний до вершины-источника&lt;br /&gt;
&lt;br /&gt;
[2] - инициалищация расстояний до остальных вершин графа&lt;br /&gt;
&lt;br /&gt;
[3] - загрузка информации об очередном ребре и обновления дистанций до соответствующих вершин. &lt;br /&gt;
&lt;br /&gt;
[4] - проверка условий выхода из цикла&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В ходе работы классический вариант алгоритма обходит граф по слоям. В каждый слой добавляются еще не посещенные вершины, достижимые из вершин предыдущего слоя. Обход вершин каждого слоя, как и их соседей, может производиться параллельно. Точно оценить число вершин в каждом слое невозможно в силу того, что их количество зависит от структуры связанности входного графа. Аналогично невозможно оценить число шагов алгоритма, за которое будут найдены все кратчайшие пути.&lt;br /&gt;
&lt;br /&gt;
Произведем оценку ширины ярусно-параллельной формы алгоритма через максимальное число вершин p в слое среди всех шагов алгоритма. Тогда число параллельных операций на данном слое будет равно сумме числа смежных вершин для каждой вершины слоя: &amp;lt;math&amp;gt;\sum_{n=1}^{p} degree(v_i)&amp;lt;/math&amp;gt;, при том для каждого слоя данное значение будет различным. Высота ярусно-параллельной формы будет равна числу шагов в алгоритме, и может быть оценена только сверху (не более &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
При квадратичном подходе к параллельной реализации алгоритма на каждом шаге алгоритма выполняется &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; операций, которые могут быть выполнены параллельно; таким образом, ширина ЯПФ данной модификации алгоритма равна &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt;. Число шагов алгоритма как и в классическом случае зависит от структуры графа, и может быть оценено сверху как &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: граф &amp;lt;math&amp;gt;G(V, E)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;|V|&amp;lt;/math&amp;gt; вершин &amp;lt;math&amp;gt;v_i&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;|E|&amp;lt;/math&amp;gt; рёбер &amp;lt;math&amp;gt;e_j = (v^{(1)}_{j}, v^{(2)}_{j})&amp;lt;/math&amp;gt;, вершина-источник &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039; (возможные варианты):&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа расстояние &amp;lt;math&amp;gt;d(v)&amp;lt;/math&amp;gt;, определенное как число ребер, лежащих на кратчайшем пути от вершины &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt; к &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.&lt;br /&gt;
# для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; исходного графа значение достижимости (достижима или нет) от вершины-источника &amp;lt;math&amp;gt;u&amp;lt;/math&amp;gt;.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
* Распределённый алгоритм поиска вширь является вычислительным ядром бенчмарка [http://www.graph500.org Graph500].&lt;br /&gt;
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функции &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph/doc/breadth_first_visit.html breadth_first_visit]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library] (функция &amp;lt;code&amp;gt;[http://www.boost.org/libs/graph_parallel/doc/html/breadth_first_search.html breadth_first_search]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Java: [http://webgraph.di.unimi.it WebGraph] (класс &amp;lt;code&amp;gt;[http://webgraph.di.unimi.it/docs/it/unimi/dsi/webgraph/algo/ParallelBreadthFirstVisit.html ParallelBreadthFirstVisit]&amp;lt;/code&amp;gt;), многопоточная реализация.&lt;br /&gt;
* Python: [https://networkx.github.io NetworkX] (функция &amp;lt;code&amp;gt;[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.traversal.breadth_first_search.bfs_edges.html bfs_edges]&amp;lt;/code&amp;gt;).&lt;br /&gt;
* Python/C++: [https://networkit.iti.kit.edu NetworKit] (класс &amp;lt;code&amp;gt;[https://networkit.iti.kit.edu/data/uploads/docs/NetworKit-Doc/python/html/graph.html#networkit.graph.BFS networkit.graph.BFS]&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
&lt;br /&gt;
[[En:Breadth-first search (BFS)]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%B4%D0%BB%D1%8F_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D1%8B%D1%85_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86_%D0%BA_%D1%82%D1%80%D1%91%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%D1%83_%D0%B2%D0%B8%D0%B4%D1%83&amp;diff=23821</id>
		<title>Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%B4%D0%BB%D1%8F_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D1%8B%D1%85_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86_%D0%BA_%D1%82%D1%80%D1%91%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%D1%83_%D0%B2%D0%B8%D0%B4%D1%83&amp;diff=23821"/>
		<updated>2017-11-08T09:00:59Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Существующие реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Приведение симметричной вещественной матрицы к трёхдиагональному виду методом Хаусхолдера (отражений)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(n^3)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;2n^2+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;n^2/2&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;(n^2+n)/2&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;(n^2+3n)/2&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Метод Хаусхолдера&#039;&#039;&#039; (в советской математической литературе чаще называется &#039;&#039;&#039;методом отражений&#039;&#039;&#039;) используется для приведения симметричных вещественных матриц к трёхдиагональному виду, или, что то же самое, для разложения &amp;lt;math&amp;gt;A=QTQ^T&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; - ортогональная, &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; — симметричная трёхдиагональная матрица)&amp;lt;ref&amp;gt;В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.&amp;lt;/ref&amp;gt;. При этом матрица &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; хранится и используется не в своём явном виде, а в виде произведения матриц отражения&amp;lt;ref name=&amp;quot;VOLA&amp;quot;&amp;gt;Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.&amp;lt;/ref&amp;gt;. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием одномерного дополнительного массива. &lt;br /&gt;
&lt;br /&gt;
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В методе Хаусхолдера для выполнения &amp;lt;math&amp;gt;QTQ^T&amp;lt;/math&amp;gt;-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений) с последующим умножением на те же матрицы справа. &lt;br /&gt;
&lt;br /&gt;
{{Шаблон:Матрица отражений}}&lt;br /&gt;
&lt;br /&gt;
На &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-м шаге метода с помощью преобразования отражения &amp;quot;убираются&amp;quot; ненулевые поддиагональные элементы, начиная с &amp;lt;math&amp;gt;i+2&amp;lt;/math&amp;gt;-го в &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-м столбце. После умножения на эту же матрицу отражения справа автоматически убираются и ненулевые наддиагональные элементы, начиная с &amp;lt;math&amp;gt;i+2&amp;lt;/math&amp;gt;-го в &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-й строке, а полученная модификация снова приобретает симметричный вид. &lt;br /&gt;
&lt;br /&gt;
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде &amp;lt;math&amp;gt;U=E-\frac{1}{\gamma}vv^*&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; находится через координаты текущего &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го столбца так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;s&amp;lt;/math&amp;gt; - вектор размерности &amp;lt;math&amp;gt;n+1-i&amp;lt;/math&amp;gt;, составленный из элементов &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го столбца, начиная с &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;(s,s)=0&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;v=e_{i}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\gamma = \frac{1}{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В остальных случаях по алгоритму вычисляется &amp;lt;math&amp;gt;u = \frac{1}{\sqrt{(s,s)}}s&amp;lt;/math&amp;gt;, и далее &amp;lt;math&amp;gt;v_{j}=0&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;j \lt i&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;v_{j}=u_{j-i+1}&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;j \gt i&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;v_{i}=1&amp;lt;/math&amp;gt;, если &amp;lt;math&amp;gt;u_{1}=0&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)&amp;lt;/math&amp;gt; для остальных значений. При этом &amp;lt;math&amp;gt;\gamma = 1+|u_{1}|=|v_{i}|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
После вычисления вектора &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; подстолбцы справа от ведущего модифицируются по формулам &amp;lt;math&amp;gt;x&#039;=x-\frac{(x,v)}{\gamma}v&amp;lt;/math&amp;gt;. Потом по аналогичным формулам модифицируются строки ниже ведущей. Благодаря ассоциативности этих операций после вычисления вектора &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; можно сразу выписать формулы модификации всех элементов справа и снизу от ведущих столбца и строки. Оказывается, что если для каждого столбца матрицы &amp;lt;math&amp;gt;x^{(j)}&amp;lt;/math&amp;gt; с номером &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt; известно &amp;lt;math&amp;gt;\beta_{j}=\frac{(x^{(j)},v)}{\gamma}&amp;lt;/math&amp;gt;, то для модифицируемого элемента матрицы &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; в позиции &amp;lt;math&amp;gt;(i,j)&amp;lt;/math&amp;gt; выполняется модификация &amp;lt;math&amp;gt;y&#039; = y - \beta_{i} v_{i} - \beta_{j} v_{j} - \Delta v_{i} v_{j}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\Delta = \frac{(\beta,v)}{\gamma}&amp;lt;/math&amp;gt;. При этом все эти модификации на каждом конкретном шаге алгоритма для разных пар &amp;lt;math&amp;gt;(i,j)&amp;lt;/math&amp;gt; можно выполнять независимо друг от друга, в том числе и параллельно.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
[[File:HausLeftSym1.png|thumb|right|800px|Рисунок 1. Граф первой половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером &amp;lt;math&amp;gt;n-4&amp;lt;/math&amp;gt;. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Зелёные кружки - операция вида &amp;lt;math&amp;gt;a+b^2&amp;lt;/math&amp;gt;, салатовые - операция вида &amp;lt;math&amp;gt;a+bc&amp;lt;/math&amp;gt;. Синий кружок - вычисление параметров матрицы отражения, светло-красные - вычисление коэффициентов &amp;lt;math&amp;gt;\beta_i&amp;lt;/math&amp;gt;для следующего полушага, жёлтые - вычисление вектора &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
[[File:HousLeftSym2.png|thumb|right|600px|Рисунок 2. Граф второй половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером &amp;lt;math&amp;gt;n-3&amp;lt;/math&amp;gt;. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Синий, жёлтые и светло-красные кружки выполняются на первом полушаге (см. Рисунок 1). Голубые кружки -  операции &amp;lt;math&amp;gt;a+bc&amp;lt;/math&amp;gt;, зелёное - умножение, чёрные - операции &amp;lt;math&amp;gt;a+bc+de+fce&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений &amp;lt;math&amp;gt;(s,s)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(x,v)&amp;lt;/math&amp;gt; для всех подстолбцов &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;справа от текущего, а также проводимые над нижним правым квадратом матрицы операции вида &amp;lt;math&amp;gt;y&#039;=y-ab-cd-fbd&amp;lt;/math&amp;gt;, с учётом симметрии матрицы.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений &amp;lt;math&amp;gt;(s,s)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(x,v)&amp;lt;/math&amp;gt; для всех подстолбцов &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; справа от текущего, а также массовые покомпонентные операции &amp;lt;math&amp;gt;y&#039;=y-ab-cd-fbd&amp;lt;/math&amp;gt;. При этом, однако, строгая последовательность выполнения первых двух подшагов не обязательна, в силу связи получаемых векторов &amp;lt;math&amp;gt;s&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; можно одновременно с &amp;lt;math&amp;gt;(s,s)&amp;lt;/math&amp;gt; вычислять и произведения &amp;lt;math&amp;gt;(x,s)&amp;lt;/math&amp;gt; с последующим выражением через них коэффициентов модификации. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательность выполнения алгоритма обычно записывается как последовательное &amp;quot;обнуление&amp;quot; поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним &amp;lt;math&amp;gt;(n-1)&amp;lt;/math&amp;gt;-м.&lt;br /&gt;
&lt;br /&gt;
При этом в каждом &amp;quot;обнуляемом&amp;quot; &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-м столбце &amp;quot;обнуляются&amp;quot; сразу все его поддиагональные элементы одновременно, с &amp;lt;math&amp;gt;(i+1)&amp;lt;/math&amp;gt;-го до &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;-го. &lt;br /&gt;
&lt;br /&gt;
Каждое &amp;quot;обнуление&amp;quot; &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го столбца состоит из двух шагов: &lt;br /&gt;
а) вычисление параметров матрицы отражения &amp;lt;math&amp;gt;U_{i}&amp;lt;/math&amp;gt; такой, чтобы при умножении на неё слева &amp;quot;обнулились&amp;quot; все поддиагональные его элементы;&lt;br /&gt;
б) одновременное умножение слева матрицы отражения &amp;lt;math&amp;gt;U_{i}&amp;lt;/math&amp;gt; и справа  матрицы отражения &amp;lt;math&amp;gt;U_{i}^T&amp;lt;/math&amp;gt;на текущую версию матрицы.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также массовых модификаций элементов вида &amp;lt;math&amp;gt;y&#039;=y-\alpha v - \beta w - \Delta vw&amp;lt;/math&amp;gt;. Они, если не учитывать возможную разреженность, составляют (в главном члене) по &amp;lt;math&amp;gt;O(n^3)&amp;lt;/math&amp;gt; операций действительного умножения и сложения/вычитания.&lt;br /&gt;
&lt;br /&gt;
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам &#039;&#039;с кубической сложностью&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунках 1 и 2 приведён граф алгоритма шага метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается от подстолбца, где выполняется очередное исключение, только одним элементом.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Для понимания ресурса параллелизма в симметричном приведении матрицы порядка &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; к трёхдиагональной методом Хаусхолдера нужно рассмотреть критический путь графа. &lt;br /&gt;
&lt;br /&gt;
Как видно из описания разных вершин, вычисления при &amp;quot;обнулении&amp;quot; &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по &amp;lt;math&amp;gt;2(n-i)&amp;lt;/math&amp;gt; умножений и сложений - и коррекции вычислений, которые составляют &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; операций. &lt;br /&gt;
&lt;br /&gt;
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt; сложений/вычитаний. &lt;br /&gt;
 &lt;br /&gt;
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида &amp;lt;math&amp;gt;a+bc&amp;lt;/math&amp;gt;.  &lt;br /&gt;
&lt;br /&gt;
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам &#039;&#039;с квадратичной сложностью&#039;&#039;. При классификации по ширине ЯПФ его сложность будет также &#039;&#039;квадратичной&#039;&#039; (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути).&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: плотная симметричная квадратная матрица &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; (элементы &amp;lt;math&amp;gt;a_{ij}&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;(n^2+n)/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: трёхдиагональная матрица &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; (ненулевые элементы &amp;lt;math&amp;gt;r_{ij}&amp;lt;/math&amp;gt; в последовательном варианте хранятся в элементах исходной матрицы &amp;lt;math&amp;gt;a_{ij}&amp;lt;/math&amp;gt;), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы &amp;lt;math&amp;gt;a_{ij}&amp;lt;/math&amp;gt; и в одном дополнительном столбце размерности n).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;(n^2+3n)/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Соотношение последовательной и параллельной сложности, как хорошо видно, является &#039;&#039;линейным&#039;&#039;, что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина &#039;&#039;квадратична&#039;&#039;, что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) &#039;&#039;линейным&#039;&#039; по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. &lt;br /&gt;
&lt;br /&gt;
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, &#039;&#039;линейна&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм в рамках выбранной версии полностью детерминирован.&lt;br /&gt;
&lt;br /&gt;
Вычислительная погрешность в методе отражений (Хаусхолдера) растет &#039;&#039;линейно&#039;&#039;, как и в методе Гивенса (вращений).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной симметричной вещественной матрицы к трёхдиагональному виду на Фортране 77 можно записать так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
	DO  I = 1, N-2&lt;br /&gt;
&lt;br /&gt;
            DO K = I, N&lt;br /&gt;
              SX(K)=A(N,I)*A(N,K)&lt;br /&gt;
            END DO&lt;br /&gt;
            DO J = N-1, I+1, -1&lt;br /&gt;
                 SX(I)=SX(I)+A(J,I)*A(J,I)&lt;br /&gt;
            END DO &lt;br /&gt;
            DO K = I+1, N&lt;br /&gt;
              DO J = N-1, K, -1&lt;br /&gt;
                 SX(K)=SX(K)+A(J,I)*A(J,K)&lt;br /&gt;
              END DO&lt;br /&gt;
              DO J = K-1,I+1,-1&lt;br /&gt;
                 SX(K)=SX(K)+A(J,I)*A(K,J)&lt;br /&gt;
              END DO&lt;br /&gt;
            END DO&lt;br /&gt;
&lt;br /&gt;
               ALPHA = SQRT (SX(I))&lt;br /&gt;
               IF (A(I+1,I).NE.0.) THEN&lt;br /&gt;
                  BETA = 1./ALPHA&lt;br /&gt;
                  DO J = I+2, N&lt;br /&gt;
                    A(J,I)=A(J,I)*BETA&lt;br /&gt;
                  END DO&lt;br /&gt;
                  SX(I) = A(I+1,I)*BETA+SIGN(1.,A(I+1,I))                     &lt;br /&gt;
                  A(I+1,I)=ALPHA&lt;br /&gt;
                  G=1./ABS(SX(I)) ! 1/gamma&lt;br /&gt;
                  SX2=0.&lt;br /&gt;
                  DO K = I+2, N&lt;br /&gt;
                     SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))&lt;br /&gt;
                     SX2=SX(K)*A(K,I)+SX2                     &lt;br /&gt;
                  END DO&lt;br /&gt;
                  SX2=G*SX2&lt;br /&gt;
                  DO K = I+2, N&lt;br /&gt;
                     A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2&lt;br /&gt;
                     DO J = K+1, N&lt;br /&gt;
                       A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)&lt;br /&gt;
                     END DO&lt;br /&gt;
                  END DO&lt;br /&gt;
               ELSE&lt;br /&gt;
                  IF (ALPHA.NE.0.) THEN&lt;br /&gt;
                     BETA = 1./ALPHA&lt;br /&gt;
                     DO J = I+2, N&lt;br /&gt;
                       A(J,I)=A(J,I)*BETA&lt;br /&gt;
                     END DO&lt;br /&gt;
                     SX(I) = -1. &lt;br /&gt;
                     A(I+1,I)=ALPHA&lt;br /&gt;
                     G=1.! 1/gamma&lt;br /&gt;
                     SX2=0.&lt;br /&gt;
                     DO K = I+2, N&lt;br /&gt;
                        SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))&lt;br /&gt;
                        SX2=SX(K)*A(K,I)+SX2                 &lt;br /&gt;
                     END DO&lt;br /&gt;
                     SX2=G*SX2&lt;br /&gt;
                     DO K = I+2, N  &lt;br /&gt;
                        A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2                   &lt;br /&gt;
                        DO J = K+1, N&lt;br /&gt;
                          A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)&lt;br /&gt;
                        END DO&lt;br /&gt;
                     END DO&lt;br /&gt;
                  ELSE&lt;br /&gt;
                     SX(I)=1.&lt;br /&gt;
                  END IF&lt;br /&gt;
               END IF               &lt;br /&gt;
&lt;br /&gt;
		&lt;br /&gt;
	END DO&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Здесь симметричная трёхдиагональная матрица хранится в диагонали и нижней кодиагонали массива A, вектора v - в поддиагональной части массива, за исключением первых их элементов, для которых выделен массив SX.&lt;br /&gt;
&lt;br /&gt;
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству.  &lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_1.png|thumb|center|700px|Рисунок 3. Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Данный профиль обладает явной итерационной структурой, при этом видно, что итерации очень похожи. Основное их отличие заключается в наборе используемых данных – на каждой следующей итерации несколько первых элементов отбрасываются их рассмотрения, то есть чем позже итерация, тем меньше данных в ней задействовано. Также можно отметить, что число обращений в каждой последующей итерации немного уменьшается. Для того чтобы проанализировать общий профиль, в таком случае чаще всего достаточно изучить одну итерацию. Рассмотрим самую первую из них более детально.&lt;br /&gt;
&lt;br /&gt;
На рис. 4 показан набор обращений в рамках первой итерации (выделен на рис. 3 зеленым). Его можно разбить на несколько фрагментов и рассмотреть их отдельно. Строение фрагментов 2-5 можно оценить по общему графику для итерации. Фрагмент 2 представляет собой набор последовательных переборов в обратном порядке с небольшим шагом, причем число элементов в переборе постепенно уменьшается. Такое строение характеризуется достаточно высокой пространственной локальностью, поскольку часто происходит обращений к близко расположенным данным, однако низкой временной, поскольку данные повторно не используются. То же самое верно и для фрагмента 4, основное отличие которого заключается в том, что переборы выполняются в прямом порядке. Отметим, что некоторое искривление данного фрагмента связано не со строением самого фрагмента, а с разным числом параллельно выполняющихся обращений (см., например, правую область фрагмента 6), чего не наблюдается при выполнении фрагмента 2.&lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, одна итерация]]&lt;br /&gt;
&lt;br /&gt;
Далее, фрагменты  3 и 5 состоят из небольшого числа обращений к данным, расположенным далеко друг от друга. Такие фрагменты характеризуются низкой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
Оставшиеся фрагменты требуют более детального рассмотрения. Перейдем к изучению фрагмента 1, который целиком представлен на рис. 5. Здесь мы можем видеть несколько этапов, каждый из которых представляет собой последовательный перебор элементов с шагом по памяти 1, причем в некоторых случаях данные много раз используются повторно (особенно это заметно в правой нижней области рисунка, где выполняется множество обращений к одному и тому же элементу). При условии, что в данном фрагменте задействовано всего около 40 элементов, можно говорить, что данный набор обращений обладает высокой пространственной и временной локальностью. &lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_3.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим детально фрагмент 6 (рис. 6). Здесь можно увидеть, что повторные обращения к данным выполняются не подряд, а через некоторые промежутки, однако число задействованных элементов также очень невелико, так что и в этом случае локальность (и пространственная, и временная) будет высокой.&lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_4.png|thumb|center|500px|Рисунок 6. Профиль обращений, одна итерация, фрагмент 6]]&lt;br /&gt;
&lt;br /&gt;
В целом можно сказать, что рассматриваемая итерация обладает достаточно высокой пространственной и временной локальностью. Поскольку остальные итерации устроены подобным образом, данные вывод можно перенести и на общий профиль обращений.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно высока – значение daps лишь немногим уступает тесту Linpack, который обладает высокой эффективностью работы с памятью, и немного превосходит, например, реализацию метода Якоби или метода Гаусса.&lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_daps.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости вширь реализации согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2&amp;quot; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [16:81] с шагом n^2;&lt;br /&gt;
*	размер матрицы [1000 : 16000] с шагом 1000.&lt;br /&gt;
&lt;br /&gt;
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:&lt;br /&gt;
&lt;br /&gt;
*	минимальная эффективность реализации 4.763e-05%;&lt;br /&gt;
*	максимальная эффективность реализации 0.011%.&lt;br /&gt;
&lt;br /&gt;
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации алгоритма в процедуре PDSYTRD из Scalapack в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:PDSYTRD Perf2.png|thumb|center|700px|Рисунок 8. Параллельная реализация метода Хаусхолдера QR-разложения. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]&lt;br /&gt;
[[file:PDSYTRD eff2.png|thumb|center|700px|Рисунок 9. Параллельная реализация метода Хаусхолдера QR-разложения. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация метода Хаусхолдера, представленная в пакете SCALAPACK библиотеки Intel MKL (метод pdgehrd).  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, сеть FDR Infiniband, а также компилятор Intel с опциями, рекомендованными Intel MKL Advisor. &lt;br /&gt;
На рисунках показана эффективность реализации метода Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду для размерности матрицы 15000, запуск проводился на 81 ядре.&lt;br /&gt;
&lt;br /&gt;
[[file:PDGETRD cpu.png|thumb|center|700px|Рисунок 10. График загрузки CPU при выполнении метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы средний уровень загрузки составляет около 90%. Это хорошая картина для программ, запущенных c использованием технологии Hyper Threading, т.е. всех виртуальных ядер, доступных для данной архитектуры. К концу итерации виден более высокий уровень интенсивности вычислений в сравнении с началом и серединой вычислений.&lt;br /&gt;
&lt;br /&gt;
[[file:PDGETRD LoadAvg.png|thumb|center|700px|Рисунок 11. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 140 и только к концу итерации снижается до 80. Это свидетельствует о стабильной работе программы на протяжении всего выполнения со 140 нитями на одном узле или около 10 нитей на 1 физическое ядро на каждом узле. Это указывает на статичную загрузку аппаратных ресурсов процессами, однако их число для узла слишком велико, что может указывать на не очень рациональные опции компиляции, которые могут приводить к снижению производительности из-за накладных расходов на переключение контекста между нитями.&lt;br /&gt;
&lt;br /&gt;
 [[file:PDGETRD L1.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов достаточно низкое и находится на уровне 5 млн/сек в среднем по всем узлам. Интересен факт снижения числа промахов к концу итерации, хотя снижение и не очень значительное, но вполне заметное.&lt;br /&gt;
&lt;br /&gt;
 [[file:PDGETRD L2.png|thumb|center|700px|Рисунок 13. График кэш-промахов L2 в секунду при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов тоже достаточно низкое и находится на уровне 1 млн/сек в среднем по всем узлам. На графике промахов второго уровня факт снижения числа промахов к концу итерации проявляется более явно и снижение более значительное и заметное (с 1.2 млн до 0.5 млн.).&lt;br /&gt;
&lt;br /&gt;
 [[file:PDGETRD L3.png|thumb|center|700px|Рисунок 14. График кэш-промахов L3 в секунду при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов достаточно небольшое и быстро убывает с уровня 90 тыс/сек до 0 в среднем по всем узлам. Это указывает на то, что задача достаточно хорошо укладывается в кэш-память третьего уровня, особенно к концу итерации.&lt;br /&gt;
[[file:PDGETRD Inf data.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно низкая интенсивность использования коммуникационной сети на каждой итерации. Причем к концу каждой итерации интенсивность передачи данных сильно снижается. Это указывает на бОльшую необходимость в обмене данными между процессами в начале итерации и большую локальность вычислений к концу итерации.&lt;br /&gt;
 [[file:PDGETRD Inf pckts.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается большая «кучность» показаний максимального минимального и среднего значений в сравнении с графиком скорости передачи в байт/сек. Это говорит о том, что, вероятно, процессы обмениваются сообщениями различной длины, что указывает на неравномерное распределение данных. Также наблюдается рост интенсивности использования сети к концу каждой итерации. Особенно это различие заметно к концу итерации.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно и эффективно использовала память. Использование памяти и коммуникационной среды недостаточно интенсивное, что может указывать на снижение эффективности и увеличение производительности при существенном росте размера задачи.&lt;br /&gt;
Для существующей в SCALAPACK параллельной реализации характерно сильное снижение числа кэш-промахов и обменов к концу итерации.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для приведения к трехдиагональному виду именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
[[Категория:Разложения матриц]]&lt;br /&gt;
&lt;br /&gt;
[[En:The Householder (reflection) method for reducing symmetric matrices to tridiagonal form]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%B4%D0%BB%D1%8F_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D1%8B%D1%85_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86_%D0%BA_%D1%82%D1%80%D1%91%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%D1%83_%D0%B2%D0%B8%D0%B4%D1%83&amp;diff=23813</id>
		<title>Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%B4%D0%BB%D1%8F_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D1%8B%D1%85_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86_%D0%BA_%D1%82%D1%80%D1%91%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%D1%83_%D0%B2%D0%B8%D0%B4%D1%83&amp;diff=23813"/>
		<updated>2017-11-08T00:37:37Z</updated>

		<summary type="html">&lt;p&gt;Ikramov: /* Динамические характеристики и эффективность реализации алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{algorithm&lt;br /&gt;
| name              = Приведение симметричной вещественной матрицы к трёхдиагональному виду методом Хаусхолдера (отражений)&lt;br /&gt;
| serial_complexity = &amp;lt;math&amp;gt;O(n^3)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;2n^2+O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt;n^2/2&amp;lt;/math&amp;gt;&lt;br /&gt;
| input_data        = &amp;lt;math&amp;gt;(n^2+n)/2&amp;lt;/math&amp;gt;&lt;br /&gt;
| output_data       = &amp;lt;math&amp;gt;(n^2+3n)/2&amp;lt;/math&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]&lt;br /&gt;
&lt;br /&gt;
== Свойства и структура алгоритма ==&lt;br /&gt;
&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Метод Хаусхолдера&#039;&#039;&#039; (в советской математической литературе чаще называется &#039;&#039;&#039;методом отражений&#039;&#039;&#039;) используется для приведения симметричных вещественных матриц к трёхдиагональному виду, или, что то же самое, для разложения &amp;lt;math&amp;gt;A=QTQ^T&amp;lt;/math&amp;gt; (&amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; - ортогональная, &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; — симметричная трёхдиагональная матрица)&amp;lt;ref&amp;gt;В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.&amp;lt;/ref&amp;gt;. При этом матрица &amp;lt;math&amp;gt;Q&amp;lt;/math&amp;gt; хранится и используется не в своём явном виде, а в виде произведения матриц отражения&amp;lt;ref name=&amp;quot;VOLA&amp;quot;&amp;gt;Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.&amp;lt;/ref&amp;gt;. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием одномерного дополнительного массива. &lt;br /&gt;
&lt;br /&gt;
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В методе Хаусхолдера для выполнения &amp;lt;math&amp;gt;QTQ^T&amp;lt;/math&amp;gt;-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений) с последующим умножением на те же матрицы справа. &lt;br /&gt;
&lt;br /&gt;
{{Шаблон:Матрица отражений}}&lt;br /&gt;
&lt;br /&gt;
На &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-м шаге метода с помощью преобразования отражения &amp;quot;убираются&amp;quot; ненулевые поддиагональные элементы, начиная с &amp;lt;math&amp;gt;i+2&amp;lt;/math&amp;gt;-го в &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-м столбце. После умножения на эту же матрицу отражения справа автоматически убираются и ненулевые наддиагональные элементы, начиная с &amp;lt;math&amp;gt;i+2&amp;lt;/math&amp;gt;-го в &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-й строке, а полученная модификация снова приобретает симметричный вид. &lt;br /&gt;
&lt;br /&gt;
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде &amp;lt;math&amp;gt;U=E-\frac{1}{\gamma}vv^*&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; находится через координаты текущего &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го столбца так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;s&amp;lt;/math&amp;gt; - вектор размерности &amp;lt;math&amp;gt;n+1-i&amp;lt;/math&amp;gt;, составленный из элементов &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го столбца, начиная с &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го.&lt;br /&gt;
&lt;br /&gt;
Если &amp;lt;math&amp;gt;(s,s)=0&amp;lt;/math&amp;gt;, то &amp;lt;math&amp;gt;v=e_{i}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;\gamma = \frac{1}{2}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
В остальных случаях по алгоритму вычисляется &amp;lt;math&amp;gt;u = \frac{1}{\sqrt{(s,s)}}s&amp;lt;/math&amp;gt;, и далее &amp;lt;math&amp;gt;v_{j}=0&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;j \lt i&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;v_{j}=u_{j-i+1}&amp;lt;/math&amp;gt; при &amp;lt;math&amp;gt;j \gt i&amp;lt;/math&amp;gt;, а &amp;lt;math&amp;gt;v_{i}=1&amp;lt;/math&amp;gt;, если &amp;lt;math&amp;gt;u_{1}=0&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)&amp;lt;/math&amp;gt; для остальных значений. При этом &amp;lt;math&amp;gt;\gamma = 1+|u_{1}|=|v_{i}|&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
После вычисления вектора &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; подстолбцы справа от ведущего модифицируются по формулам &amp;lt;math&amp;gt;x&#039;=x-\frac{(x,v)}{\gamma}v&amp;lt;/math&amp;gt;. Потом по аналогичным формулам модифицируются строки ниже ведущей. Благодаря ассоциативности этих операций после вычисления вектора &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; можно сразу выписать формулы модификации всех элементов справа и снизу от ведущих столбца и строки. Оказывается, что если для каждого столбца матрицы &amp;lt;math&amp;gt;x^{(j)}&amp;lt;/math&amp;gt; с номером &amp;lt;math&amp;gt;j&amp;lt;/math&amp;gt; известно &amp;lt;math&amp;gt;\beta_{j}=\frac{(x^{(j)},v)}{\gamma}&amp;lt;/math&amp;gt;, то для модифицируемого элемента матрицы &amp;lt;math&amp;gt;y&amp;lt;/math&amp;gt; в позиции &amp;lt;math&amp;gt;(i,j)&amp;lt;/math&amp;gt; выполняется модификация &amp;lt;math&amp;gt;y&#039; = y - \beta_{i} v_{i} - \beta_{j} v_{j} - \Delta v_{i} v_{j}&amp;lt;/math&amp;gt;, где &amp;lt;math&amp;gt;\Delta = \frac{(\beta,v)}{\gamma}&amp;lt;/math&amp;gt;. При этом все эти модификации на каждом конкретном шаге алгоритма для разных пар &amp;lt;math&amp;gt;(i,j)&amp;lt;/math&amp;gt; можно выполнять независимо друг от друга, в том числе и параллельно.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
[[File:HausLeftSym1.png|thumb|right|800px|Рисунок 1. Граф первой половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером &amp;lt;math&amp;gt;n-4&amp;lt;/math&amp;gt;. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Зелёные кружки - операция вида &amp;lt;math&amp;gt;a+b^2&amp;lt;/math&amp;gt;, салатовые - операция вида &amp;lt;math&amp;gt;a+bc&amp;lt;/math&amp;gt;. Синий кружок - вычисление параметров матрицы отражения, светло-красные - вычисление коэффициентов &amp;lt;math&amp;gt;\beta_i&amp;lt;/math&amp;gt;для следующего полушага, жёлтые - вычисление вектора &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
[[File:HousLeftSym2.png|thumb|right|600px|Рисунок 2. Граф второй половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером &amp;lt;math&amp;gt;n-3&amp;lt;/math&amp;gt;. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Синий, жёлтые и светло-красные кружки выполняются на первом полушаге (см. Рисунок 1). Голубые кружки -  операции &amp;lt;math&amp;gt;a+bc&amp;lt;/math&amp;gt;, зелёное - умножение, чёрные - операции &amp;lt;math&amp;gt;a+bc+de+fce&amp;lt;/math&amp;gt;.]]&lt;br /&gt;
&lt;br /&gt;
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений &amp;lt;math&amp;gt;(s,s)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(x,v)&amp;lt;/math&amp;gt; для всех подстолбцов &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt;справа от текущего, а также проводимые над нижним правым квадратом матрицы операции вида &amp;lt;math&amp;gt;y&#039;=y-ab-cd-fbd&amp;lt;/math&amp;gt;, с учётом симметрии матрицы.&lt;br /&gt;
&lt;br /&gt;
=== Макроструктура алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений &amp;lt;math&amp;gt;(s,s)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;(x,v)&amp;lt;/math&amp;gt; для всех подстолбцов &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; справа от текущего, а также массовые покомпонентные операции &amp;lt;math&amp;gt;y&#039;=y-ab-cd-fbd&amp;lt;/math&amp;gt;. При этом, однако, строгая последовательность выполнения первых двух подшагов не обязательна, в силу связи получаемых векторов &amp;lt;math&amp;gt;s&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; можно одновременно с &amp;lt;math&amp;gt;(s,s)&amp;lt;/math&amp;gt; вычислять и произведения &amp;lt;math&amp;gt;(x,s)&amp;lt;/math&amp;gt; с последующим выражением через них коэффициентов модификации. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательность выполнения алгоритма обычно записывается как последовательное &amp;quot;обнуление&amp;quot; поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним &amp;lt;math&amp;gt;(n-1)&amp;lt;/math&amp;gt;-м.&lt;br /&gt;
&lt;br /&gt;
При этом в каждом &amp;quot;обнуляемом&amp;quot; &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-м столбце &amp;quot;обнуляются&amp;quot; сразу все его поддиагональные элементы одновременно, с &amp;lt;math&amp;gt;(i+1)&amp;lt;/math&amp;gt;-го до &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;-го. &lt;br /&gt;
&lt;br /&gt;
Каждое &amp;quot;обнуление&amp;quot; &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го столбца состоит из двух шагов: &lt;br /&gt;
а) вычисление параметров матрицы отражения &amp;lt;math&amp;gt;U_{i}&amp;lt;/math&amp;gt; такой, чтобы при умножении на неё слева &amp;quot;обнулились&amp;quot; все поддиагональные его элементы;&lt;br /&gt;
б) одновременное умножение слева матрицы отражения &amp;lt;math&amp;gt;U_{i}&amp;lt;/math&amp;gt; и справа  матрицы отражения &amp;lt;math&amp;gt;U_{i}^T&amp;lt;/math&amp;gt;на текущую версию матрицы.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также массовых модификаций элементов вида &amp;lt;math&amp;gt;y&#039;=y-\alpha v - \beta w - \Delta vw&amp;lt;/math&amp;gt;. Они, если не учитывать возможную разреженность, составляют (в главном члене) по &amp;lt;math&amp;gt;O(n^3)&amp;lt;/math&amp;gt; операций действительного умножения и сложения/вычитания.&lt;br /&gt;
&lt;br /&gt;
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам &#039;&#039;с кубической сложностью&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
&lt;br /&gt;
На рисунках 1 и 2 приведён граф алгоритма шага метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается от подстолбца, где выполняется очередное исключение, только одним элементом.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Для понимания ресурса параллелизма в симметричном приведении матрицы порядка &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; к трёхдиагональной методом Хаусхолдера нужно рассмотреть критический путь графа. &lt;br /&gt;
&lt;br /&gt;
Как видно из описания разных вершин, вычисления при &amp;quot;обнулении&amp;quot; &amp;lt;math&amp;gt;i&amp;lt;/math&amp;gt;-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по &amp;lt;math&amp;gt;2(n-i)&amp;lt;/math&amp;gt; умножений и сложений - и коррекции вычислений, которые составляют &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; операций. &lt;br /&gt;
&lt;br /&gt;
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt; умножений и &amp;lt;math&amp;gt;n^2&amp;lt;/math&amp;gt; сложений/вычитаний. &lt;br /&gt;
 &lt;br /&gt;
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида &amp;lt;math&amp;gt;a+bc&amp;lt;/math&amp;gt;.  &lt;br /&gt;
&lt;br /&gt;
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам &#039;&#039;с квадратичной сложностью&#039;&#039;. При классификации по ширине ЯПФ его сложность будет также &#039;&#039;квадратичной&#039;&#039; (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути).&lt;br /&gt;
&lt;br /&gt;
=== Входные и выходные данные алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Входные данные&#039;&#039;&#039;: плотная симметричная квадратная матрица &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; (элементы &amp;lt;math&amp;gt;a_{ij}&amp;lt;/math&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём входных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;(n^2+n)/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Выходные данные&#039;&#039;&#039;: трёхдиагональная матрица &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; (ненулевые элементы &amp;lt;math&amp;gt;r_{ij}&amp;lt;/math&amp;gt; в последовательном варианте хранятся в элементах исходной матрицы &amp;lt;math&amp;gt;a_{ij}&amp;lt;/math&amp;gt;), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы &amp;lt;math&amp;gt;a_{ij}&amp;lt;/math&amp;gt; и в одном дополнительном столбце размерности n).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &amp;lt;math&amp;gt;(n^2+3n)/2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Свойства алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Соотношение последовательной и параллельной сложности, как хорошо видно, является &#039;&#039;линейным&#039;&#039;, что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина &#039;&#039;квадратична&#039;&#039;, что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) &#039;&#039;линейным&#039;&#039; по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. &lt;br /&gt;
&lt;br /&gt;
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, &#039;&#039;линейна&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм в рамках выбранной версии полностью детерминирован.&lt;br /&gt;
&lt;br /&gt;
Вычислительная погрешность в методе отражений (Хаусхолдера) растет &#039;&#039;линейно&#039;&#039;, как и в методе Гивенса (вращений).&lt;br /&gt;
&lt;br /&gt;
== Программная реализация алгоритма ==&lt;br /&gt;
=== Особенности реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной симметричной вещественной матрицы к трёхдиагональному виду на Фортране 77 можно записать так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;fortran&amp;quot;&amp;gt;&lt;br /&gt;
	DO  I = 1, N-2&lt;br /&gt;
&lt;br /&gt;
            DO K = I, N&lt;br /&gt;
              SX(K)=A(N,I)*A(N,K)&lt;br /&gt;
            END DO&lt;br /&gt;
            DO J = N-1, I+1, -1&lt;br /&gt;
                 SX(I)=SX(I)+A(J,I)*A(J,I)&lt;br /&gt;
            END DO &lt;br /&gt;
            DO K = I+1, N&lt;br /&gt;
              DO J = N-1, K, -1&lt;br /&gt;
                 SX(K)=SX(K)+A(J,I)*A(J,K)&lt;br /&gt;
              END DO&lt;br /&gt;
              DO J = K-1,I+1,-1&lt;br /&gt;
                 SX(K)=SX(K)+A(J,I)*A(K,J)&lt;br /&gt;
              END DO&lt;br /&gt;
            END DO&lt;br /&gt;
&lt;br /&gt;
               ALPHA = SQRT (SX(I))&lt;br /&gt;
               IF (A(I+1,I).NE.0.) THEN&lt;br /&gt;
                  BETA = 1./ALPHA&lt;br /&gt;
                  DO J = I+2, N&lt;br /&gt;
                    A(J,I)=A(J,I)*BETA&lt;br /&gt;
                  END DO&lt;br /&gt;
                  SX(I) = A(I+1,I)*BETA+SIGN(1.,A(I+1,I))                     &lt;br /&gt;
                  A(I+1,I)=ALPHA&lt;br /&gt;
                  G=1./ABS(SX(I)) ! 1/gamma&lt;br /&gt;
                  SX2=0.&lt;br /&gt;
                  DO K = I+2, N&lt;br /&gt;
                     SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))&lt;br /&gt;
                     SX2=SX(K)*A(K,I)+SX2                     &lt;br /&gt;
                  END DO&lt;br /&gt;
                  SX2=G*SX2&lt;br /&gt;
                  DO K = I+2, N&lt;br /&gt;
                     A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2&lt;br /&gt;
                     DO J = K+1, N&lt;br /&gt;
                       A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)&lt;br /&gt;
                     END DO&lt;br /&gt;
                  END DO&lt;br /&gt;
               ELSE&lt;br /&gt;
                  IF (ALPHA.NE.0.) THEN&lt;br /&gt;
                     BETA = 1./ALPHA&lt;br /&gt;
                     DO J = I+2, N&lt;br /&gt;
                       A(J,I)=A(J,I)*BETA&lt;br /&gt;
                     END DO&lt;br /&gt;
                     SX(I) = -1. &lt;br /&gt;
                     A(I+1,I)=ALPHA&lt;br /&gt;
                     G=1.! 1/gamma&lt;br /&gt;
                     SX2=0.&lt;br /&gt;
                     DO K = I+2, N&lt;br /&gt;
                        SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))&lt;br /&gt;
                        SX2=SX(K)*A(K,I)+SX2                 &lt;br /&gt;
                     END DO&lt;br /&gt;
                     SX2=G*SX2&lt;br /&gt;
                     DO K = I+2, N  &lt;br /&gt;
                        A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2                   &lt;br /&gt;
                        DO J = K+1, N&lt;br /&gt;
                          A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)&lt;br /&gt;
                        END DO&lt;br /&gt;
                     END DO&lt;br /&gt;
                  ELSE&lt;br /&gt;
                     SX(I)=1.&lt;br /&gt;
                  END IF&lt;br /&gt;
               END IF               &lt;br /&gt;
&lt;br /&gt;
		&lt;br /&gt;
	END DO&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Здесь симметричная трёхдиагональная матрица хранится в диагонали и нижней кодиагонали массива A, вектора v - в поддиагональной части массива, за исключением первых их элементов, для которых выделен массив SX.&lt;br /&gt;
&lt;br /&gt;
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.&lt;br /&gt;
&lt;br /&gt;
=== Локальность данных и вычислений ===&lt;br /&gt;
&lt;br /&gt;
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству.  &lt;br /&gt;
&lt;br /&gt;
==== Локальность реализации алгоритма ====&lt;br /&gt;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_1.png|thumb|center|700px|Рисунок 3. Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Общий профиль обращений в память]]&lt;br /&gt;
&lt;br /&gt;
На рис. 3 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Данный профиль обладает явной итерационной структурой, при этом видно, что итерации очень похожи. Основное их отличие заключается в наборе используемых данных – на каждой следующей итерации несколько первых элементов отбрасываются их рассмотрения, то есть чем позже итерация, тем меньше данных в ней задействовано. Также можно отметить, что число обращений в каждой последующей итерации немного уменьшается. Для того чтобы проанализировать общий профиль, в таком случае чаще всего достаточно изучить одну итерацию. Рассмотрим самую первую из них более детально.&lt;br /&gt;
&lt;br /&gt;
На рис. 4 показан набор обращений в рамках первой итерации (выделен на рис. 3 зеленым). Его можно разбить на несколько фрагментов и рассмотреть их отдельно. Строение фрагментов 2-5 можно оценить по общему графику для итерации. Фрагмент 2 представляет собой набор последовательных переборов в обратном порядке с небольшим шагом, причем число элементов в переборе постепенно уменьшается. Такое строение характеризуется достаточно высокой пространственной локальностью, поскольку часто происходит обращений к близко расположенным данным, однако низкой временной, поскольку данные повторно не используются. То же самое верно и для фрагмента 4, основное отличие которого заключается в том, что переборы выполняются в прямом порядке. Отметим, что некоторое искривление данного фрагмента связано не со строением самого фрагмента, а с разным числом параллельно выполняющихся обращений (см., например, правую область фрагмента 6), чего не наблюдается при выполнении фрагмента 2.&lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, одна итерация]]&lt;br /&gt;
&lt;br /&gt;
Далее, фрагменты  3 и 5 состоят из небольшого числа обращений к данным, расположенным далеко друг от друга. Такие фрагменты характеризуются низкой пространственной и временной локальностью.&lt;br /&gt;
&lt;br /&gt;
Оставшиеся фрагменты требуют более детального рассмотрения. Перейдем к изучению фрагмента 1, который целиком представлен на рис. 5. Здесь мы можем видеть несколько этапов, каждый из которых представляет собой последовательный перебор элементов с шагом по памяти 1, причем в некоторых случаях данные много раз используются повторно (особенно это заметно в правой нижней области рисунка, где выполняется множество обращений к одному и тому же элементу). При условии, что в данном фрагменте задействовано всего около 40 элементов, можно говорить, что данный набор обращений обладает высокой пространственной и временной локальностью. &lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_3.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, фрагмент 1]]&lt;br /&gt;
&lt;br /&gt;
Далее рассмотрим детально фрагмент 6 (рис. 6). Здесь можно увидеть, что повторные обращения к данным выполняются не подряд, а через некоторые промежутки, однако число задействованных элементов также очень невелико, так что и в этом случае локальность (и пространственная, и временная) будет высокой.&lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_4.png|thumb|center|500px|Рисунок 6. Профиль обращений, одна итерация, фрагмент 6]]&lt;br /&gt;
&lt;br /&gt;
В целом можно сказать, что рассматриваемая итерация обладает достаточно высокой пространственной и временной локальностью. Поскольку остальные итерации устроены подобным образом, данные вывод можно перенести и на общий профиль обращений.&lt;br /&gt;
&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
&lt;br /&gt;
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.&lt;br /&gt;
&lt;br /&gt;
На рисунке 7 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно высока – значение daps лишь немногим уступает тесту Linpack, который обладает высокой эффективностью работы с памятью, и немного превосходит, например, реализацию метода Якоби или метода Гаусса.&lt;br /&gt;
&lt;br /&gt;
[[file:householder_qtq_daps.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки daps]]&lt;br /&gt;
&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
=== Масштабируемость алгоритма и его реализации ===&lt;br /&gt;
==== Масштабируемость алгоритма ====&lt;br /&gt;
==== Масштабируемость реализации алгоритма ====&lt;br /&gt;
Проведём исследование масштабируемости вширь реализации согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере &amp;quot;Ломоносов-2&amp;quot; [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].&lt;br /&gt;
&lt;br /&gt;
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: &lt;br /&gt;
&lt;br /&gt;
*	число процессоров [16:81] с шагом n^2;&lt;br /&gt;
*	размер матрицы [1000 : 16000] с шагом 1000.&lt;br /&gt;
&lt;br /&gt;
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:&lt;br /&gt;
&lt;br /&gt;
*	минимальная эффективность реализации 4.763e-05%;&lt;br /&gt;
*	максимальная эффективность реализации 0.011%.&lt;br /&gt;
&lt;br /&gt;
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации алгоритма в процедуре PDSYTRD из Scalapack в зависимости от изменяемых параметров запуска.&lt;br /&gt;
&lt;br /&gt;
[[file:PDSYTRD Perf2.png|thumb|center|700px|Рисунок 8. Параллельная реализация метода Хаусхолдера QR-разложения. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]&lt;br /&gt;
[[file:PDSYTRD eff2.png|thumb|center|700px|Рисунок 9. Параллельная реализация метода Хаусхолдера QR-разложения. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&gt;
Для проведения экспериментов использовалась реализация метода Хаусхолдера, представленная в пакете SCALAPACK библиотеки Intel MKL (метод pdgehrd).  Все результаты получены на суперкомпьютере «Ломоносов-2». Использовались процессоры Intel Xeon E5-2697v3, сеть FDR Infiniband, а также компилятор Intel с опциями, рекомендованными Intel MKL Advisor. &lt;br /&gt;
На рисунках показана эффективность реализации метода Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду для размерности матрицы 15000, запуск проводился на 81 ядре.&lt;br /&gt;
&lt;br /&gt;
[[file:PDGETRD cpu.png|thumb|center|700px|Рисунок 10. График загрузки CPU при выполнении метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы средний уровень загрузки составляет около 90%. Это хорошая картина для программ, запущенных c использованием технологии Hyper Threading, т.е. всех виртуальных ядер, доступных для данной архитектуры. К концу итерации виден более высокий уровень интенсивности вычислений в сравнении с началом и серединой вычислений.&lt;br /&gt;
&lt;br /&gt;
[[file:PDGETRD LoadAvg.png|thumb|center|700px|Рисунок 11. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно и приблизительно равняется 140 и только к концу итерации снижается до 80. Это свидетельствует о стабильной работе программы на протяжении всего выполнения со 140 нитями на одном узле или около 10 нитей на 1 физическое ядро на каждом узле. Это указывает на статичную загрузку аппаратных ресурсов процессами, однако их число для узла слишком велико, что может указывать на не очень рациональные опции компиляции, которые могут приводить к снижению производительности из-за накладных расходов на переключение контекста между нитями.&lt;br /&gt;
&lt;br /&gt;
 [[file:PDGETRD L1.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов достаточно низкое и находится на уровне 5 млн/сек в среднем по всем узлам. Интересен факт снижения числа промахов к концу итерации, хотя снижение и не очень значительное, но вполне заметное.&lt;br /&gt;
&lt;br /&gt;
 [[file:PDGETRD L2.png|thumb|center|700px|Рисунок 13. График кэш-промахов L2 в секунду при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов тоже достаточно низкое и находится на уровне 1 млн/сек в среднем по всем узлам. На графике промахов второго уровня факт снижения числа промахов к концу итерации проявляется более явно и снижение более значительное и заметное (с 1.2 млн до 0.5 млн.).&lt;br /&gt;
&lt;br /&gt;
 [[file:PDGETRD L3.png|thumb|center|700px|Рисунок 14. График кэш-промахов L3 в секунду при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике кэш-промахов последнего уровня видно, что число промахов достаточно небольшое и быстро убывает с уровня 90 тыс/сек до 0 в среднем по всем узлам. Это указывает на то, что задача достаточно хорошо укладывается в кэш-память третьего уровня, особенно к концу итерации.&lt;br /&gt;
[[file:PDGETRD Inf data.png|thumb|center|700px|Рисунок 15. График скорости передачи по сети Infiniband в байт/сек при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных по сети Infiniband наблюдается достаточно низкая интенсивность использования коммуникационной сети на каждой итерации. Причем к концу каждой итерации интенсивность передачи данных сильно снижается. Это указывает на бОльшую необходимость в обмене данными между процессами в начале итерации и большую локальность вычислений к концу итерации.&lt;br /&gt;
 [[file:PDGETRD Inf pckts.png|thumb|center|700px|Рисунок 16. График скорости передачи по сети Infiniband в пакетах/сек при работе метода Хаусхолдера (отражений) приведения матрицы к трёхдиагональному виду]]&lt;br /&gt;
&lt;br /&gt;
На графике скорости передачи данных в пакетах в секунду наблюдается большая «кучность» показаний максимального минимального и среднего значений в сравнении с графиком скорости передачи в байт/сек. Это говорит о том, что, вероятно, процессы обмениваются сообщениями различной длины, что указывает на неравномерное распределение данных. Также наблюдается рост интенсивности использования сети к концу каждой итерации. Особенно это различие заметно к концу итерации.&lt;br /&gt;
 &lt;br /&gt;
В целом, по данным системного мониторинга работы программы можно сделать вывод о том, что программа работала стабильно и эффективно использовала память. Использование памяти и коммуникационной среды недостаточно интенсивное, что может указывать на снижение эффективности и увеличение производительности при существенном росте размера задачи.&lt;br /&gt;
Для существующей в SCALAPACK параллельной реализации характерно сильное снижение числа кэш-промахов и обменов к концу итерации.&lt;br /&gt;
&lt;br /&gt;
=== Выводы для классов архитектур ===&lt;br /&gt;
&lt;br /&gt;
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.&lt;br /&gt;
&lt;br /&gt;
=== Существующие реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для QR-разложения матриц именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.&lt;br /&gt;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Статьи в работе]]&lt;br /&gt;
[[Категория:Разложения матриц]]&lt;br /&gt;
&lt;br /&gt;
[[En:The Householder (reflection) method for reducing symmetric matrices to tridiagonal form]]&lt;/div&gt;</summary>
		<author><name>Ikramov</name></author>
	</entry>
</feed>