\documentclass[12pt,fleqn,a4paper]{book}
%
%
%
\usepackage[koi8-r]{inputenc}
\usepackage[russian,english]{babel}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{amsfonts}
\usepackage{amsthm}
\usepackage{url}
\usepackage{EZlist}
%
%
%
\pagestyle{headings}
\newtheorem{theorem}{Теорема}[chapter]
\newtheorem{lemma}{Лемма}[chapter]
\newtheorem{proposition}{Утверждение}[chapter]
\newtheorem{fact}{Факт}[chapter]
\theoremstyle{definition}
\newtheorem{problem}{Задача}[chapter]
\newtheorem{exercise}{Упражнение}[chapter]
\newtheorem{example}{Пример}[chapter]
\newtheorem{definition}{Определение}[chapter]
\newtheorem{remark}{Замечание}[chapter]
\newtheorem{algorithm}{Алгоритм}[chapter]
\newcommand{\marginnote}[1]{}
\def\gap{\medskip\centerline{\fbox{\Huge\bfseries{ПРОБЕЛ В КОНСПЕКТЕ.}}}\medskip}
\advance\headheight by 7pt
\def\headsep{15mm}
\newcommand{\lecture}[3]{%
\def\rightmark{\fbox{\parbox{125mm}{\lecturername: \coursetitle%
\hfil\phantom{.}}}}
\def\leftmark{\fbox{\parbox{125mm}{Лекция {#1}. {#2}%
\hfil\phantom{.}}}}
\renewcommand\chaptername{Лекция}\renewcommand\thechapter{#1}\chapter{{#2}\\{\small }}
\thispagestyle{headings}}
%
%
%
\newcommand\lecturername{Э. А. Гирш}
\newcommand\coursetitle{``Информатика'' (отд.математики, 1 курс)}
%
%
%
%
%
%
\begin{document}
\selectlanguage{russian}
\sloppy
%
%
%
\lecture{5}{Алгоритмы на графах (I).\\\ {\small\vbox{Граф и его представление в машине. Поиск в глубину. Минимальное остовное дерево.}}}{2:15}
%
%
%
%
\section{Представление графа в машине}\marginnote{10}
\begin{definition}
Ориентированный граф $G$ --- это пара конечных множеств $(V,E)$,
где $V$ называется множеством вершин, а $E\subseteq V\times V$ ---
множеством ребер (ребро $(v_1,v_2)$ можно представить себе как
стрелку из $v_1$ в $v_2$). Будем считать, что в графе нет петель,
т.е. $\forall v\ (v,v)\notin E$.

Неориентированный граф отличается от ориентированного тем, что на ребрах не указано
направление, т.е. ребра --- множества, а не
упорядоченные пары; тогда множество ребер $E\subseteq \{\{u,v\}\,|\,u,v\in V, u\neq v\}$.

Часто рассматривают графы с весами --- когда каждому ребру приписано
некоторое число, называемое весом этого ребра.
\end{definition}

Поскольку множество $V$ конечно, можно считать его элементы последовательными
натуральными числами.

Вот некоторые способы представить граф в машине (выбор конкретного
способа определяется задачей --- в каком виде граф нам задан, и алгоритмом ---
какие операции нам нужны):
\begin{itemize}
\item матрица смежности:
\begin{itemize}
\item \leftskip -5mm двумерный массив: $A[i,j]=1 \Leftrightarrow (i,j)\in E$,
\item матрица с пропусками,
\item неявное задание (при помощи \emph{функции} $f$): $f(i,j)=1 \Leftrightarrow (i,j)\in E$;
\end{itemize}
\item массив (из $|V|$ элементов), в котором $A[i]$ ---
      массив/список/дерево ребер, исходящих из вершины $i$.
\end{itemize}
\begin{remark}
Время работы алгоритмов будем выражать не через длину входа,
а через $|E|$, $|V|$ и другие параметры графа --- это точнее
и охватывает большее число случаев --- например, когда граф
заведомо разреженный
(через длину входа всегда сможем выразить).
\end{remark}
%
\section{Поиск в глубину}\marginnote{20}
Рекурсивно обрабатываем вершины 
(ориентированного или неориентированного) графа
так, чтобы каждую вершину и каждое ребро обработать ровно один раз.

Именно, отмечаем вершину $v$, которую начали обрабатывать
(проставляем момент времени $b[v]$ начала ее обработки).
Затем \emph{последовательно} рассматриваем всех ее соседей $v_i$:
если $v_i$ еще не начали обрабатывать, рекурсивно
обрабатываем $v_i$ (заметим, что в процессе рекурсивной обработки
вершины $v_i$ мы можем обработать $v_{i+1}$!).
Закончив эти рекурсивные вызовы, проставим момент $e[v]$ конца обработки
вершины $v$.

Если после вызова этой рекурсивной процедуры из <<главной программы>>
в графе остались необработанные вершины, берем любую из них и обрабатываем 
той же процедурой --- и так,
пока необработанные вершины не кончатся.

Побочным эффектом поиска в глубину является возможность построения
\emph{леса поиска в глубину} --- совокупности ребер, по которым
мы шли, совершая рекурсивные вызовы (\emph{лес} --- это граф,
являющийся объединением нескольких не связанных между собой деревьев).

\begin{lemma}\label{lem:dfs-corr}
Каждая вершина будет обработана ровно один раз.
Каждое ориентированное ребро будет проверено (в цикле поиска соседей)
ровно один раз.
\end{lemma}
\begin{proof}
Мы никогда не начинаем обработку вершины, которую уже обрабатывали.
\end{proof}

\begin{lemma}\label{lem:dfs-time}
Поиск в глубину занимает $O(|E|+|V|)$ операций с вершинами и ребрами,
т.е. время $O((|E|+|V|)\log |V|)$.
\end{lemma}
\begin{proof}
Следует из леммы~\ref{lem:dfs-corr}.
Обращений к каждой вершине и данным, с ней связанным, --- 
не более, чем количество ее соседей 
(значит, всего таких обращений --- $O(|E|)$).
\end{proof}
\begin{remark}
Тут важно представление графа: матрица смежности не подойдет!
(Сразу получится время $O(|V|^2)$.) 
\end{remark}

\subsection{Топологическая сортировка}\marginnote{15}

\paragraph{Задача} о топологической сортировке вершин ориентированного графа:
расположить вершины ориентированного графа в таком порядке, чтобы все ребра шли от меньшей
(в этом порядке) вершины к большей.

\paragraph{Решение:} применить поиск в глубину и отсортировать вершины по
убыванию времени окончания обработки.

\begin{lemma}
В графе без (ориентированных) циклов
сортировка по убыванию времени окончания обработки
является топологической сортировкой. 
\end{lemma}
\begin{proof}
Пусть в графе имеется ребро $(v,w)$, хотя $e[v]<e[w]$.
Есть два варианта <<скобочной структуры>>: 
\smallskip
\begin{enumerate}
\item[\{\}():] $b[v]<e[v]<b[w]<e[w]$, но это противоречит тому, что
   поиск в глубину делает рекурсивный вызов для всех соседей $v$,
   обработка которых еще не началась (в частности, для $w$);
\smallskip
\item[\{()\}:] $b[w]<b[v]<e[v]<e[w]$, но это означает, что есть ориентированный
   путь из $w$ в $v$, т.е. имеется ориентированный цикл.
\end{enumerate}
\end{proof}

\begin{remark}
Если же в графе имеются ориентированные циклы,
то топологическую сортировку произвести невозможно.
\end{remark}

\subsection{Компоненты сильной связности}\marginnote{30}

\paragraph{Задача:} разбить ориентированный граф на компоненты сильной связности,
т.е. на подмножества вершин, в каждом из которых
имеется (ориентированный) путь из любой вершины в любую (в обоих направлениях), 
а для любых двух вершин $u$ и $v$ из разных компонент 
либо нет пути из $u$ в $v$, либо нет пути из $v$ в $u$.

\paragraph{Решение:}
\begin{itemize}
\item произвести поиск в глубину, найдя время окончания обработки
$e[v]$ для каждой вершины;
\item произвести поиск в глубину в графе $G^t$, отличающемся от графа
$G$ тем, что ребра в нем идут в 
противоположном направлении; причем следующую вершину $v$
для вызова процедуры рекурсивного поиска из <<главной программы>>
выбирать в порядке убывания $e[v]$;
\item полученные деревья поиска в графе $G^t$ и будут сильно связными компонентами.
\end{itemize}

\begin{lemma}\label{lem:subtree}
При поиске в глубину, если есть путь из $v$ в $w$,
и вершина $v$ начала обрабатываться до вершины $w$,
то вершина $w$ попадет в то же самое дерево
поиска, что и $v$.
\end{lemma}
\begin{proof}[Доказательство (индукция по длине пути).]
Пусть $y$ --- первая вершина на пути из $v$ в $w$,
не попавшая в то же дерево поиска, что и $v$,
а $x$ --- последняя попавшая туда вершина 
(т.е. $x$ идет непосредственно перед $y$ в этом пути).

Если бы обработка вершины $y$ еще не была начата,
когда началась обработка вершины $x$,
то $y$ безусловно попала бы в то же дерево поиска, что и $x$ (а значит,
то же, что и $v$). Если бы обработка $y$ была начата, но не закончена
до начала обработки $x$,
ситуация была бы такой же, поскольку одновременно могут обрабатываться
только вершины, попадающие в одно дерево поиска. Более того, то же самое
было бы, если бы обработка $y$ не была закончена до начала обработки $v$. 

Следовательно, вершина $y$ уже была обработана к тому моменту, когда
была начата обработка вершины $v$. По предположению индукции, $y$ и $w$
попали в одно и то же дерево поиска, но это противоречит тому, что 
обработка $w$ еще не была начата, когда начала обрабатываться вершина $v$!
\end{proof}

\begin{theorem}\label{components}
Описанная выше процедура корректна.
\end{theorem}
\begin{proof}[Доказательство (в котором мы пользуемся леммой~\ref{lem:subtree})]
1) Пусть две вершины $v$ и $w$ связаны в обе стороны, но не попали в одно
дерево поиска графа $G^t$. Пусть дерево, содержащее вершину $v$, возникло
раньше. Но тогда %по лемме \ref{lem:subtree}
$w$ обязана была попасть в это дерево.

2) Пусть пути из $v$ в $w$ нет, но они попали в одно дерево.
Достаточно показать, что имеется путь из
корня $r$ этого дерева в обе вершины $v$ и $w$ (пока мы только знаем,
что есть обратные пути).

