Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Костюк - Основы программирования

.pdf
Скачиваний:
134
Добавлен:
30.05.2015
Размер:
1.3 Mб
Скачать

121

6.4 Топологическая сортировка

Граф, в котором не существует ни одного цикла, называется ациклическим. В ориентированном ациклическом графе, имеющем n вершин, можно выполнить раз­ метку вершин числами 1, …, n таким образом, что если из вершины i есть дуга в вершину j, то метка вершины i должна быть меньше метки вершины j. Такая раз­ метка называется топологической сортировкой.

Как доказывается в теории графов, в ориентированном ациклическом графе обя­ зательно существует хотя бы одна вершина, в которую не входит ни одна дуга. Тогда этой вершине можно приписать метку номер 1. После удаления из графа этой верши­ ны и выходящих из нее ребер в графе появится (если не было раньше) еще хотя бы одна вершина, в которую не входит ни одна дуга, ей приписывается следующая мет­ ка, и т.д., пока в графе еще остаются непомеченные вершины. Такой алгоритм вычис­ ляет одну из возможных топологических сортировок, которых может существовать несколько для одного и того же графа.

Пример 6.9. Алгоритм топологической сортировки ориентированного ацикличе­ ского графа из n вершин. Пусть граф задан в виде массива смежных вершин.

На первом этапе требуется вычислить количество ребер, входящих в каждую из вершин графа, и записать эти числа в массив R. Для этого можно использовать алго­ ритм (6.9). На последующих этапах алгоритма в массив P вначале записываются но­

мера вершин, в которые не входит ни одно из ребер. Затем для каждой записанной в массив P вершины i как бы удаляются выходящие из нее ребра (на самом деле из каждого элемента j массива R вычитается 1, если из вершины i есть дуга в верши­

ну j). При этом, как только обнаружится новая вершина с нулевым числом входя­ щих в нее ребер, она также выписывается в массив P. Алгоритм заканчивает свою работу, когда не останется вершин в графе, которые еще можно записать в массив P.

k:=0; t:=1;

for i:=1 to n do

if R[i]=0 then begin k:=k+1; P[k]:=i

end;

while t<=k do begin i:=P[t];

for j:=S[i] to S[i]+L[i]-1 do (6.12) begin q:=D[j]; R[q]:= R[q]-1;

if R[q]=0 then begin k:=k+1; P[k]:=q

end end; t:=t+1

end;

122

Трудоемкость алгоритма (6.12) – линейная от числа дуг в графе, так как каждая вершина просматривается по разу, и при этом просматриваются все выходящие из нее дуги.

Заметим также, что если граф не является ациклическим, то наступает момент при выполнении цикла while, когда нет ни одной вершины с нулевым количеством

входящих в нее ребер, хотя еще не все вершины записаны в массив P. В этом случае цикл while прекратит выполнение досрочно, и при этом окажется, что k<n. По­

следнее соотношение легко проверить после завершения алгоритма (6.12).

Конец примера.

Сформированная алгоритмом (6.12) в массиве P топологическая сортировка со­

держит номера вершин графа, для которых выполняется условие: k<l, если между вершинами P[k] и P[l] вообще нет дуги, либо есть дуга из P[k] в P[l], но

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

Обратная перестановка определяется следующим образом. Заполним элементы в массиве Q числами 1, …, n, так, что Q[i]=i. Тогда на i–м месте в массиве то­

пологической сортировки P находится вершина P[Q[i]] графа. Если отсортиро­ вать массив P с одновременной перестановкой элементов массива Q, то Q и будет массивом обратной перестановки. На самом деле ничего сортировать не нужно: су­ ществует гораздо более простой алгоритм.

Пример 6.10. Алгоритм вычисления обратной перестановки. Задан массив P, n элементов которого образует некоторую перестановку чисел 1, …, n. Алгоритм (6.13) вычисляет в массиве Q обратную перестановку чисел 1, …, n.

 

 

 

 

for i:=1 to n do

