<?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=Elijah</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=Elijah"/>
	<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/Elijah"/>
	<updated>2026-10-23T02:42:04Z</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24130</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24130"/>
		<updated>2017-11-22T10:47:27Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Математическое описание алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода  &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;, величины &amp;lt;math&amp;gt;D(W)&amp;lt;/math&amp;gt; определяются рекурсивно, &amp;lt;math&amp;gt;D(W)=1&amp;lt;/math&amp;gt; для концевых вершин.&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение остовного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритм производит инциализацию связанных компонент графа [1], с последующим выделением тривиальных компонент (размера 1 и 2) [2]; затем, производится инициализация уровней для всех вершин [3] при помощи последовательных поисков в ширину [4],[BFS], выполняемых до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее производится поиск ребер, вошедших в остовное дерево [5], с последовательным построением данного дерева на основе поиска в глубину [6],[7]. После этого друг за другом производится вычисление необходимых метрик для каждой из вершин L, H и D [8],[9]. Перед завершением работы алгоритма на основе данных метрик производится вычисление мостов в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально разрабатывался для параллельной реализации, его время работы составляет &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации связанных компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в обратном порядке обходе графа производится при помощи поиска в глубину [5],[6], который должен выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Каждое из вычислений величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;, а так же проверка ребер на свойство, является ли каждое их них мостом [8],[9],[10], может выполняться параллельно за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24129</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24129"/>
		<updated>2017-11-22T10:47:12Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Математическое описание алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода  &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;, величины &amp;lt;math&amp;gt;D(W)&amp;lt;/math&amp;gt; определяются рекурсивно,&amp;lt;math&amp;gt;D(W)=1&amp;lt;/math&amp;gt; для концевых вершин.&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение остовного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритм производит инциализацию связанных компонент графа [1], с последующим выделением тривиальных компонент (размера 1 и 2) [2]; затем, производится инициализация уровней для всех вершин [3] при помощи последовательных поисков в ширину [4],[BFS], выполняемых до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее производится поиск ребер, вошедших в остовное дерево [5], с последовательным построением данного дерева на основе поиска в глубину [6],[7]. После этого друг за другом производится вычисление необходимых метрик для каждой из вершин L, H и D [8],[9]. Перед завершением работы алгоритма на основе данных метрик производится вычисление мостов в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально разрабатывался для параллельной реализации, его время работы составляет &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации связанных компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в обратном порядке обходе графа производится при помощи поиска в глубину [5],[6], который должен выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Каждое из вычислений величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;, а так же проверка ребер на свойство, является ли каждое их них мостом [8],[9],[10], может выполняться параллельно за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24128</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24128"/>
		<updated>2017-11-22T10:45:28Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Математическое описание алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода  &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;, величины &amp;lt;math&amp;gt;D(W)&amp;lt;/math&amp;gt; определяются рекурсивно.&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение остовного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритм производит инциализацию связанных компонент графа [1], с последующим выделением тривиальных компонент (размера 1 и 2) [2]; затем, производится инициализация уровней для всех вершин [3] при помощи последовательных поисков в ширину [4],[BFS], выполняемых до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее производится поиск ребер, вошедших в остовное дерево [5], с последовательным построением данного дерева на основе поиска в глубину [6],[7]. После этого друг за другом производится вычисление необходимых метрик для каждой из вершин L, H и D [8],[9]. Перед завершением работы алгоритма на основе данных метрик производится вычисление мостов в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально разрабатывался для параллельной реализации, его время работы составляет &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации связанных компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в обратном порядке обходе графа производится при помощи поиска в глубину [5],[6], который должен выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Каждое из вычислений величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;, а так же проверка ребер на свойство, является ли каждое их них мостом [8],[9],[10], может выполняться параллельно за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24127</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24127"/>
		<updated>2017-11-22T10:43:29Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Ресурс параллелизма алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода  &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение остовного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритм производит инциализацию связанных компонент графа [1], с последующим выделением тривиальных компонент (размера 1 и 2) [2]; затем, производится инициализация уровней для всех вершин [3] при помощи последовательных поисков в ширину [4],[BFS], выполняемых до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее производится поиск ребер, вошедших в остовное дерево [5], с последовательным построением данного дерева на основе поиска в глубину [6],[7]. После этого друг за другом производится вычисление необходимых метрик для каждой из вершин L, H и D [8],[9]. Перед завершением работы алгоритма на основе данных метрик производится вычисление мостов в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально разрабатывался для параллельной реализации, его время работы составляет &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации связанных компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в обратном порядке обходе графа производится при помощи поиска в глубину [5],[6], который должен выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Каждое из вычислений величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt;, а так же проверка ребер на свойство, является ли каждое их них мостом [8],[9],[10], может выполняться параллельно за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24126</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24126"/>
		<updated>2017-11-22T10:33:06Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Информационный граф */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода  &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение остовного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритм производит инциализацию связанных компонент графа [1], с последующим выделением тривиальных компонент (размера 1 и 2) [2]; затем, производится инициализация уровней для всех вершин [3] при помощи последовательных поисков в ширину [4],[BFS], выполняемых до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее производится поиск ребер, вошедших в остовное дерево [5], с последовательным построением данного дерева на основе поиска в глубину [6],[7]. После этого друг за другом производится вычисление необходимых метрик для каждой из вершин L, H и D [8],[9]. Перед завершением работы алгоритма на основе данных метрик производится вычисление мостов в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24125</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24125"/>
		<updated>2017-11-22T10:32:13Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Информационный граф */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода  &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение остовного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы алгоритм производит инциализацию связанных компонент графа [1], с последующим выделением тривиальных компонент (размера 1 и 2) [2]; затем производится инициализация уровней для всех вершин [3] при помощи последовательных поисков в ширину [4],[BFS] выполняемых до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее производится поиск ребер, вошедших в остовное дерево [5], с последовательным построением данного дерева на основе поиска в глубину [6],[7]. После этого друг за другом производится вычисление необходимых метрик для каждой из вершин L, H и D [8],[9]. Перед завершением работы алгоритма на основе данных метрик производится вычисление мостов в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24124</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24124"/>
		<updated>2017-11-22T10:28:16Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Макроструктура алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода  &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение остовного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24123</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24123"/>
		<updated>2017-11-22T10:26:43Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Схема реализации последовательного алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода  &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение остовного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24121</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24121"/>
		<updated>2017-11-22T10:19:55Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Математическое описание алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода  &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение островного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24120</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24120"/>
		<updated>2017-11-22T10:19:23Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Математическое описание алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины &amp;lt;math&amp;gt;N(v) &amp;lt;/math&amp;gt; выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение островного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24118</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24118"/>
		<updated>2017-11-22T09:45:44Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Математическое описание алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w) &amp;gt; N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение островного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24117</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24117"/>
		<updated>2017-11-22T09:45:27Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Математическое описание алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;=N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение островного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24116</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=24116"/>
		<updated>2017-11-22T09:44:40Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Вычислительное ядро алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Наиболее вычислительно затратной частью алгоритма является поиск в ширину в исходном графе, вычисляющий величины &amp;lt;math&amp;gt;N(v)&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;