Пусть $p$ --- последняя вершина на пути из $v$ в $r$ 
в дереве поиска для графа $G^t$, которая не лежит в той же компоненте,
что и $r$; а $q$ --- следующая за $p$ на этом пути.
Нам достаточно показать наличие пути из $q$ в $p$.

Поскольку $e[r]>e[p],\;e[q]$ в дереве поиска для исходного графа $G$,
имеется два варианта.
\smallskip
\begin{enumerate}
\item[(а)] 
Вершина $q$ лежит в поддереве с корнем $r$. 
Если $p$ лежит на пути из $r$ в $q$ в этом поддереве,
то получаем требуемое утверждение.
Если $p$ была обработана раньше $q$, то также
существует путь из $q$ в $p$ в этом дереве. 
Остается случай, когда $p$ начала обрабатываться
позже $q$ (и позже $r$!). Но, поскольку ее обработка закончилась все же раньше $r$,
то она попала в поддерево с корнем $r$.
\smallskip
\item[(б)] Вершина $q$ была полностью обработана до начала обработки $r$.
Если бы $p$ была обработана раньше $q$, но по ребру $(p,q)$ мы не пошли,
то существовал бы путь из $q$ в $p$.
Значит, $p$ --- родитель $q$.
Однако почему вершина $r$ не попала в поддерево поиска с корнем $q$?
Значит, на пути из $q$ в $r$ есть вершина $x$, которая уже начала
обрабатываться до того, как $q$ была обработана, --- т.е. есть
путь из $q$ в $p$ через $x$.
\end{enumerate}
\end{proof}