(6.13)

 

Q[P[i]]:=i;

 

Конец примера.

 

Вопросы и задания

1.Неориентированный граф задан списком ребер. Написать программу, которая вводит этот список и формирует матрицу смежности графа.

2.Ориентированный взвешенный граф задан списком дуг и их весами. Написать программу, которая вводит этот список и формирует матрицу смежности графа.

3.Ориентированный взвешенный граф задан списком дуг и их весами. Используя алгоритм (6.1), написать программу, которая вводит этот список и формирует представление графа списками смежных вершин с весами.

4.Неориентированный взвешенный граф задан списком ребер и их весами. Используя алго­ ритм (6.2), написать программу, которая вводит этот список и формирует представление графа массивом смежных вершин с весами.

123

5.Ориентированный граф задан матрицей смежности. Написать программу, которая форми­ рует представление графа массивом смежных вершин.

6.Неориентированный граф задан матрицей смежности. Используя алгоритмы (6.3) и (6.4), написать программу, которая выделяет компоненты связности графа и для каждой компо­ ненты выводит следующие данные: 1) номер компоненты; 2) номера входящих в нее вер­ шин.

7.Неориентированный граф задан массивом смежных вершин. Используя алгоритмы (6.4) и (6.6), написать программу, которая выделяет компоненты связности графа и для каждой компоненты выводит следующие данные: 1) номер компоненты; 2) номера входящих в нее вершин.

8.Ориентированный граф задан массивом смежных вершин. Задан также номер u одной из

вершин. Используя алгоритм (6.6), написать программу, которая для всех вершин подсчи­ тывает до них расстояние (в количестве дуг) от вершины u.

9.Используя алгоритмы (6.7) и (6.8), написать программу, которая вводит лабиринт, задан­ ный в двумерном массиве, как в примере 6.6, вычисляет и выводит наиболее короткий маршрут из начальной клетки в конечную.

10.Ориентированный граф задан массивом смежных вершин. Используя алгоритмы (6.7) и (6.8), написать программу, которая проверяет существование для этого графа эйлерова цикла и, если цикл существует, строит и выводит такой цикл.

11.Неориентированный граф задан матрицей смежности. Используя алгоритмы (6.7) и (6.8), написать программу, которая проверяет существование для этого графа эйлерова цикла и, если цикл существует, строит и выводит такой цикл.

12.Граф задан списками смежных вершин. Используя алгоритм (6.11), написать программу, которая строит и выводит все существующие гамильтоновы циклы в графе.

13.Граф задан массивом смежных вершин. Используя алгоритм (6.11), написать программу, которая строит и выводит все существующие гамильтоновы циклы в графе.

14.Пусть выполненная топологическая сортировка вершин некоторого ориентированного графа записана в массиве P. Что означает обратная к P перестановка?

15.Ориентированный граф задан массивом смежных вершин. Используя алгоритм (6.12), на­ писать программу, которая выполняет топологическую сортировку вершин графа. Если топологическая сортировка невозможна, то программа должна выдать об этом сообщение.

16.Ориентированный граф задан матрицей смежности. Используя алгоритм (6.12), написать программу, которая выполняет топологическую сортировку вершин графа. Если тополо­ гическая сортировка невозможна, то программа должна выдать об этом сообщение.

17.Ориентированный граф задан списками смежных вершин. Используя алгоритм (6.12), на­ писать программу, которая выполняет топологическую сортировку вершин графа. Если топологическая сортировка невозможна, то программа должна выдать об этом сообщение.

18.Доказать правильность алгоритма вычисления обратной перестановки (6.13).

Глава 7 Алгоритмы с векторами и матрицами

7.1 Сложение и умножение векторов и матриц

Векторы в алгоритмах представляются одномерными, а матрицы – двумерными массивами. Сложение и различные виды умножения векторов и матриц реализуются непосредственно по определению соответствующих операций.

Пример 7.1. Алгоритм вычисления суммы векторов (массивов) A и B ности n. Результат – вектор (массив) C.