На первом шаге работы алгоритма строится остовный лес графа с использованием модифицированного алгоритма Шилоаха-Вишкина. &lt;br /&gt;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение островного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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:Tarjan-Vishkin biconnected components algorithm]]&lt;/div&gt;</summary>
		<author><name>Elijah</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=24115</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=24115"/>
		<updated>2017-11-22T09:41:53Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Масштабируемость алгоритма */&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;
Алгоритм обладает значительным потенциалом масштабируемости, так как, во-первых, поиск в ширину, на котором он основывается, поддается эффективному распараллеливанию либо при помощи квадратичного подхода, либо при помощи параллельных очередей (по очереди на процессор). Во-вторых, дополнительный ресурс параллелизма можно задействовать при параллельной обработке трех получающихся множеств вершин на каждом шаге алгоритма, используя механизмы 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>Elijah</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=24112</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=24112"/>
		<updated>2017-11-22T08:37:25Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Масштабируемость алгоритма */&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>Elijah</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=24111</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=24111"/>
		<updated>2017-11-22T08:36:04Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Масштабируемость алгоритма */&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>Elijah</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=24110</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=24110"/>
		<updated>2017-11-22T08:35:39Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Масштабируемость алгоритма */&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>Elijah</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=24039</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=24039"/>
		<updated>2017-11-19T14:10:03Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Возможные способы и особенности параллельной реализации алгоритма */&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>Elijah</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=24038</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=24038"/>
		<updated>2017-11-19T14:08:42Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Вычислительное ядро алгоритма */&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>Elijah</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=24037</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=24037"/>
		<updated>2017-11-19T14:07:01Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Вычислительное ядро алгоритма */&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>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22843</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22843"/>
		<updated>2017-08-15T14:26:14Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Схема реализации последовательного алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительно затратной частью алгоритма является поиск в ширину, вычисляющий величины &amp;lt;math&amp;gt;N(v)&amp;lt;/math&amp;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&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;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