\subsection{Упражнения}\marginnote{5}
\begin{exercise}[Полусвязность]
Ориентированный граф называется полусвязным, 
если для любых двух его вершин $v$ и $w$
в нем имеется (ориентированный) путь либо из $v$ в $w$,
либо из $w$ в $v$.
Выяснить, является ли данный граф полусвязным.\qed
\end{exercise}

\begin{exercise}[Мосты и точки раздела]
Дан неориентированный связный граф.
Точкой раздела называется вершина, при удалении которой граф теряет связность.
Мостом называется ребро, при удалении которого граф теряет связность.
Найти в данном графе все мосты и точки раздела.\qed
\end{exercise}

%
\section{Построение минимального остовного дерева}\marginnote{20}
\emph{Остовное дерево} (неориентированного графа) --- это дерево,
содержащее все вершины графа и некоторые из его ребер.

Для данного графа с весами
построим остовное дерево с минимальным суммарным весом ребер. 
%
%
%
%
%
%
Будем <<растить>> дерево постепенно.
В процессе построения будет получаться некоторый лес, являющийся
подграфом будущего остовного дерева.
На каждом шаге будем добавлять к этому лесу ребро минимального возможного веса,
соединяющее некоторую компоненту связности $C$ с вершиной \emph{другой} компоненты
(минимум берется по всем ребрам, исходящим из компоненты $C$;
выбор компоненты $C$ --- произволен).