for i:=1 to n do

C[i]:=A[i]+B[i];

Конец примера.

Пример 7.2. Алгоритм вычисления суммы матриц (двумерных массивов) размерности n×m. Результат – матрица (двумерный массив) C.

for i:=1 to n do for j:=1 to m do

C[i,j]:=A[i,j]+B[i,j];

Конец примера.

размер­

(7.1)

A и B

(7.2)

Пример 7.3. Алгоритм вычисления скалярного произведения векторов (масси­ вов) A и B размерности n. Результат – переменная d.

d:=0;

for i:=1 to n do (7.3) d:=d+A[i]*B[i];

Конец примера.

Пример 7.4. Алгоритм вычисления произведения вектора (одномерного массива) A размерности n и матрицы (двумерного массива) B размерности n×m. Результат

– вектор (одномерный массив) C размерности m. Векторы A и C являются векто­ рами-строками.

125

for j:=1 to m do begin d:=0;

for i:=1 to n do

d:=d+A[i]*B[i,j]; (7.4)

C[j]:=d

end;

Конец примера.

Пример 7.5. Алгоритм вычисления произведения матрицы (двумерного массива) A размерности n×m и вектора (одномерного массива) B размерности m. Результат

– вектор (одномерный массив) C размерности n. Векторы A и C являются векто­ рами-столбцами.

for i:=1 to n do begin d:=0;

for j:=1 to m do

d:=d+A[i,j]*B[j]; (7.5)

C[i]:=d

end;

Конец примера.

Пример 7.6. Алгоритм вычисления произведения матрицы (двумерного массива) A размерности n×m и матрицы (двумерного массива) B размерности m×k. Ре­ зультат – матрица (двумерный массив) C размерности n×k.

for i:=1 to n do for j:=1 to k do

begin d:=0;

for l:=1 to m do (7.6) d:=d+A[i,l]*B[l,j];

C[i,j]:=d

end;

Конец примера.

Трудоемкость приведенных алгоритмов определяется количеством повторений самого внутреннего цикла. В частности, трудоемкость алгоритма (7.6) вычисления произведения матриц имеет порядок n×k×m.

Для квадратных матриц наряду с операцией произведения определена операция возведения матрицы в положительную целочисленную степень. Для возведения в p

ю

степень достаточно (p – 1) раз перемножить матрицу саму на себя. При больших

p

можно использовать более эффективный алгоритм, который годится для возведе­

ния в целочисленную степень любых объектов. Требуется лишь, чтобы операция умножения для этих объектов обладала свойством ассоциативности (выполнение свойства коммутативности не обязательно).

126

Пример 7.7. Алгоритм возведения числа (объекта) x в положительную целочис­ ленную степень p. Результат – число (объект) z.

z:=1; s:=p; y:=x; while s>0 do begin

if odd(s) then begin

s:=s-1; z:=z*y (7.7) end;

s:=s div 2; y:=y*y end;

Завершимость алгоритма следует из того, что до выполнения цикла s≥1, а при каждом исполнении цикла s уменьшается (но остается неотрицательным, т.е. s=0

после окончания цикла).

Предусловие цикла: z=1, s=p, y=x; постусловие: s=1.

Инвариант: z*ys = xp. Правильность инварианта следует из соотношений z*ys = (z*y)*ys-1 = z*(z*y)s/2 и из соотношений предусловия.

Из правильности инварианта и постусловия следует, что после завершения цикла z = xp.

Подсчитаем количество выполнений цикла, учитывая, что при каждом исполне­

нии выполняется одно или два умножения. При каждом исполнении цикла число

s

уменьшается не менее

чем в 2 раза. Поэтому, если первоначально 2k-1

<

s = p ≤ 2k, то после

k шагов s=1, а после k+1 шагов цикл завершится. Таким

образом, общее количество умножений не превысит 2(1 + log2 p).

Конец примера.