&lt;br /&gt;
На первом шаге работы алгоритма строится остовный лес графа при помощи последовательных вызовов поисков в ширину от еще не посещенных на предыдущих шагах вершин. При необходимости может быть выполнен шаг Trim, выделяющий вершины, принадлежащие связанным компонентам единичного размера.&lt;br /&gt;
&lt;br /&gt;
После этого производится построение островного дерева при помощи поиска в глубину, с подсчетом величин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Затем производятся вычисления величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; по формулам, приведенным в пунктах b,c в предыдущем разделе.&lt;br /&gt;
&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22842</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22842"/>
		<updated>2017-08-15T14:07:57Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Ресурс параллелизма алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительно затратной частью алгоритма является поиск в ширину, вычисляющий величины &amp;lt;math&amp;gt;N(v)&amp;lt;/math&amp;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&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;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]]. &lt;br /&gt;
&lt;br /&gt;
Нумерация вершин &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; в порядке обходе графа поиском в глубину [5],[6] должно выполняться сугубо последовательно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций, и может, вообще говоря, серьезно снизить производительность реализации на параллельных архитектурах.&lt;br /&gt;
&lt;br /&gt;
Вычисление величин &amp;lt;math&amp;gt;L&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H&amp;lt;/math&amp;gt; и ребер, являющихся мостами [8],[9],[10] может выполняться каждое за &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;, а высота ЯПФ, вообще говоря, зависит от структуры входного графа (числа связанных компонент, и, как следствие, числа необходимых поисков в ширину).&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22841</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22841"/>
		<updated>2017-08-15T13:36:03Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Ресурс параллелизма алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительно затратной частью алгоритма является поиск в ширину, вычисляющий величины &amp;lt;math&amp;gt;N(v)&amp;lt;/math&amp;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&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;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах.&lt;br /&gt;
&lt;br /&gt;
Изначальные инициализации компонент и уровней вершин [1] и [3] могут выполняться параллельно за &amp;lt;math&amp;gt;O(|V|)&amp;lt;/math&amp;gt; операций. Шаг трим [2] так же может быть выполнен за &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; параллельных атомарных операций. Поиски в ширину от каждой корневой вершины [4],[BFS] производятся последовательно друг за другом, однако сами поиски в ширину обладают значительным образом параллелизма, описанным в соответствующем [[Поиск_в_ширину_(BFS)|разделе]].&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22840</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22840"/>
		<updated>2017-08-15T13:29:09Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Вычислительное ядро алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительно затратной частью алгоритма является поиск в ширину, вычисляющий величины &amp;lt;math&amp;gt;N(v)&amp;lt;/math&amp;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&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;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&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;
&#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;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22839</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22839"/>
		<updated>2017-08-15T13:28:11Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Вычислительное ядро алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительно затратной частью алгоритма является поиск в ширину, вычисляющий величины &amp;lt;math&amp;gt;N(v)&amp;lt;/math&amp;gt;, вычислительным ядром которого является обход вершин, смежной к выбранной вершине v с последующем добавлением еще не посещенных вершин в множество P. Данная операция выполняться на каждом шаге для каждой вершины v \in F.&lt;br /&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;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&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;
&#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;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22838</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22838"/>
		<updated>2017-08-15T13:24:46Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Вычислительное ядро алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Вычислительное ядро алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Вычислительно затратной частью алгоритма является поиск в ширину, вычислительным ядром которого является обход вершин, смежной к выбранной вершине v с последующем добавлением еще не посещенных вершин в множество P. Данная операция выполняться на каждом шаге для каждой вершины v \in F.&lt;br /&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;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&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;