\begin{lemma}
Такой алгоритм корректен независимо от способа выбора
очередной компоненты (и ребра среди ребер одинакового веса).
\end{lemma}
\begin{proof}[Доказательство (индукция по построению дерева).]
Пусть, имея лес $F$, мы добавили к нему ребро $(u,v)$,
минимальное по весу из ребер, соединяющих некоторую компоненту $U$ 
с другими компонентами леса $F$.
Считая (по предположению индукции),
что существует минимальное остовное дерево $T$ исходного графа, содержащее лес $F$,
покажем, что существует и минимальное остовное дерево,
содержащее $F\cup\{(u,v)\}$.

Итак, пусть $T$ не содержит $(u,v)$.
Тем не менее, в $T$ есть путь $\gamma$ из $u$ в $v$ (не содержащий
ребра $(u,v)$). Рассмотрим первое ребро $e$ на пути $\gamma$,
выводящее за пределы компоненты $U$.
Наш алгоритм устроен так, что вес ребра $e$ --- не меньше веса ребра $(u,v)$.
Следовательно, $T\setminus \{e\}\cup \{(u,v)\}$ --- 
искомое минимальное остовное дерево.
\end{proof}

Имеется несколько возможных реализаций этого алгоритма. 

\medskip
\begin{description}
\item[Алгоритм Борувки.] % Bor{\r u}vka.] 
На очередном шаге выбираем для каждой вершины
(одновременно) ребро минимального веса, исходящее из нее.
Затем \emph{стягиваем каждую из получающихся из этих ребер компонент
связности в одну точку}.
%
%
%
%
\item[Алгоритм Крускала.] %Kruskal.]
      Рассматриваем все ребра в порядке, соответствующем весу;
      очередное ребро добавляем к лесу, если оно соединяет разные
      компоненты связности.
\item[Алгоритм Прима.] %Prim.]
      Конструируемый нами лес состоит из дерева и независимых вершин.
      Выбираем ребро минимального веса среди ребер, соединяющих вершины
      нашего дерева с остальными вершинами.
\end{description}
Легко видеть, что любой из этих методов можно реализовать за 
$O(|E|\log |V|)$ операций с вершинами \emph{и весами}.
Например, в первом из этих методов на каждом шаге количество вершин
уменьшается, как минимум, вдвое; каждый шаг можно реализовать за $O(|E|)$ операций
с вершинами и ребрами:
поиск минимального элемента занимает линейное время,
слияние списков, содержащих $|E|$ элементов, --- время $O(|E|)$;
при стягивании ребра, ведущие в вершины той же компоненты,
можно не удалять, а считать б\'ольшими по весу.
%
\end{document}