Для возведения матрицы в степень в алгоритме (7.7) операцию умножения чисел в выражениях z*y и y*y необходимо заменить алгоритмом умножения матриц (7.6), а присваивание z:=1 – на присваивание единичной матрицы.

7.2 Решение систем линейных уравнений

Систему линейных уравнений можно представить в матричном виде:

 

 

(7.1)

A × x = b ,

где A – квадратная матрица коэффициентов уравнений, x – вектор-столбец неиз­ вестных, b – вектор-столбец правых частей.

Единственное решение такой системы существует, если матрица A невыро­ жденная, т.е. ее определитель не равен нулю. Известно много методов решения си­ стем линейных уравнений. Метод Гаусса состоит в том, что вначале матрица приво­ дится к треугольной, т.е. такой, у которой все элементы, лежащие ниже главной

127

диагонали, равны нулю. Это делается путем сложения одного из уравнений с другим, умноженным на специально подобранный коэффициент, в результате коэффициент при одной из переменных становится нулевым, и этот процесс (прямой ход) продол­ жается до получения треугольной матрицы. Далее тем же способом обнуляются эле­ менты, лежащие выше главной диагонали (этот этап называют обратным ходом). В результате матрица становится диагональной, по ней неизвестные вычисляются пу­ тем деления правой части на соответствующий диагональный коэффициент в левой части уравнения.

Рассмотрим промежуточный этап прямого хода, когда требуется обнулить коэф­

фициенты ai + 1,i , ai + 2,i , …, an,i

(в примере формулы i = 3):

 

 

 

éa11

a12

a13

...

a1n

ù

é x1

ù

éb1

ù

 

 

ê

0

a22

a23

...

 

ú

ê

ú

ê

ú

 

 

ê

a2n ú

êx2

ú

êb2

ú

 

 

ê

0

0

a33

...

a3n

ú ×

êx3

ú

= êb3

ú

(7.2)

 

ê

 

 

 

 

 

ú

ê

ú

ê

ú

 

 

ê

 

 

 

 

 

ú

ê

ú

ê

ú

 

 

ê

0

0

an3

...

 

ú

ê

ú

ê

ú

 

 

ë

ann û

ëxn

û

ëbn

û

 

Для этого из k–го

уравнения,

k = i + 1, …, n, необходимо вычесть i–е уравне­

ние, умноженное на коэффициент

c = ak ,i / ai,i .

При этом возникает следующая

проблема: делитель ai,i

может оказаться равным нулю, и тогда коэффициент

c вы­

числить невозможно. Более того, если этот коэффициент не равен, но близок к нулю, то результат (неизвестные x1, ..., xn) будет вычислен с большими ошибками. Дело в том, что вычисления над вещественными числами в компьютере выполняются при­ ближенно, т.е. с ошибками округления, а в этом случае ошибки возрастают во много раз. Для решения этой проблемы в методе Гаусса выбирают ведущий (имеющий максимальное абсолютное значение) элемент среди еще не обработанных коэффици­ ентов матрицы. Проще всего его выбирать из ai,i , ai + 1,i , …, an,i . Чтобы решение си­ стемы уравнений из-за этого не изменилось, необходимо строку с выбранным эле­ ментом поменять местами с i–й строкой матрицы (обменяв также соответствующие коэффициенты вектора b ).

Пример 7.8. Алгоритм реализует метод Гаусса с частичным выбором ведущего элемента. Массив A размерностью n×(n+1) содержит коэффициенты матрицы n×n левой части уравнения (7.1) и коэффициенты правой части уравнения (в послед­ нем столбце). Результат – массив X из n неизвестных.

Прямой ход записан в виде алгоритма (7.8), обратный ход – (7.9), вычисдение неизвестных – (7.10).

Оценим трудоемкость алгоритма. На этапе прямого хода самый трудоемкий этап

– это вычитание уравнений. Количество исполнений T(n) в нем самого внутреннего цикла при параметре внешнего цикла i=1 равно (n – 1)(n + 1), при i=2 равно