&#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;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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=22837</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=22837"/>
		<updated>2017-08-15T13:21:19Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Входные и выходные данные алгоритма */&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22836</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22836"/>
		<updated>2017-08-15T13:20:54Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Входные и выходные данные алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&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;
&#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;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22835</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22835"/>
		<updated>2017-08-15T12:21:28Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Ресурс параллелизма алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln(|V|))&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;
&#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;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22834</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22834"/>
		<updated>2017-08-15T12:20:16Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Входные и выходные данные алгоритма */&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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln n)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt; процессорах.&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;math&amp;gt;O(|E|)&amp;lt;/math&amp;gt; в случае хранения массива принадлежности каждого ребра к множеству мостов в графе&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;math&amp;gt;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22833</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22833"/>
		<updated>2017-08-15T12:19:12Z</updated>

		<summary type="html">&lt;p&gt;Elijah: &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 &amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_width          = &amp;lt;math&amp;gt; O(|V|) &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;max 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;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln n)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt; процессорах.&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &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;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22832</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22832"/>
		<updated>2017-08-15T12:17:21Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Входные и выходные данные алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln n)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt; процессорах.&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; &lt;br /&gt;
&lt;br /&gt;
Список ребер, являющихся мостами в графе.&lt;br /&gt;
	&lt;br /&gt;
&#039;&#039;&#039;Объём выходных данных&#039;&#039;&#039;: &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;max O(|V|)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22831</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22831"/>
		<updated>2017-08-15T12:14:15Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|600px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D [8],[9]. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln n)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22830</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22830"/>
		<updated>2017-08-15T12:13:24Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее на рисунке 1 представлен информационный граф алгоритма, демонстрирующий его основные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|1000px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
В начале работы производится инциализация связанных компонент графа [1] и выделение тривиальных компонент (размера 1 и 2) [2], затем инициализация уровней для всех вершин [3] и последовательные поиски в ширину [4],[BFS] до тех пор, пока все вершины не будут размечены различными уровнями. &lt;br /&gt;
&lt;br /&gt;
Далее происходит поиск ребер, которые войдут в остовное дерево [5] и последовательное построение данного дерева на основе поиска в глубину [6],[7]. Затем друг за другом вычисляются необходимые метрики для каждой их вершин L, H и D. В конце на основе данных метрик вычисляются мосты в графе [10].&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln n)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22829</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22829"/>
		<updated>2017-08-15T11:24:16Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Информационный граф */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма.&lt;br /&gt;
&lt;br /&gt;
[[file:tarjan_vishkin_ig.png|thumb|center|1000px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln n)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</name></author>
	</entry>
	<entry>
		<id>https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Tarjan_vishkin_ig.png&amp;diff=22828</id>
		<title>Файл:Tarjan vishkin ig.png</title>
		<link rel="alternate" type="text/html" href="https://xn--80aecvaldz.xn--80ahdrnmvk.xn--p1ai/w/ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Tarjan_vishkin_ig.png&amp;diff=22828"/>
		<updated>2017-08-15T11:23:38Z</updated>

		<summary type="html">&lt;p&gt;Elijah: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22827</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22827"/>
		<updated>2017-08-15T11:21:59Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Последовательная сложность алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(|E| + |V|)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма. Информационный граф алгоритма — ориентированный граф, состоящий из вершин, соответствующих операциям алгоритма, и направленных дуг, соответствующих передаче данных (результаты одних операций передаются в качестве аргументов другим операциям) между ними. &lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan Vishkin.png|thumb|center|1000px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим подробнее данный информационный граф: в начале работы алгоритма происходит поиск в ширину (BFS) для построения дерева {1, 2}, затем поиск в глубину для нумерации вершин {3, 4, 5}, а затем подсчет величин Low, Disc {6} и сохранение ответа {7}.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln n)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22826</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22826"/>
		<updated>2017-08-15T11:21:29Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Макроструктура алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln |V|)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма. Информационный граф алгоритма — ориентированный граф, состоящий из вершин, соответствующих операциям алгоритма, и направленных дуг, соответствующих передаче данных (результаты одних операций передаются в качестве аргументов другим операциям) между ними. &lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan Vishkin.png|thumb|center|1000px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим подробнее данный информационный граф: в начале работы алгоритма происходит поиск в ширину (BFS) для построения дерева {1, 2}, затем поиск в глубину для нумерации вершин {3, 4, 5}, а затем подсчет величин Low, Disc {6} и сохранение ответа {7}.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln n)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22825</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0-%D0%92%D0%B8%D1%88%D0%BA%D0%B8%D0%BD%D0%B0_%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_%D0%B4%D0%B2%D1%83%D1%81%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D0%B8&amp;diff=22825"/>
		<updated>2017-08-15T11:20:29Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Общее описание алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Параллельный &#039;&#039;&#039;алгоритм Тарьяна-Вишкина&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&amp;gt; находит [[Связность в графах|компоненты двухсвязности]] неориентированного графа за время &amp;lt;math&amp;gt;O(\ln |V|)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(|V| + |E|)&amp;lt;/math&amp;gt; процессорах. Алгоритм может быть адаптирован для поиска [[Связность в графах|мостов]]. Эффективность алгоритма Тарьяна-Вишкина подтверждена в последнее время&amp;lt;ref&amp;gt;Edwards, James A, and Uzi Vishkin. “Better Speedups Using Simpler Parallel Programming for Graph Connectivity and Biconnectivity,” PMAM’12, 103–114, New York, USA: ACM Press, 2012. doi:10.1145/2141702.2141714&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;Guojing Cong, and David A Bader. “An Experimental Study of Parallel Biconnected Components Algorithms on Symmetric Multiprocessors (SMPs),” 45b, IEEE, 2005. doi:10.1109/IPDPS.2005.100.&amp;lt;/ref&amp;gt; как на системах архитектуры SMP, так и при вычислениях на GPU.&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&gt;