(n – 2) n , и т.д. Всего T(n) = (n – 1)(n + 1) + (n – 2) n + … + 1∙3 ≈ n3/3, см. формулу

128

(2.1). Нетрудно также видеть, что количество исполнений внутреннего цикла на эта­ пе обратного хода приближенно равно n2/2, а на этапе вычисления неизвестных – n. Таким образом, общая трудоемкость всего алгоритма имеет порядок n3.

 

 

 

for i:=1 to n-1

do

{выбор ведущего элемента:}

begin

v:=i;

n do

for

j:=i+1 to

 

if abs(A[j,i])>abs(A[v,i]) then v:=j;

if v<>i then

 

{перестановка i–го уравнения с v–м}

for j:=i to

n+1 do

 

begin z:=A[i,j]; A[i,j]:=A[v,j];

 

A[v,j]:=z

 

for

end;

n do

{вычитание уравнений}

k:=i+1 to

begin c:=A[k,i]/A[i,i];

for j:=i to n+1 do A[k,j]:=A[k,j]-c*A[i,j] end;end

for i:=n downto 2 do begin

for k:=1 to i-1 do {вычитание уравнений} begin c:=A[k,i]/A[i,i];

A[k,n+1]:=A[k,n+1]-c*A[i,n+1]; A[k,i]:=0 end;end

(7.8)

(7.9)

 

 

for i:=1 to n do

(7.10)

X[i]:=A[i,n+1]/A[i,i];

 

В алгоритме (7.8), реализующем этап прямого хода, предполагается, что при вы­ боре ведущего элемента A[i,i] каждый раз получается ненулевое значение. Если

же это не так, то при последующем на него делении произойдет аварийное прекраще­ ние выполнения программы. Это значит, что определитель системы уравнений (7.1) равен нулю. Для предотвращения деления на нуль алгоритм (7.8) надо дополнить со­ ответствующей проверкой. Более того, вместо проверки на равенство нулю выбран­ ного ведущего элемента A[i,i], необходимо проверять, что его абсолютное значе­ ние не меньше некоторого заданного малого eps, например, равного 10–6. При на­ рушении этого условия вычисления необходимо прекратить, так как единственного решения системы не существует.

Конец примера.

При выборе ведущего элемента можно просматривать не i–й столбец, а i–ю строку матрицы A. Но тогда следует переставлять не строки, а столбцы, при этом

129

следует запоминать перестановки, так как в результате вычисленные неизвестные x1, ..., xn также меняются местами между собой. Более того, можно просматривать все элементы матрицы A, лежащие в строках с i–й по n–ю и в столбцах с i–го по n– й. Тогда потребуется перестановка не только строк, но и столбцов.

Заметим также, что для упрощения алгоритма можно не делать отдельно обрат­ ный ход, сразу обнуляя элементы матрицы, лежащие в i–м столбце выше и ниже i– го элемента. Для этого в алгоритме (7.8) необходимо основной цикл выполнять до n, а цикл вычитания уравнений – от 1 до n, исключив i–й шаг. Правда, при этом ко­ личество исполнений внутреннего цикла на этапе вычитания уравнений будет при­ ближенно равно n3/2, а не n3/3, т.е. увеличится примерно в 1,5 раза.

А теперь рассмотрим получение общего решения системы уравнений (7.1) с прямоугольной матрицей A из m строк и n столбцов. Возможно три варианта ре­ шений такой системы:

1)система имеет единственное решение тогда и только тогда, когда ранг r мат­ рицы A равен n, и «лишние» m r уравнений (если таковые есть) являются линей­ ными комбинациями r остальных уравнений;

2)система противоречива и не имеет ни одного решения, если «лишние» m r уравнений не являются линейными комбинациями r остальных уравнений;

3) система имеет бесконечно много решений, если ранг r матрицы A меньше n, и «лишние» m r уравнений являются линейными комбинациями r остальных уравнений.