Для этого для каждой вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; определяется указатель на родительскую вершину &amp;lt;math&amp;gt;P(v)&amp;lt;/math&amp;gt;; на шаге инициализации &amp;lt;math&amp;gt;P(v):=v&amp;lt;/math&amp;gt;. Далее остовный лес строится чередованием двух параллельных операций:&lt;br /&gt;
&lt;br /&gt;
•	Удвоение указателей: &amp;lt;math&amp;gt;P(v):=P(P(v))&amp;lt;/math&amp;gt; ;&lt;br /&gt;
&lt;br /&gt;
•	Соединение: &amp;lt;math&amp;gt;P(P(v)):=P(w)&amp;lt;/math&amp;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;P(v)=v&amp;lt;/math&amp;gt;на текущем шаге),  &amp;lt;math&amp;gt;w&amp;lt;/math&amp;gt;– некоторая вершина, для которой существует ребро &amp;lt;math&amp;gt;(v,w)\in E&amp;lt;/math&amp;gt;. Ребро помечается, как принадлежащее остовному лесу . &lt;br /&gt;
&lt;br /&gt;
Далее производятся следующие шаги:&lt;br /&gt;
&lt;br /&gt;
•	перенумерация вершин в обратном порядке обхода (post-order);&lt;br /&gt;
&lt;br /&gt;
•	вычисление количества потомков &amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	вычисление значений &amp;lt;math&amp;gt;L(v)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;H(v)&amp;lt;/math&amp;gt; для каждой вершины;&lt;br /&gt;
&lt;br /&gt;
•	пометка рёбер &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостами, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-D(w)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Основной идеей для обеспечения завершения работы алгоритма за &amp;lt;math&amp;gt;O( \ln n)&amp;lt;/math&amp;gt; шагов является систематическое использование операции удвоения указателей.&lt;br /&gt;
&lt;br /&gt;
=== Схема реализации последовательного алгоритма ===&lt;br /&gt;
=== Последовательная сложность алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Последовательный вариант алгоритма аналогичен [[Алгоритм Тарьяна поиска компонент двусвязности|алгоритму Тарьяна]]&amp;lt;ref&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; и имеет линейную сложность &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Информационный граф ===&lt;br /&gt;
Далее представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма. Информационный граф алгоритма — ориентированный граф, состоящий из вершин, соответствующих операциям алгоритма, и направленных дуг, соответствующих передаче данных (результаты одних операций передаются в качестве аргументов другим операциям) между ними. &lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan Vishkin.png|thumb|center|1000px|Рисунок 1 – Информационный граф алгоритма Тарьяна-Вышкина]]&lt;br /&gt;
&lt;br /&gt;
Рассмотрим подробнее данный информационный граф: в начале работы алгоритма происходит поиск в ширину (BFS) для построения дерева {1, 2}, затем поиск в глубину для нумерации вершин {3, 4, 5}, а затем подсчет величин Low, Disc {6} и сохранение ответа {7}.&lt;br /&gt;
&lt;br /&gt;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Алгоритм изначально параллельный, время работы &amp;lt;math&amp;gt;O(\ln n)&amp;lt;/math&amp;gt; на &amp;lt;math&amp;gt;O(m + n)&amp;lt;/math&amp;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;
===== Структура обращений в память и качественная оценка локальности =====&lt;br /&gt;
===== Количественная оценка локальности =====&lt;br /&gt;
=== Возможные способы и особенности параллельной реализации алгоритма ===&lt;br /&gt;
&lt;br /&gt;
Программа, реализующая алгоритм Тарьяна-Вышкина, состоит из двух частей: CPU части, отвечающей за параллельные вычисления на многоядерных CPU, а так же копирование данных на GPU и общее управлениями вычислениями, и GPU части, отвечающей только за вычисления на  графическом ускорителе.&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;
&lt;br /&gt;
*	размер графа [2^18 : 2^26].&lt;br /&gt;
&lt;br /&gt;
Проведем отдельные исследования масштабируемости вширь реализации алгоритма Тарьяна-Вишкина.&lt;br /&gt;
&lt;br /&gt;
Основной характеристикой для сравнения было выбрано время выполнения, так как производительность (определения как TEPS то есть число ребер графа, которое алгоритм обрабатывает в секунду) для данной операции не отражает реальную эффективность обработки.&lt;br /&gt;
&lt;br /&gt;
Анализируя время выполнения можно оценить, насколько понижается эффективность обработки графа при увеличении его размера (данные перестают помещаться в кэш, в память GPU, узла и т.д.). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[file:Tarjan vishkin scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Тарьяна-Вишкина масштабируемость вширь: производительность в зависимости от размера графа]]&lt;br /&gt;
&lt;br /&gt;
=== Динамические характеристики и эффективность реализации алгоритма ===&lt;br /&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;/div&gt;</summary>
		<author><name>Elijah</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%C2%AB%D0%BC%D0%BE%D1%81%D1%82%D0%BE%D0%B2%C2%BB_%D0%B2_%D0%B3%D1%80%D0%B0%D1%84%D0%B5&amp;diff=22824</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%A2%D0%B0%D1%80%D1%8C%D1%8F%D0%BD%D0%B0_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%C2%AB%D0%BC%D0%BE%D1%81%D1%82%D0%BE%D0%B2%C2%BB_%D0%B2_%D0%B3%D1%80%D0%B0%D1%84%D0%B5&amp;diff=22824"/>
		<updated>2017-08-15T11:19:11Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Последовательная сложность алгоритма */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Свойства и структура алгоритма ==&lt;br /&gt;