В последнем случае можно «лишние» n r неизвестных считать независимыми и задавать для них произвольный набор значений, каждый раз получая новое реше­ ние для r «основных» неизвестных.

Алгоритм получения общего решения должен привести матрицу A к такому виду, чтобы система уравнений (7.1) была следующей:

é1

0

0

a1,r+1

ê

1

0

a2,r+1

ê0

êê

 

 

 

ê0 0 1 ar,r+1

ê0

0

0

0

ê

 

 

 

ê

 

 

 

ê

0

0

0

ë0

a1,n

a2,n

a2,n

0

0

ù

 

 

 

é b1

ù

 

ú

 

 

 

ê b

ú

 

ú

é x1

ù

ê

2

ú

 

ú

ê

x2

ú

ê

 

ú

 

ú

× ê

ú

ê

 

ú

(7.3)

ú

 

= ê br

ú

ú

ê

 

ú

ê

 

ú

 

ú

ê

xn

ú

b

ú

 

ë

û

ê

r+1

 

ú

 

ê

 

ú

 

ú

 

 

 

ê b

ú

 

û

 

 

 

ë

m

û

 

Для этого при выборе ведущего элемента ai,i необходимо просматривать все эле­ менты матрицы A, лежащие в строках с i–й по n–ю и в столбцах с i–го по n–й, выполняя последующую перестановку как двух строк, так и двух столбцов. Тогда ва­ риант решений системы (7.3) определяется следующими проверками:

130

1) система имеет единственное решение, если ранг r = n, и коэффициенты пра­ вой части br + 1, …, bm (если они существуют) все равны нулю;

2) система не имеет ни одного решения, если хотя бы один из коэффициентов

правой части br + 1, …, bm

не равен нулю;

r < n , и коэффициенты

3) система имеет бесконечно много решений, если ранг

правой части br + 1, …, bm

(если они существуют) все равны нулю.

В первом случае значения неизвестных равны первым n

коэффициентам правой

части:

x1 = b1, …, xn = bn .

(7.4)

 

В третьем случае неизвестные xr + 1 , …, xn считаются независимыми, а неизвест­

ные x1 , …, xr вычисляются через них по формулам:

 

x1 = b1 a1,r + 1xr + 1 – … – a1,nxn , … , xr = br ar,r + 1xr + 1 – … – ar,nxn .

(7.5)

Пример 7.9. Алгоритм получения общего решения системы уравнений (7.1):

i:=1;

if n<=m then r:=n else r:=m; while i<=r do

begin {Выбор ведущего элемента A[v,u] (7.12)} if abs(A[v,u])<eps then r:=i-1;

else begin {Перестановка строк и столбцов (7.13)} (7.11) {Деление i–й строки на A[i,i] (7.14)}

{Вычитание уравнений (7.15)} i:=i+1

end end;

Предполагается, что первые n столбцов массива A содержат элементы матри­ цы A размером m×n, а (n+1)–й столбец массива A содержат элементы правой ча­

сти b1, …, bm системы уравнений (7.1). Перед выполнением алгоритма (7.11) необхо­

димо задать значение достаточно малой величины eps и сформировать массив перестановок неизвестных: L[1]:=1, …, L[n]:=n.

Рассмотрим отдельные этапы алгоритма (7.11). Выбор ведущего элемента запи­ сан в алгоритме (7.12), перестановка строк и столбцов – в алгоритме (7.13), деление i–й строки на A[i,i] – в алгоритме (7.14), вычитание уравнений – в алгоритме (7.15).

Оценим трудоемкость алгоритма в предположении, что m = n, и что ранг систе­ мы уравнений также равен n. Нетрудно видеть, что общее количество исполнений внутреннего цикла на этапе выбора ведущего элемента приблизительно равно n3/3, а

на этапе вычитания уравнений – n3/2. Остальные этапы (перестановка строк и столб­ цов, деление i–й строки на диагональный элемент) дают порядка n2 исполнений вну­

тренних циклов, поэтому общая трудоемкость имеет порядок n3.