=== Общее описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Алгоритм Тарьяна&#039;&#039;&#039; &amp;lt;ref&amp;gt;Tarjan, R Endre. “A Note on Finding the Bridges of a Graph.” Information Processing Letters 2, no. 6 (April 1974): 160–61. doi:10.1016/0020-0190(74)90003-9.&amp;lt;/ref&amp;gt; находит [[Связность в графах|мосты]] в неориентированном графе за время &amp;lt;math&amp;gt;O(m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;math&amp;gt;T&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-&amp;gt;w&amp;lt;/math&amp;gt;, если в дереве  имеется &amp;lt;math&amp;gt;e=(v,w)&amp;lt;/math&amp;gt;, и вершина  находится дальше от корня &amp;lt;math&amp;gt;r&amp;lt;/math&amp;gt;, чем вершина &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt;. Далее будем считать дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; направленным графом, содержащим рёбра указанного вида.&lt;br /&gt;
 &lt;br /&gt;
•	&amp;lt;math&amp;gt;v=&amp;gt;w&amp;lt;/math&amp;gt;, если в ориентированном дереве &amp;lt;math&amp;gt;T&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;
&lt;br /&gt;
•	&amp;lt;math&amp;gt;v***w&amp;lt;/math&amp;gt;, если в графе &amp;lt;math&amp;gt;G&amp;lt;/math&amp;gt; существует ребро &amp;lt;math&amp;gt;e=(v,w)&amp;lt;/math&amp;gt;, не принадлежащее дереву &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
•	&amp;lt;math&amp;gt;N(v)&amp;lt;/math&amp;gt; – нумерация вершин в обратном порядке обхода вершин (post-order).&lt;br /&gt;
•	&amp;lt;math&amp;gt;D(v)&amp;lt;/math&amp;gt; – количество потомков вершины &amp;lt;math&amp;gt;v&amp;lt;/math&amp;gt; в ориентированном дереве &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;, то есть .&lt;br /&gt;
•	&amp;lt;math&amp;gt;S(v)={w | v =&amp;gt; w \or \exist u(v =&amp;gt; u \and u***w}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
•	&amp;lt;math&amp;gt;L(v) = \min S(v)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H(v) = \max S(v)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Алгоритм Тарьяна основан на следующем свойстве: ребро  является мостом тогда и только тогда, когда &amp;lt;math&amp;gt;v-&amp;gt;w, H(w) &amp;lt;= N(w), L(w) &amp;gt; N(w) - D(w)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Математическое описание алгоритма ===&lt;br /&gt;
&lt;br /&gt;
1.	Для каждой компоненты связности графа найти какое-либо остовное дерево &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;.&lt;br /&gt;
2.	Перенумеровать вершины &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; в порядке обратного обхода.&lt;br /&gt;
&lt;br /&gt;
3.	В порядке возрастания номера вершины выполнить следующие действия:&lt;br /&gt;
&lt;br /&gt;
a.	&amp;lt;math&amp;gt;D(v) := 1+ \sum_{v \rightarrow w}D(w) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
b.	&amp;lt;math&amp;gt; L(v) := \min { \{N(v) - D(v)+1 \} \cup \{ L(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c.	&amp;lt;math&amp;gt; H(v) := \max { \{ N(v) \} \cup \{ H(w) | v \rightarrow w \} \cup \{ N(w) | v \cdots w \} }&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
d.	Пометить ребро &amp;lt;math&amp;gt;v \rightarrow w&amp;lt;/math&amp;gt; мостом, если &amp;lt;math&amp;gt;H(w)&amp;lt;=N(w)&amp;lt;/math&amp;gt; и &amp;lt;math&amp;gt;L(w)&amp;gt;N(w)-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;
&lt;br /&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;
Алгоритм Тарьяна может работать с любым остовным деревом, поэтому можно применить эффективно параллелизуемый [[Поиск в ширину (BFS)|поиск в ширину]]. Последующие вычисления также могут быть параллелизованы.&lt;br /&gt;
&lt;br /&gt;
Параллельный [[Алгоритм Тарьяна-Вишкина поиска компонент двусвязности|алгоритм Тарьяна-Вишкина]]&amp;lt;ref&amp;gt;Tarjan, Robert Endre, and Uzi Vishkin. “An Efficient Parallel Biconnectivity Algorithm.” SIAM Journal on Computing 14, no. 4 (1985): 862–74.&amp;lt;/ref&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;
&lt;br /&gt;
== Литература ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Категория:Начатые статьи]]&lt;/div&gt;</summary>
		<author><name>Elijah</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=22823</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=22823"/>
		<updated>2017-08-15T11:17:17Z</updated>

		<summary type="html">&lt;p&gt;Elijah: &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;/div&gt;</summary>
		<author><name>Elijah</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=22822</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=22822"/>
		<updated>2017-08-15T11:16:23Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Ресурс параллелизма алгоритма */&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|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;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|)&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;/div&gt;</summary>
		<author><name>Elijah</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=22821</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=22821"/>
		<updated>2017-08-15T11:13:30Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Ресурс параллелизма алгоритма */&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|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;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|)&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;, а высота ЯПФ зависит от числа шагов алгоритма, и ограничена сверху O(|V|).&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;/div&gt;</summary>
		<author><name>Elijah</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=22820</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=22820"/>
		<updated>2017-08-15T11:11:43Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Входные и выходные данные алгоритма */&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|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;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|)&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;
=== Входные и выходные данные алгоритма ===&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;/div&gt;</summary>
		<author><name>Elijah</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=22819</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=22819"/>
		<updated>2017-08-15T11:10:24Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Последовательная сложность алгоритма */&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|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;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|)&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;
=== Входные и выходные данные алгоритма ===&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},&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(m + 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;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &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;/div&gt;</summary>
		<author><name>Elijah</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=22818</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=22818"/>
		<updated>2017-08-15T08:40:34Z</updated>

		<summary type="html">&lt;p&gt;Elijah: &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|ln(|V|))&amp;lt;/math&amp;gt;&lt;br /&gt;
| pf_height         = &amp;lt;math&amp;gt;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|)&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;n&amp;lt;/math&amp;gt; вершинами и &amp;lt;math&amp;gt;m&amp;lt;/math&amp;gt; рёбрами составляет &amp;lt;math&amp;gt;O(m \ln(n))&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;
=== Входные и выходные данные алгоритма ===&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},&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(m + 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;
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более &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;/div&gt;</summary>
		<author><name>Elijah</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=22817</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=22817"/>
		<updated>2017-08-15T07:59:51Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Ресурс параллелизма алгоритма */&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|Рисунок 2. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&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|Рисунок 9. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов 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;/div&gt;</summary>
		<author><name>Elijah</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=22816</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=22816"/>
		<updated>2017-08-15T07:47:42Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Информационный граф */&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;
При использовании атомарных операций для вычисления минимума релаксация рёбер может производится параллельно. В этом случае потребуется &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;
=== Входные и выходные данные алгоритма ===&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|Рисунок 2. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&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|Рисунок 9. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов 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;/div&gt;</summary>
		<author><name>Elijah</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=22815</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=22815"/>
		<updated>2017-08-14T18:22:48Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Ресурс параллелизма алгоритма */&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;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях {2 и 3}, соответствующим операциям инициализации массива дистанций (2) и обновления массива c использованием данных массива ребер {3}. Операция {4} - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&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;
=== Ресурс параллелизма алгоритма ===&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; процессоров.&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|Рисунок 2. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&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|Рисунок 9. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов 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;/div&gt;</summary>
		<author><name>Elijah</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=22814</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=22814"/>
		<updated>2017-08-14T18:22:06Z</updated>

		<summary type="html">&lt;p&gt;Elijah: /* Свойства алгоритма */&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;
Нижний уровень параллелизма на графе алгоритма расположен на уровнях {2 и 3}, соответствующим операциям инициализации массива дистанций (2) и обновления массива c использованием данных массива ребер {3}. Операция {4} - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.&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;
=== Ресурс параллелизма алгоритма ===&lt;br /&gt;
&lt;br /&gt;
При использовании атомарных операций для вычисления минимума релаксация рёбер может производится параллельно. В этом случае потребуется &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt; шагов при использовании &amp;lt;math&amp;gt;O(m)&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|Рисунок 2. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]&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|Рисунок 9. График загрузки CPU при выполнении алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике загрузки процессора видно, что почти все время работы программы не загружены и средний уровень загрузки составляет около 5%. Это достаточно неэффективная картина даже для программ, запущенных c использованием одного ядра в реализации. &lt;br /&gt;
&lt;br /&gt;
[[file:Bellman-Ford LoadAvg.png|thumb|center|700px|Рисунок 10. График числа процессов, ожидающих вхождения в стадию счета (Loadavg), при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
&lt;br /&gt;
На графике числа процессов, ожидающих вхождения в стадию счета (Loadavg), видно, что на протяжении всей работы программы значение этого параметра постоянно на уровне 1. Это указывает на постоянную загрузку аппаратных ресурсов не более чем 1 процессом, однако их число для узла слишком мало, что с одной стороны может указывать на не очень рациональные использование ресурсов.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L1.png|thumb|center|700px|Рисунок 11. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов первого уровня видно, что число промахов очень высокое и находится на уровне 140 млн/сек это очень высокий показатель, показывающие потенциальную причину неэффективности.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford L2.png|thumb|center|700px|Рисунок 12. График кэш-промахов L1 в секунду при работе алгоритма Беллмана-Форда]]&lt;br /&gt;
 &lt;br /&gt;
На графике кэш-промахов второго уровня видно, что число промахов достаточно тоже крайне высокое и находится на уровне 140 млн/сек, что указывает на крайне неэффективную работу с памятью.&lt;br /&gt;
&lt;br /&gt;
 [[file:Bellman-Ford-L3.png|thumb|center|700px|Рисунок 13. График кэш-промахов 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;/div&gt;</summary>
		<author><name>Elijah</name></author>
	</entry>
</feed>