. . , , !""#$"% #&'"#(("% )'!*&!#+ & !$"(& $), ")#- .- "(.(& &/"0#1 $)/1 *!&.#2, )$!+#1"- !'!&#+ 02.03.01 «!(%!(#! # %9+('/ !#» (',. ; 088-4 / 121-14, 23 (-)'- 2014 ,.) '!"-'" 2015 519.1(07) 22.174.1-73 953 D*( . . , *!&.+!- (.% '#!.2 #E'%!(## "(#((! &/$#"#(9, %.#'&!#- , .(' (1#$"#1 !, 'E""' 953 , . . %)#!('/ !,'#(%/: %G"(&!, ,'!E/, ./: $). ")# / . . /&!. – '!"-'" : #). E.'. -(, 2015. – 152 ". ISBN 978-5-7638-3155-9 !""%('/ %!0#/ "")/ '."(!&#- %G"(&, ,'!E&, .&, &!G20# '!D##, (0#- # !,'#(%/. !! D! &/$#"#(92 "G"(# !,'#(%&. K'.!*!$ .- "(.(&, )$!+#1"- !'!&#+ 02.03.01 «!(%!(#! # %9+('/ !#». ! !"#!$ %&.: http://catalog.sfu-kras.ru ISBN 978-5-7638-3155-9 ' 519.1(07) 22.174.1$73 © #)#'"#2 E.'!9/2 #&'"#((, 2015 ПРЕДИСЛОВИЕ Комбинаторный анализ – раздел дискретной математики, рассматривающий решение задач выбора и расположения элементов некоторого конечного множества в соответствии с заданными правилами. Всякое правило выбора задает способ построения некоторой конечной конструкции из элементов исходного множества, называемой комбинаторным объектом (или комбинаторной конфигурацией). Простейшими комбинаторными объектами являются сочетания, размещения, перестановки, а более сложными – графы, алфавитные коды, латинские квадраты, блок-схемы. В рамках комбинаторного анализа комбинаторные объекты изучаются преимущественно в аспекте двух вопросов: существует ли комбинаторный объект, удовлетворяющий заданному правилу выбора, и если да, сколько таких объектов можно построить. Значительную часть комбинаторного анализа составляют методы и алгоритмы решения следующих типов задач: – задачи на построение. Это конструирование одних объектов на основе других с помощью некоторых операций, а также перечисление (генерация) всех комбинаторных объектов заданного типа; – задачи распознавания. Это проверка условия, которому должен удовлетворять построенный комбинаторный объект; – оптимизационные задачи (задачи комбинаторной оптимизации). Это нахождение в конечной совокупности комбинаторных объектов, таких, которые обладают избранным свойством в наибольшей или наименьшей степени. Алгоритмы решения таких типов задач принято называть комбинаторными алгоритмами. Эти алгоритмы часто называют также алгоритмами дискретной математики, поскольку они оперируют комбинаторными объектами как дискретными математическими структурами. 3 Класс комбинаторных алгоритмов очень обширный. Он охватывает алгоритмы генерации комбинаторных объектов (подмножеств, перестановок, сочетаний, разбиений и т. п.), алгоритмы сортировки, базовые алгоритмы на графах (обходы в глубину и ширину, специальные обходы, поиск с возвратом и т. п.), алгоритмы решения задач комбинаторной оптимизации, потоковые алгоритмы, алгоритмы целочисленной оптимизации и многие другие. Поскольку число комбинаторных объектов, удовлетворяющих заданному правилу выбора, может быть огромным, то одним из основных критериев качества комбинаторных алгоритмов является вычислительная (или ресурсная) сложность, т. е. время и память, необходимые для их выполнения. Комбинаторные алгоритмы востребованы многими современными приложениями, базирующимися на информационных компьютерных технологиях. Вследствие этого в учебных планах математических и программистских специальностей университетов предусмотрены дисциплины, целью которых является изучение, программирование и анализ вычислительной сложности комбинаторных алгоритмов. Имеется немало литературы по дискретной математике, в которой содержатся традиционные для этой дисциплины разделы, включая описания важнейших комбинаторных алгоритмов [2–8, 15, 16, 19, 25]. Но остается потребность в сборниках задач и упражнений прикладной направленности. Данное учебное пособие в определенной мере удовлетворяет эту потребность. Учебное пособие предназначено для практических занятий по дисциплине «Комбинаторные алгоритмы». Здесь приводится теоретический материал и описание заданий. Излагаемые в учебном пособии алгоритмы заимствованы из [1−25], но с авторскими трактовками, иллюстрациями, примерами и некоторыми оригинальными приемами анализа алгоритмов. Объем учебного пособия рассчитан на один семестр. Учебное пособие содержит три главы и два приложения. Каждая глава освещает отдельную тему и включает несколько заданий. В спектр рассматриваемых тем входят: перечисление простейших комбинаторных объектов, алгоритмы на графах, алфавитное кодирование. Для понимания представленных тем вполне достаточно знакомства со школьным курсом алгебры. Всего в учебное пособие включено девять заданий, каждое из которых – это отдельная комбинаторная проблема с краткой и четкой формулировкой, примерами, алгоритмами 4 и прикладными задачами. Задания содержат шесть равноценных по трудности вариантов. При их выполнении допускается применение любого языка программирования. Краткая теоретическая справка, предваряющая каждое задание, позволяет студентам самостоятельно изучить рассматриваемую тему и решить на компьютере предлагаемые прикладные задачи. В приложениях представлена дополнительная информация, необходимая студентам для разработки и анализа алгоритмов и программ. В прил. 1 рассмотрены элементы теории сложности вычислений, приведены асимптотические формализмы. В прил. 2 даны рекомендации по выбору машинного представления данных и тестов. Учебное пособие включает более 50 рисунков, алгоритмы описаны на естественном языке, теоретический материал изложен с использованием традиционных математических обозначений. Основные термины и понятия выделены курсивом и вынесены в алфавитный указатель. Учебное пособие предназначено для студентов младших курсов университетов, обучающихся по направлению 02.03.01 «Математика и компьютерные науки», а также может быть полезно преподавателям и практикующим программистам. 5 ГЛАВА 1 ПЕРЕЧИСЛЕНИЕ ПРОСТЕЙШИХ КОМБИНАТОРНЫХ ОБЪЕКТОВ ЗАДАНИЕ 1 Множества: представления и операции Рассматривается машинное представление конечных множеств в виде битовых шкал. Цель задания: практика программирования теоретико-множественных операций и отношений между множествами, заданными битовыми шкалами. 1.1. Основные понятия и обозначения В окружающей действительности существуют как отдельные объекты, так и их совокупности, или множества. Например, имеется отдельная почтовая марка и коллекция марок, произведение А. С. Пушкина «Евгений Онегин» и полное собрание сочинений этого писателя, число 5 и совокупность всех натуральных чисел. Под множеством X обычно понимается любая совокупность определенных и различимых между собой объектов, мыслимая как единое целое. Эти объекты называются элементами множества X. Символом ∈ обозначается отношение принадлежности. Запись x ∈ X означает, что элемент x принадлежит множеству X. Если элемент x не принадлежит множеству X, то пишут x ∉ X. Если множество состоит из конечного числа элементов, то оно называется конечным множеством, а в противном случае − бесконечным. Конечные множества можно описывать, перечисляя их элементы. Элементы, принадлежащие конечному множеству, записывают между двумя фигурными скобками через запятую. Гораздо чаще множества задаются посредством указания характеристического свойства – признака, которому удовлетворяют элементы этого множества, и только 6 они. Для такого задания обычно используются фигурные скобки, внутри которых приводится характеристическое свойство. Так, множество {1, 2, 3, 4, 5} можно определить следующим образом: {x | x ∈ Z+ и 1 ≤ x ≤ 5}, где Z+ – множество целых неотрицательных чисел. Отношение включения между множествами A и B обозначается через A ⊆ B. Оно означает, что каждый элемент множества A есть элемент множества B. В данном случае говорят, что A – это подмножество множества B. Если в A существует элемент, не принадлежащий B, то A не является подмножеством множества B (A ⊆ B). Пусть A и B некоторые множества. Множества A и B равны (A = B), если для любого x имеем: x ∈ A тогда и только тогда, когда x ∈ B. Другими словами, A = B тогда и только тогда, когда A ⊆ B и B ⊆ A. Если A ⊆ B и A ≠ B, то записывают A ⊂ B и отмечают, что множество A есть собственное подмножество множества B. Поскольку множество однозначно задается своими элементами, то порядок их перечисления не играет роли. Кроме того, любой элемент может входить во множество не более одного раза. Множество, не содержащее элементов, называется пустым и обозначается ∅. Пустое множество − подмножество любого множества. Обычно уже в самом определении конкретного множества явно или неявно ограничивается совокупность допустимых объектов. Например, если речь идет о множестве чисел, делящихся на 3, то ясно, что оно является подмножеством множества целых чисел. Целесообразно совокупность допустимых объектов зафиксировать явным образом и считать, что все рассматриваемые множества являются подмножествами этой совокупности – универсального множества. 1.2. Битовая шкала множества Множество как математический объект часто используется в программах, и для его машинного представления имеется ряд способов [5, 17, 19]. Определение машинного представления множества предполагает указание структуры данных, подходящей для хранения информации о принадлежности элементов множеству. Наиболее распространенный способ машинного представления конечных множеств − битовые шкалы. 7 Пусть Х = {х1, ..., xn} – универсальное непустое конечное множество. В дальнейшем рассматриваются только подмножества этого множества. Всякому множеству A ⊆ X можно однозначно сопоставить битовую шкалу – n-разрядный двоичный вектор (a1, ..., an), каждый i-й разряд (i = l, ..., n) которого определяется так: ⎧1, если x i ∈ A, ai = ⎨ ⎩ 0, если x i ∉ A. Битовая шкала характеризует принадлежность элемента из X множеству А, поэтому ее также называют характеристическим вектором множества А ⊆ Х (А в Х). Известно, что существует 2n различных n-разрядных двоичных векторов, что полностью совпадает с числом всех различных подмножеств n-элементного множества X. Следует заметить, что использование битовых шкал предполагает неизменный (выбранный наперед) порядок перечисления элементов универсального множества X. При этом число элементов в X может быть сколь угодно большим, но обязательно конечным. Пример 1.1. Пусть заданы множества X = {х1, х2, x3, x4, х5}, А = {х2, х3}, B = ∅, C = {х1, х2, x3, x4, х5}. Тогда битовые шкалы этих множеств имеют вид, указанный в табл. 1.1. Таблица 1.1 Множество Битовая шкала A 0 1 1 0 0 B 0 0 0 0 0 C 1 1 1 1 1 Битовые шкалы очень удобны для выполнения основных операций над конечными множествами, а также проверки отношений между ними. 8 1.3. Теоретико-множественные операции и их реализация битовыми шкалами Приведем правила образования битовых шкал дополнения, объединения, пересечения, разности и симметрической разности множеств. Каждое из этих правил основано на определении соответствующей теоретико-множественной операции. Дополнение. Пусть множество A ⊆ X имеет битовую шкалу (a1, ..., an). Тогда битовая шкала (b1, ..., bn) множества A = {x | x ∈ X и x ∉ A}, являющегося дополнением для A, определяется путем инвертирования всех разрядов шкалы (a1, ..., an): bi = 1 – ai, i = l, ..., n. Объединением множеств A, B ⊆ X называется множество C = A ∪ B = {x | x ∈ A или x ∈ B}, состоящее из всех тех элементов, которые принадлежат хотя бы одному из множеств A или B. Пусть множества А и В описываются шкалами (a1, ..., an) и (b1, ..., bn) соответственно. Тогда битовая шкала (c1, …, cn) множества C вычисляется по правилу ci = mах {ai, bi}, i = l, ..., n. Пересечение множеств A, B ⊆ X есть множество C = A ∩ B = {x | x ∈ A и x ∈ B}, содержащее все элементы, которые принадлежат A и B одновременно. Пусть множествам А и В соответствуют битовые шкалы (a1, ..., an) и (b1, ..., bn). Тогда битовая шкала (c1, …, cn) множества C определяется следующим образом: ci = min {ai, bi}, i = l, ..., n. Разность. Если множества A, B ⊆ X представляются битовыми шкалами (a1, ..., an), (b1, ..., bn), то битовая шкала (c1, …, cn) множества C = A \ B = {x | x ∈ A и x ∉ B } = A ∩ B , 9 являющегося разностью множеств A и B, определяется правилом ci = min {ai, 1 − bi}, i = l, ..., n. Симметрической разностью множеств A, B ⊆ X называется множество C = A ⊕ B = (A \ B) ∪ (B \ A), которое содержит все элементы из A, не принадлежащие множеству B, а также все элементы из B, не принадлежащие A. Пусть множествам А и В соответствуют шкалы (a1, ..., an) и (b1, ..., bn). Тогда битовая шкала (c1, …, cn) множества C вычисляется так: ci = max {min (ai, 1 – bi), min (bi, 1 – ai)}, i = l, ..., n. Пример 1.2. Рассмотрим два множества A = {1, 2, 3, 4, 5}, B = {4, 5, 6, 10}. Они являются подмножествами конечного универсального множества X = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}. Битовые шкалы исходных множеств A, B и множеств, которые получаются в результате применения к ним теоретико-множественных операций, приведены в табл. 1.2. Таблица 1.2 Множество Битовая шкала A 1 1 1 1 1 0 0 0 0 0 B 0 0 0 1 1 1 0 0 0 1 А 0 0 0 0 0 1 1 1 1 1 B 1 1 1 0 0 0 1 1 1 0 A∪B 1 1 1 1 1 1 0 0 0 1 A∩В 0 0 0 1 1 0 0 0 0 0 A\B 1 1 1 0 0 0 0 0 0 0 B\A 0 0 0 0 0 1 0 0 0 1 A⊕B 1 1 1 0 0 1 0 0 0 1 10 1.4. Отношения над множествами Рассмотрим правила, с помощью которых проверяются основные отношения над множествами, при условии, что множества заданы битовыми шкалами. Отношение принадлежности элемента множеству. Пусть множество A ⊆ X задано шкалой (a1, ..., an). Элемент хi ∈ X является элементом множества А, если аi = 1. Отношение включения. Пусть множества A, B ⊆ X определены битовыми шкалами (a1, ..., an) и (b1, ..., bn) соответственно. Множество А содержится в В (А ⊆ В), если для любого i = l, ..., n справедливо неравенство ai ≤ bi. Отношение равенства. Множества A, B ⊆ X равны тогда и только тогда, когда соответствующие им битовые шкалы равны, т. е. ai = bi для любого i = l, ..., n. Отношение равномощности. Мощность конечного множества A ⊆ X – это число элементов, принадлежащих А (обозначается как | А |). Если множество A ⊆ X задано битовой шкалой (a1, ..., an), то | A|= n ∑a . i =1 i Множества A, B ⊆ X с битовыми шкалами (a1, ..., an) и (b1, ..., bn) равномощны, когда | А | = | В |, что отвечает равенству n n ∑a = ∑b . i =1 i i =1 i Пример 1.3. Пусть задано универсальное множество X = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}. Рассмотрим четыре его подмножества: A = {1, 2, 3, 4, 5}, B = {5, 3, 1, 2, 4}, C = {4, 5, 6, 7, 10}, D = {1, 10, 7, 4, 5, 6}. Битовые шкалы этих множеств указаны в табл. 1.3. Здесь C ⊆ D. Кроме того, А, В, C, D ⊆ X. Множества A и B равны. Равномощными являются такие пары множеств: A и B, A и C, B и C. 11 Таблица 1.3 Множество Битовая шкала X 1 1 1 1 1 1 1 1 1 1 A 1 1 1 1 1 0 0 0 0 0 B 1 1 1 1 1 0 0 0 0 0 C 0 0 0 1 1 1 1 0 0 1 D 1 0 0 1 1 1 1 0 0 1 1.5. Расстояние между множествами При решении некоторых задач требуется определить «близость» заданного множества к множеству, выполняющему роль эталона (образца). В этих случаях пользуются понятием «расстояние». Пусть множества A, B ⊆ X определены своими битовыми шкалами (a1, ..., an) и (b1, ..., bn) соответственно. Расстоянием Хемминга между множествами A и B называется число dh (A, B), равное n d h ( A, B) = ∑ ci , i =1 где (c1, …, cn) – битовая шкала множества C = А ⊕ В. Таким образом, расстояние Хемминга dh (A, B) задает количество разрядов, в которых битовые шкалы множеств A и B отличаются друг от друга. Относительным расстоянием Хемминга между множествами A, B ⊆ X называется число, определяемое формулой d h ( A, B) . n В отличие от dh (A, B) данное число является безразмерной величиной, и для него верны границы d h 0 ( A, B) = 0 ≤ d h 0 ( A, B) ≤ 1. Очевидно, что d h 0 ( A, A) = 0, d h 0 ( A, A ) = 1. 12 Пример 1.4. Пусть X = {х1, х2, x3, x4, х5} и множества A, B, C ⊆ X заданы битовыми шкалами (табл. 1.4). Таблица 1.4 Множество Битовая шкала A 0 1 1 0 0 B 1 0 0 1 1 C 1 0 0 1 1 Битовые шкалы указывают, что А = B и В = С. Поэтому имеем d h ( A, B) = 5, d h 0 ( A, B) = 1, d h ( B, C ) = 0, d h 0 ( B, C ) = 0. Если допустить, что шкала множества А равна (0, 1, 1, 1, 1), то d h ( A, B) = 3, d h 0 ( A, B) = 3 / 5 = 0,6. Необходимо отметить, что когда мощность универсального множества Х = {х1, ..., хn} велика, а его подмножества не очень велики, то применение битовых шкал не является эффективным с точки зрения объема используемой памяти. В этом случае целесообразно применять списки. Алгоритмы вычисления операций над множествами, заданными упорядоченными списками, можно найти в работе [19]. 1.6. Порядок выполнения задания 1.6.1. Изучить формулы расчета расстояний Хемминга, правила образования битовых шкал для основных теоретико-множественных операций и отношений. Реализовать их в виде программных процедур. Каждую из них оформить так, чтобы, с одной стороны, ее можно было автономно тестировать, а с другой – использовать при разработке программ решения задач. Оценить вычислительную сложность процедур по времени и используемой памяти в зависимости от n – длины битовых шкал (см. рекомендации в прил. 1). 1.6.2. Разработать и отладить программу решения задач, связанных с почтовым индексом. 13 1.7. Варианты О почтовом индексе. Для написания цифр почтового индекса принято использовать множество X из 9 элементов, которые обозначены буквами на рис. 1.1, а сами цифры изображены на рис. 1.2. Здесь множество X играет роль универсального множества. Будем считать, что его элементы перечисляются в алфавитном порядке: X = {a, b, c, d, e, f, g, h, i}. Следует для каждой из десяти цифр записать множество Аk (k = 0, ..., 9), элементы которого образуют запись этой цифры, и найти соответствующую битовую шкалу. Так, цифре 0 отвечает множество A0 = {a, b, d, f, h, i} и битовая шкала (1, 1, 0, 1, 0, 1, 0, 1, 1). Требуется разработать программу, которая позволяла бы по X и битовым шкалам множеств Аk (k = 0, ..., 9) решить задачи, указанные в табл. 1.5. a b f c e g i Рис. 1.1 Рис. 1.2 14 d h Таблица 1.5 Номер варианта 1 Задача Найти среди Аk (k = 0, ..., 9) пары непересекающихся множеств. Для заданной цифры найти все цифры, которые отстоят от нее не более чем на 1 / 3 (в смысле относительного расстояния Хемминга) 2 Найти среди Аk (k = 0, ..., 9) пары равномощных множеств. Для заданной цифры найти наиболее близкую ей цифру (в смысле расстояния Хемминга) 3 Найти среди Аk (k = 0, ..., 9) пары множеств, для которых справедливо отношение включения. Для заданного почтового индекса найти пару наименее близких цифр (в смысле расстояния Хемминга) 4 Найти среди Аk (k = 0, ..., 9) множества, содержащие заданный элемент х ∈ X. Найти среди Аk (k = 0, ..., 9) пару наиболее близких множеств (в смысле расстояния Хемминга) 5 Для каждого элемента х ∈ X найти число множеств Аk, в которые он входит. Найти среди Аk (k = 0, ..., 9) пару наименее близких множеств (в смысле расстояния Хемминга) 6 Найти элемент х ∈ X, который наиболее редко используется в Аk (k = 0, ..., 9). Для заданной цифры найти наименее близкую цифру (в смысле расстояния Хемминга) 15 ЗАДАНИЕ 2 Генерация всех подмножеств конечного множества Во многих переборных алгоритмах необходимо последовательно рассмотреть все подмножества некоторого конечного множества и среди них найти то, которое удовлетворяет заданному условию. Иногда бывает важно, чтобы каждое последующее подмножество отличалось от предыдущего наименьшим образом. Изучить известные методы генерации подмножеств конечного множества и получить практические навыки программной реализации переборных алгоритмов – цель задания. 2.1. Формулировка задачи Пусть задано универсальное конечное множество Х = {х1, ..., xn}. Требуется построить все различные его подмножества. Это задача на построение комбинаторных объектов − подмножеств конечного множества. Заметим, что множество Х имеет в точности 2n различных подмножеств. Каждое из подмножеств A ⊆ X однозначно задается битовой шкалой (a1, ..., an), т. е. двоичным вектором длины n. Значит, построение всех различных подмножеств множества Х = {х1, ..., xn} можно свести к генерации всех n-разрядных двоичных векторов. Рассмотрим три популярных алгоритма генерации двоичных векторов [5, 17]. В них двоичные векторы формируются в одномерном массиве B = (B[1], …, B[n]) длины n, элементы которого принимают значения 0 и 1. На вход алгоритмов поступает целое число n > 0. 2.2. Счет в двоичной системе счисления Самый простой метод генерации всех двоичных векторов − счет в двоичной системе счисления, как это осуществляется в алгоритме 2.1. Алгоритм 2.1. Порождение n-разрядных двоичных векторов 1. В качестве начального взять вектор B = (0, ..., 0), т. е. для каждого i = 1, …, n выполнить присваивание В[i]:= 0. 16 2. Если В = (1, ..., 1), то перейти на пункт 6. Иначе идти дальше. 3. Вывести текущее значение В. 4. Найти m как наибольшее значение i, для которого В[i] = 0. 5. Положить B[m]:= 1 и для каждого j = m + 1, …, n выполнить присваивание B[j]:= 0. Вернуться к пункту 2. 6. Вывести текущее значение В. Останов. Нетрудно убедиться, что данный алгоритм последовательно генерирует двоичные разложения целых чисел 0, 1, …, 2n –1. Пример 2.1. В табл. 2.1. приведены результаты работы алгоритма 2.1 для n = 3. Заметим, что при записи вектора B во втором столбце этой таблицы опущены (для краткости) скобки и разделяющие запятые. Таблица 2.1 Целое число i 0 1 2 3 4 5 6 7 Вектор В 000 001 010 011 100 101 110 111 Значение m 3 2 3 1 3 2 3 – Из предоставленного примера видно, что в алгоритме 2.1 последовательно получаемые подмножества могут сильно отличаться друг от друга. Так, соседние двоичные наборы (0, 1, 1) и (1, 0, 0) различаются во всех разрядах и соответствуют непересекающимся подмножествам. 2.3. Бинарные коды Грея Рассмотрим метод генерации всех n-разрядных двоичных векторов в таком порядке, что каждый последующий двоичный вектор получается из предыдущего изменением одного разряда, что отвечает добавлению в подмножество или удалению из него лишь одного элемента. 17 Данный метод опирается на следующий факт. Если последовательность двоичных векторов B1, B2, ..., Bm k содержит все m = 2 векторов длины k, причем вектор Bi отличается от вектора Bi + 1 в точности в одном разряде (i = 1, ..., m – l), то последовательность B1 0, B2 0, ..., Bm 0, Bm l, Bm − 1 l, ..., B1 1 содержит все двоичные векторы длины k + 1, причем каждые два соседних вектора также отличаются только в одном разряде. Получаемая этим способом последовательность двоичных векторов B1, B2, ..., Bm, где m = 2n, называется бинарным кодом Грея порядка n. Из данного определения непосредственно следует алгоритм 2.2 рекурсивного построения кода Грея. Согласно данному алгоритму последовательность двоичных векторов кода Грея порядка k + 1 строится на основе последовательности B1, B2, ..., Bm (m = 2k) двоичных векторов кода Грея порядка k. Алгоритм 2.2. Рекурсивное построение кода Грея 1. Поместить 0 после каждого двоичного вектора Bi (i = 1, …, m). Получится последовательность B1 0, B2 0, ..., Bm 0. 2. Реверсировать последовательность B1, B2, ..., Bm, т. е. изменить порядок перечисления двоичных векторов на обратный. В полученной последовательности после каждого двоичного вектора записать 1. Результирующая последовательность примет вид Bm l, Bm − 1 l, ..., B1 1. 3. Поместить последовательность, построенную в пункте 2, вслед за последовательностью из пункта 1. Пример 2.2. Построим бинарный код Грея порядка n = 3. Очевидно, что коду Грея порядка n = 1 соответствует последовательность из двух одноразрядных двоичных векторов: 18 0, 1. Поместим 0 после каждого из указанных двоичных векторов. При записи этих векторов скобки и разделяющие запятые опустим (точно так же будем кратко записывать двоичные вектора и далее). В результате получим последовательность 00, 10. Реверсируем последовательность 0, 1 и поместим 1 после каждого из ее элементов. В результате придем к последовательности 11, 01. В итоге для n = 2 получим 00, 10, 11, 01. Чтобы построить код Грея порядка n = 3, вновь запишем 0 после каждого элемента приведенной последовательности и 1 после элементов реверсированной последовательности. В результате имеем 000, 100, 110, 010, 011, 111, 101, 001. Процесс нерекурсивного построения кода Грея порядка n реализует следующий алгоритм. Алгоритм 2.3. Нерекурсивное построение кода Грея 1. Вначале взять вектор B = (0, ..., 0), т. е. для каждого i = 1, …, n выполнить присваивание B[i]:= 0. 2. Положить i:= 0, где i – число построенных на данный момент двоичных векторов. 3. Вывести текущее значение B. 4. Положить i:= i + 1. 5. Найти р − наибольшую степень двойки, которая делит нацело i. 6. Если р < n, то инвертировать разряд с номером (p + 1) надлежащим образом: B[p + 1]:= 1 – B[p + 1]. Далее вернуться к пункту 3. В противном случае останов. Пример 2.3. Последовательность двоичных векторов, порожденных алгоритмом 2.3 для n = 3, приведена в табл. 2.2. 19 Таблица 2.2 i – число созданных двоичных векторов р – наибольшая степень двойки, делящая нацело число i (p + 1) – номер инвертируемого разряда Двоичный вектор B 0 – – 000 1 0 1 100 2 1 2 110 3 0 1 010 4 2 3 011 5 0 1 111 6 1 2 101 7 0 1 001 2.4. Порядок выполнения задания 2.4.1. Изучить алгоритмы 2.1–2.3. Оценить вычислительную сложность алгоритмов. Реализовать один из них – тот, что указан в варианте задания (табл. 2.3), в виде программной процедуры. Оформление процедуры должно допускать ее автономное тестирование. Осуществить тестирование с помощью примеров 2.1–2.3. 2.4.2. Написать и отладить программу решения оптимизационной задачи, используя метод исчерпывающего поиска (табл. 2.3). Найти условия, при которых задача не имеет решения. Проверку этих условий предусмотреть в программе. Определить способы внешнего и внутреннего представления исходных, промежуточных и результирующих данных. Указать набор тестов для контроля правильности работы программы. Использовать рекомендации, приведенные в прил. 2. Оценить сложность решения задачи по времени и памяти. 20 2.5. Варианты Согласно табл. 2.3 вариант задания определяет номер алгоритма, генерирующего все n-разрядные двоичные вектора, и название оптимизационной задачи, которую требуется решить. Каждая из предлагаемых оптимизационных задач является NP-трудной и может быть сформулирована как задача целочисленного линейного программирования с двоичными (или булевыми) переменными [10, 20]. Одним из методов, возможно, не самым лучшим, решения NP-трудных задач является метод исчерпывающего поиска − полный перебор всех возможных наборов значений двоичных переменных и выбор среди них тех, которые обладают требуемым свойством в наибольшей или наименьшей степени. Именно так следует решать задачу, указанную в варианте задания. Для организации исчерпывающего поиска необходимо использовать разработанную процедуру генерации двоичных векторов. Таблица 2.3 Номер варианта Номер алгоритма Название задачи 1 2.1 Задача размещения баз 2 2.2 Задача выбора проектов 3 2.3 Задача о доставке 4 2.1 Задача о доставке 5 2.3 Задача выбора проектов 6 2.2 Задача размещения баз Задача размещения баз. Некоторая территория разделена на n районов. Предполагается, что военная база, расположенная в какомто районе, может контролировать не только этот район, но и соседние, граничащие с ним районы. Каждый район характеризуется определенными убытками, связанными с расположением в нем военной базы. Требуется найти такие места размещения военных баз, чтобы суммарные убытки были минимальные и вся территория была под контролем. 21 Формализуем постановку задачи. Пусть ci – размер убытков, связанных с размещением военной базы в районе i, и A = {aij} – матрица, отражающая отношение соседства между районами территории: ⎧1, если район i граничит с районом j , ai j = ⎨ ⎩0 в противном случае (i, j = 1, ... , n ). Введем двоичные переменные: ⎧1, если военная база размещена в районе j , xj = ⎨ ⎩ 0 в противном случае ( j = 1, ... , n ). Требуется найти значения переменных x1, …, xn, при которых минимизируются суммарные убытки n ∑c j =1 j xj и выполняются условия n ∑a j =1 1j x j ≥ 1, ..., n ∑a j =1 nj x j ≥ 1. Если c1 = c2 = … = cn = l, то рассматриваемая задача сводится к отысканию наименьшего числа военных баз, способных контролировать всю территорию, и мест размещения этих баз. Пример 2.4. Пусть некоторая территория разделена на районы, которым присвоены номера 1, 2, ..., 9 (рис. 2.1). Тогда матрица А, отражающая отношение соседства между районами этой территории, имеет вид, указанный на рис. 2.2. Рис. 2.1 22 ⎛1 ⎜1 ⎜ ⎜0 ⎜1 А = ⎜0 ⎜0 ⎜0 ⎜0 ⎜ ⎝0 1 0 1 0 0 0 0 0⎞ ⎟ 1 1 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1 1 0 1 0 0 0 1 1 1 0 1 0 1 0 1 1 0 0 1 0 1 0 0 1 1 0 0 0 1 0 1 1 1 ⎟ 0 ⎟ 0⎟ 0⎟ 1⎟ 0⎟ ⎟ 1 ⎟ 1⎠ Рис. 2.2 При c1 = c2 = … = c9 = 1 военные базы можно разместить в районах с номерами {1, 6, 7}. Существуют и другие оптимальные решения. Минимальное число баз, контролирующих всю территорию, неизменно равно 3. Задача выбора проектов. Имеются n проектов S = {s1, ..., sn} и m ресурсов R = {r1, ..., rm}. Для каждого проекта si ∈ S задано множество ресурсов Ri ⊆ R, необходимых для его реализации. Считается, что всякий проект может быть реализован за один и тот же промежуток времени. Если два различных проекта si, sj ∈ S используют один и тот же ресурс rk ∈ Ri ∩ Rj ≠ ∅, то они не могут выполняться одновременно. Требуется найти в S = {s1, ..., sn} максимальное (по включению) множество независимых проектов, т. е. таких проектов, которые могут выполняться одновременно. Пусть матрица A = {aij} отражает отношение «проект-ресурс»: ⎧ 1, если проект s i использует ресурс r j , ai j = ⎨ ⎩0 в противном случае (i = 1, ... , n; j = 1, ... , m ). Заметим, что строки этой матрицы являются битовыми шкалами для Ri ⊆ R (i = l, ..., n), а R играет роль универсального множества. Определим двоичные переменные: ⎧1, если выбран проект s i , xi = ⎨ ⎩0 в противном случае (i = 1, ... , n ). 23 Тогда задачу выбора проектов можно выразить так: найти значения переменных x1, …, xn, при которых достигается максимум суммы n ∑x i =1 i и выполняются условия Ri ∩ Rk = ∅, для любых 1 ≤ i < k ≤ n, при которых xi = xk = 1. Эти условия отражают требования независимости выбранных проектов. Пример 2.5. Пусть S = {s1, s2, s3, s4, s5}, R = {r1, r2, r3, r4}. Потребность проектов в ресурсах описывает матрица A, приведенная на рис. 2.3. Здесь максимальные множества независимых проектов: {s2, s3, s4}, {s2, s3, s5}. ⎛1 ⎜0 ⎜ А= 0 ⎜ ⎜⎜ 0 ⎝1 1 1 0 0 0 1 0 1 0 0 1⎞ ⎟ 0 ⎟ 0 ⎟ 1⎟ ⎟ 1⎠ Рис. 2.3 Задача о доставке. Заданы n клиентов и m маршрутов. Фирма каждый день доставляет клиентам товары на грузовых машинах, которые передвигаются по заданным маршрутам. Любой маршрут требует в течение дня одного транспортного средства и характеризуется определенными затратами, например, стоимостью расходуемого топлива. Для каждого маршрута известны клиенты, которые могут быть обслужены при движении транспортного средства по этому маршруту. Цель состоит в том, чтобы найти такое множество маршрутов, которое позволяет обслужить с минимальными затратами всех клиентов, причем каждого клиента только один раз в день, т. е. с помощью одного маршрута. Введем двоичные переменные: ⎧ 1, если выбран маршрут j, xj = ⎨ ⎩0 в противном случае ( j = 1, ... , m). 24 Пусть стоимость затрат, связанных с маршрутом j, равна cj, и матрица A = {aij} отражает отношение «клиент-маршрут»: ⎧ 1, если клиент i обслуживае тся маршрутом j , ai j = ⎨ ⎩0 в противном случае (i = 1, ... , n; j = 1, ... , m ). Необходимо найти значения двоичных переменных x1, …, xm, при которых достигается минимум суммарных затрат m ∑c j =1 j xj и выполняются условия m ∑ a1 j x j = 1, ..., j =1 m ∑a j =1 nj x j = 1. Если c1 = c2 = ... = cm = l, то задача сводится к поиску наименьшего числа маршрутов, позволяющих обслуживать каждого клиента один раз в день. Пример 2.6. Пусть число клиентов равно 5, а число маршрутов равно 4. Матрица A, описывающая отношение «клиент-маршрут», приведена на рис. 2.4. ⎛1 ⎜0 ⎜ А= 1 ⎜ ⎜⎜ 0 ⎝1 0 1 0 1 0 1 1 1 0 0 0⎞ ⎟ 1 ⎟ 0 ⎟ 1⎟ ⎟ 0⎠ ⎛1 ⎜0 ⎜ А= 1 ⎜ ⎜⎜ 0 ⎝1 Рис. 2.4 1 0 1 0 1 1 1 0 1 0 1⎞ ⎟ 1 ⎟ 0 ⎟ 1⎟ ⎟ 0⎠ Рис. 2.5 Если с1 = 15, с2 = 15, с3 = 20, с4 = 10, то множество маршрутов {1, 4} – оптимальное решение задачи о доставке. Поскольку в A второй и четвертый столбцы совпадают, то существует и другое оптимальное решение {1, 2}. Однако если матрицу А изменить так, как указано на рис. 2.5, то задача о доставке не имеет решения. 25 ЗАДАНИЕ 3 Пересчет и перечисление сочетаний и перестановок Цель задания: изучение формул пересчета и алгоритмов перечисления таких простейших комбинаторных объектов, как сочетания и перестановки. Из большого числа известных алгоритмов взяты для рассмотрения только те, которые генерируют комбинаторные объекты в лексикографическом порядке. 3.1. Определение комбинаторных объектов Пусть Х = {х1, ..., xn} − некоторое конечное множество. Размещением элементов из Х по k (или из n по k) называется упорядоченное подмножество из k элементов, принадлежащих Х (1 ≤ k ≤ n). Число A(n, k) различных размещений из n по k при 1 ≤ k ≤ n равно A( n , k ) = n ( n − 1) ... ( n − k + 1) = n! . ( n − k )! Считают, что A(0, 0) = А(n, 0) = 1 и A(n, k) = 0, если k > n. Перестановка из n элементов – частный случай размещения при k = n. Поэтому число P(n) различных перестановок из n элементов равно P ( n ) = n ( n − 1) ... 2 ⋅ 1 = n! Полагают, что P(0) = 0! = 1. Сочетанием элементов из Х по k (или из n по k) называется неупорядоченное подмножество из k элементов, принадлежащих Х. Сочетание отличается от размещения тем, что в нем не учитывается порядок элементов. Поэтому каждому сочетанию соответствует k! размещений. Отсюда следует формула для числа C(n, k) сочетаний из n элементов по k (1 ≤ k ≤ n): A( n, k ) n! C ( n, k ) = = . k! k ! ( n − k )! Из этой формулы вытекает справедливость равенств: C (0, 0) = C ( n, 0) = C ( n, n ) = 1, C ( n , k ) = C ( n, n − k ). 26 Считают, что C(n, k) = 0 при k > n. Величины A(n, k), P(n), P(n, k) называются комбинаторными числами. Формулы их вычисления позволяют пересчитать соответствующие комбинаторные объекты. 3.2. Генерация сочетаний Во многих практических задачах возникает необходимость перечислить все различные комбинаторные объекты и выбрать из них те, которые удовлетворяют заданным условиям. Иногда требуется среди всех различных комбинаторных объектов найти такой, который доставляет максимум (минимум) некоторой целевой функции. Решение подобных задач базируется на алгоритмах генерации комбинаторных объектов, которых известно достаточно много [5, 12, 17]. Выбранные для изучения и анализа алгоритмы конструируют сочетания и перестановки в лексикографическом порядке. Без ограничения общности можно принять Х = {1, …, n}. Тогда каждому сочетанию из n по k, т. е. k-элементному подмножеству множества Х, взаимно однозначно соответствует вектор, состоящий из k разрядов, упорядоченных в порядке возрастания. Например, подмножеству {4, 6, 1} соответствует вектор (1, 4, 6). Наиболее естественным порядком перечисления таких векторов является лексикографический порядок. Его называют лексикографическим потому, что он лежит в основе упорядочения слов в словаре. Например, слово лес в словаре стоит перед словом шалаш, так как в русском алфавите буква л стоит перед буквой ш. Слово лес стоит перед словом лето. Хотя первые две буквы этих слов совпадают, третья буква слова лес стоит в алфавите перед третьей буквой слова лето. Точно так же число 135 предшествует числу 145. Рассмотрим два вектора с элементами из линейно упорядоченного множества Х = {1, …, n}: А = (а1, …, аm), В = (b1, …, bk). Считается, что вектор A лексикографически предшествует вектору B, если выполнено одно из условий: 27 – a 1 < b 1; – ai = bi для всех 1 ≤ i ≤ j и аj + 1 < bj + 1; – ai = bi для всех 1 ≤ i ≤ m и m < k. Пример 3.1. Для Х = {1, 2, 3, 4, 5} и k = 3 существует C (5, 3) = 5! = 10 3! 2! сочетаний, которые образуют следующую лексикографическую последовательность векторов: (1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5), (2, 3, 4), (2, 3, 5), (2, 4, 5), (3, 4, 5). Алгоритм генерации всех k-элементных подмножеств в лексикографическом порядке основан на следующем наблюдении: за вектором (а1, …, аk) непосредственно следует вектор (а1, …, аp − 1, ар + 1, ар + 2, …, ар + k – p + 1), р = max {i | ai < n – k + i}. Надо отметить, что р – номер первого слева изменяемого разряда в векторе (а1, …, аk), а величина (n – k + i) – максимально возможное значение i-го разряда этого вектора (1 ≤ i ≤ k, 1 ≤ k ≤ n). В алгоритме 3.1 k-элементные подмножества n-элементного множества X = {1, …, n} формируются в одномерном массиве A = (A[1], …, A[k]). Исходными данными являются величины n и k, где 1 ≤ k ≤ n. Начинается генерация с вектора A = (1, …, k). Каждый следующий вектор получается из предыдущего изменением разрядов в «хвосте» вектора на идущие подряд целые числа, но так, чтобы последний разряд не превосходил n, а первый изменяемый разряд был на 1 больше, чем соответствующий разряд в предыдущем векторе. Алгоритм 3.1. Генерация k-элементных подмножеств n-элементного множества 1. В качестве начального взять вектор A = (1, …, k), т. е. для всякого i = 1, ..., k выполнить присваивание A[i]:= i. 28 2. Положить p:= k, где p – номер первого изменяемого разряда. 3. Вывести текущее значение А. 4. Если A[k] = n, то последний разряд увеличивать нельзя, поэтому следует положить p:= p – 1. В противном случае p:= k. 5. Если p ≥ 1, то выполнить изменение разрядов в «хвосте» вектора для каждого i = k, k – 1, …, p следующим образом: A[i]:= A[p] + i – p + 1 и вернуться к пункту 3. В противном случае останов. Пример 3.2. Последовательность векторов, порожденных алгоритмом 3.1 для n = 7 и k = 5, приведена в табл. 3.1. При записи этих векторов скобки и разделяющие запятые опущены. Количество векторов равно 7! C ( 7 , 5) = = 21 . 2! 5! Таблица 3.1 № п/п Вектор A Значение р № п/п Вектор A Значение р 1 2 3 4 5 6 7 8 9 10 12345 12346 12347 12356 12357 12367 12456 12457 12467 12567 – 5 5 4 5 4 3 5 4 3 11 12 13 14 15 16 17 18 19 20 21 13456 13457 13467 13567 14567 23456 23457 23467 23567 24567 34567 2 5 4 3 2 1 5 4 3 2 1 29 3.3. Генерация перестановок Рассмотрим теперь процесс генерации n! различных перестановок множества Х = {1, …, n}. Все эти перестановки можно порождать в лексикографическом порядке одну за другой с помощью следующего алгоритма. Алгоритм 3.2. Генерация перестановок 1. Сначала взять перестановку A = (1, …, n). 2. Вывести текущее значение А. 3. Пусть A = (А[1], …, A[n]) – текущая перестановка. Выполнить просмотр A справа налево в поисках самой правой позиции i, в которой A[i] < A[i + 1]. Если такой позиции i нет, то текущая перестановка А является последней. Процесс генерации завершить. Иначе идти дальше. 4. Выполнить просмотр A = (А[1], …, A[i], …, A[n]) слева направо от A[i] в поисках наименьшего из таких элементов A[j], что i < j и A[i] < A[j]. Переставить местами элементы A[i] и A[j], т. е. выполнить следующие присваивания: C:= A[i], A[i]:= A[j], A[j]:= C. 5. Пусть A = (А[1], …, A[i], …, A[n]) – перестановка, полученная на предыдущем шаге. Реверсировать (или изменить на обратный) порядок перечисления элементов A[i + 1], …, A[n] «хвоста» перестановки А. Заметим, что элементы обращаемого «хвоста» всегда расположены в порядке убывания. Перейти к пункту 2. Пример 3.3. Рассмотрим для n = 8 текущую перестановку А = (2, 6, 5, 8, 7, 4, 3, 1). Для нее имеем i = 3, j = 5. Транспозиция элементов A[3] = 5, A[5] = 7 дает перестановку (2, 6, 7, 8, 5, 4, 3, 1). После реверсии отрезка этой перестановки, начиная с четвертого по восьмой элемент, получаем результирующую перестановку (2, 6, 7, 1, 3, 4, 5, 8), которая следует за текущей перестановкой в лексикографическом порядке. 30 Пример 3.4. При n = 4 результаты работы алгоритма 3.2 приведены в табл. 3.2. Таблица 3.2 № п/п Перестановка i j № п/п Перестановка i j 1 2 3 4 5 6 7 8 9 10 11 12 1234 1243 1324 1342 1423 1432 2134 2143 2314 2341 2413 2431 3 2 3 2 3 1 3 2 3 2 3 1 4 4 4 3 4 4 4 4 4 3 4 3 13 14 15 16 17 18 19 20 21 22 23 24 3124 3142 3214 3241 3412 3421 4123 4132 4213 4231 4312 4321 3 2 3 2 3 1 3 2 3 2 3 – 4 4 4 3 4 2 4 4 4 3 4 – Замечание. Перечисление размещений из n элементов по k всегда можно выполнить, комбинируя алгоритмы 3.1 и 3.2: вначале получить текущее сочетание из n по k с помощью алгоритма 3.1, а затем продуцировать из него k! различных перестановок по алгоритму 3.2 и далее снова к следующему сочетанию. Конечно, такая процедура не гарантирует лексикографического порядка перечисления размещений. 3.4. Порядок выполнения задания 3.4.1. Изучить алгоритмы 3.1−3.3 и оценить их вычислительную сложность. 3.4.2. Реализовать алгоритмы 3.1, 3.2 в виде процедур. Каждую из процедур оформить так, чтобы, с одной стороны, ее можно было автономно тестировать, а с другой – применять в других процедурах и программах. Для тестирования процедур использовать примеры 3.1–3.4. 31 3.4.3. Разработать и отладить программу решения комбинаторной задачи аналитической геометрии из табл. 3.3 согласно номеру варианта задания. Для этого предварительно требуется: – формализовать постановку задачи, используя формулы определения основных объектов аналитической геометрии; – найти и учесть условия, при которых задача не имеет решения; – разработать алгоритм решения задачи, используя алгоритм 3.1 генерации сочетаний. Оценить теоретическую сложность алгоритма по времени и памяти; – определить форму представления в программе исходных данных задачи (см. рекомендации в прил. 2); – разработать контрольные примеры для проверки правильности работы программы (см. рекомендации в прил. 2). 3.4.4. Разработать и отладить программу решения квадратичной задачи о назначениях. При разработке программы использовать алгоритм 3.2 генерации перестановок. Программа должна осуществлять поиск точного решения задачи путем перечисления и анализа всех различных перестановок. Ограничение на множество допустимых решений определяется номером варианта задания (табл. 3.4). Контрольные примеры для проверки правильности работы программы выбрать самостоятельно, используя пример 3.5. 3.5. Варианты Квадратичная задача о назначениях [10, 20]. Пусть имеется множество городов М = {1, …, n} и множество заводов P = {1, …, n}, которые можно построить в этих городах, по одному заводу в каждом городе. Мощности обоих множеств равны n = | М | = | Р |. Известна (n × n)-матрица C = {сij}, определяющая стоимость перевозки одной тонны груза из любого города в любой другой. При этом сii = 0 и сij > 0 для всех j ≠ i (i, j = 1, …, n). Задана также (n × n)-матрица V = {vij}, отражающая необходимые объемы перевозок между различными двумя заводами. Здесь vii = 0 и vij > 0 для всех j ≠ i (i, j = 1, …, n). 32 Таблица 3.3 Номер варианта 1 Комбинаторная задача аналитической геометрии Множество точек на плоскости называется регулярным, если вместе с каждой парой различных точек оно содержит также еще одну – третью вершину правильного треугольника с вершинами в этих точках. Определить, регулярно ли заданное множество точек 2 Задано множество точек на плоскости. Выбрать из него такие три точки, которые не лежат на одной прямой и составляют треугольник наименьшей площади 3 На плоскости заданы множество точек Х и точка d ∉ X. Найти все различные тройки точек a, b, c из Х такие, что четырехугольник с вершинами из {a, b, c, d} образует параллелограмм 4 Задано множество точек на плоскости. Выбрать из них четыре разные точки, которые являются вершинами квадрата наибольшего периметра 5 Даны два множества точек на плоскости. Выбрать такие три различные точки первого множества, чтобы они не лежали на одной прямой, а треугольник с вершинами в этих точках накрывал (содержал строго внутри себя) все точки второго множества Задано множество точек на плоскости. Выбрать из них четыре разные точки, не лежащие на одной прямой и образующие ромб наибольшей площади 6 Требуется определить, как выгоднее всего разместить заводы для минимизации затрат на перевозки. Другими словами, при заданных матрицах C и V необходимо найти взаимно однозначное отображение f: М → P, для которого достигается минимум суммы n S = n ∑∑ c i = 1 j =1 33 ij ⋅ v f (i ) f ( j ) . При такой формулировке допустимым решением задачи является любая подстановка (рис. 3.1). 2 … i … n Последовательность городов из М f(2) … f(i) … f(n) Последовательность заводов из P 1 f(1) Рис. 3.1 Между тем возможны ограничения на строительство некоторых заводов в тех или иных городах. Такие ограничения удобно задавать матрицей запрещенных назначений A = {aij}, где ⎧1, если завод i нельзя строить в городе j , ai j = ⎨ ⎩0 в противном случае (i, j = 1, ... , n ). Матрица A ограничивает множество допустимых решений, среди которых необходимо найти оптимальное решение задачи. Несмотря на простую формулировку, найти точное решение квадратичной задачи о назначениях не удается уже при n = 15, поскольку его поиск сводится к полному перебору всех допустимых решений [10, 20] и требует очень много времени. Эта задача интересна возможностью применения при ее решении алгоритма генерации перестановок. Пример 3.5. Пусть n = 3 и матрицы C, V имеют вид, указанный на рис. 3.2, 3.3. Здесь матрицы C и V симметричные, хотя в общем случае это необязательно. Существуют ограничения на размещение заводов по городам в виде матрицы A (рис. 3.4). ⎛ 0 1 2⎞ ⎜ ⎟ C = ⎜1 0 3⎟ ⎜ 2 3 0⎟ ⎝ ⎠ ⎛ 0 5 6⎞ ⎜ ⎟ V = ⎜ 5 0 3⎟ ⎜ 6 3 0⎟ ⎝ ⎠ Рис. 3.2 Рис. 3.3 34 ⎛ 1 0 0⎞ ⎜ ⎟ A = ⎜ 0 1 0⎟ ⎜0 0 1⎟ ⎝ ⎠ Рис. 3.4 Согласно матрице А из 3! = 6 всех возможных подстановок ⎛1 2 3 ⎞ ⎟⎟ , P1 = ⎜⎜ ⎝1 2 3 ⎠ ⎛1 2 3 ⎞ ⎟⎟ , P2 = ⎜⎜ ⎝1 3 2 ⎠ ⎛1 2 3 ⎞ ⎟⎟ , P4 = ⎜⎜ ⎝2 3 1⎠ ⎛1 2 3 ⎞ ⎟⎟ , P5 = ⎜⎜ ⎝3 1 2⎠ ⎛1 2 3 ⎞ ⎟⎟ , P3 = ⎜⎜ ⎝ 2 1 3⎠ ⎛1 2 3 ⎞ ⎟⎟ P6 = ⎜⎜ ⎝3 2 1 ⎠ допустимыми решениями задачи являются лишь подстановки P4 и Р5. Они соответствуют полным беспорядкам, т. е. для них при всех i = 1, 2, 3 верно: f(i) ≠ i. Вычислим для P4 и Р5 суммы затрат на перевозки: S(Р4) = (с11 · v22 + с12 · v23 + с13 · v21) + (с21 · v32 + с22 · v33 + с23 · v31) + + (с31 · v12 + с32 · v13 + с33 · v11) = (3 + 10) + (3 + 18) + (10 + 18) = 62; S(Р5) = (c11 · v33 + c12 · v31 + c13 · v32) + (c21 · v13 + c22 · v11 + c23 · v12) + + (c31 · v23 + c32 · v21 + c33 · v22) = (6 + 6) + (6 + 15) + (6 + 15) = 54. Таким образом, подстановка Р5 дает наиболее выгодный вариант размещения заводов. Таблица 3.4 Матрица A № 1 1 1 2 2 Матрица A № ... n 1 1 4 1 2 ... 1 2 1 0 0 1 1 1 1 ... 1 1 ... 1 1 0 1 1 1 1 n n 1 1 1 1 n 1 35 0 Матрица A № 1 2 2 1 1 1 1 2 n 1 3 n 1 1 5 1 1 2 ... 1 1 1 2 1 1 1 1 1 ... n 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 ... 1 ... Матрица A № ... n 1 1 1 0 1 1 1 1 1 1 ... 1 1 1 1 1 1 1 1 0 1 n 1 n 1 2 1 2 6 1 1 1 1 2 ... 1 1 1 1 1 1 1 2 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 0 ... n 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 n 1 1 1 1 1 1 0 1 Замечание. В табл. 3.4 числами 1−6 обозначены номера вариантов. В указанных матрицах A запрещенные позиции выделены единицами. Нулевые позиции считаются разрешенными. 36 ГЛАВА 2 АЛГОРИТМЫ НА ГРАФАХ ЗАДАНИЕ 4 Графы: представления и операции Теория графов, обладая стройной системой понятий и обозначений, является продуктивным средством моделирования систем и процессов информационного характера. Из всех разделов дискретной математики именно графы и особенно алгоритмы на графах широко применяются в программировании. В задании вводится язык теории графов [16], излагаются способы машинного представления графов. Цель задания: практика программирования операций над графами и преобразования одного способа представления графа в другой. 4.1. Основные понятия и обозначения Пусть V – непустое конечное множество, V(2) – множество всех его двухэлементных подмножеств. Пара G = (V, E), где E – произвольное подмножество множества V(2), называется графом (или неориентированным графом). Граф G = (V, E) конструируется из элементов конечного множества V, поэтому он является комбинаторным объектом. Элементы множества V принято называть вершинами, а элементы множества E – ребрами графа G = (V, E). В свою очередь, вершины и ребра – элементы этого графа. Число | V | вершин графа называется его порядком. Если | V | = n и | E |= m, то граф G = (V, E) называется (n, m)-графом. Две вершины графа u и v смежны, когда е = {u, v} ∈ Е, и не смежны в противном случае. Если е = {u, v} – ребро графа, то вершины u и v называются его концами. Два ребра смежны, если они имеют общий конец. 37 Вершину v и ребро е называют инцидентными, если v является концом ребра е, и неинцидентными в противном случае. Таким образом, смежность есть отношение между однородными элементами графа, тогда как инцидентность является отношением между разнородными элементами. Множество всех вершин графа, смежных с некоторой вершиной v, называется окрестностью вершины v и обозначается N(v). Степенью вершины v графа называется число инцидентных ей ребер, т е. число вершин, образующих ее окрестность. Степень вершины обозначается d(v) (или deg v). Тем самым d(v) = | N(v) |. Вершина степени 0 считается изолированной, степени 1 – висячей. Список степеней вершин графа называется его степенной последовательностью. Пусть d1, …, dn – степени вершины графа порядка n, выписанные в порядке неубывания: d1 ≤ d2 ≤ …≤ dn. Упорядоченный набор (d1, …, dn) называется вектором степеней графа G и обозначается d(G). Маршрутом (или (v1, vk + 1)-маршрутом) в графе называется чередующаяся последовательность вершин и ребер v1, e1, v2, e2, v3, …, ek, vk + 1, в которой любые два соседних элемента инцидентны. Если v1 = vk + 1, то маршрут замкнут (или цикличен). Если все ребра различны, то маршрут называется цепью. Если все вершины (а значит, и ребра) различны, то маршрут называется простой цепью. Замкнутая цепь называется циклом, замкнутая простая цепь – простым циклом. Граф без циклов является ациклическим (или лесом). Длина маршрута (цикла) определяется числом составляющих его ребер. Две вершины в графе считаются связными, если существует соединяющая их простая цепь. Граф, в котором любые две вершины связные, называется связным. Граф G' = (V', E') называется подграфом графа G = (V, E), если V' ⊆ V и E' ⊆ E. Всякий максимальный (по числу вершин и ребер) связный подграф графа G называется его компонентой связности. Число компонент связности графа G обозначается через k(G). Если k(G) = 1, то G – связный граф. Если k(G) > 1, то G – несвязный граф. 38 Графы принято изображать в виде рисунков, состоящих из точек и линий. При этом точки соответствуют вершинам графа, а соединяющие пары точек линии – ребрам. Граф, состоящий из одной вершины, называется тривиальным. Граф является безреберным, если в нем нет ребер, т. е. Е = ∅. Безреберный граф порядка n обозначается On. Граф называется полным, если любые две его вершины смежны, т. е. Е = V(2). Полный граф порядка n обозначается Kn. Граф называется двудольным, если существует такое разбиение множества его вершин на две части (доли), что концы каждого ребра принадлежат разным долям. Граф считается взвешенным, если каждому его ребру е ∈ Е приписано вещественное число w(е) – вес ребра. Граф порядка n является помеченным, если его вершинам присвоены некоторые метки, например, номера от 1 до n. Подобным образом можно пометить ребра графа. Пример 4.1. Граф G, изображенный на рис. 4.1, является связным двудольным графом, поскольку он состоит из одной компоненты связности и множество его вершин V = {1, 2, 3, 4} разбивается на две доли V1 = {1, 3}, V2 = {2, 4} так, что V1 ∪ V2 = V, V1 ∩ V2 = ∅ и концы каждого ребра принадлежат разным долям. Кроме того, для вершин данного графа имеем: d(1) = 2, d(2) = 2, d(3) = 1, d(4) = 1. Исходя из этого графу G соответствует вектор степеней d(G) = (1, 1, 2, 2). В графе G нет циклов, а вершины с номерами 3 и 4 соединены простой цепью: 3, е1, 2, е2, 1, е3, 4. 4.2. Отношения и операции Над помеченными графами определены отношения равенства, включения и некоторые операции, такие как дополнение, объединение графов, удаление элементов графа. Два графа G1 = (V1, E1) и G2 = (V2, E2) называются равными (обозначается G1 = G2), если V1 = V2 и E1 = E2. 39 Граф G1 = (V1, E1) вложен в граф G2 = (V2, E2) (обозначается G1 ⊆ G2), если G1 – подграф графа G2, т. е. V1 ⊆ V2 и E1 ⊆ E2. При V1 = V2 и E1 ⊆ E2 граф G1 называется остовным подграфом графа G2. Дополнением графа G = (V, E) называется граф G = (V, V(2) \ E). Другими словами, граф G имеет то же множество вершин, что и граф G, и любые две несовпадающие вершины смежны в G тогда и только тогда, когда они не смежны в G. Объединением графов G1 = (V1, E1) и G2 = (V2, E2) называется граф G1 ∪ G2 = G(V, E), для которого V = V1 ∪ V2, E = E1 ∪ E2. Объединение G1 ∪ G2 называется дизъюнктным, если V1 ∩ V2 = ∅. Ясно, что при G = G1 ∪ G2 всегда G1 ⊆ G и G2 ⊆ G. Пример 4.2. На рис. 4.1 показан граф G и его дополнение G . Очевидно, что G ∪ G = K4. e1 2 3 2 3 4 1 4 e2 1 e3 G Рис. 4.1 G Удаление вершины v ∈ V из графа G = (V, E) приводит к графу G – v, содержащему все вершины графа G, за исключением v, и все ребра графа G, не инцидентные v. Таким образом, граф G – v является максимальным подграфом графа G, который не включает вершину v. Удаление ребра e ∈ E из графа G = (V, E) приводит к остовному подграфу G – e, содержащему все ребра графа G, за исключением e. Пусть u и v – две вершины графа G. Пусть также H = G – u – v. К графу Н добавим новую вершину v*, соединив ее ребром с каждой вершиной из N(u) ∪ N(v). Таким образом, построенный граф получается из G отождествлением (или слиянием) вершин u и v. 40 4.3. Родственные графам объекты Иногда введенное определение графа оказывается недостаточным, и приходится рассматривать более общие объекты: мультиграф, псевдограф, орграф. Мультиграф – это граф, в котором допускаются кратные ребра, т. е. Е является не множеством, а семейством элементов из V(2). В псевдографе кроме кратных ребер допускаются еще петли − ребра, соединяющие вершину саму с собой. Для ориентированных графов (или орграфов) множество V(2) заменяется декартовым квадратом V2 = V × V, состоящим из упорядоченных пар (u, v) элементов множества V. Итак, орграф – пара G = (V, E) , где V – множество вершин, Е – множество ориентированных ребер, которые называются дугами, Е ∈ V2. Если е = (u, v) – дуга, то вершины u и v называются ее началом и концом соответственно. На рисунке дуги принято отмечать стрелками, указывающими направление от начала к концу дуги. Для орграфов цепь называется путем, а цикл – контуром. Другая терминология применяется и в отношении степеней вершин. Число дуг, исходящих из вершины v, называется полустепенью исхода, а входящих – полустепенью захода. Обозначаются эти числа, соответственно, d − (v) и d + (v). Для орграфов справедливы все определенные ранее отношения и операции над неориентированными графами. Однако если полный неориентированный граф порядка n имеет m = n (n – 1) / 2 ребер, то полный орграф того же порядка имеет m = n2 дуг. С учетом этого факта вычисляется дополнение орграфа. 4.4. Способы машинного представления Изображение графов на плоскости в виде точек и соединяющих их линий является наиболее понятным для человека способом представления графов. Однако если требуется решать с помощью ЭВМ задачи, связанные с графами, то данный способ становится бесполезным. Его в лучшем случае можно использовать для внешнего представления входных и выходных данных алгоритмов. 41 Известны различные способы машинного представления графов, которые различаются объемом занимаемой памяти и скоростью выполнения операций над графами. Рассмотрим из них четыре основных [11]. Способ 1. Матрица инцидентности. Это классический матричный способ представления графа. Пусть G = (V, E) – помеченный (n, m)-граф, V = {1, …, n}, n ≥ 2, E = {e1, …, em}, m ≥ 1. Матрицей инцидентности графа G c заданной нумерацией вершин и ребер называется (n × m)-матрица A(G) = {aij}, элементы которой определяются следующим образом: ⎧1, если вершина i инцидентна ребру e j , ai j = ⎨ ⎩0 в противном случае (i = 1, ... , n; j = 1, ... , m). Для орграфов определение матрицы инцидентности видоизменяется: ⎧ 1, если вершина i является концом дуги e j , ⎪ a i j = ⎨− 1, если вершина i является началом дуги e j , ⎪ ⎩ 0 в противном случае (i = 1, ... , n; j = 1, ... , m). Если орграф имеет петли, т. е. дуги вида еj = (i, i), то их обычно договариваются обозначать aij = 2, хотя возможны и другие соглашения. Матрица инцидентности пригодна для представления мультиграфов и псевдографов. Пример 4.3. Пусть заданы граф G1, орграф G2 (рис. 4.2, 4.3). Соответствующие им матрицы инцидентности приведены на рис. 4.4, 4.5. e2 1 e1 4 e2 e4 2 1 e3 e1 3 4 G1 Рис. 4.2 2 e3 e4 3 G2 Рис. 4.3 42 e6 e5 ⎛1 ⎜0 ⎜ A(G1 ) = 0 ⎜ ⎜⎜ 1 ⎝0 1 0 0⎞ ⎟ 0 1 0 ⎟ 1 1 1 ⎟ 0 0 1⎟ ⎟ 0 0 0⎠ ⎛ 1 ⎜ 0 A( G 2 ) = ⎜ ⎜ 0 ⎝−1 Рис. 4.4 −1 0 1 0 0 1 0 −1 0 0 0⎞ ⎟ 0 −1 1 ⎟ 0 1 −1 ⎟ 2 0 0⎠ Рис. 4.5 С алгоритмической точки зрения, матрица инцидентности как способ задания графов имеет ряд существенных недостатков. Вопервых, требуется для (n, m)-графа nm ячеек памяти, причем большинство из них будет занято нулями. Во-вторых, неудобен доступ к данным. Ответы на элементарные вопросы типа «Существует ли дуга (u, v)?», «Какие вершины смежны с вершиной v?» требуют в худшем случае перебора всех столбцов матрицы. В-третьих, матрица инцидентности непригодна для представления взвешенных графов. Способ 2. Матрица смежности. Пусть G = (V, E) – помеченный граф порядка n ≥ 1, V = {1, …, n}. Матрицей смежности графа G c заданной нумерацией вершин называется (n × n)-матрица B(G) = {bij}, элементы которой определяются следующим образом: ⎧ 1, если вершины i и j смежны , bi j = ⎨ ⎩0 в противном случае (i , j = 1, ... , n ). Всякий орграф G = (V, E) можно также задать матрицей смежности B(G) = {bij}, где ⎧ 1, если (i, j ) ∈ E , bi j = ⎨ ⎩0 в противном случае (i , j = 1, ... , n ). Если в орграфе есть петли, т. е. дуги вида (i, i), то для них, как правило, полагают bii = 1. Пример 4.4. Матрицы смежности для графа G1 и орграфа G2, (рис. 4.2, 4.3) указаны на рис. 4.6, 4.7. 43 ⎛0 ⎜0 ⎜ B ( G1 ) = 1 ⎜ ⎜⎜ 1 ⎝0 0 1 1 0⎞ ⎟ 0 1 0 0 ⎟ 1 0 1 0 ⎟ 0 1 0 0⎟ ⎟ 0 0 0 0⎠ Рис. 4.6 B (G 2 ) ⎛0 ⎜ ⎜0 =⎜ ⎜0 ⎜1 ⎝ 1 0 1 1 0 1 0 0 0 ⎞⎟ 0⎟ ⎟ 0⎟ 1 ⎟⎠ Рис. 4.7 Неориентированные графы всегда имеют симметричные матрицы смежности. Главным преимуществом матрицы смежности является то, что за один шаг можно получить ответ на вопрос «Существует ли в графе ребро{u, v}?». Другими словами, матрица смежности обеспечивает «прямой доступ» к ребрам графа. Недостаток данного способа представления графа – это большая потребность в памяти: нужно n2 ячеек. Для взвешенных графов и орграфов вместо матриц смежности используют матрицы весов. Пусть G = (V, E) – помеченный граф, для которого V = {1, …, n}, w(i, j) – вес ребра {i, j} ∈ E. Матрицей весов графа G называется (n × n)-матрица W(G) = {wij}, элементы которой определяются формулой ⎧ w(i , j ), если {i , j} ∈ E , wi j = ⎨ ⎩ 0 в противном случае (i , j = 1, ... , n ). Аналогично определяется матрица весов для орграфа. Способ 3. Перечень ребер. Данный способ является наиболее экономичным в отношении памяти (особенно для разреженных графов, когда m гораздо меньше n2). Он предполагает задание графа G = (V, E) с помощью последовательности пар < u, v >. Пара < u, v > соответствует ребру {u, v}∈ E, если граф неориентированный, или дуге (u, v) ∈ E, если ориентированный. Поскольку пару < u, v > можно хранить, используя две ячейки (по одной на каждую концевую вершину), то для хранения всей последовательности пар достаточно 2m ячеек. Если граф взвешенный, то понадобится еще m ячеек для хранения весов ребер (дуг) графа. 44 Этот способ также не лишен недостатков. Во-первых, чтобы определить, содержит ли граф заданное ребро, надо посмотреть в худшем случае все элементы последовательности, т. е. выполнить 2m элементарных операций. Ситуацию можно несколько улучшить, предварительно упорядочив пары < u, v > лексикографически, и затем применить двоичный поиск. Во-вторых, если граф G = (V, E) имеет изолированные вершины, то необходимо хранить множество V вершин графа G = (V, E), а это еще n дополнительных ячеек памяти. Пример 4.5. Перечни ребер для графа G1 и орграфа (рис. 4.2, 4.3) представлены на рис. 4.8, 4.9 соответственно. 1 1 2 3 3 4 3 4 1 2 3 4 4 4 Рис. 4.8 G2 2 3 2 1 2 4 Рис. 4.9 Способ 4. Списки смежности. При таком способе задания графа G = (V, E) каждой вершине i ∈ V ставится в соответствие ее окрестность в виде списка Ni вершин, смежных с ней. Кроме того, формируется массив М = (М1, …, Мn) указателей на списки смежности вершин, т. е. в Mi хранится указатель на первый элемент списка Ni (i = 1, …, n). Примечательно, что каждое ребро {i, j} неориентированного графа представляется дважды: через вершину i в списке Ni и через вершину j в списке Nj. Поэтому для неориентированного (n, m)-графа требуется 4m + n ячеек памяти, а для ориентированного – только 2m + n. Для проверки условия, содержит ли граф заданное ребро, необходимо выполнить порядка n элементарных операций. Очевидно, что данный способ применим также для взвешенных графов и орграфов. 45 Пример 4.6. Списки смежности для графа G1 и орграфа G2 (рис. 4.2, 4.3) изображены на рис. 4.10, 4.11 соответственно. МM 1 3 2 3 3 1 2 4 1 3 5 4 nil nil 4 nil 4 nil nil nil Рис. 4.10 МM 1 2 nil 2 3 nil 3 2 nil 4 1 2 Рис. 4.11 Выбор того или иного способа представления графа зависит от конкретной задачи. Нередки случаи, когда в программе необходимо перейти от одного способа представления графа к другому. 46 4.5. Порядок выполнения задания 4.5.1. Разработать алгоритм и программу перехода от способа A представления графа к способу В. Номер варианта задания определяет значения А и В (табл. 4.1). Оценить вычислительную сложность разработанного алгоритма. 4.5.2. Разработать алгоритм и программу проверки отношений и выполнения операций над графами. Отношения и операции определяются номером варианта задания (табл. 4.1). Считать, что в качестве исходных данных выступают один или два графа в зависимости от варианта задания. Исходные графы заданы и вводятся перечнями их вершин и ребер. Графы могут быть неориентированными и ориентированными. Для представления графов в памяти ЭВМ взять способ В. Тестовые примеры придумать самостоятельно. Оценить сложность алгоритма и программы по времени и используемой памяти. 4.6. Варианты Таблица 4.1 Номер Способ варианта представления графа Отношения и операции над графами А В 1 1 2 Операция объединения 2 1 3 Отношение включения 3 2 3 Операция дополнения 4 2 4 Отождествление двух заданных вершин 5 3 4 Удаление заданной вершины Удаление заданного ребра 6 1 4 Отношение равенства 47 ЗАДАНИЕ 5 Базовые задачи и алгоритмы на графах Во многих задачах теории графов необходимо обойти некоторый граф, посещая каждую его вершину в точности один раз и выполняя при этом некоторую систематическую обработку информации, относящуюся к этой вершине. К таким задачам относятся: отыскание остовного дерева; распознавание связности, двудольности и ацикличности графа; поиск «узких» мест графа; проверка достижимости вершин и др. Все эти задачи и алгоритмы их решения составляют операционный базис теории графов. Изучение методов систематического перебора вершин графа и практика решения на компьютере базовых задач теории графов – цель данного задания. 5.1. Обход вершин графа в глубину или ширину Среди различных методов систематического обхода всех вершин графа наиболее известны поиск в глубину и поиск в ширину [5, 6, 12−19]. Алгоритмы поиска лежат в основе многих задач на графах. В алгоритме 5.1, реализующем обход вершин графа в глубину или ширину, предполагается, что граф G = (V, E), где V = {v1, …, vn}, n ≥ 1, задан списками смежности. Это означает, что для каждой вершины v ∈ V известна ее окрестность N(v). Обход начинается с вершины s ∈ V, которая также должна быть задана. Алгоритм 5.1. Поиск в глубину или ширину 1. Инициализация. Пусть Х – одномерный массив для записи меток вершин графа. Вначале все вершины графа считать неотмеченными: для каждой вершины v ∈ V положить Х[v]:= 0. 2. Начало обхода. Исходную вершину s записать в структуру данных Q и отметить, полагая X[s]:= 1. 3. Посещение вершины. Из Q извлечь вершину u и вывести ее в качестве очередной пройденной вершины. 48 4. Пополнение Q. Проанализировать каждую вершину ω ∈ N(u). Если X[ω] = 0, т. е. вершина ω еще не отмечена, то записать ее в Q и отметить, полагая X[ω]:= 1. 5. Продолжение или завершение обхода. Если Q ≠ ∅, то перейти к пункту 3. Иначе обход завершить. Если структура данных Q – стек (удаление и добавление элементов производится с одного конца), то обход вершин графа по алгоритму 5.1 соответствует поиску в глубину. Поиск в глубину называют иногда обходом в вертикальном порядке. Если Q – очередь (удаление элементов производится с одного конца, а добавление – с другого), то обход называется поиском в ширину, или обходом в горизонтальном порядке. Для связного графа G алгоритм 5.1 обеспечивает обход всех вершин графа, поскольку в Q попадают по порядку все вершины из множества N(v1) ∪ … ∪ N(vn) = V. При этом каждая вершина обходится только один раз. Действительно, посещаются только те вершины, которые попали в Q (пункт 3 алгоритма). В Q записываются только неотмеченные вершины, которые сразу же отмечаются (пункт 4 алгоритма). Оценим сложность алгоритма 5.1. Каждое включение и исключение вершины из Q выполняется за время О (1). Поэтому для всей работы, связанной с изменением Q, достаточно времени О (n), где n = | V |. Для каждой вершины u ∈ V пункт 4 алгоритма выполняется d(u) = | N(u) | раз. Поэтому затраты на его выполнение в целом можно оценить так: ⎞ ⎛ O ⎜ ∑ d (u ) ⎟ = O ( m), ⎠ ⎝ u ∈V где m = | Е | – число ребер графа. Таким образом, временная сложность поиска в глубину или ширину составляет O (n + m). Заметим, что здесь использовано равенство ∑ d (u) = 2m, u ∈V справедливое для любого (n, m)-графа G = (V, E) и известное в теории графов как «лемма о рукопожатиях». 49 Пример 5.1. Рассмотрим (7, 10)-граф G = (V, E), для которого V = {1, 2, 3, 4, 5, 6, 7} (рис. 5.1). При поиске в глубину вершины этого графа будут проходиться в следующем порядке: 1, 5, 7, 6, 4, 3, 2. Обход начинается с вершины s = l. 1 2 4 N(1) = (2, 3, 5) N(2) = (1, 4, 5) N(3) = (1, 5) N(4) = (2, 5) N(5) = (1, 2, 3, 4, 6, 7) N(6) = (5, 7) N(1) = (5, 6) 3 5 6 7 Рис. 5.1 На рис. 5.2 и в табл. 5.1 приведены результаты обхода: ПГномера (номера по порядку поиска в глубину) вершин; метки (исходные номера) вершин; текущее состояние стека Q. Таблица 5.1 7* 5* Вершина u 1* 2* 6* 3* 4* ПГ-номер Метка 1* 2* 3* 4* 5* 6* 7* 1 5 7 6 4 3 2 Стек Q 2, 3, 5 2, 3, 4, 6, 7 2, 3, 4, 6 2, 3, 4 2, 3 2 ∅ Рис. 5.2 При поиске в ширину для s = 1 вершины будут проходиться в следующем порядке: 1, 2, 3, 5, 4, 6, 7. На рис. 5.3 и в табл. 5.2 указаны результаты этого обхода: ПШ-номера (номера по порядку поиска в ширину) вершин; метки вершин; текущее состояние очереди Q. 50 Таблица 5.2 2' 5' 1' 2' 5' 4' 7' 1' 3' 4' 6' 7' Очередь Q Вершина u 3' 6' Рис. 5.3 ПШ-номер Метка 1' 2' 3' 4' 5' 6' 7' 1 2 3 5 4 6 7 2, 3, 5 3, 5, 4 5, 4 4, 6, 7 6, 7 7 ∅ 5.2. Базовые задачи на графах Поиск в глубину или ширину лежит в основе алгоритмов решения большого числа задач, сформулированных на языке теории графов. Рассмотрим основные задачи, укажем особенности их решения с помощью алгоритма 5.1. Задача выделения компонент связности графа. Эта задача может быть решена с помощью поиска в глубину или ширину. Пусть поиск начат с вершины s ∈ V. После его окончания, т. е. выделения компоненты связности, содержащей вершину s, следует проверить, все ли вершины графа отмечены. Если все вершины отмечены, то процесс обхода завершить. Если нет, то обход возобновить с любой неотмеченной вершины. Не представляет труда в алгоритме 5.1 предусмотреть вычисление числа k(G) компонент связности исходного графа G и вывод сообщения о начале обхода вершин i-й компоненты, 1 ≤ i ≤ k(G). Задача распознавания связности графа. Это частный случай предыдущей задачи. Если после однократного применения к графу алгоритма 5.1 все его вершины оказались отмеченными, то граф связен. В противном случае граф не является связным. Задача распознавания дерева. Решение задачи сводится к проверке связности графа и подсчету числа ребер в нем. Напомним, что если исходный (n, m)-граф связен и m = n – 1, то он является деревом [16]. 51 Задача отыскания остовного дерева. Пусть G = (V, E) – связный граф и T – остовной его подграф. Если T – дерево, то его называют остовным (или стягивающим) деревом графа G. Очевидно, что для каждого связного графа всегда существует остовное дерево: разрушая циклы, т. е. удаляя «лишние» ребра, всегда можно получить остовное дерево. Известно, что число «лишних» ребер в связном (n, m)-графе G равно λ(G) = m – n + 1, где λ(G) – цикломатическое число графа G. В общем случае цикломатическое число графа G с k = k(G) компонентами связности выражается формулой λ(G) = m – n + k. Легко доказать, что граф ацикличен тогда и только тогда, когда λ(G) = 0 [16]. Очевидно, что при k(G) > 1 не существует остовного дерева. Если граф G помечен и является деревом, то он имеет единственное остовное дерево T, совпадающее с самим графом G. В общем случае всякий связный помеченный граф имеет, как правило, несколько различных остовных деревьев. Некоторые из них можно найти, используя поиск в глубину или ширину. Для построения остовного дерева алгоритм 5.1 необходимо модифицировать следующим образом: – дополнительно к массиву Х меток вершин надо формировать одномерный массив Y длины n = | V |. Для каждой вершины графа запоминать в Y непосредственно предшествующую ей вершину. Так, в пункте 4 следует полагать Y[ω]:= u, если ω – неотмеченная вершина из N(u), а в пункте 2 для начальной вершины s установить Y[s]:= 0; – в пункте 3, извлекая из Q всякую отличную от s вершину u, создавать и выводить ребро {u, v}, где v = Y[u]. Совокупность всех таких n – 1 ребер и образует остовное дерево. Пример 5.2. Для графа G из примера 5.1 поиск в глубину из вершины 1 дает остовное дерево, изображенное на рис. 5.4. В табл. 5.3 приведены состояния массива Y после обхода вершин для этого дерева. 52 Таблица 5.3 2 1 4 3 5 6 7 Рис. 5.4 Вершина ω Вершина u – предшественница для ω 1 2 3 4 5 6 7 0 1 1 5 1 5 5 Из рис. 5.4 видно, что при построении остовного дерева исходный граф G теряет 4 ребра (они отмечены пунктирными линиями). Это соответствует значению цикломатического числа λ(G) = 10 – 7 + 1 = 4 графа G, для которого m = 10, n = 7, k = 1. Задача отыскания остовного леса. Пусть G = (V, E) – произвольный граф, связный или несвязный. Всякий остовной ациклический подграф T графа G называется его остовным (или стягивающим) лесом. Очевидно, что остовной лес (n, m)-графа с k = k(G) компонентами связности определяется через остовные деревья этих компонент и, следовательно, содержит m = n – k ребер. Таким образом, задача отыскания остовного леса сводится к выделению компонент связности и нахождению для каждой из компонент остовного дерева. Все это можно реализовать с помощью поиска в глубину или ширину. Задача проверки существования в графе G = (V, E) цикла, проходящего через заданное ребро e = {a, b} ∈ E. Проверка этого условия эквивалентна проверке наличия (a, b)-цепи в графе G – e. Последнее можно выполнить, применяя поиск в глубину или ширину из вершины a. Если вершины a и b принадлежат одной и той же компоненте связности графа G – e, то в графе G существует цикл, проходящий через ребро e = {a, b}, в противном случае такой цикл не существует. Задача поиска «узких» мест графа. Вершина v графа G называется точкой сочленения (или разделяющей вершиной), если граф 53 G – v имеет больше компонент связности, чем G. В частности, если G связен и v – точка сочленения этого графа, то G – v не связен. Аналогично ребро графа называется мостом, если его удаление увеличивает число компонент связности. Таким образом, точки сочленения и мосты – это своего рода «узкие» места графа. Заметим, что в дереве все вершины, кроме висячих, являются точками сочленения, а все ребра − мостами. Очевидно, что концевая вершина моста является точкой сочленения, если она не является висячей, т. е. в графе есть другие ребра, инцидентные этой вершине. Пример 5.3. Граф, изображенный на рис. 5.1, имеет одну точку сочленения – вершину с номером 5. Мостов в этом графе нет. Поиск «узких» мест графа сводится к задаче выделения компонент связности. Действительно, удаляя поочередно вершины (ребра) графа и каждый раз вычисляя число компонент в полученном графе, можно найти все точки сочленения (мосты) исходного графа G. Задача распознавания блока. Связный граф без точек сочленения называют блоком. Блоки играют важную роль в теории графов. Ряд задач достаточно уметь решать для блоков. Решение задачи распознавания блоков сводится к проверке связности графа и наличия в нем точек сочленения. Задача распознавания двудольности графа. Граф G = (V, E) называется двудольным, если существует такое разбиение множества его вершин V на две доли V1 и V2 (V1 ∪ V2 = V, V1 ∩ V2 = ∅), что концы каждого ребра е ∈ Е принадлежат разным долям. Если G – двудольный граф, то пишут G = (V1, V2, E). Полный двудольный граф – это граф, который содержит все ребра, соединяющие множества V1 и V2. Если |V1| = р, |V2| = q, то полный двудольный граф обозначается Kpq. Необходимые и достаточные условия двудольности графа устанавливает теорема Кенига [14, 16]: граф двудольный тогда и только тогда, когда в нем нет циклов нечетной длины. По теореме Кенига все ациклические графы двудольные. Пример 5.4. Граф из примера 5.1 не является двудольным, так как он содержит циклы, некоторые из которых имеют длину 3 и 5 (рис. 5.1). Теорема Кенига подсказывает простой способ распознавания двудольности графа, основанный на поиске в глубину или ширину. Не 54 ограничивая общности, будем полагать, что исходный граф G = (V, E) связен, поскольку каждую компоненту связности можно рассматривать отдельно, ибо дизъюнктное объединение двудольных графов является двудольным графом. Вначале с помощью алгоритма 5.1 необходимо множество вершин исходного графа разнести по уровням. Здесь номер уровня вершины v – это расстояние от начальной вершины s до вершины v, т. е. длина кратчайшей по числу ребер (s, v)-цепи. Для этого алгоритм 5.1 следует модифицировать следующим образом: – дополнительно к массиву Х меток вершин создать одномерный массив Y, в который записывать для каждой вершины графа непосредственно предшествующую ей вершину. В пункте 4 элемент Y[ω] полагать равным u, если ω – неотмеченная вершина из N(u), а в пункте 2 для начальной вершины s установить Y[s]:= 0; – создать одномерный массив Z, в котором формировать для каждой вершины номер уровня. С этой целью в пункте 3, извлекая из Q всякую отличную от s вершину u, полагать v:= Y[u], Z[u]:= Z[v] + 1. Это означает, что номер уровня вершины u на единицу выше уровня её предшественницы – вершины v. Таким образом, после завершения систематического обхода всех вершин графа каждая из них будет отнесена к некоторому уровню. Далее множество вершин V необходимо разбить на доли V1 и V2: – все вершины с четными номерами уровней записать в V1; – все остальные вершины отнести к V2. Критерий двудольности такой: если любые две несовпадающие вершины из одной доли не являются смежными, то G = (V1, V2, E) – двудольный граф. Вообще достаточной является проверка отношения смежности для всех пар вершин с одинаковыми номерами уровней (почему?). Пример 5.5. Рассмотрим граф G из примера 5.1. Для него поиск в глубину из вершины 1 дает остовное дерево (рис. 5.4) и разносит вершины графа по трем уровням (рис. 5.5). Номера уровней на рис. 5.5 55 указаны в скобках. Поскольку существуют пары смежных в G вершин с одинаковыми номерами уровней, то рассматриваемый граф не является двудольным. 2 2(1) 1 1(0) 5(1) 4(2) 4 3 5 7(2) 7 6 3(1) 6(2) Рис. 5.5 Задача о достижимости вершин орграфа. Пусть заданы орграф G = (V, E) и некоторая его вершина s ∈ V. Требуется найти множество всех вершин орграфа, достижимых из s. Говорят, что вершина v достижима из вершины s, если в G существует путь, идущий от вершины s к вершине v. Напомним, что путь – ориентированная цепь. Данная задача решается с помощью алгоритма 5.1. Для орграфа обход вершины следует производить с учетом направленности дуг. Направленность дуг полностью определяется списками смежности орграфа. Обход надо начинать с вершины s. Все вершины, которые удается отметить и пройти, достижимы из s. Пример 5.6. Пусть задан орграф G (рис. 5.6). 2 1 4 5 N(1) = (2) N(2) = (3) N(3) = (2, 5) N(4) = (1, 2) 3 N(1) = ∅ Рис. 5.6 56 Требуется выяснить, какие вершины достижимы из вершины 4. На рис. 5.7 и в табл.5.4 приведены результаты обхода орграфа в глубину из вершины 4: ПГ – номера вершин; метки вершин; текущее состояние стека Q. В результате обхода все вершины орграфа оказались отмеченными, а значит, достижимыми из вершины 4. Таблица 5.4 Вершина u 5* 1* 4* 2* ПГ-номер Метка 3* 1* 2* 3* 4* 5* 4 2 3 5 1 Рис. 5.7 Стек Q 1, 2 1, 3 1, 5 1 ∅ Задача нахождения всех источников орграфа. Источником орграфа называется вершина, из которой достижимы все другие вершины. Нахождение всех источников орграфа сводится к перебору всех вершин и решению для каждой из них задачи о достижимости. Задача проверки существования в орграфе G = (V, E) контура, проходящего через заданную дугу e = (a, b) ∈ E. Проверка этого условия равносильна проверке наличия (a, b)-пути в орграфе G – e. Эту проверку можно выполнить, используя поиск в глубину или ширину из вершины b. Если вершина a достижима из вершины b в G – e, то в G существует контур, проходящий через дугу (a, b). Так, например, в орграфе с рис. 5.6 имеется контур, проходящий через дугу (2, 3). Других контуров в этом орграфе нет. 5.3. Порядок выполнения задания 5.3.1. Изучить алгоритм 5.1. Разработать и отладить программные процедуры поиска в глубину и поиска в ширину. 5.3.2. Разработать алгоритмы и программы решения базовых задач теории графов с использованием алгоритма 5.1. Состав задач и стратегия обхода вершин определяются номером варианта задания (табл. 5.5). Оценить вычислительную сложность разработанных алгоритмов. 57 5.4. Варианты Таблица 5.5 Номер варианта 1 2 3 4 5 6 Задача Стратегия обхода вершин Выделение компонент связности графа Поиск в глубину Достижимость вершин орграфа Поиск в ширину Отыскание остовного дерева Поиск в ширину Распознавание связности Поиск в ширину Нахождение всех источников орграфа Поиск в глубину Отыскание остовного дерева Поиск в глубину Распознавание дерева Поиск в ширину Проверка существования контура, проходящего через заданную дугу Поиск в ширину Отыскание остовного леса Поиск в глубину Поиск «узких» мест графа Поиск в глубину Проверка существования цикла, проходящего через заданное ребро Поиск в глубину Отыскание остовного леса Поиск в ширину Распознавание блока Поиск в глубину Отыскание всех источников орграфа Поиск в ширину Распознавание двудольности графа Поиск в ширину Распознавание дерева Поиск в глубину Проверка существования цикла, проходящего через заданное ребро Поиск в ширину Распознавание двудольности графа Поиск в глубину 58 ЗАДАНИЕ 6 Построение минимального остова Одной из важнейших тем, связанных с графами, традиционно считаются оптимизационные задачи. В этом задании рассматривается одна из немногих быстрорешаемых оптимизационных задач на графах − построение минимального остова. Цель задания: изучение двух известных методов решения данной задачи и их программная реализация; практика решения прикладных задач, сводимых к минимальному остову. 6.1. Формулировка задачи Пусть задан связный взвешенный граф G = (V, E), V = {v1, …, vn}, n ≥ 1, E = {e1, …, em}, m ≥ 1, на множестве ребер которого определена весовая функция. Эта функция приписывает каждому ребру е = {vi, vj} ∈ Е графа вещественное число w(е) = wij – вес ребра e. Требуется из конечного (возможно, очень большого) числа остовных деревьев заданного графа найти одно, у которого сумма весов входящих в него ребер минимальна. Это дерево называют минимальным остовом, а саму задачу – задачей о минимальном остове. Решение данной задачи для связного графа всегда существует и, возможно, не единственное. Задача о минимальном остове возникает, например, при проектировании сетей коммуникаций, когда некоторые центры требуется соединить системой каналов связи так, чтобы любые два центра были достижимы друг из друга, и общая длина каналов связи была минимальна. В этой ситуации центры можно считать вершинами полного графа с весами ребер, равными длинам соединяющих эти центры каналов. Тогда искомая сеть – минимальное остовное дерево. Для решения задачи о минимальном остове имеются несколько эффективных алгоритмов. Рассмотрим два из них: алгоритм Дж. Краскала (1956 г.), алгоритм Р. Прима (1957 г.). 6.2. Алгоритм Краскала Идея алгоритма Краскала состоит в формировании остовного дерева T путем выбора в исходном связном графе G = (V, E) ребер 59 с наименьшим весом, но так, чтобы не возникал цикл. Эта «жадная» стратегия всегда дает точное решение задачи о минимальном остове. Алгоритм 6.1. Алгоритм Краскала 1. Построить граф Т1 = On + e1 присоединением к безреберному nвершинному графу On ребра е1 ∈ E минимального веса. 2. Если граф Тi уже построен и i < n – 1, то выполнить построение графа Ti + 1 = Ti + ei + 1, где ei + 1 есть ребро графа G, имеющее минимальный вес среди ребер, не входящих в Ti и не содержащих циклов с ребрами из Ti. 3. Процесс завершить, как только будет построен граф Tn − 1. Доказано [16], что граф Tn − 1 является минимальным остовным деревом. В пункте 2 алгоритма Краскала предусмотрена проверка наличия в графе Ti + 1 = Ti + ei + 1 цикла, проходящего через ребро ei + 1. Для этого можно использовать алгоритм 5.1 поиска в глубину или ширину (см. задание 5). Сложность поиска в глубину или ширину составляет О (n + m), где n = | V |, m = | E |. Если считать, что ребра графа G = (V, E) предварительно упорядочены, то временные затраты на подсоединение m = n – 1 ребер к графу On составят О (nm). Для упорядочения множества E по неубыванию весов известны алгоритмы с временем работы О (m ⋅ log m) [13]. Таким образом, реализация «жадной» стратегии приводит (независимо от способа машинного представления графа G) к алгоритму с временем исполнения О (nm), т. е. к полиномиальному алгоритму. Алгоритм Краскала особенно эффективен для «редких» графов − графов, слабо насыщенных ребрами. Пример 6.1. Используя алгоритм Краскала, определим минимальный остов графа G, изображенного на рис. 6.1 (в скобках даны веса ребер). На рис. 6.2 приведена последовательность графов Т1, Т2, Т3, Т4, Т5, получаемая в результате выполнения алгоритма Краскала. На рис. 6.1 минимальный остов выделен темными линиями. Суммарный вес ребер минимального остова составляет 19. 60 1 2 (10) 3 (2) (6) (8) 6 (9) (5) (3) (1) (4) 5 4 Рис. 6.1 Примечательно, что в алгоритме Краскала некоторые промежуточные графы Ti (1 ≤ i < n – 1) могут быть несвязными, однако для связного исходного графа G результирующий граф Tn − 1 всегда связен, содержит m = n – 1 ребер, т. е. является деревом. 2 6 (1) 5 TТ11 (2) 3 6 (3) (1) 5 5 Т2 T2 (2) 3 (2) 6 (1) 2 2 3 4 Т3 T3 1 2 3 (2) (8) 6 (1) (1) 5 6 (5) (5) (3) (3) Т44 T 4 5 Рис. 6.2 61 Т5 T5 4 6.3. Алгоритм Прима Алгоритм Прима отличается от алгоритма Краскала тем, что на каждом шаге строится не просто ациклический граф, а связный ациклический граф, т. е. дерево. В литературе данный алгоритм иногда называют алгоритмом «ближайшего соседа» или «локально жадным». Алгоритм 6.2. Алгоритм Прима 1. Выбрать ребро е1 ={a, b} минимального веса и создать дерево T1 = (V1, E1), где V1 = {a, b}, E1 = {e1}. 2. Если дерево Ti = (Vi, Ei) уже построено, то выполнить построение дерева Ti + 1. Для этого среди ребер, которые инцидентны вершинам из Vi, не принадлежат множеству Ei и не образуют цикл с ребрами из Ei, выбрать ребро ei + 1 минимального веса. Дерево Ti + 1 получается добавлением ребра ei + 1 к Ti вместе с его не входящим в Vi концом. 3. Процесс завершить, как только будет построено дерево Tn − 1. Доказано, что дерево Tn − 1 является минимальным остовом графа G = (V, E). Известны реализации алгоритма Прима, вычисляющие минимальный остов графа порядка n за время О (n2) [11, 16]. Пример 6.2. Рассмотрим граф G, изображенный на рис. 6.1. Результаты поэтапного построения для него остовного дерева минимального веса с помощью алгоритма Прима приведены на рис. 6.3. Из рис. 6.2, 6.3 видно, что порядок выбора ребер с помощью алгоритмов Краскала и Прима может быть различным. Однако в данном случае граф G имеет единственный минимальный остов, поэтому окончательный результат работы обоих алгоритмов один и тот же. 6.4. Некоторые замечания Если граф G не является связным, то задача о минимальном остове сводится к нахождению минимального остовного леса. Такой лес можно построить, применяя последовательно алгоритм Краскала (или Прима) к каждой компоненте связности графа G. 62 3 6 6 6 (1) (1) 5 ТT11 (3) (3) (1) 5 4 Т2 T2 2 (2) 5 4 Т3 T3 (2) 2 1 3 (5) 3 (8) 6 (1) (3) (1) 5 6 (5) Т4 T4 4 5 (5) (3) Т5 T5 4 Рис. 6.3 В некоторых ситуациях требуется построить остов не минимального, а максимального веса. К этой задаче также применимы алгоритмы Краскала и Прима. Следует только всюду минимальный вес заменить на максимальный или изменить знаки весов ребер на противоположные. Более того, в алгоритмах Краскала и Прима предполагается, что вес дерева представляет собой монотонно возрастающую функцию от весов входящих в него ребер, т. е. при замене ребра с весом wj на ребро с весом wi < wj вес дерева уменьшается [14]. Таким образом, если w1, …, wn − 1 – веса каких-либо n – 1 ребер, образующих остовное дерево графа G, то в качестве веса этого дерева можно брать, например, величины: w1 + w2 + … + wn − 1, w21 + w22 + … + w2n − 1, w1 ⋅ w2 ⋅ … ⋅ wn − 1. Если веса всех ребер графа G равны 1, то алгоритмы Краскала и Прима приводят к одному из возможных остовных деревьев этого графа. 63 6.5. Порядок выполнения задания 6.5.1. Изучить указанный в варианте задания алгоритм построения минимального остова. Разработать детальное описание алгоритма. Считать, что на вход алгоритма поступает взвешенный граф, содержащий несколько компонент связности. На выходе алгоритм должен выдавать перечень ребер остовного леса с указанием его отдельных компонент. Реализовать алгоритм в виде программной процедуры. Оформление процедуры должно допускать её автономное тестирование. Для тестирования использовать примеры 6.1, 6.2. 6.5.2. Разработать и отладить программу решения прикладной задачи с использованием алгоритма Краскала или Прима согласно номеру варианта задания (табл. 6.1). 6.6. Варианты Номер варианта задания определяет алгоритм построения минимального остова и название прикладной задачи (табл. 6.1). Таблица 6.1 Номер варианта Алгоритм Задача 1 Краскала Схема мелиорации рисовых полей 2 Прима Схема мелиорации рисовых полей 3 Краскала Проектирование надежной сети передачи информации 4 Прима Проектирование надежной сети передачи информации 5 Краскала Дерево максимального взаимопонимания 6 Прима Дерево максимального взаимопонимания 64 Схема мелиорации рисовых полей. При выращивании риса небольшие участки земли, огороженные земляными валиками, заливают водой, а позднее, перед снятием урожая, эту воду спускают. Такие участки земли называют чеками. Вид карты рисового поля, состоящего из многих чеков, приведен на рис. 6.4. Ребра графа соответствуют земляным валикам, а вершины – точкам их пересечений. Для заполнения чеков в некоторых земляных валиках открывают проходы и после заполнения их водой закрывают. Каждый земляной валик характеризуется трудоемкостью установления в нем прохода. На рис. 6.4 эти трудоемкости указаны в скобках. Требуется найти схему установления проходов в земляных валиках, которая позволяла бы открывать или закрывать все чеки рисового поля с минимальной трудоемкостью. 11 Vv v3 (3) V4v4 Vv2 2 (5) (10) (10) V3 (8) (1) (5) V6 (2) (10) (1) v V8 8 (7) (1) (10) V5 v5 (2) (2) (1) V7 v7 (3) (1) V9 v9 (1) V11v 11 (6) 10 V v10 Рис. 6.4 Каждый чек ограничен циклом. Очевидно, что для спуска воды достаточно в каждом цикле разрушить (или удалить) одно ребро. Если разрушить по одному ребру в каждом цикле, то оставшиеся целыми земляные валики образуют остовное дерево исходного графа. Остовное дерево максимальной трудоемкости однозначно определяет места наименее затратного расположения проходов – это ребра, не вошедшие в этот остов. 65 Проектирование надежной сети передачи информации. На графе, изображенном на рис. 6.5, каждая вершина представляет некоторое лицо, а ребро {vi, vj} отражает тот факт, что лицо vi может общаться с лицом vj, и наоборот, т. е. между vi и vj существует канал связи. Передаче сообщения от vi к vj приписывается вероятность pij того, что послание может быть перехвачено посторонним лицом. Эти вероятности в процентах даны на рис. 6.5. Vv22 Vv33 (15) (6) (8) (18) V v11 (11) (5) (18) (11) V 8 v8 (14) (9) (4) V4 v4 (7) (9) (6) vV55 V9 v9 V7 (10) (15) Vv6 6 (17) (3) (12) (7) Vv11 11 (5) V12 v12 (19) (13) (2) vV1010 Рис. 6.5 Требуется определить такой способ передачи конфиденциального сообщения между 12 лицами, при котором вероятность утечки информации будет минимальной. Очевидно, что пути передачи сообщения должны образовывать остовное дерево графа. Необходимо найти такое остовное дерево, которое минимизирует величину С = 1 – ∏ (1 – рij), где произведение берется по тем ребрам, которые образуют это дерево. Поскольку функция С монотонно возрастающая относительно pij, то требуемое остовное дерево будет совпадать с минимальным остовом, при этом веса ребер определяются следующим образом: w(vi, vj) = pij. 66 Дерево максимального взаимопонимания. В коллективе, состоящем из 7 человек, была произведена оценка взаимопонимания между членами коллектива по десятибалльной системе (высший балл – 10, низший – 1). Результаты этой оценки представлены в (7 × 7)-матрице W = {wij} (рис. 6.6). ⎛0 ⎜4 ⎜3 W = ⎜ 10 ⎜6 ⎜7 ⎝7 ⎞ ⎟ 8⎟ 3⎟ 5⎟ 9⎟ 0⎠ 4 3 10 6 7 7 0 5 4 4 3 9 5 0 1 3 2 4 1 0 10 8 4 3 10 0 4 3 2 8 4 0 9 8 3 5 9 Рис. 6.6 Матрицу W можно рассматривать в качестве матрицы весов полного неориентированного графа порядка 7. Вершины этого графа – сотрудники коллектива, а веса ребер – уровень взаимопонимания между сотрудниками. В этой ситуации максимальный остов полного графа с матрицей весов W – дерево максимального взаимопонимания, которое в определенной мере характеризует наиболее эффективные контакты между членами коллектива при решении общих вопросов. Требуется найти это дерево. 67 ЗАДАНИЕ 7 Построение кратчайших путей В задании формулируется оптимизационная задача, которая интересна своими многочисленными приложениями: задача о кратчайшем пути. Здесь рассматриваются два классических алгоритма её решения (алгоритм Дейкстры и алгоритм Флойда), которые должен знать каждый математик и программист. Цель задания: изучение данных алгоритмов и практика их использования при решении прикладных задач. 7.1. Формулировка задачи Пусть дан орграф G = (V, E), V = {v1, …, vn}, n ≥ 1, E = {e1, …, em}, m ≥ 1, дугам которого приписаны веса, устанавливаемые матрицей W = {wij}. Элемент этой матрицы, т. е. число W[vi, vj] = wij, будем называть длиной дуги e = (vi, vj). Если в орграфе G отсутствует некоторая дуга (vi, vj), положим wij = ∞. Определим длину пути как сумму длин отдельных дуг, составляющих этот путь. Задача о кратчайшем пути состоит в нахождении пути минимальной длины (кратчайшего пути) от заданной начальной вершины s ∈ V до заданной конечной вершины t ∈ V при условии, что такой путь существует, т. е. вершина t достижима из вершины s. Элементы матрицы весов W могут быть положительными, отрицательными вещественными числами или нулями. Однако если орграф G содержит контур отрицательной длины, вершины которого достижимы из s, то решение задачи о кратчайшем пути не существует. Действительно, если такой контур все же есть и vi – некоторая его вершина, то, двигаясь от s к vi, обходя данный контур достаточно большое число раз и попадая, наконец, в t, можно получить путь сколь угодно малой длины. Таким образом, для любых двух вершин s и t орграфа G с произвольной вещественной матрицей весов W решение задачи о кратчайшем (s, t)-пути существует, если – вершина t достижима из s; – в G нет контуров отрицательной длины. 68 Предположение об ориентированности графа G несущественно, так как неориентированное ребро всегда можно «расщепить» на две дуги противоположной ориентации и придать им длину «расщепляемого» ребра. Отсюда вытекает, что ребра не должны иметь отрицательные длины, поскольку после их «расщепления» отвечающие им дуги противоположной направленности образуют контуры отрицательной длины. Существует много практических интерпретаций задачи о кратчайшем пути. Например, вершины графа могут соответствовать городам, а каждая дуга – некоторой дороге, длина которой представлена весом дуги. Выполняется поиск кратчайшего пути между городами. Если вес дуги интерпретировать как стоимость передачи информации между вершинами, то в этом случае ищется самый дешевый путь передачи данных. Возникает другая ситуация, когда вес дуги e = (vi, vj) равен pij вероятности безотказной работы канала связи. Если предположить, что отказы каналов не зависят друг от друга, то вероятность исправности пути передачи данных равна произведению вероятностей составляющих его дуг. Заменяя веса pij на величины qij = – log pij, поиск наиболее надежного пути можно свести к нахождению кратчайшего пути в графе. Далее рассматриваются два общеизвестных алгоритма решения задачи о кратчайшем пути [11–17]. 7.2. Случай неотрицательных весов. Алгоритм Дейкстры Предположим, что веса дуг исходного орграфа G = (V, E) неотрицательны, т. е. W[vi, vj] = wij ≥ 0 для любой дуги e = (vi, vj) ∈ Е. В этом случае решение задачи о кратчайшем пути является менее трудоемким, чем в общем случае. Первый эффективный алгоритм построения кратчайшего пути в орграфе с неотрицательными весами дуг предложил голландский математик Е. Дейкстра в 1959 г. Алгоритм Дейкстры основан на приписывании каждой вершине v ∈ V двух временных меток: – метка D[v] дает верхнюю оценку длины (s, v)-пути; – метка Н[v] указывает вершину, непосредственно предшествующую вершине v в этом пути. 69 Значения D[v] постепенно уменьшаются с помощью некоторой итерационной процедуры. На каждой итерации только для одной вершины v* временные метки становятся постоянными. Это свидетельствует о том, что для вершины v* значение D[v*] – точная длина кратчайшего (s, v*)-пути, а Н[v*] – вершина, предшествующая вершине v* в этом (s, v*)-пути. Вершины с постоянными метками исключаются из дальнейшего рассмотрения. Все такие вершины отмечаются 1 в массиве Х, т. е. полагается Х[v*]:= 1, если v* – вершина с постоянными метками. После того как вершина t получила постоянные метки, процесс завершается. Значение D[t] дает значение кратчайшего (s, t)-пути. Опишем этот алгоритм подробнее. Будем считать, что орграф G задан матрицей весов W = {wij}. Алгоритм 7.1. Алгоритм Дейкстры: определение длины кратчайшего (s, t)-пути 1. Инициализация. Для начальной вершины s установить: – D[s]:= 0 (кратчайший (s, s)-путь имеет длину 0); – H[s]:= 0 (вершине s ничего не предшествует); – X[s]:= 1 (вершина s имеет постоянные метки). – Для каждой вершины v ∈ V, v ≠ s положить: – D[v]:= ∞ (кратчайший (s, v)-путь имеет длину меньше ∞); – H[v]:= 0 (вершине v ничего пока не предшествует); – X[v]:= 0 (вершина v имеет временные метки). Выполнить присваивание р:= s, где р – вершина, которая получила постоянные метки последней. 2. Обновление меток. Пусть Γ(р) – множество вершин, являющихся концами всех исходящих из р дуг. Для всех вершин v ∈ Γ(р) с временными метками пересчитать значения меток по правилу: – если D[v] > D[p] + W[p, v], то найден более короткий (s, v)-путь через вершину р и D[v]:= D[p] + W[p, v], Н[v]:= p (рис. 7.1); – иначе метки D[v] и Н[v] не менять. 3. Превращение временных меток в постоянные. Пусть V' – множество вершин v ∈ V с временными метками (для них X[v] = 0). Найти первую вершину v*, такую, что D[v*] = min {D[v], v ∈ V'}. Положить Х[v*]:= 1, т. е. считать метки для v* постоянными. 70 4. Подготовка к следующей итерации или завершение процесса. Положить р:= v*. Если р = t, то найден кратчайший (s, t)-путь, при этом D[t] – длина этого пути. Иначе возврат к пункту 2. p D[p] W[p, v] Γ(p) s D[v] v Рис. 7.1 Сам кратчайший (s, t)-путь можно извлечь из Н с помощью следующего алгоритма. Алгоритм 7.2. Алгоритм Дейкстры: нахождение последовательности вершин кратчайшего (s, t)-пути 1. Начать с вершины t. Для этого положить v:= t и вывести v в качестве вершины кратчайшего (s, t)-пути. 2. До тех пор пока v ≠ s (не достигнута начальная вершина) выполнять действия: – v:= H[v], где H[v] задает номер вершины, предшествующей v; – вывод v в качестве очередной вершины кратчайшего (s, t)пути. 3. При v = s процесс завершить. Выведенная последовательность вершин указывает кратчайший (s, t)-путь в обратном порядке. Оценим время работы алгоритма Дейкстры. Вычислительные затраты максимальны, когда вершина t получает постоянные метки последней и G является полным орграфом без петель. В этом случае число итераций алгоритма 7.1 равно n – 1, т. е. каждый из его пунктов 2–4 выполняется n – 1 раз. Очевидно, что пункт 4 выполняется за время О (1), а для выполнения пунктов 2, 3 достаточно времени О (n). Таким образом, в целом время определения кратчайшего (s, t)-пути составляет О (n2). Заметим, что построение последовательности вершин, образующих кратчайший (s, t)-путь, можно осуществить за время О (n). Значит, в целом время работы алгоритма Дейкстры составляет O(n2). 71 Пример 7.1. Пусть требуется найти кратчайший путь между вершинами 1 и 8 в орграфе, изображенном на рис. 7.2. Матрица весов графа приведена на рис. 7.3 (пустые места матрицы соответствуют значению ∞). Веса дуг на рис. 7.2 даны в скобках. 2 4 (3) 6 (2) (9) (2) (2) (6) 1 (2) (5) (2) (7) (1) 3 (1) 8 (1) (3) 5 7 Рис. 7.2 ⎛_ ⎜_ ⎜ ⎜_ _ W= ⎜ ⎜_ ⎜_ ⎜_ ⎜_ ⎝ 2 1 6 _ 9 _ _⎞ _ _ _ _ 3 _ 5 1 _ 7 _ _ _ ⎟ _ _ _ _ 2 2 _ _⎟ _ _ _ _ _ 3 _ _ _ _ _ 2 _ _ _ _ _ _ 1⎟ _ _ _ _ _ _ _⎠ ⎟ ⎟ _⎟ 2⎟ ⎟ Рис. 7.3 Алгоритм 7.1 работает следующим образом. Инициализация. Для начальной вершины с номером 1 полагаем D[1]:= 0, Н[1]:= 0, X[1]:= 1. Эта вершина получает постоянные метки, поэтому р:= 1. Для всех остальных вершин D[i]:= ∞, Н[i]:= 0, X[i]:= 0, i = 2, …, 8. Эти вершины пока имеют временные метки. Первая итерация. Обновление меток: Γ(1) = {2, 3, 4, 6}; все метки для вершин из Γ(1) временные; полагаем D[2]:= min {∞, 0 + 2} = 2, D[3]:= min {∞, 0 + 1} = 1, D[4]:= min {∞, 0 + 6} = 6, D[6]:= min {∞, 0 + 9} = 9, H[2]:= 1; H[3]:= 1; H[4]:= 1; H[6]:= 1. 72 Превращение временных меток в постоянные метки: min {D[2], D[3], D[4], D[5], D[6], D[7], D[8]}= = min {2, 1, 6, ∞, 9, ∞, ∞} = 1 = D[3]. Полагаем X[3]:= 1 и р:= 3, т. е. для вершины 3 метки становятся постоянными. Вторая итерация. Обновление меток: Γ(3) = {5, 6}; обе вершины имеют временные метки; обновляем их: D[5]:= min {∞, 1 + 1} = 2, H[5]:= 3; D[6]:= min {9, 1 + 7} = 8, H[6]:= 3. Превращение временных меток в постоянные метки: min {D[2], D[4], D[5], D[6], D[7], D[8]}= = min {2, 6, 2, 8, ∞, ∞} = 2 = D[2]. Метки для вершины 2 становятся постоянными, поэтому X[2]:= 1 и р:= 2. Последующие итерации. Продолжая этот процесс, получим картину, представленную в табл. 7.1 (постоянные метки отмечены полужирным шрифтом). На шестой итерации алгоритма 7.1 конечная вершина с номером 8 получает постоянные метки. Алгоритм завершает работу. Однако если выполнить еще одну – седьмую итерацию, то все восемь вершин орграфа будут иметь постоянные метки. Это значит, что определены все кратчайшие (1, i)-пути, где i = 2, ..., 8. Например, алгоритм 7.2 дает такой кратчайший (1, 8)-путь: (1, 3, 5, 7, 8). Его длина равна D[8] = 6. На рис. 7.2 кратчайший (1, 8)-путь выделен темными дугами. Кратчайший (1, 6)-путь таков: (1, 2, 4, 6). Этот путь имеет длину D[6] = 7. 73 Таблица 7.1 Итерация № Метки 0 1 2 3 4 5 6 7 Вершины 1 2 3 4 5 6 7 8 D[i] 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ Н[i] 0 0 0 0 0 0 0 0 X[i] 1 0 0 0 0 0 0 0 D[i] 0 2 1 6 ∞ 9 ∞ ∞ Н[i] 0 1 1 1 0 1 0 0 X[i] 1 0 1 0 0 0 0 0 2 8 ∞ ∞ D[i] 0 2 1 6 Н[i] 0 1 1 1 3 3 0 0 X[i] 1 1 1 0 0 0 0 0 D[i] 0 2 1 5 2 8 ∞ ∞ Н[i] 0 1 1 2 3 3 0 0 X[i] 1 1 1 0 1 0 0 0 D[i] 0 2 1 5 2 8 5 ∞ Н[i] 0 1 1 2 3 3 5 0 X[i] 1 1 1 1 1 0 0 0 D[i] 0 2 1 5 2 7 5 ∞ Н[i] 0 1 1 2 3 4 5 0 X[i] 1 1 1 1 1 0 1 0 D[i] 0 2 1 5 2 7 5 6 Н[i] 0 1 1 2 3 4 5 7 X[i] 1 1 1 1 1 0 1 1 D[i] 0 2 1 5 2 7 5 6 Н[i] 0 1 1 2 3 4 5 7 X[i] 1 1 1 1 1 1 1 1 74 Примечание Инициализация р=1 Γ(1) = {2, 3, 4, 6} p=3 Γ(3) = {5, 6} p=2 Γ(2) = {4, 5} p=5 Γ(5) = {7} p=4 Γ(4) = {5, 6} p=7 Γ(7) = {8} p=8 Достигнута вершина t=8 Γ(8) = ∅ p=6 Метки всех вершин постоянные Пример 7.2. Пусть требуется найти кратчайший (1, 3)-путь в орграфе с одной отрицательной дугой (рис. 7.4). Применение алгоритма Дейкстры к данному орграфу дает длину кратчайшего (1, 3)-пути, равную 1 (табл. 7.2). Однако из рис. 7.4 видно, что это не соответствует действительности. 2 (-5) (5) 1 3 (1) Рис. 7.4 Таблица 7.2 Итерация № 1 2 Вершины Примечание Метки 1 2 3 D[i] 0 ∞ ∞ Н[i] 0 0 0 X[i] 1 0 0 D[i] 0 5 1 Н[i] X[i] 0 1 1 0 1 1 Инициализация р=1 Γ(1) = {2, 3} p=3 Достигнута вершина t = 3 Данный пример показывает неприменимость алгоритма Дейкстры к орграфу с произвольной матрицей весов. 7.3. Дерево кратчайших путей Решение ряда прикладных задач сводится к нахождению дерева кратчайших путей с корнем в заданной вершине s. Такое дерево дает кратчайшие (s, v)-пути от заданной вершины s до любой вершины v ∈ V орграфа G = (V, E). Если веса всех дуг орграфа G неотрицательны, то дерево кратчайших путей можно построить с помощью алгоритма Дейкстры. 75 Для этого пункт 4 алгоритма 7.1 необходимо модифицировать так, чтобы алгоритм заканчивал работу только после получения всеми вершинами постоянных меток. Если к тому же вместе с превращением меток вершины v* в постоянные (пункт 3 алгоритма) заносить дугу (H[v*], v*) во множество Y, то после завершения работы алгоритма граф T = (V, Y) – дерево кратчайших путей с корнем в вершине s. Для любой вершины v ∈ V единственный (s, v)-путь в дереве T является кратчайшим (s, v)-путем в орграфе G. Пример 7.3. На рис. 7.5 изображено дерево T = (V, Y) кратчайших путей для орграфа из примера 7.1. 2 (3) 4 (2) 6 (2) 1 8 (1) (1) 3 (1) 5 (3) 7 Рис. 7.5 Вершина 1 – корень дерева. Дуги дерева будут записываться в Y согласно порядку: (1, 3), (1, 2), (3, 5), (2, 4), (5, 7), (7, 8), (4, 6). 7.4. Случай произвольной матрицы весов. Алгоритм Флойда Рассмотрим алгоритм нахождения кратчайших путей между всеми парами вершин орграфа G = (V, E), где V = {1, .., n}. Этот алгоритм был предложен Р. Флойдом в 1962 г. и применим как к неотрицательным, так и к произвольным матрицам весов. Алгоритм Флойда базируется на последовательности n преобразований (итераций) начальной матрицы весов W = {wij}. При этом на k-й итерации матрица представляет длины кратчайших путей между каждой парой вершин с тем ограничением, что всякий (i, j)-путь содержит в качестве промежуточных только вершины из множества {1, ..., k} ⊆ V. 76 Считается, что орграф G = (V, E), V = {1, .., n} задан матрицей весов W, при этом W[vi, vi] = 0 для i = 1, ..., n, и W[vi, vj] = ∞, если в орграфе отсутствует дуга (i, j). Для того чтобы после окончания алгоритма иметь возможность найти кратчайший путь между какойлибо парой вершин, необходимо хранить и обновлять матрицу Н. Элемент Н[i, j] указывает вершину, непосредственно предшествующую вершине j в кратчайшем (i, j)-пути. Между тем длины кратчайших путей формируются в матрице D. Алгоритм 7.3. Алгоритм Флойда: определение длин кратчайших путей между всеми парами вершин 1. Инициализация. Записать в D исходную матрицу весов Сформировать начальное состояние матрицы H: W. – если W[i, j] = ∞ или i = j, то положить H[i, j]:= 0, – иначе Н[i, j]:= i, т. е. при наличии в орграфе дуги (i, j) вершина i непосредственно предшествует вершине j. Установить k:= 0, где k – номер итерации алгоритма. 2. Текущая итерация. Положить k:= k + 1. Для всех i ∈ V и j ∈ V, таких, что i ≠ k и j ≠ k, выполнить пересчет длины (i, j)-пути по правилу D[i, j]:= min {D[i, j], (D[i, k] + D[k, j])}. Если оказалось, что D[i, k] + D[k, j] < D[i, j], то найден более короткий (i, j)-путь через вершину k. Этот путь следует запомнить, полагая H[i, j]:= H[k, j]. 3. Проверка на завершение процесса: – если существует такое i, что D[i, i] < 0, то в орграфе G существует контур отрицательного веса, содержащий вершину с номером i. Останов, решения нет; – если все D[i, i] ≥ 0 и k = n, то решение получено. Останов, матрица D дает длины всех кратчайших путей; – если все D[i, i] ≥ 0, но k < n, то вернуться к пункту 2, т. е. итерационный процесс продолжить. 77 После завершения работы алгоритма 7.3 любой (i, j)-путь легко извлечь из матрицы Н с помощью следующего алгоритма. Алгоритм 7.4. Алгоритм Флойда: нахождение последовательности вершин кратчайшего (i, j)-пути 1. Начать с вершины j. Для этого положить q:= j и вывести q в качестве вершины (i, j)-пути. 2. До тех пор пока q ≠ i (не достигнута начальная вершина) выполнять действия: – q:= H[i, q], где H[i, q] задает вершину, предшествующую q; – вывод q в качестве очередной вершины кратчайшего (i, j)пути. 3. При q = i процесс завершить. Выведенная последовательность вершин указывает кратчайший (i, j)-путь в обратном порядке. Обоснование оптимальности решения, полученного с помощью алгоритма Флойда, можно найти в [16]. Оценим сложность алгоритма Флойда. Очевидно, что для выполнения каждого из пунктов 1–3 алгоритма 7.3 достаточно времени O(n2). Поскольку данный алгоритм выполняет не более чем n итераций, то его сложность составляет O(n3). Построение последовательности вершин, составляющих всякий кратчайший (i, j)-путь, с помощью алгоритма 7.4 можно выполнить за время О(n). Значит, в целом время работы алгоритма Флойда составляет O(n3). Пример 7.4. Рассмотрим орграф, изображенный на рис. 7.6. Требуется найти в нем кратчайшие пути между всеми парами вершин. (-2) 1 (5) (3) (-3) 2 (4) (2) (-3) 4 (5) Рис. 7.6 78 3 Алгоритм 7.3 работает следующим образом. Инициализация. Полагаем k:= 0. Начальный вид матрицы D и H указан на рис. 7.7. ⎛0 ⎜∞ D =⎜ ⎜∞ ⎝4 − 2 3 − 3⎞ ⎛0 ⎜0 H =⎜ ⎜0 ⎝4 ⎟ 0 2 ∞ ⎟ ∞ 0 −3 ⎟ 5 5 0⎠ 1 1 1⎞ ⎟ 0 2 0 ⎟ 0 0 3 ⎟ 4 4 0⎠ Рис. 7.7 Первая итерация (k = 1). Выполняем пересчет всех элементов матрицы D, кроме первой строки и первого столбца. Одновременно вносим изменения в соответствующие элементы матрицы Н. Для D[2, 2] имеем: D[2, 2]:= min {D[2, 2], (D[2, 1] + D[1, 2])} = min {0, (∞ – 2)} = 0, т. е. элементы D[2, 2] и H[2, 2] остаются без изменений. Далее D[2, 3]:= min {D[2, 3], (D[2, 1] + D[1, 3])} = min {2, (∞ + 3)} = 2, т. е. элементы D[2, 3] и H[2, 3] остаются без изменений. Продолжая этот процесс, убеждаемся, что изменяются только значения D[4, 2] и H[4, 2]: D[4, 2]:= min {D[4, 2], (D[4, 1] + D[1, 2])} = min {5, (4 – 2)} = 2; H[4, 2]:= H[1, 2]. Обновленный вид матриц D и H приведен на рис. 7.8. Поскольку все диагональные элементы матрицы D неотрицательны и k < n (k = 1, n = 4), то выполняем следующую итерацию. ⎛0 ⎜∞ D =⎜ ⎜∞ ⎝4 − 2 3 − 3⎞ 0 2 ∞ 0 2 5 ⎟ ∞ ⎟ −3 ⎟ 0⎠ H= Рис. 7.8 79 ⎛0 ⎜0 ⎜ ⎜0 ⎝4 1 1 1⎞ ⎟ 0 2 0 ⎟ 0 0 3 ⎟ 1 4 0⎠ Вторая итерация (k = 2). Пересчету подлежат все элементы матрицы D, кроме второй строки и второго столбца. Однако в действительности изменяются лишь следующие элементы: D[1, 3]:= min {D[1, 3], D[1, 2] + D[2, 3]} = min {3, (–2 + 2)} = 0; H[1, 3]:= H[2, 3]; D[4, 3]:= min {D[4, 3], (D[4, 2] + D[2, 3])} = min {5, (2 + 2)} = 4; H[4, 3]:= H[2, 3]. Обновленный вид матриц D и H приведен на рис. 7.9. Все диагональные элементы матрицы D неотрицательны и k < n (k = 2, n = 4). Процесс продолжается. ⎛0 ⎜∞ D =⎜ ⎜⎜ ∞ ⎝4 −2 0 ∞ 2 0 − 3⎞ ⎟ 2 ∞ ⎟ 0 −3 ⎟⎟ 4 0⎠ ⎛0 ⎜0 H =⎜ ⎜0 ⎝4 1 0 0 1 2 2 0 2 1⎞ ⎟ 0 ⎟ 3 ⎟ 0⎠ Рис. 7.9 Третья итерация (k = 3). Пересчету подлежат все элементы матрицы D, кроме третьей строки и третьего столбца. Изменяется лишь один из них D[2, 4], для него также обновляется элемент H[2, 4]: D[2, 4]:= min {D[2, 4], (D[2, 3] + D[3, 4])} = min {∞, (2 – 3)} = –1; H[2, 4]:= H[3, 4]. Обновленный вид матриц D и H приведен на рис. 7.10. Все диагональные элементы матрицы D неотрицательны и k < n (k = 3, n = 4). Процесс продолжается. ⎛0 ⎜∞ D =⎜ ⎜⎜ ∞ ⎝4 −2 0 ∞ 2 0 − 3⎞ ⎟ 2 −1 ⎟ 0 −3 ⎟⎟ 4 0⎠ ⎛0 ⎜0 H =⎜ ⎜0 ⎝4 1 0 0 1 2 2 0 2 1⎞ ⎟ 3 ⎟ 3 ⎟ 0⎠ Рис. 7.10 Четвертая итерация (k = 4). Пересчету подлежат все элементы матрицы D, кроме последней строки и последнего столбца. Изменяют свои значения только следующие элементы: 80 D[2, 1]:= min {D[2, 1], (D[2, 4] + D[4, 1])} = min {∞, (–1 + 4)} = 3; H[2, 1]:= H[4, 1]; D[3, 1]:= min {D[3, 1], (D[3 ,4] + D[4, 1])} = min {∞, (–3 + 4)} = 1; H[3, 1]:= H[4, 1]; D[3, 2]:= min {D[3, 2], (D[3, 4] + D[4, 2])} = min {∞, (–3 + 2)} = –1; H[3, 2]:= H[4, 2]. Поскольку k = n, то решение получено и оно указано на рис. 7.11. В результирующей матрице D нет отрицательных диагональных элементов. Это говорит о том, что в исходном орграфе (рис. 7.6) нет контуров отрицательной длины. ⎛0 ⎜3 D =⎜ ⎜1 ⎝4 − 2 0 − 3⎞ 0 2 −1 0 2 4 ⎛0 ⎜4 H =⎜ ⎜4 ⎝4 ⎟ −1 ⎟ −3 ⎟ 0⎠ 1 2 1⎞ ⎟ 0 2 3 ⎟ 1 0 3 ⎟ 1 2 0⎠ Рис. 7.11 Используя алгоритм 7.4, восстановим, например, кратчайший (2, 1)-путь по матрице Н: H[2, 1] = 4, H[2, 4] = 3, H[2, 3] = 2. Следовательно, (2, 3, 4, 1) – кратчайший (2, 1)-путь длины D[2, 1] = 3. 7.5. Кратчайшие контуры и транзитивное замыкание Если в алгоритме 7.3 всем элементам D[i, i] придать начальные значения ∞, то конечное значение D[i, i] будет равно весу кратчайшего контура, проходящего через вершину с номером i (i = 1, …, n). Пример 7.5. Рассмотрим орграф, изображенный на рис. 7.12. Результаты выполнения алгоритма 7.3 приведены на рис. 7.13. Значения всех диагональных элементов начальной матрицы D равны ∞. В результирующей матрице D[1, 1] = D[2, 2] = D[3, 3] = 2. Это означает, что через каждую вершину орграфа проходит кратчайший контур длины 2. 81 (1) 2 3 (1) (1) (1) GG 1 Рис. 7.12 k=0 D= k=1 D= k=2 k=3 ⎛∞ ⎜1 ⎜ ⎝∞ ⎛∞ ⎜1 ⎜ ⎝∞ ⎛2 ⎜ D= 1 ⎜ ⎝2 1 ∞⎞ ⎟ ∞ 1 ⎟ 1 ∞⎠ 1 ∞⎞ ⎟ 2 1 ⎟ 1 ∞⎠ 1 2⎞ ⎟ 2 1 ⎟ 1 2⎠ ⎛0 ⎜ 1 0⎞ H= 2 ⎜ ⎝0 0 3 2 ⎟ 0⎠ ⎟ ⎛0 ⎜ 1 0⎞ H= 2 ⎜ ⎝0 1 3 2 ⎟ 0⎠ ⎛2 ⎜ 1 2⎞ 1 3 2 ⎟ 2⎠ H= 2 ⎜ ⎝2 ⎟ ⎟ Без изменений Рис. 7.13 С задачей определения кратчайших путей тесно связана задача вычисления транзитивного замыкания орграфа. Транзитивным замыканием орграфа G = (V, E), V = {1, .., n}, называется орграф G* = (V*, E*), для которого V* = V, а дуга (i, j) ∈ Е* тогда и только тогда, когда вершина j достижима из вершины i в G. Транзитивное замыкание всегда содержит все петли – дуги вида (i, i), так как всякая вершина i ∈ V достижима из самой себя с помощью пути длины 0 (i = 1, …, n). Транзитивное замыкание можно найти с помощью алгоритма Флойда, полагая веса всех дуг равными 1. 82 Матрица смежности В(G*) = {bij} транзитивного замыкания (или достижимости) определяется из матрицы D кратчайших путей так: ⎧1, если D[i , j ] < ∞ или i = j , bi j = ⎨ ⎩0 в противном случае (i, j = 1, ... , n ). В задании 4 предполагалось, что в матрице смежности петли орграфа отмечаются числом 1. Поэтому здесь bii = 1 для всех i = 1, …, n. 2 3 B(G*) = ⎛1 ⎜1 ⎜ ⎝1 1 1⎞ 1 1 1 ⎟ 1⎠ ⎟ G* G* 1 Рис. 7.14 Например, транзитивным замыканием орграфа с рис. 7.12 является полный орграф порядка 3 (рис.7.14). 7.6. Порядок выполнения задания 7.6.1. Изучить алгоритм Дейкстры и реализовать его в виде программной процедуры. Процедура должна заканчивать работу после получения всеми вершинами орграфа постоянных меток. Считать, что на вход алгоритма Дейкстры поступают: – матрица весов W = {wij} орграфа; – номера вершин s и t, для которых надо найти кратчайший путь. На выходе процедура должна выдавать длину кратчайшего (s, t)пути и сам этот путь в виде последовательности вершин орграфа. Необходимо предусмотреть промежуточную печать состояний массивов D, H, X для каждой итерации алгоритма. При тестировании процедуры использовать примеры 7.1–7.3. 83 7.6.2. Изучить алгоритм Флойда, включая восстановление любого кратчайшего (s, t)-пути по матрице Н. Реализовать алгоритм в виде программной процедуры. Полагать, что на вход алгоритма Флойда поступают: – матрица весов W = {wij} орграфа; – номера вершин s и t, для которых следует найти кратчайший путь. На выходе процедура должна выдавать матрицу D кратчайших путей между всеми парами вершин, а также длину кратчайшего (s, t)пути и сам этот путь в виде последовательности вершин орграфа. Дополнительно к этому необходимо предусмотреть промежуточную печать состояний матриц D и Н для каждой итерации алгоритма. При отладке процедуры использовать примеры 7.1, 7.2, 7.4, 7.5. 7.6.3. Решить прикладную задачу согласно номеру варианта задания (табл. 7.3). 7.7. Варианты Таблица 7.3 Номер варианта 1 2 3 4 5 6 Прикладная задача Сетевое планирование работ Надежная вычислительная сеть Система двусторонних дорог Плотная система двусторонних дорог Две системы двусторонних дорог Система односторонних дорог Сетевое планирование работ. Допустим, что нужно реализовать большой проект и этот проект состоит из n этапов. Например, в качестве проекта выступает строительство здания. Этап i может состоять в строительстве стен, этап j – вставка окон, этап k – прокладка электропроводки и т. п. На очерёдность выполнения этапов наложено ограничение предшествования вида: для всякого этапа j задано множество этапов Γ+(j), которые должны быть завершены до того, как начнётся выполнение j. 84 При этом каждая пара этапов (i, j), где i ∈ Γ+(j), характеризуется величиной tij, равной минимальной задержке по времени между началом этапа i и началом этапа j. Очевидно, что отношение предшествования между этапами проекта можно изобразить графически, в виде так называемого сетевого графика. Вершины сетевого графика – этапы проекта. Сетевой график содержит дугу (i, j) с весом tij, если этап i предшествует этапу j. По своему построению сетевой график – взвешенный безконтурный орграф. Действительно, предположение о существовании некоторого контура, содержащего вершину i, приводит к (нелепому для данной задачи) выводу о возможном повторении этапа i. В задаче требуется найти минимальное время, необходимое для реализации проекта. Другими словами, нужно найти в орграфе самый длинный путь между вершиной s, изображающей начало, и вершиной t, изображающей завершение всех необходимых для реализации проекта работ. Самый длинный путь в сетевом графике называется критическим путем, так как этапы, относящиеся к этому пути, определяют полное время реализации проекта, и всякая задержка с началом выполнения любого из этих этапов приводит к задержке выполнения проекта в целом. С помощью изменения знаков для всех tij (i, j = 1, …, n) данную задачу можно свести к задаче о кратчайшем (s, t)-пути, которая в этом случае будет иметь решение, поскольку сетевой график не имеет контуров. Вершина s – одна из вершин, для которых полустепень захода равна 0, т. е. d +(s) = 0. Вершина t – одна из вершин, для которых полустепень исхода равна 0, т. е. d –(t) = 0. Разумно предположить, что такие вершины s и t всегда единственные, поскольку этого всегда можно добиться введением фиктивных вершин и дуг. Исходные данные: сетевой график, заданный матрицей весов (временных задержек). Результаты: длина критического (s, t)-пути и сам (s, t)-путь. Рекомендация. Использовать алгоритм Флойда. Алгоритм Дейкстры применять в данном случае нельзя (почему?). Предусмотреть поиск вершин s и t. Разработанную программу применить для вычисления критического пути сетевого графика, изображенного на рис. 7.15. Числа в скобках, стоящие у дуг, указывают продолжительность задержек в днях. 85 7 9 (30) (9) (14) (8) 2 (5) Начало 1 (16) (7) (19) (10) (11) 4 8 6 (11) 3 (17) (18) 12 (13) 5 (3) (14) 10 (26) (8) (21) (15) (19) (7) Конец 13 14 (12) 11 (30) Рис. 7.15 Надежная вычислительная сеть. Имеется вычислительная сеть, состоящая из n центров хранения и переработки информации. Некоторые пары центров соединены каналами двусторонней связи. Обмен информацией между любыми двумя центрами осуществляется либо непосредственно по соединяющему их каналу, если он есть, либо через другие каналы и центры. Всякий канал, непосредственно связывающий центр i с центром j, характеризуется величиной рij – вероятностью безотказной работы (или надежностью). Требуется для любой пары s и t центров сети найти наиболее надежный (s, t)-путь обмена информацией. Такой сети естественно сопоставить неориентированный взвешенный граф: вершины – центры, ребра – каналы сети, веса ребер – надежность каналов. Если каждое ребро «расщепить» на две дуги противоположной ориентации и придать им вес «расщепляемого» ребра, то получим взвешенный орграф. Надежность всякого (s, t)-пути в этом орграфе определяется формулой Ρ(s, t) = ∏ pij, где произведение берется по всем тем дугам, которые образуют (s, t)-путь. Здесь предполагается, что отказы каналов не зависят друг от друга. Задача нахождения наиболее надежного (s, t)-пути сводится к задаче о кратчайшем (s, t)-пути с матрицей весов Q = {qij}, которая получается из исходной матрицы весов W = {pij} следующим образом: 86 ⎧ − log p i j , если 0 < p i j ≤ 1, qi j = ⎨ ⎩ ∞ , если p i j = 0 (i , j = 1, ... , n ). Действительно, логарифмирование обеих частей вышеуказанной формулы надежности (s, t)-пути приводит к соотношению log Ρ(s, t) = ∑ log pij = – ∑ qij, где суммирование берется по всем дугам, которые образуют (s, t)-путь. Учитывая, что 0 < pij ≤ 1, имеем qij ≥ 0 (i, j = 1, …, n). Исходные данные: граф, заданный матрицей весов (вероятностей безотказной работы); s и t – номера вершин графа. Результаты: наиболее надежный (s, t)-путь. Рекомендация. Использовать алгоритм Флойда. Разработанную программу применить для нахождения наиболее надежного (s, t)-пути в графе, изображенном на рис. 6.5 (см. задание 6). Учесть, что вероятности pij указаны на рис. 6.5 в процентах. Система двусторонних дорог. Задана система двусторонних дорог, соединяющих между собой n городов. Всякая дорога, непосредственно связывающая город i с городом j, характеризуется длиной wij. Очевидно, что такой системе дорог соответствует неориентированный взвешенный граф: вершины – города, ребра – дороги, веса ребер – длины дорог. Если граф связен, то для любой пары городов s и t всегда существует хотя бы один (s, t)-путь, т. е. город t достижим из города s. Требуется найти: – столицу, т. е. такой город, для которого сумма расстояний до остальных городов минимальна (под расстоянием между двумя городами понимается длина кратчайшего пути, соединяющего эти города); – кратчайшие пути движения от столицы до всех других городов; – K-периферию для заданного целого числа K (K-периферией называется множество городов, расстояния от которых до столицы больше K км). 87 Если каждое ребро графа «расщепить» на две дуги противоположной направленности и придать им вес «расщепляемого» ребра, то получим взвешенный орграф. Применив к орграфу алгоритм Дейкстры и построив для каждой вершины дерево кратчайших путей, можно дать ответы на все сформулированные ранее задачи. Однако гораздо проще здесь воспользоваться алгоритмом Флойда. Исходные данные: связный граф, заданный матрицей весов дуг (длин дорог), число K – расстояние для выделения периферии. Результаты: номер вершины, которая является столицей; список дуг, образующих дерево кратчайших путей с корнем в столице; номера вершин, входящих в K-периферию. Рекомендация. Решить задачу с помощью алгоритма Флойда. Расчеты выполнить для графа с рис. 6.5 (см. задание 6). Веса ребер этого графа – длины дорог. Плотная система двусторонних дорог. Этой системе дорог соответствует взвешенный мультиграф, который отличается от графа тем, что в нем одна и та же пара различных вершин может быть связана более чем одним ребром. Такие ребра называются кратными. Каждое из кратных ребер, связывающих вершины i и j, имеет свой собственный вес. В таком мультиграфе: вершины – города, ребра – дороги, веса ребер – длины дорог. Требуется для заданной пары городов найти кратчайший путь между ними (с точностью до дорог, через которые проходит этот путь). Для решения задачи необходимо вначале мультиграф преобразовать в обыкновенный граф, а затем последний – в орграф «расщеплением» каждого ребра на две дуги противоположной направленности. Пусть в мультиграфе G = (V, E), V = {1, .., n}, имеются два кратных ребра е1 = {i, j} и e2 = {i, j} с весами w1ij и w2ij соответственно. Устранить кратность этих ребер, т. е. научиться их различать, можно путем установки на кратных ребрах двух фиктивных вершин. Например, так, как это указано на рис. 7.16. Фиктивные вершины играют роль промежуточных населенных пунктов, через которые проходят дороги, связывающие одну и ту же пару городов. Очевидно, что такое преобразование не влияет на длины кратчайших путей и позволяет однозначно идентифицировать кратные ребра. Однако оно увеличивает число вершин, т. е. размерность задачи. 88 w1ij i w2ij i1 w1ij / 2 j i w2ij / 2 j1 w1ij / 2 j w2ij / 2 Рис. 7.16 Исходные данные: связный мультиграф, заданный перечнем ребер с указанием веса каждого ребра; s и t – номера вершин мультиграфа. Результаты: длина кратчайшего (s, t)-пути и сам это путь, включая фиктивные вершины (промежуточные населенные пункты). Именно фиктивные вершины уточняют, по каким дорогам проходит кратчайший путь. Рекомендация. По перечню ребер необходимо сформировать матрицу весов, вводя фиктивные вершины для кратных ребер. Для поиска кратчайших путей использовать алгоритм Дейкстры. Разработанную программу применить для графа, изображенного на рис. 7.17. 2 (5) 3 (7) (10) (8) (4) (7) 5 (8) (2) (3) 1 (12) (10) (9) 6 (4) 4 Рис. 7.17 Две системы двусторонних дорог. Заданы две системы двусторонних дорог (железных и шоссейных) с одним и тем же множеством городов V = {1, ..., n}. Система железных дорог описывается неориентированным графом G1 = (V, E1) с матрицей весов P = {pij}, а система шоссейных дорог − неориентированным графом G2 = (V, E2) с матрицей весов Q = {qij}. Здесь вершины – города, ребра – дороги, веса ребер – длины дорог. Требуется найти кратчайший путь из города s в город t, который может проходить как по железным, так и по шоссейным дорогам. Кроме того, необходимо указать города, определяющие места пересадок с одного транспорта на другой вдоль этого пути. 89 Данную задачу можно решать различными способами. Например, она может быть решена с использованием алгоритмов Дейкстры с матрицей весов W = {wij}, которая определяется по правилу: wij = min {pij, qij}, (i, j = 1, ..., n). Примечательно, что для неориентированных графов G1 и G2 матрицы весов симметричны, т. е. в них фактически реализовано «расщепление» каждого ребра на две дуги противоположной направленности. Для определения мест пересадок требуется формировать вспомогательную матрицу Z = {zij} следующим образом: – если pij < qij, то zij = 0 (дуга (i, j) отвечает железной дороге); – иначе zij = 1 (дуга (i, j) соответствует шоссейной дороге). Исходные данные: два связных графа, заданных своими матрицами весов P и Q; s и t – номера вершин, между которыми следует найти кратчайший путь. Результаты: длина кратчайшего (s, t)-пути и сам этот путь, включая номера вершин, которые задают места пересадок. Рекомендация. Использовать алгоритм Дейкстры. Разработанную программу применить для систем дорог, изображенных в виде одного графа с двумя типами ребер (рис. 7.18). Темные ребра – железные дороги, светлые ребра – шоссейные дороги. 1 (1) (1) 3 (10) (7) (5) (4) (3) (5) 7 4 (7) 2 (10) (6) (3) (5) (1) 5 (1) (1) (3) (2) 9 (10) 8 (10) 6 Рис. 7.18 Система односторонних дорог. Задана система односторонних дорог, соединяющих между собой n городов. Всякая дорога, идущая из города i в город j, характеризуется длиной wij. Очевидно, что та- 90 кой системе дорог соответствует орграф G, в котором вершины – города, дуги – дороги, веса дуг – длины дорог. Требуется – найти транзитивное замыкание орграфа G, т. е. выяснить, всякий ли город достижим из любого другого; – определить, есть ли город, выезжая из которого можно объехать все или часть городов и вернуться обратно (наличие контуров в орграфе); – определить, есть ли город, куда можно попасть из любого другого города, проезжая не более K км. Исходные данные: матрица весов орграфа; K – расстояние. Результаты: матрица смежности транзитивного замыкания исходного орграфа; длины контуров орграфа или сообщение о том, что контуров нет; номер вершины, указывающей город, куда можно попасть из любого другого города, проезжая не более K км. Рекомендация. Использовать алгоритм Флойда. Разработанную программу применить к орграфам, изображенным на рис. 7.15, 7.19. 1 2 (10) (3) (15) 5 (4) (5) (17) (4) 4 (9) 3 Рис. 7.19 Числа в скобках, стоящие у дуг, указывают в данном случае длины дорог между соответствующими городами. 91 ГЛАВА 3 АЛФАВИТНОЕ КОДИРОВАНИЕ ЗАДАНИЕ 8 Однозначность декодирования Теория кодирования – раздел дискретной математики, имеющий обширную область приложений. Кодирование буквально пронизывает сегодняшний мир информационных технологий и является центральным вопросом при решении самых разных практических задач хранения, защиты, передачи и обработки данных. Основы теории кодирования заложил в 1948 г. К. Шеннон. Им были сформулированы и доказаны две фундаментальные теоремы (для канала без помех и канала с помехами), которые определили развитие теории кодирования в последующие годы. К настоящему времени наиболее полно исследованы алфавитные коды, им посвящена глава 3 учебного пособия. Здесь рассматриваются две основные задачи алфавитного кодирования: распознавание однозначного декодирования и оптимальное кодирование. Цель данного задания: изучение, разработка и программирование критериев однозначного декодирования алфавитных кодов. 8.1. Основные понятия и обозначения Пусть Α = {a1, ..., an} – конечное непустое множество, или алфавит. Элементы алфавита называются буквами. Конечная последовательность α = ai1 ... aik букв алфавита Α называется словом, а число | α | – его длиной. Пустое слово (обозначается Λ) – это слово, в котором нет букв, т. е. | Λ | = 0. 92 Конкатенацией слов α1 и α2 называется слово α1α2. Заметим, что αΛ = Λα = α, т. е. если слову α предшествует или следует за ним пустое слово, то в результате конкатенации получаем то же самое слово α. Множество всех слов в алфавите Α, включая пустое слово, принято обозначать Α*. Рассмотрим другой алфавит Β = {b1, ..., bq} c множеством Β* всех его слов β. Пусть S ⊆ Α*. Всякая функция φ: S → Β*, ставящая в соответствие каждому слову α ∈ S слово β = ϕ(α) ∈ Β*, называется кодированием. При этом слова из S называются сообщениями, а их образы – кодами сообщений. Алфавит Α называется исходным алфавитом, а Β – кодирующим алфавитом. Обратная функция ϕ–1, если она существует, называется декодированием. В зависимости от числа букв в кодирующем алфавите различают двоичное, троичное и в общем случае q-ичное кодирование. Так, при двоичном кодировании в качестве кодирующего алфавита чаще всего выступает множество Β = {0, 1}. Кодирование φ может сопоставлять код β всему сообщению α как единому целому или же строить β из кодов букв, входящих в сообщение α. В последнем случае речь идет об алфавитном (или побуквенном) кодировании. Это простейший способ кодирования. Алфавитное кодирование задается схемой ϕ: а1 → β1, ... аn → βn, где аi ∈ Α, βi ∈ Β*, i = 1, …, n, которая устанавливает соответствие между буквами алфавита Α и некоторыми словами из Β*. Множество кодов букв {β1, …, βn} = {ϕ(a1), …, ϕ(an)} обозначается ϕ(Α) и называется множеством элементарных кодов (или алфавитным кодом). При алфавитном кодировании код сообщения – конкатенация кодов букв сообщения, т. е. каждому слову α = ai1 ... aik ставится в соответствие слово ϕ(α) = ϕ( ai ... ai ) = ϕ( ai ) ... ϕ( ai ) = β i ... β i = β ∈ B * . 1 k 1 k 1 k 93 Если | β1 | = … = | βn |, то алфавитный код ϕ(Α) = {β1, …, βn} называется равномерным, в противном случае – неравномерным. Число ℓ = | β1 | = … = | βn | называется разрядностью равномерного алфавитного кода ϕ(Α) = {β1, …, βn}. 8.2. Формулировка задачи Одной из типичных задач теории кодирования является задача распознавания однозначного декодирования, которая формулируется следующим образом. При заданных алфавитах Α, Β и алфавитном коде ϕ(Α) = {βi = ϕ(ai) | ai ∈Α, βi ∈ Β*, i = 1, …, n} определить, допускает ли ϕ однозначное декодирование, т. е. существует ли для функции ϕ обратная функция ϕ–1. Однозначное декодирование кода ϕ(Α) означает, что каждый код сообщения является кодом ровно одного сообщения. Пример 8.1. Рассмотрим неравномерное кодирование, для которого Α = {a, b}, Β = {0, 1} и схема кодирования имеет вид ϕ: а → 0, ... b → 01. Здесь сообщению α = abbaab соответствует код сообщения β = ϕ(abbaab) = ϕ(a) ϕ(b) ϕ(b) ϕ(a) ϕ(a) ϕ(b) = 001010001. Очевидно, что алфавитный код ϕ(Α) = {0, 01} является однозначно декодируемым, поскольку в данном случае процесс разделения кода сообщения на элементарные коды можно организовать следующим образом: сначала выделить пары (01) и заменить их буквой b, а затем оставшиеся буквы 0 заменить буквой a. Например, для кода сообщения β = 001010001 имеем β = 0 (01) (01) 00 (01) = ϕ(a) ϕ(b) ϕ(b) ϕ(a) ϕ(a) ϕ(b) = ϕ(abbaab). 94 Пример 8.2. Пусть Α = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, Β = {0, 1}, и кодирование выполняется по схеме 0 → 0000, 1 → 0001, 2 → 0010, 3 → 0011, 4 → 0100, ϕ: 5 → 0101, 6 → 0110, 7 → 0111, 8 → 1000, 9 → 1001. Это так называемое двоично-десятичное кодирование. В данном случае сообщениям α1 = 333, α2 = 77 соответствуют коды ϕ(α1) = ϕ(333) = ϕ(3) ϕ(3) ϕ(3) = 001100110011, ϕ(α2) = ϕ(77) = ϕ(7) ϕ(7) = 01110111. Двоично-десятичный код ϕ(Α) является 4-разрядным равномерным кодом, в котором все элементарные коды попарно различны. Очевидно, что такой код допускает однозначное декодирование, которое сводится к разделению кода сообщения на слова длины 4 и замене каждого из них соответствующей буквой алфавита Α. Например, слово β = 000010010110 является кодом сообщения α = 096, так как β = (0000) (1001) (0110) = ϕ(0) ϕ(9) ϕ(6) = ϕ(096). Очевидны следующие достаточные и необходимые условия однозначного декодирования алфавитного кода: – если ϕ(Α) является равномерным кодом, в котором все элементарные коды попарно различны, то он всегда допускает однозначное декодирование; – однозначно декодируемый код ϕ(Α) не может содержать равных элементарных кодов, а также элементарного кода, представимого в виде конкатенации других его элементарных кодов. 95 Пример 8.3. Пусть Α = {a, b, c}, Β = {0, 1} и ϕ(Α) = {0, 10, 100}. Здесь β1 = 0, β2 = 10, β3 = 100 = β2 β1, поэтому код ϕ(Α) не является однозначно декодируемым. Например, слово β = 0010100010 – образ следующих сообщений α1 = aabcab и α2 = aabbaab, так как ϕ(α1) = ϕ(aabcab) = (0) (0) (10) (100) (0) (10) = 0010100010, ϕ(α2) = ϕ(aabbaab) = (0) (0) (10) (10) (0) (0) (10) = 0010100010. Приведенные необходимые и достаточные условия носят частный характер. Известны более общие эффективно вычисляемые критерии распознавания однозначного декодирования алфавитного кода [8, 25]. Наиболее важные из них обсуждаются далее. 8.3. Критерии однозначного декодирования Пусть слово β ∈ Β* имеет вид β = β′ β′′, где β′, β′′∈ Β*. Тогда слово β′ называется префиксом (или началом), а β′′ – постфиксом (или окончанием) слова β. При этом пустое слово Λ и само β считаются префиксами и постфиксами слова β. Все префиксы и постфиксы слова β, отличные от Λ и β, называются собственными. Например, для слова β = 011 собственными префиксами выступают слова 0 и 01, а собственными постфиксами – 1 и 11. Алфавитный код ϕ(Α) называется префиксным, если ни один элементарный код из ϕ(Α) не является префиксом другого элементарного кода из ϕ(Α). О таких кодах говорят, что они обладают свойством префикса. Очевидно, что если в ϕ(Α) имеется хотя бы одна пара совпадающих элементарных кодов βi, βj, т. е. βi = βj, то префиксность ϕ(Α) нарушается, поскольку βi = βj Λ и βj – префикс для βi, βj = βi Λ и βi – префикс для βj. То же самое имеет место и в случае, когда ϕ(Α) имеет элементарный код, представимый в виде конкатенации других элементарных кодов из ϕ(Α). Очевидно, что пустое слово Λ не может быть составляющей алфавита префиксного кода, поскольку Λ является префиксом любого слова. В дальнейшем будем полагать, что Λ ∉ ϕ(Α). 96 Пусть задан алфавитный код ϕ(Α) = {β1, …, βn}. Обозначим через β i слово, получающееся из βi ∈ ϕ(Α) «обращением»: βi = β i1 ... β ik , β i = β ik ... β i1 . Под ϕ (Α) будем понимать алфавитный код ϕ (Α) = { β1 , …, β n }. Справедливо следующее достаточное условие однозначного декодирования алфавитного кода, которое нетрудно доказать методом от противного. Утверждение 8.1. Если ϕ(Α) или ϕ (Α) является префиксным кодом, то ϕ(Α) допускает однозначное декодирование. Пример 8.4. Вернемся к алфавитному коду ϕ(Α) = {0, 01} из примера 8.1. Этот код не является префиксным, так как β1 = 0 – префикс для β2 = 01. Однако ϕ (Α) = {0, 10} обладает свойством префикса. По утверждению 8.1 код ϕ(Α) допускает однозначное декодирование, что подтверждает результат примера 8.1. Пример 8.5. Равномерный двоично-десятичный код ϕ(Α) = {0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, 1000, 1001} из примера 8.2 обладает свойством префикса, поэтому он является однозначно декодируемым. Кома-код − это разновидность префиксного кода. При его использовании каждая буква алфавита Α кодируется элементарным двоичным кодом из единиц, в конце которого стоит нуль. Так, при | Α | = 5 кома-код имеет вид ϕ(Α) = {0, 10, 110, 1110, 11110}. Кома-код легко строится, но он обладает явным недостатком: его элементарные коды могут быть очень длинными и занимать большой объем памяти. Поэтому на практике используются более экономные префиксные коды. Важно отметить, что свойство префикса является достаточным, но не является необходимым условием однозначного декодирования алфавитного кода. Это иллюстрирует следующий пример. 97 Пример 8.6. Пусть заданы Α = {a, b, c, d, e}, Β = {0, 1, 2, 3} и код ϕ(Α) = {01, 32, 0, 12033, 20}. Здесь коды ϕ(Α) и ϕ(Α) = {10, 23, 0, 33021, 02} не обладают свойством префикса. Однако в примере 8.11 показано, что код ϕ(Α) допускает однозначное декодирование. Из-за простоты декодирования префиксные коды иногда называют мгновенными кодами. Рассмотрим процесс декодирования префиксного кода. Пусть Α = {a1, … , an} – исходный алфавит, Β = {b1, … , bq} – кодирующий алфавит, при этом n > q > 1. Предположим, что кодирование выполняется побуквенно на основе схемы ϕ: а1 → β1, ... аn → βn, где ai ∈ Α, βi ∈ Β*, i = 1, …, n. Задан код β = ϕ(α) ≠ Λ некоторого сообщения α. Декодирование слова β ∈ B*, т. е. определение α = ϕ−1(β) можно выполнить с помощью следующего алгоритма. Алгоритм 8.1. Декодирование слова β = ϕ(α) по префиксному коду ϕ(Α) 1. Просматривать слово β по одной букве, начиная с первой. Как только прочитанный префикс (не обязательно собственный) совпадает с некоторым элементарным кодом из ϕ(Α), следует этот префикс заменить соответствующей буквой из Α, а затем исключить из слова β. 2. Пункт 1 повторять до тех пор, пока β ≠ Λ. Очевидно, что время работы данного алгоритма составляет O (mn), где m = | β |, n = | Α |. Алгоритм 8.1 легко модифицировать на случай, когда ϕ(Α) является префиксным кодом. Для этого необходимо просмотр слова β вести с последней буквы, т. е. справа налево, и выделять не префиксы, 98 а постфиксы. Ясно, что алгоритм декодирования однозначных, но не обязательно префиксных кодов является гораздо более сложным, чем алгоритм 8.1. По этой причине на практике используются в основном префиксные коды. Пример 8.7. Рассмотрим алфавит Α = {a, b, c}. Для кодирования используем кома-код ϕ(Α) = {0, 10, 110}, который является префиксным, а значит, однозначно декодируемым. Слову α = abbc в этом коде соответствует слово β = ϕ(α) = 01010110. Применим к слову β алгоритм 8.1. Считываем первую букву 0 из β. Ей соответствует буква a ∈ Α. Производим декодирование и исключаем 0 из дальнейшего рассмотрения. В результате имеем: α = а, β = 1010110. Считываем следующую букву 1. Такого элементарного кода нет. После считывания следующей буквы 0 получаем префикс 10, которому соответствует элементарный код буквы b ∈ Α. После декодирования и исключения префикса 10 из β имеем: α = ab, β = 10110. Аналогично на третьей итерации алгоритма можно декодировать еще один префикс 10. В результате получим: α = abb, β = 110. На четвертой итерации декодируется вся оставшаяся часть слова β, поскольку в ϕ(Α) нет элементарных кодов 1 и 11. В итоге имеем: α = abbc и β = Λ. Процесс декодирования завершается. Для распознавания однозначного декодирования можно применять следующее необходимое условие [25]. Утверждение 8.2 (неравенство Макмиллана). Для всякого однозначно декодируемого кода в q-буквенном алфавите с набором длин элементарных кодов ℓ1, …, ℓn всегда выполняется неравенство n ∑ i =1 1 q i ≤ 1. Для двоичного кодирования неравенство Макмиллана принимает вид n ∑ i =1 1 ≤ 1. 2 i 99 По утверждению 8.2, если для алфавитного кода ϕ(А) = {β1, …, βn} неравенство Макмиллана не выполняется, т. е. n ∑ i =1 1 q i > 1, где ℓi = | βi |, i = 1, …, n, то ϕ(Α) не допускает однозначного декодирования. При выполнении неравенства Макмиллана об однозначности кода ϕ(Α) ничего сказать нельзя. Пример 8.8. Возьмем из примера 8.6 однозначно декодируемый код ϕ(А) = {01, 32, 0, 12033, 20}. Для него q = 4, ℓ1 = ℓ2 = 2, ℓ3 = 1, ℓ4 = 5, ℓ5 = 2. Вычислим сумму 5 ∑ i =1 1 1 1 1 1 1 449 = 2 + 2 + 1 + 5 + 2 = . i 1024 4 4 4 4 4 q Неравенство Макмиллана выполняется. Пример 8.9. Пусть заданы алфавиты Α = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, Β = {0, 1} и код ϕ(А) = {0, 1, 10, 11, 100, 101, 110, 111, 1000, 1001}. Заметим, что ϕ(Α) не обладает свойством префикса. Для ϕ(Α) имеем q = 2, ℓ1 = ℓ2 = 1, ℓ3 = ℓ4 = 2, ℓ5 = ℓ6 = ℓ7 = ℓ8 = 3, ℓ9 = ℓ10 = 4. Выполним вычисление левой части неравенства Макмиллана: 10 ∑ i =1 1 1 1 1 1 1 = 2⋅ 1 + 2⋅ 2 + 4⋅ 3 + 2⋅ 4 = 2 . i 8 2 2 2 2 2 Неравенство не выполняется, поэтому код ϕ(Α) не является однозначно декодируемым. Действительно, слово β = 111111 разделяется на элементарные коды двумя способами: β = (11) (11) (11), β = (111) (111). Отсюда β = ϕ(333) и β = ϕ(77) одновременно. Следующий пример показывает, что неравенство Макмиллана не может служить достаточным условием распознавания однозначного декодирования кода. 100 Пример 8.10. Рассмотрим алфавиты Α = {a, b, c, d, e}, Β = {0, 1} и код ϕ(Α) = {100, 001, 101, 1101, 11011}. Нетрудно убедиться, что для ϕ(Α) неравенство Макмиллана выполняется. Между тем ϕ(Α) не является однозначно декодируемым. Например, слово β = 11011101 допускает две расшифровки: β = (11011) (101) = ϕ(e) ϕ(c) = ϕ(ec), β = (1101) (1101) = ϕ(d) ϕ(d) = ϕ(dd). Существуют необходимые и одновременно достаточные условия однозначного декодирования алфавитного кода. Эти условия проверяются с помощью соответствующих алгоритмов: алгоритма Маркова и алгоритма анализа окончаний [8, 25]. Приведем второй из них как наиболее простой и эффективный. Пусть задан исходный алфавитный код ϕ(Α) = {β1, … , βn}, для которого n > 1 и Λ ∉ ϕ(Α). Алгоритм 8.2. Распознавание однозначности кода ϕ(Α) путем анализа окончаний 1. Положить W:= ∅. Рассмотреть все пары элементарных кодов βi, βj ∈ ϕ(Α), i ≠ j. Если среди них есть равные пары кодов, то процесс завершить ответом: ϕ(Α) не является однозначно декодируемым кодом. В противном случае найти пары, в которых один элементарный код является собственным префиксом другого элементарного кода. Для каждой такой пары найти собственное окончание, которое остается после удаления префикса из начальной части более длинного элементарного кода. Например, для пары βi = 10 и βj = 10010 окончанием будет слово 010. Все найденные собственные окончания записать в W и перейти на пункт 2. Если таких пар нет в ϕ(А), т. е. W не изменилось и осталось пустым, то процесс завершить ответом: ϕ(А) – префиксный, а значит, однозначно декодируемый код. 2. Выполнить сравнение всех возможных пар слов, одно из которых принадлежит W, а другое – алфавитному коду ϕ(Α), и выделить собственные окончания. Все новые окончания добавить в W. 101 3. Пункт 2 повторять до тех пор, пока будут появляться новые окончания. 4. Выполнить проверку условия: – если W ∩ ϕ(Α) = ∅, т. е. ни одно окончание из W не совпадает ни с одним элементарным кодом из ϕ(Α), процесс завершить ответом: ϕ(Α) является однозначно декодируемым кодом; – иначе ответ: ϕ(Α) не допускает однозначного декодирования. Очевидно, что алгоритм 8.2 имеет полиномиальную сложность. Проиллюстрируем его работу на примерах. Пример 8.11. Вернемся к коду из примера 8.6: ϕ(Α) = {01, 32, 0, 12033, 20}. Применим к нему алгоритм 8.2. В результате попарного сравнения в пункте 1 элементарных кодов из ϕ(Α) получим W = {1}. После первой итерации пункта 2 имеем W = {1, 2033}. Повторение пункта 2 дает W = {1, 2033, 33}, после чего W уже не изменяется. Итак, W ∩ ϕ(Α) = ∅ и ϕ(Α) − однозначно декодируемый код. Пример 8.12. Для алфавитного кода ϕ(Α) = {0, 1, 10, 11, 100, 101, 110, 111, 1000, 1001} из примера 8.9 алгоритм 8.2 работает следующим образом. Выполнение пункта 1 дает W = {0, 1, 00, 01, 10, 11, 000, 001}. В пункте 2 множество W не пополняется. В итоге W ∩ ϕ(Α) = {0, 1, 10, 11} ≠ ∅. Поэтому ϕ(Α) не является однозначно декодируемым кодом, что подтверждает результат примера 8.9. Пример 8.13. Вновь рассмотрим алфавитный код ϕ(Α) = {100, 001, 101, 1101, 11011}. 102 Из примера 8.10 известно, что данный код не допускает однозначного декодирования. В самом деле, в процессе выполнения алгоритма 8.2 множество W принимает вид W = {1, 00, 01, 101, 1011}. Имеем неоднозначность алфавитного кода ϕ(А), поскольку W ∩ ϕ(Α) = = {101} ≠ ∅. Алгоритм 8.2 также работает верно, когда один элементарный код является конкатенацией других. Пример 8.14. Пусть задан алфавитный код ϕ(Α) = {01, 11, 011101}, где β1 = 01, β2 = 11, β3 = 011101 = β1 β2 β1. Сравнение элементарных кодов в пункте 1 приводит к W = {1101}. При выполнении пункта 2 множество W пополняется окончанием 01. В итоге W = {1101, 01}, W ∩ ϕ(Α) = {01} ≠ ∅. Это свидетельствует о том, что ϕ(Α) не является однозначно декодируемым кодом. 8.4. Порядок выполнения задания 8.4.1. Разработать алгоритм и программу проверки достаточных условий однозначного декодирования алфавитного кода, сформулированных в утверждении 8.1. Оценить вычислительную сложность разработанного алгоритма. При отладке программы использовать примеры 8.3–8.7. 8.4.2. Разработать алгоритм и программу проверки необходимых условий однозначного декодирования алфавитного кода, определенных утверждением 8.2. Оценить вычислительную сложность разработанного алгоритма. Правильность работы программы проверить на примерах 8.8–8.10. 8.4.3. Изучить алгоритмы 8.1 и 8.2 и выполнить оценку их вычислительной сложности. Реализовать эти алгоритмы в виде программ. При отладке программ использовать примеры 8.11–8.14. 8.4.4. Программы из пунктов 8.4.1–8.4.3 объединить в единую программу, которая должна распознавать различными способами од103 нозначность алфавитного кода, кодировать и декодировать сообщение префиксным кодом. С помощью этой программы найти однозначно декодируемые коды из табл. 8.1, а найденные префиксные коды применить к четверостишию стихотворения А. С. Пушкина из табл. 8.2. 8.5. Варианты Таблица 8.1 Номер варианта Алфавитный код ϕ(А) = {β1, …, βn} 1 {01, 201, 112, 122, 0112} {001, 021, 102, 201, 001121, 010121101} {0, 01, 0010001001} 2 {0, 101010, 01010101} {01, 011, 100, 2100, 101210, 001210} {01, 011, 100, 2100, 10110, 00112} 3 {01, 12, 021, 0102, 10112} {01, 12, 012, 111, 0102, 10112, 01112} {01, 12, 012, 0102, 020112} 4 {01, 10, 210, 121, 0210, 0112} {01, 10, 210, 201, 0210, 011022, 2221} {01, 10, 210, 201, 0210, 011022, 221} 5 {01, 12, 01121, 21201} {10, 12, 012, 101, 2100} {01, 12, 011, 01210, 201120, 2011220} 6 {01, 10, 210, 201, 0210, 011022} {01, 12, 011, 01210, 20120, 2011220} {10, 01, 12, 012, 2100, 12011, 12010} 104 Таблица 8.2 Номер варианта 1 2 3 4 5 6 Текст α я_помню_чудное_мгновенье:_ передо_мной_явилась_ты,_ как_мимолетное_виденье,_ как_гений_чистой_красоты. в_томленьях_грусти_безнадежной,_ в_тревогах_шумной_суеты_ звучал_мне_долго_голос_нежный_ и_снились_милые_черты. шли_годы._бурь_порыв_мятежный_ рассеял_прежние_мечты,_ и_я_забыл_твой_голос_нежный,_ твои_небесные_черты. в_глуши,_во_мраке_заточенья_ тянулись_тихо_дни_мои_ без_божества,_без_вдохновенья,_ без_слез,_без_жизни,_без_любви. душе_настало_пробужденье:_ и_вот_опять_явилась_ты,_ как_мимолетное_виденье,_ как_гений_чистой_красоты. и_сердце_бьется_в_упоенье,_ и_для_него_воскресли_вновь_ и_божество,_и_вдохновенье,_ и_жизнь,_и _слезы,_и_любовь. 105 ЗАДАНИЕ 9 Оптимальное кодирование и сжатие текстов Часто необходимо сжимать данные, чтобы минимизировать объем памяти для их хранения или время передачи данных. Для сжатия текстовых данных преимущественно используют оптимальные алфавитные коды. Все они основаны на двух принципах: выполнение свойства префикса; кодирование более короткими элементарными кодами тех букв, которые чаще встречаются в тексте. Наиболее известный из этих кодов – код Хаффмена [2, 25]. Цель данного задания – изучение и программирование алгоритмов построения кода Хаффмена, практика анализа алгоритмов. 9.1. Средняя длина элементарного кода Предположим, что некоторый источник генерирует сообщения в алфавите Α = {a1, ..., an}. Для кодирования сообщений используется алфавит Β = {b1, ..., bq}, при этом n > q > 1. Кодирование выполняется побуквенно на основе схемы ϕ: а1 → β1, ... аn → βn, (аi ∈ Α, βi ∈ Β*, i = 1, …, n). Схема ϕ определяет алфавитный код ϕ(Α) = {β1, ..., βn} как некоторую совокупность элементарных кодов. Будем далее полагать, что элементарные коды в ϕ(Α) перечисляются в том же порядке, что и соответствующие им буквы алфавита Α. Очевидно, что если ϕ(Α) является однозначно декодируемым кодом, то алфавитный код ϕ*(Α), получаемый из ϕ(Α) перестановкой элементарных кодов, также является однозначно декодируемым. Когда длины элементарных кодов равны (ϕ(Α) – равномерный код), то перестановка βi в ϕ(Α) не влияет на длину кода сообщения. Но если длины элементарных кодов различны, то длина кода сообщения зависит от состава букв в сообщении и от того, какие элементарные коды каким буквам назначены. 106 Интуитивно ясно, что для минимизации длины кодов сообщений надо часто встречающиеся буквы кодировать короткими элементарными кодами, оставив более длинные слова буквам, появляющимся в сообщениях гораздо реже. В математике мерой частоты появления того или иного события (в нашем случае появления буквы аi ∈ Α в сообщении α) является вероятность. Не останавливаясь на строгом определении, заметим, что вероятность некоторого события определяет долю случаев, в которых это событие произошло, к общему числу случаев. В дальнейшем вероятность события аi будем обозначать pi. Пример 9.1. Пусть Α = {a1, a2, a3, a4} – исходный алфавит. Известны вероятности 1 1 1 1 p1 = , p2 = , p3 = , p4 = . 2 4 8 8 Это означает, что в сообщении α ∈ Α* длиной 1000 букв около 500 раз появится буква a1, около 250 раз – буква a2 и примерно 125 раз – каждая из букв a3 и a4. Важно отметить, что р1 + р2 + р3+ р4 = 1, т. е. появление в непустом сообщении α буквы a1, или a2, или a3, или a4 является достоверным событием – событием с вероятностью 1. В таком случае говорят о полной группе событий. Если использовать 2-разрядный равномерный код ϕ1(Α) = {00, 01, 10, 11}, то сообщению α длины 1000 будет всегда отвечать код сообщения ϕ1(α) длины 2000. Применение неравномерного префиксного кода ϕ2(Α) = {0, 10, 110, 111} приводит к коду сообщения ϕ2(α), длина которого примерно равна 500 | β1| + 250 | β2| + 125 | β3| + 125 | β4| = = 500 + 250 · 2 + 125 · 3 +125 · 3 = 1750, так что | ϕ2(α) | < | ϕ1(α) |. 107 Примечательно, что в примере 9.1 буквы в алфавите Α перечислены в порядке убывания вероятностей их появления, а элементарные коды в ϕ2(Α) – в порядке возрастания длин этих кодов. Таким образом, применение неравномерного кода и учет вероятностных характеристик источника сообщений позволяет уменьшить длину кодов сообщений. Основной характеристикой алфавитного кода является средняя длина его элементарных кодов. Определим это понятие. Пусть заданы алфавиты Α = {a1, ..., an}, Β = {b1, ..., bq}, n > q > 1. Кроме того, известен набор вероятностей P = ( p1 , ..., p n ), n ∑p i =1 i = 1, где pi – вероятность появления буквы аi ∈ Α в словах, генерируемых источником сообщений. Не ограничивая общности, будем считать, что p1 ≥ p2 ≥ ... ≥ pn − 1 ≥ pn > 0, т. е. из Α сразу исключим буквы, которые не могут появляться в сообщениях, а остальные упорядочим по убыванию вероятностей их появления. Пусть ϕ(Α) = {β1, ..., βn} – алфавитный код, в котором слово βi является кодом буквы аi ∈ Α и ℓi – длина βi ∈ Β* (i = 1, …, n). Тогда число ℓcp = p1ℓ1 + ... + pnℓn = n ∑ piℓi i =1 называется средней длиной элементарного кода для ϕ(Α) относительно Ρ. Значение ℓcp − это среднее число букв кодирующего алфавита Β, приходящихся на одну букву исходного алфавита Α. Пример 9.2. Рассмотрим для ⎛1 1 1 1⎞ Α = {a1, a2, a3, a4}, Β = {0, 1}, Ρ = ⎜ , , , ⎟ ⎝2 4 8 8⎠ два алфавитных кода ϕ1(Α) = {0, 10, 110, 111}, ϕ2(Α) = {111, 110, 10, 0}, 108 где ϕ2(Α) получается из ϕ1(Α) перестановкой элементарных кодов. Вычислим среднюю длину элементарного кода для ϕ1(Α) и ϕ2(Α): 1 ·1+ 2 1 ℓcp (ϕ2, Ρ) = ·3+ 2 ℓcp (ϕ1, Ρ) = 1 ·2+ 4 1 ·3+ 4 1 ·3+ 8 1 ·2+ 8 1 3 ·3=1 = 1,75; 8 4 1 · 1 = 2,625. 8 Следовательно, значение ℓcp зависит от порядка назначения элементарных кодов буквам алфавита Α. Обозначим через L = (ℓ1, ..., ℓn) набор длин элементарных кодов для ϕ(Α) = {β1, ..., βn}, где ℓi = | βi |, i = 1, …, n. Заметим, что если наборы Ρ = (p1, …, pn), L = (ℓ1, …, ℓn) рассматривать как векторы, то ℓcp – скалярное произведение этих векторов. Нетрудно убедиться в справедливости следующего утверждения. Утверждение 9.1. При p1 ≥ p2 ≥ ... ≥ pn наименьшее значение скалярного произведения векторов Ρ = (p1, …, pn), L = (ℓ1, …, ℓn) достигается, когда ℓ1 ≤ ℓ2 ≤ ... ≤ ℓn. Ясно, что равномерные коды можно считать частным случаем неравномерных кодов, когда ℓ1 = ℓ2 = ... = ℓn. В этих условиях n ∑ piℓi ℓcp = i =1 n = ℓ1 ∑ pi = ℓ1, i =1 т. е. значение ℓcp совпадает с разрядностью равномерного кода и не зависит от порядка назначения элементарных кодов буквам алфавита Α. В случае равновероятности появления букв алфавита Α в передаваемых сообщениях, когда 1 p1 = ... = p n = , n ℓcp также не зависит от того, какие элементарные коды каким буквам назначены, и ℓcp = n ∑ pi ℓi i =1 = 1 (ℓ1 + ... + ℓn). n 109 9.2. Формулировка задачи Пусть заданы алфавиты Α = {a1, ..., an}, Β = {b1, ..., bq}, n > q > 1, и набор вероятностей n ∑p P = ( p1 , ..., p n ), i =1 i = 1, p1 ≥ p 2 ≥ ... ≥ p n > 0. Предположим, что буквы алфавита Α появляются в сообщениях независимо друг от друга. Тогда стремление уменьшить длины кодов сообщений сводится к минимизации величины ℓcp. Имеем следующую оптимизационную задачу: для заданных Α, Β, Ρ требуется найти такой набор L = (ℓ1, …, ℓn), который доставлял бы минимум ℓcp и удовлетворял неравенству Макмиллана n ∑ i =1 1 q i ≤ 1, q = | B | . Используемое в формулировке задачи неравенство Макмиллана гарантирует возможность построения префиксного, а значит, однозначно декодируемого кода по найденному набору длин L = (ℓ1, …, ℓn). Об этом свидетельствует следующее утверждение. Утверждение 9.2 [25]. Если числа ℓ1, …, ℓn удовлетворяют неравенству Макмиллана, то существует алфавитный префиксный код ϕ(Α) = {β1, ..., βn}, для которого ℓi = | βi |, i = 1, …, n. Префиксный алфавитный код, для которого набор L = (ℓ1, …, ℓn) длин его элементарных кодов является решением сформулированной задачи, называется оптимальным (Ρ, q)-кодом. Элегантный алгоритм точного решения этой задачи предложил Д. Хаффмен. Не умаляя общности, в дальнейшем ограничимся рассмотрением оптимальных (P, 2)-кодов. 110 9.3. Свойства оптимальных кодов Оптимальные (P, 2)-коды обладают определенными свойствами, которые используются при их построении. Утверждение 9.3. Если ϕ(Α) является оптимальным (Ρ, 2)кодом, то для любых его элементарных кодов βi и βj, соответствующих вероятностям pi и pj, при pi > pj всегда верно ℓi ≤ ℓj. Другими словами, если буква аi ∈ Α встречается в сообщениях более часто, чем буква аj ∈ Α, то она при оптимальном кодировании не может кодироваться более длинным словом, чем аi. Справедливость утверждения 9.3 непосредственно вытекает из утверждения 9.1. Утверждение 9.4. Если ϕ(Α) – оптимальный (Р, 2)-код, то среди его элементарных кодов, имеющих максимальную длину ℓ, найдутся два элементарных кода с одним и тем же префиксом длины ℓ – 1 и различающиеся только в последнем разряде. Утверждение 9.5 (теорема редукции). Пусть задан двоичный алфавитный код ϕ = {β1, ..., βn} с набором вероятностей P = ( p1 , ..., p n ), n ∑p i =1 i = 1, p1 ≥ p 2 ≥ ... ≥ p n > 0. Положим δ = pn − 1 + pn и рассмотрим упорядоченный набор P′ = (p1, …, pj − 1, δ, pj + 1, …, pn − 2), который получен из Р исключением двух последних (наименьших) вероятностей pn − 1, pn и вставкой их суммы δ в оставшийся набор так, чтобы в получившемся наборе P′ вероятности не возрастали. Пусть βn − 1 = β′ 0, βn = β′ 1 – элементарные коды длины ℓ, принадлежащие ϕ и имеющие одинаковый префикс β′, где | β′ | = ℓ – 1. Тогда алфавитный код ϕ′ = {β1, ..., βj − 1, β′, βj + 1, ..., βn − 2} является (P′, 2)-оптимальным, если и только если код ϕ = {β1, ..., βn} является (Р, 2)-оптимальным. 111 Доказательство утверждений 9.4, 9.5 можно найти в [19, 25]. Заметим, что в теореме редукции переход от набора вероятностей Р к набору P′ является, по сути, однократным сжатием алфавита А, т. е. заменой двух самых редких букв с вероятностями pn − 1, pn одной новой буквой с вероятностью δ = pn − 1 + pn. Таким образом, теорема редукции позволяет свести задачу построения оптимального кода размерности n = | P | = | A | к аналогичной задаче размерности n – 1 = | P′ |. Данная теорема лежит в основе рекурсивного алгоритма Хаффмена построения оптимального (Р, 2)-кода. 9.4. Алгоритм Хаффмена Данный алгоритм выполняется в два этапа. Этап 1 – процесс редукции (сжатия) алфавита А до двух букв – прямой ход алгоритма. Этап 2 – построение оптимального кода – обратный ход алгоритма. Исходные данные алгоритма: – n ≥ 2, где n – число букв алфавита А; – упорядоченный набор ненулевых вероятностей P = ( p1 , ..., p n ), n ∑p i =1 i = 1, p1 ≥ p 2 ≥ ... ≥ p n > 0. В дальнейшем этот набор будем представлять одноименным одномерным массивом Р длины n, элементы которого − вещественные числа из интервала (0, 1). Выходные данные алгоритма: – упорядоченная последовательность элементарных кодов оптимального префиксного (Р, 2)-кода ϕ(Α) = {β1, ..., βn}; – набор длин элементарных кодов L = (ℓ1, …, ℓn), где ℓi = | βi |, i = 1, …, n. Далее условимся набор L длин элементарных кодов представлять одноименным одномерным целочисленным массивом L длины n, а последовательность элементарных кодов ϕ(Α) – двумерным двоичным массивом С размерности n × n, каждая i-я строка которого содержит элементарный код β i ∈ ϕ(Α). 112 Этап 1. Процесс редукции алфавита сводится к выполнению n − 2 преобразований массива Р, каждое из которых является однократной редукцией, уменьшающей длину обрабатываемой части массива Р на единицу. Однократная редукция массива Р – отбрасывание двух наименьших вероятностей и вставка их суммы δ так, чтобы массив Р оставался упорядоченным по убыванию в его обрабатываемой части. Заметим, что для построения оптимального кода надо запоминать место вставки значения δ. Пример 9.3. Для n = 8 и набора вероятностей Ρ = (0,22; 0,20; 0,16; 0,16; 0,16; 0,10; 0,10; 0,04; 0,02) результаты процесса редукции приведены в табл. 9.1. Таблица 9.1 Исходный массив Р 0,22 0,20 0,16 0,16 0,10 0,10 0,04 0,02 k j 8 – Состояние массива Р после однократных редукций 1 2 3 4 5 6 0,22 0,20 0,16 0,16 0,10 0,10 0,06 0,22 0,20 0,16 0,16 0,16 0,10 0,26 0,22 0,20 0,16 0,16 0,32 0,26 0,22 0,20 0,42 0,32 0,26 0,58 0,42 7 7 6 5 5 1 4 1 3 1 2 1 В последних двух строках табл. 9.1 указаны значения длины k обрабатываемой части массива Р и индекса j, определяющего место вставки суммы вероятностей. Поскольку здесь n = 8, то для сжатия алфавита до двух букв потребовалось выполнить 6 преобразований массива Р. Этап 2. Построение оптимального кода осуществляется с использованием длины k обрабатываемой части массива Р и индекса j, 113 определяющего место вставки суммы вероятностей. Очевидно, что пара букв при любых значениях вероятностей их появления в сообщениях оптимальным образом кодируется так: первой букве ставится в соответствие 0, а второй – 1 (или наоборот). Далее по теореме редукции оптимальный код для k букв создается на основе оптимального кода для k − 1 букв. С этой целью элементарный код β′ с индексом j исключается из массива С путем сдвига вверх элементарных кодов с индексами больше j. Затем в конец обрабатываемой части массива С добавляется пара кодов β′ 0 и β′ 1, имеющих общий префикс β′ и различающихся только в последнем разряде. Другими словами, j-я строка массива С «расщепляется» на две строки, которые помещаются в его конец. Пример 9.4. Для исходных данных из примера 9.3 результаты поэтапного построения оптимального кода приведены в табл. 9.2. По построению полученный оптимальный код префиксный. Его средняя длина равна ℓcp = 0,22 · 2 + 0,20 · 2 + 0,16 · 3 + 0,16 · 3 + 0,10 · 3 + + 0,10 · 4 + 0,04 · 5 + 0,02 · 5 = 2,8. Таблица 9.2 Состояние массива С после «расщепления» j-й строки Оптимальный код 6 5 4 3 2 1 0 1 1 00 01 00 01 10 11 01 10 11 000 001 10 11 000 001 010 011 10 11 000 001 011 0100 0101 2 3 4 5 6 7 8 k 1 1 1 1 5 7 – j 114 10 11 000 001 011 0100 01010 01011 Сформулируем алгоритм Хаффмена более точно в виде двух процедур и функции [25]. Алгоритм 9.1. Рекурсивная процедура HAFFMAN (P, k) 1. В качестве исходных данных рассматривать массив вероятностей Р, упорядоченный по убыванию, и количество букв k анализируемого алфавита. Если k = 2, то закодировать две буквы алфавита, полагая C[1, 1]:= 0, L[1]:= 1, C[2, 1]:= 1, L[2]:= 1. 2. Иначе найти сумму двух последних (наименьших) вероятностей δ:= P[k – 1] + P[k] и с помощью функции WST (k, δ) вставить δ в массив Р, не нарушая его упорядоченности. 3. Построить оптимальный код для k букв с помощью процедуры DOWN (k, j). Алгоритм 9.2. Функция WST (k, δ) 1. Положить j:= k – 1, P[j]:= δ, т. е. число δ временно записать в предпоследний элемент массива Р. 2. Найти для δ требуемую позицию в Р: при i = k – 1, …, 2 выполнить действия: – если P[i – 1] < P[i], то Q:= P[i – 1], P[i – 1]:= P[i], P[i]:= Q (элементы P[i – 1], P[i] поменять местами) и j:= i – 1 (запомнить текущую позицию числа δ); – иначе цикл не продолжать, а перейти на пункт 3. 3. Выполнение функции завершить и вернуть в качестве результата значение j – место вставки числа δ. Процедура DOWN (k, j) строит оптимальный код для k букв на основе уже построенного оптимального кода для k – 1 букв. В описании этой процедуры запись C[i, *] означает i-ю строку массива С. 115 Алгоритм 9.3. Процедура DOWN (k, j) «расщепления» j-й буквы 1. Запомнить для j-й буквы код β′:= C[j, *] и его длину ℓ:= L[j]. 2. Сдвинуть вверх все строки массива С с номерами j + 1, …, k – 1: для каждого i = j, …, k – 2 положить C[i, *]:= C[i + 1, *], L[i]:= L[i + 1]. 3. Скопировать код j-й буквы в качестве префикса для пары элементарных кодов, соответствующих частям «расщепляемой» буквы: C[k – 1, *]:= β′, C[k, *]:= β′. 4. Нарастить вновь образованные элементарные коды: C[k – 1, ℓ + 1]:= 0, C[k, ℓ + 1]:= 1. Определить длину этих кодов: L[k – 1]:= ℓ + 1, L[k]:= ℓ +1. По теореме редукции код Хаффмена (код, построенный по алгоритму Хаффмена) является оптимальным и префиксным. Необходимо отметить, что при редукции массива Р возможно появление равных вероятностей, а значит, нескольких возможностей вставки числа δ в массив Р. Это говорит о том, что может существовать несколько оптимальных (Р, 2)-кодов с одним и тем же значением ℓcp. Из теоремы редукции вытекают следующие свойства двоичных кодов Хаффмена: – если ϕ(Α) = {β1, ..., βn} – оптимальный код, то для него неравенство Макмиллана обращается в равенство; – для всякого оптимального кода ϕ(Α) = {β1, ..., βn} верны неравенства: | βi | ≤ n – 1 (i = 1, …, n); – число оптимальных кодов для n-буквенного алфавита Α не превосходит 2n (n – 1)!; – в оптимальном коде число элементарных кодов максимальной длины всегда четно. Докажите эти свойства самостоятельно. 116 9.5. Сжатие текстов Пусть имеется некоторый текст α, составленный из m букв алфавита Α = {a1, ..., an} и подлежащий хранению в памяти компьютера. Значение m может быть достаточно большим. Общепринятым для хранения текстовых данных является равномерный 8-разрядный двоичный код ASCII. Очевидно, что код ASCII не оптимален, так как он никак не учитывает вероятности появления тех или иных букв. Кроме того, в текстах обычно встречается существенно меньше, чем 28 = 256 различных букв. Все это при больших значениях m ведет к неэкономному использованию памяти компьютера (ведь для хранения m букв текста α необходимо 8m бит памяти). Потребности в памяти можно существенно снизить, если применять оптимальное кодирование текста. Операция, в результате которой заданному сообщению ставится в соответствие более короткое сообщение, принято называть сжатием (или упаковкой) данных. Существуют методы сжатия, предназначенные для различных типов данных и целей сжатия. Алгоритм Хаффмена определяет один из методов сжатия текстовых данных. Для каждого естественного языка определены вероятности появления букв в текстах. С некоторой погрешностью вместо вероятностей можно брать частоты появления букв в тексте α, которые для каждой буквы аi ∈ Α определяются формулой pi = mi , m где mi – число вхождений буквы ai в текст α и m1 + … + mn = m. Если для набора частот Ρ = (p1, …, pn) построить с помощью алгоритма Хаффмена оптимальный (Р, 2)-код и закодировать им текст α, то закодированный текст будет иметь длину примерно mℓcp бит. Таким образом, выигрыш по сравнению с кодом ASCII составит (8m – mℓcp) бит = m (8 – ℓcp) бит. Качество сжатия принято оценивать с помощью коэффициента сжатия, который измеряется в процентах и показывает, в какой степени закодированное сообщение короче исходного. 117 Применительно к текстам, закодированным кодом Хаффмена ϕ(Α), коэффициент сжатия вычисляется по формуле Kсж = [m (8 − ℓcp) / 8m] ⋅ 100 % = [(8 − ℓcp) / 8] ⋅ 100 %, где ℓcp – средняя длина элементарных кодов для ϕ(Α). Известно, что для большинства естественных языков ℓcp несколько меньше 6, поэтому Kсж ≈ 25 % есть величина выигрыша в памяти, получаемого от применения оптимального алфавитного кодирования текстов. В основе архивирующих программ с подобным значением Kсж лежат, как правило, различные модификации и усовершенствования алгоритма Хаффмена. 9.6. Порядок выполнения задания 9.6.1. Изучить свойства оптимальных кодов и алгоритм Хаффмена. Реализовать алгоритм Хаффмена в виде программы, которая строит оптимальный (Р, 2)-код и вычисляет для него ℓcp. Для отладки программы использовать примеры 9.3, 9.4. Построить оптимальные коды для заданных в табл. 9.3 наборов вероятностей. 9.6.2. Разработать программу сжатия заданного текста α с помощью алгоритма Хаффмена. Данная программа должна выполнять: – ввод текста α и вычисление его длины m = | α |; – формирование алфавита Α = {a1, ..., an} – множества букв, из которых составлен текст α, и набора P = (p1, …, pn) частот появления букв в α. В алфавит А следует включить пробел и знаки препинания; – сортировку набора Р по убыванию частот; – построение оптимального (Р, 2)-кода с помощью алгоритма Хаффмена; – кодировку текста α оптимальным (Р, 2)-кодом и вычисление длины закодированного текста; – определение коэффициента сжатия. Предусмотреть вывод на печать результатов выполнения всех перечисленных действий. Применить программу к четверостишию стихотворения А. С. Пушкина, заданному в табл. 8.3 задания 8. 118 9.7. Варианты Таблица 9.3 Номер варианта Наборы вероятностей 1 (0,4; 0,2; 0,1; 0,05; 0,05; 0,05; 0,05; 0,05; 0,05) (0,3; 0,2; 0,2; 0,2; 0,1) 2 (0,3; 0,2; 0,1; 0,1; 0,06; 0,06; 0,06; 0,06; 0,06) (0,4; 0,2; 0,1; 0,1; 0,1; 0,1) 3 (0,3; 0,3; 0,2; 0,04; 0,03; 0,03; 0,03; 0,03; 0,03; 0,01) (0,3; 0,3; 0,1; 0,1; 0,1; 0,1) 4 (0,4; 0,3; 0,08; 0,06; 0,04; 0,04; 0,04; 0,04) (0,4; 0,1; 0,1; 0,1; 0,1; 0,08; 0,06; 0,06) 5 (0,3; 0,2; 0,1; 0,1; 0,1; 0,1; 0,1) (0,20; 0,15; 0,15; 0,13; 0,12; 0,11; 0,11; 0,03) 6 (0,21; 0,20; 0,17; 0,16; 0,12; 0,08; 0,04; 0,02) (0,5; 0,2; 0,1; 0,09; 0,08; 0,03) 119 БИБЛИОГРАФИЧЕСКИЙ СПИСОК 1. Абрамов, С. А. Лекции о сложности алгоритмов / С. А. Абрамов. – М.: МЦНМО, 2009. 2. Андерсон, Д. Дискретная математика и комбинаторика / Д. Андерсон. – М.: Вильямс, 2003. 3. Ахо, А. Построение и анализ вычислительных алгоритмов / А. Ахо, Д. Хопкрофт, Д. Ульман. – М.: Мир, 1979. 4. Ахо, А. Структуры данных и алгоритмы / А. Ахо, Д. Хопкрофт, Д. Ульман. – СПб.: Вильямс, 2000. 5. Быкова, В. В. Практикум на ЭВМ по дискретной математике (вводный курс): учеб. пособие / В. В. Быкова. – Красноярск, ИЦ КрасГУ, 2005. 6. Быкова, В. В. Дискретная математика с использованием ЭВМ: учеб. пособие / В. В. Быкова – Красноярск: РИО КрасГУ, 2006. 7. Быкова, В. В. Теоретические основы анализа параметризированных алгоритмов / В. В. Быкова. – Красноярск: БИК Сиб. федер. унта, 2011. 8. Гаврилов, Г. П. Задачи и упражнения по дискретной математике: учеб. пособие / Г. П. Гаврилов, А. А. Сапоженко. – М.: ФИЗМАТЛИТ, 2004. 9. Грин, Д. Математические методы анализа алгоритмов / Д. Грин, Д. Кнут. – М.: Мир, 1987. 10. Гэри, М. Вычислительные машины и труднорешаемые задачи / М. Гэри, Д. Джонсон. – М.: Мир, 1982. 11. Касьянов, В. Н. Графы в программировании: обработка, визуализация и применение / В. Н. Касьянов, В. А. Евстигнеев. – СПб.: БХВ-Петербург, 2003. 12. Кнут, Д. Искусство программирования. − Т. 4. Комбинаторные алгоритмы / Д. Кнут. – М.: Вильямс, 2012. 13. Кормен, Т. Алгоритмы: построение и анализ / Т. Кормен, Ч. Лейзерсон, Р. Риверст. – М.: Вильямс, 2012. 14. Кристофидес, Н. Теория графов. Алгоритмический подход / Н. Кристофидес. – М.: Мир, 1978. 120 15. Кузюрин, Н. Н. Эффективные алгоритмы и сложность вычислений / Н. Н. Кузюрин, С. А. Фомин. – М.: Изд-во Моск. ун-та, 2009. 16. Лекции по теории графов / В. А. Емеличев [и др.]. – М.: Книжный дом «ЛИБРОКОМ», 2012. 17. Липский, В. Комбинаторика для программистов / В. Липский. – М.: Мир, 1988. 18. Макконелл, Д. Анализ алгоритмов. Вводный курс / Д. Макконелл. – М.: Техносфера, 2002. 19. Новиков, Ф. А. Дискретная математика для программистов / Ф. А. Новиков. – СПб.: Питер, 2008. 20. Пападимитриу, Х. Комбинаторная оптимизация / Х. Пападимитриу, К. Стайглиц. – М.: Мир, 1985. 21. Рейнгольд, Э. Комбинаторные алгоритмы. Теория и практика / Э. Рейнгольд, Ю. Нивергельт, Н. Део. – М.: Мир, 1980. 22. Рыбников, К. Введение в комбинаторный анализ / К. Рыбников. – М.: Изд-во Моск. ун-та, 1985. 23. Ху, Т. Комбинаторные алгоритмы / Т. Ху, М. Шинг. – Нижний Новгород: Изд-во Нижегород. ун-та им. Н. И. Лобачевского, 2004. 24. Юдин, Д. Б. Математики измеряют сложность / Д. Б. Юдин, А. Д. Юдин. – М.: Книжный дом «ЛИБРОКОМ», 2009. 25. Яблонский, С. В. Введение в дискретную математику: учеб. пособие / С. В. Яблонский. – М.: Наука, 1986. 121 Приложение 1 АЛГОРИТМЫ И СЛОЖНОСТЬ П.1. Алгоритмы и задачи Понятие алгоритма, подобно многим фундаментальным математическим понятиям, в прикладных разделах математики кроме точного определения используется и в неформальной трактовке. Многие компьютерные науки (теория автоматов, теория кодирования, математическая лингвистика, теория распознавания образов) в основном оперируют интуитивным определением алгоритма, основанным на здравом смысле и практическом опыте. С неформальной точки зрения под алгоритмом традиционно понимается точное предписание, определяющее вычислительный процесс, идущий от варьируемых исходных данных (называемых входом алгоритма) к искомому результату (выходу алгоритма). Современное толкование понятия алгоритма очень схоже со значением слов «метод», «способ», «процедура», «программа», но имеет свой дополнительный смысловой оттенок. Уточнение смысла выливается в постулирование ряда свойств, которыми должен обладать любой алгоритм. Укажем наиболее важные из них, раскрывающие интуитивную трактовку алгоритма. Каждый алгоритм имеет дело с данными (исходными, промежуточными, результирующими). Для размещения данных алгоритму необходима память. Память считается однородной и дискретной, т. е. состоящей из одинаковых ячеек. Описание алгоритма должно быть конечным и дискретным, т. е. состоять из конечного числа отдельных шагов, которые выполняются в дискретные моменты времени. Каждый шаг должен быть элементарным. Элементарность шага предполагает, что выполняемый объем работы на шаге мажорируется некоторой константой, зависящей от исполнителя, но не зависящей от исходных и промежуточных данных. 122 Последовательность шагов алгоритма детерминирована: после выполнения очередного шага однозначно определено, что делать на следующем шаге. Алгоритм должен быть результативным: для заданного набора исходных данных последовательность шагов обязана приводить к выдаче вполне определенного результата. Конечность действий алгоритма: для достижения результата алгоритм должен выполнить конечное число шагов, при этом требуемое количество шагов зависит только от исходных данных. Алгоритм должен быть единым для всех допустимых исходных данных, т. е. удовлетворять требованию массовости. Алгоритм предполагает наличие исполнителя или механизма реализации, который по описанию алгоритма порождает вычислительный процесс. Этот процесс разворачивается во времени и сводится к пошаговому преобразованию исходных данных в результирующие. В качестве данных, с которыми работает алгоритм, могут выступать самые разные объекты, построенные из элементов некоторого множества по определенным правилам, например, числа, тексты, матрицы, графы, изображения. В любом случае их всегда можно представить словами в конечном алфавите. Это универсальный способ машинного представления данных, а число символов в слове – естественная мера объема этих данных. Так, конечное множество и различные его подмножества традиционно задаются битовыми шкалами − характеристическими (0, 1)-векторами. Всякое изображение может быть закодировано массивом пикселей, а каждый пиксель – тремя числами, определяющими интенсивность красного, зеленого и синего цветов. Очевидно, что какими бы ни были алфавиты, их символы всегда можно закодировать с помощью двоичного алфавита. Алгоритмы разрабатываются для решения тех или иных вычислительных задач. Наличие алгоритма для задачи – это, прежде всего, возможность запрограммировать и реализовать на компьютере процесс ее решения. Под задачей принято понимать некоторый вопрос, на который нужно найти (вычислить) ответ. Различают массовые и индивидуальные задачи. Массовая задача Π задается 123 – списком параметров – свободных переменных, конкретные значения которых не определены, но указаны требования, которым они удовлетворяют; – формулировкой условий – свойств, которыми должен обладать ответ (решение задачи). Индивидуальная задача Ι получается из массовой задачи Π, если всем параметрам массовой задачи придать конкретные значения – задать исходные данные. Величина n = | Ι | характеризует размерность индивидуальной задачи. Для алгоритма, решающего задачу Ι, при условии, что исходные данные представлены в некотором алфавите, величину n = | Ι | принято называть длиной входа. Словосочетание «решить задачу» допускает различные толкования. Тем не менее, в математике и компьютерных науках используется следующая алгоритмическая позиция: задача может быть решена, если построен алгоритм, приводящий к решению задачи. К сожалению, для решения некоторых задач не существует алгоритма. Их называют алгоритмически неразрешимыми. Задачи, для которых разработаны алгоритмы решения, называют алгоритмически разрешимыми. Для установления алгоритмической разрешимости задачи достаточно найти и описать разрешающий алгоритм − алгоритм, который дает решение этой задачи. Здесь можно обойтись интуитивным представлением об алгоритме. Между тем установление алгоритмической неразрешимости задачи требует введения строгого определения. По форме постановки различают распознавательные, оптимизационные задачи и задачи на построение. Суть распознавательной задачи сводится к проверке некоторого свойства. Например, в задаче о распознавании однозначного декодирования (см. задание 8 в главе 3) необходимо ответить на вопрос, допускает ли заданный алфавитный код однозначное декодирование, т. е. всякий ли код сообщения является кодом ровно одного сообщения. Алгоритм решения задачи распознавания выполняет проверку рассматриваемого свойства и выдает один из двух ответов: «да» (свойство выполняется), «нет» (свойство не выполняется). Поэтому распознавательные задачи часто называют задачами с коротким ответом. В задаче оптимизации надо найти такое решение, которое отвечает оптимальному значению некоторой целевой функции. Примерами задач 124 оптимизации, рассмотренных в данном учебном пособии, являются квадратичная задача о назначениях (см. задание 3 в главе 1), задача о минимальном остове, задача о кратчайшем пути в графе (см. задания 6, 7 в главе 2), задача оптимального кодирования (см. задание 9 в главе 3). Алгоритм решения задачи оптимизации возвращает оптимальное решение, если оно существует, значение целевой функции, соответствующее оптимальному решению, и ответ «нет», если решение задачи отсутствует. Некоторые задачи изначально имеют распознавательную формулировку. Многие задачи, которые в исходной постановке представлены как задачи оптимизации, довольно просто приводятся к распознавательной форме. Предположим, что есть некоторая задача максимизации, в которой необходимо найти максимальное значение какой-либо числовой характеристики заданного графа G, например, мощность клики – множества вершин графа, попарно смежных между собой. Тогда соответствующая задача распознавания может быть сформулирована следующим образом: для графа G и целого числа L выяснить, имеется ли решение со значением числовой характеристики, по крайней мере, L. Между тем имеются задачи, которые нельзя привести к распознавательной форме. Это, в первую очередь, задачи на построение. Примерами таких задач являются перечисление всех простейших комбинаторных объектов (см. задания 2, 3 в главе 1), построение остовного дерева и многие другие подобные задачи на графах, основанные на систематическом просмотре элементов графа (см. задание 5 в главе 2). П.2. Сложность алгоритма Целью анализа алгоритма является оценка его качества. В анализе алгоритмов применяют различные критерии качества в виде количественных характеристик алгоритма. Сложность − одна из важнейших характеристик алгоритма. Существуют различные трактовки этого понятия [1−4, 7, 10, 18, 24]. Укажем основные. Под логической сложностью алгоритма понимают трудоемкость его разработки, выраженную, например, числом человекомесяцев, потраченным на создание алгоритма. Очевидно, что такая оценка качества алгоритма не всегда объективна, ведь время написания алгоритма сильно зависит от профессионализма разработчика. 125 Статическая сложность алгоритма отражает длину записи алгоритма в некоторой фиксированной форме. Типичным образцом статической сложности является длина описания алгоритма, задаваемая количеством операторов использованного языка программирования. Эта оценка качества также не лишена недостатка, поскольку короткий алгоритм может работать неприемлемо долго. Экономичность функционирования является наиболее приемлемым и практически значимым критерием качества алгоритма. Этот критерий называют вычислительной (или ресурсной) сложностью алгоритма. В анализе алгоритмов используется главным образом именно этот критерий качества. Вычислительная сложность характеризует потребность алгоритма в ресурсах (времени исполнения и объеме памяти), нужных для реализации предписанных вычислений. Соответственно различают временную и ёмкостную сложности алгоритма. Следует заметить, что в реальных алгоритмах временная и ёмкостная сложности, как правило, зависимы: сокращение одной из них достигается за счет возрастания другой. И все же наиболее важной из них является временная сложность, так как именно она определяет временные границы разрешимости задачи и позволяет судить о практической значимости алгоритма. Под временной сложностью алгоритма понимают время его выполнения, измеряемое в элементарных шагах, необходимых для достижения запланированного результата. Число шагов − это математическое время выполнения алгоритма, которое задает физическое время с точностью до константы. Емкостная сложность алгоритма определяется количеством условных единиц памяти (например, машинных слов или просто бит), используемых алгоритмом в процессе вычислений. Чтобы сделать выводы относительно вычислительной сложности алгоритма в достаточной мере универсальными, предполагают, что алгоритм выполняется на некотором абстрактном компьютере, который включает в себя процессор, одноадресную оперативную память и набор элементарных операций. Такой компьютер был назван равнодоступной адресной машиной (РАМ) [1, 3]. Полагается, что РАМ в состоянии выполнять арифметические операции, операции сравнения, пересылки, условной и безусловной передачи управления. Подобные операции считаются элементарными и выполняемыми за константное время. Их можно рассматривать в качестве шагов алгоритма. 126 Память РАМ состоит из неограниченного числа ячеек, к которым имеется прямой доступ. В ячейке может храниться бит, число, символ или другая единица информации. Итак, применительно к РАМ одна элементарная операция – условная единица времени, а одна ячейка – условная единица памяти. Общее число шагов, необходимое РАМ для решения задачи в соответствии с заданным алгоритмом, определяет временную сложность алгоритма, а использованное при этом число ячеек памяти – емкостную сложность. Одно из основных свойств алгоритма – массовость – применимость к некоторому допустимому множеству исходных данных. Разумеется, что в общем случае число шагов алгоритма и требуемая память зависят, прежде всего, от исходных данных. Эти зависимости принято описывать с помощью функций. В современных публикациях по анализу алгоритмов их называют функциями временной и емкостной сложности алгоритма и обозначают через t(n), v(n), где n − целое положительное число, характеризующее длину входа алгоритма. Покажем на простых примерах, как формируются функции временной и емкостной сложности алгоритма. Пример П.1. Рассмотрим задачу о кратчайшем пути в графе G = (V, E). Для решения данной задачи можно предложить переборный алгоритм, который из конечного числа всех возможных путей между двумя заданными вершинами находит кратчайший. Такой алгоритм при небольшом числе вершин может оказаться не хуже (с точки зрения времени выполнения), чем любой классический алгоритм, например, алгоритм Дейкстры или алгоритм Флойда (см. задание 7 в главе 2). Если число вершин начать увеличивать, то классические алгоритмы начнут работать заведомо быстрее переборного алгоритма. Габариты исходного графа G = (V, E) в общем случае описываются числами n = ⎢V ⎢ и m = ⎢E ⎢. При этом различные алгоритмы могут оперировать всевозможными способами представления графа (матрицей смежности, матрицей инциденций, списками смежности и др.). Несмотря на это, во всех случаях длина входа алгоритма определяется значениями n и m, а время работы алгоритма напрямую зависит от значений этих чисел. Известно, что в полном графе числа n и m связаны между собой формулой m = n (n – 1) / 2, которую часто используют для перехода к одному показателю измерения длины входа алгоритма. 127 Пример П.2. Требуется сложить две прямоугольные (n × m)матрицы X, Y вещественных чисел и результат записать в матрицу Z. Пусть для хранения каждого элемента матриц X, Y и Z достаточно одной условной единицы памяти. Очевидный алгоритм решения данной задачи включает в себя два вложенных друг в друга цикла: 1. Цикл по i от 1 до n 2. Цикл по j от 1 до m 3. Z[i, j]:= X[i, j] + Y[i, j] На шаге 3 выполняются две операции: сложение и присваивание. Этот шаг повторяется для всякого i и каждого j. Переменная i принимает n значений, а переменная j получает m значений. На их изменение требуется 3m + 1 и 3n + 1 операций соответственно: вначале установление наименьшего значения 1, затем увеличение на 1 и последующее сравнение с наибольшим значением. Таким образом, время выполнения алгоритма можно выразить в виде следующей функции: t(n, m) = n(2m + 3m + 1) + (3n + 1) = 5nm +4n + 1. Очевидно, что размер матриц в данном случае задает количество поступающих на вход алгоритма данных и обусловливает тем самым длину входа алгоритма. Можно вместо двух параметров n и m использовать один, положив N = max {n, m}. Тогда t(N) = 5N 2 + 4N + 1. Емкостная сложность алгоритма подсчитывается так: если для хранения одного элемента матрицы достаточно одной условной единицы памяти, то v(n, m) = 3nm, или v(N) = 3N 2. Поскольку анализ емкостной сложности алгоритмов не столь актуален в силу технических возможностей современных компьютеров, то в центре внимания обычно функция временной сложности t(n). При формировании t(n) возможна ориентация на один из случаев: – худший случай, когда t(n) характеризует максимально возможное время работы алгоритма – время работы на «плохих» входах; – лучший случай, когда t(n) задает минимальное время выполнения алгоритма – время работы при некоторых «хороших» исходных данных; – средний случай, когда t(n) учитывает вероятности появления тех или иных («хороших» или «плохих») входных данных. 128 С практической точки зрения чрезвычайно важен худший случай, поскольку он позволяет оценивать потенциальные возможности анализируемого алгоритма. Поэтому традиционно ориентируются именно на этот случай. Имеется еще одна концептуальная позиция к определению функций сложности. Обычно рассматривают поведение t(n) в асимптотике при n → ∞. Получаемые при этом оценки задают t(n) главный порядок (порядок роста) функции временной сложности, скрывая при этом значения коэффициентов и аддитивные компоненты неглавных порядков. Применение асимптотических оценок вызвано несколькими причинами. Во-первых, многие практические задачи имеют большую размерность. Во-вторых, на практике, как правило, нет необходимости в точном построении выражения для функции сложности, что зачастую и выполнить непросто. В-третьих, переход от физического времени и реальных компьютеров к математическому времени и РАМ уже вносит ошибку в определение сложности алгоритма. Стоимость этого перехода – константа пропорциональности. Математические методы, применяемые в анализе алгоритмов, требуют уточнения свойств функций t(n). Принято полагать, что t(n) – монотонно возрастающие функции, областью значений которых выступает множество неотрицательных вещественных чисел, а областью определения – множество неотрицательных целых чисел Z+. Таким образом, t(n) ≥ 0 для всех n ∈ Z+. Эти предположения вытекают из природы t(n). Что касается свойства монотонности, то разумно считать, что увеличение объема исходных данных вызывает повышение времени работы алгоритма. Закономерен вопрос: существуют ли алгоритмы, для которых нарушается условие монотонности функции t(n). Да, существуют. Но это скорее исключение, чем правило. П.3. Асимптотические формализмы, используемые в анализе алгоритмов При анализе алгоритмов используют принятые в математике асимптотические обозначения, позволяющие выделить главный порядок функции [9, 13]. 129 Пусть u(n) и w(n) − это неотрицательные вещественнозначные функции, определенные на множестве Z+ и являющиеся при n → ∞ бесконечно большими величинами. При этом функция w(n) ≠ 0, начиная с некоторого возможно большого значения n0. Суть асимптотического оценивания состоит в изучении интересующей нас функции u(n) относительно другой (более простой по виду) функции w(n) путем сравнения их отношения: u ( n) . w( n) (П.1) Функцию u(n) определяют как о[w(n)] и говорят, что она при больших значениях n меньше функции w(n) по порядку роста, если lim n→∞ u (n) = 0. w( n ) (П.2) В этом случае пишут u(n) = o [w(n)], или u(n) w(n). Принято считать, что функция u(n) эквивалентна (или асимптотически равна) функции w(n), и пишут u(n) w(n), если lim n→∞ u (n) = 1. w( n ) Полагают, что функция u(n) не больше функции w(n) по порядку роста, и пишут u(n) = O [w(n)], или u(n) w(n), если существует вещественная константа с1 > 0 и неотрицательное целое число n0, такие, что для всех n ≥ n0 выполняется неравенство: u (n) ≤ c1 . w( n ) (П.3) Эту оценку называют асимптотической верхней оценкой для u(n). Подобным образом определяют асимптотическую нижнюю оценку для функции u(n): запись u(n) = Ω [w(n)], или w(n) u(n), верна, если отношение (П.1) ограничено снизу, т. е. существует вещественная константа с2 > 0 и неотрицательное целое число n0, такие, что для всех n ≥ n0 выполняется неравенство: 130 c2 ≤ u ( n) . w( n ) (П.4) Если неравенства (П.3), (П.4) для всех n ≥ n0 верны одновременно c2 ≤ u (n) ≤ c1 , c1 > 0, c 2 > 0, w( n ) (П.5) то пишут u(n) = Θ [w(n)], или u(n) w(n), и считают, что функция u(n) равна w(n) по порядку роста. Эту оценку называют асимптотически точной оценкой для u(n). Заметим, что если отношение (П.1) имеет конечный и отличный от нуля предел при n → ∞, т. е. существует вещественная константа с > 0, такая, что lim n→∞ u (n) = c, w( n ) (П.6) то неравенства (П.5) справедливы и u(n) = Θ [w(n)]. Этот факт, как и определение o-малое через предел (П.2), дает возможность применить в анализе алгоритмов аппарат математического анализа для вычисления пределов (например, правило Лопиталя и формулу Стирлинга). Пример П.3. Пусть u(n) = 3n2 + 7n + 2. Тогда u(n) = о (n3), поскольку предел lim n→∞ 3n 2 + 7 n + 2 =0 n3 равен нулю. Подобным образом можно показать, что u(n) = о (n2 ln n), u(n) = о (2n). Кроме того, u(n) = Θ (n2), так как предел lim n→ ∞ 3n 2 + 7 n + 2 =3 n2 равен константе. Отсюда u(n) = O (n2) и u(n) = Ω (n2). Используя введенные определения асимптотических обозначений, нетрудно доказать справедливость свойств, сформулированных в следующих утверждениях. 131 Утверждение П.1. Для любых функций u(n), w(n) и h(n), не обращающихся в 0 хотя бы при n ≥ n0, справедливы следующие свойства асимптотических оценок. 1) Рефлексивность всех оценок, за исключением о-малое: u(n) = Θ [u(n)], u(n) = O [u(n)], u(n) = Ω [u(n)]. 2) Симметричность асимптотически точной оценки: если u(n) = Θ [w(n)], то w(n) = Θ [u(n)]. 3) Транзитивность всех оценок: если u(n) = o [w(n)] и w(n) = o [h(n)], то u(n) = o [h(n)]; если u(n) = Θ [w(n)] и w(n) = Θ [h(n)], то u(n) = Θ [h(n)]; если u(n) = O [w(n)] и w(n) = O [h(n)], то u(n) = O [h(n)]; если u(n) = Ω [w(n)] и w(n) = Ω [h(n)], то u(n) = Ω [h(n)]. Утверждение П.2. Для любых двух функций u(n) и w(n), не обращающихся в 0 хотя бы при n ≥ n0, справедливы высказывания. 1) Если u(n) = o [w(n)], то u(n) = O [w(n)] (o-малое влечет Oбольшое), но обратное не всегда верно. 2) u(n) = Θ[w(n)] тогда и только тогда, когда u(n) = O [w(n)] и u(n) = Ω [w(n)] (Θ-оценка влечет O-оценку и Ω-оценку). 3) Если u(n) = O [w(n)], то w(n) = Ω [u(n)], и наоборот, если u(n) = Ω [w(n)], то w(n) = O [u(n)]. Утверждение П.3. Для любых функций, не обращающихся в 0 хотя бы при n ≥ n0, справедливы высказывания. 1) Правило суммы: если u(n) = Θ [w1(n)], h(n) = Θ [w2(n)] и w2(n) ≼ w1(n), то u(n) + h(n) = Θ [w1(n)]. 2) Правило произведения: если u(n) = Θ [w1(n)], h(n) = Θ [w2(n)], то u(n) ⋅ h(n) = Θ [w1(n) ⋅ w2(n)]. Правила 1 и 2 верны и для О-оценки. В анализе алгоритмов принято сравнивать алгоритмы между собой по порядку роста их функций сложности. Пусть t1(n), t2(n) – функции временной сложности алгоритмов A1 и A2 соответственно. Считают, что алгоритм A1 асимптотически быстрее алгоритма A2, если t1(n) t2(n), т. е. t1(n) = o [t2(n)]. 132 Алгоритм A1 асимптотически не медленнее алгоритма A2, если t1(n) t2(n), т. е. t1(n) = O [t2(n)]. При t2(n) t1(n), т. е. когда t1(n) = Ω [t2(n)], алгоритм A1 асимптотически не быстрее алгоритма A2. Алгоритмы A1, A2 асимптотически равнозначны по времени выполнения, если t1(n) t2(n), т. е. t1(n) = Θ [t2(n)]. При t(n) = Θ (1) алгоритм сводится к выполнению постоянного числа элементарных операций. Время выполнения такого алгоритма ограничено сверху константой, не зависящей от объема обрабатываемых данных. При анализе конкретного алгоритма всегда стремятся получить асимптотически точную Θ-оценку его функции сложности. Если это не удается, то ограничиваются O-оценкой. В обширной литературе по анализу алгоритмов O-оценка наиболее употребительная. Эта оценка позволяет «похвалить» исследуемый алгоритм, указав, что он работает не медленнее, чем некоторый другой алгоритм. Замечания 1. С позиции асимптотического подхода шаг алгоритма элементарный, если он состоит из конечного числа операций, выполняемых за время Θ (1). К таким операциям традиционно относят: – арифметические операции (+, –, ×, /), осуществляемые над постоянным числом (обычно 2) операндов фиксированного размера; – операции присваивания, выполняемые над одним операндом фиксированного размера; – операции сравнения (<, ≤, >, ≥, =, ≠), применяемые к двум операндам фиксированного размера; – логические операции (AND, OR, NOT), осуществляемые над постоянным числом операндов фиксированного размера; – побитовые (поразрядные) операции, выполняемые над постоянным числом операндов фиксированного размера; – операции ввода / вывода, используемые для чтения или записи постоянного числа элементов данных фиксированного размера; – условные операции ветвления, когда проверяемое условие представлено логическим выражением над постоянным числом операндов фиксированного размера; – условные операции ветвления. 133 2. При оценке времени выполнения элементарных функций следует проявлять осторожность. Например, если значение функции sin ϕ надо вычислить при небольшом значении ϕ, то предположение о времени Θ (1), необходимом для реализации этой операции, разумно. Однако при очень больших значениях | ϕ | для нахождения значения sin ϕ потребуется цикл, число повторений которого будет зависеть от | ϕ |. 3. Операнд фиксированного размера – это операнд, для хранения которого достаточно Θ (1) памяти. Пример П.4. Рассмотрим пример, иллюстрирующий сравнение двух алгоритмов решения одной и той же задачи. Пусть требуется для заданного значения x = b вычислить значение полинома P(x) = an xn + an – 1 xn – 1 + … + a2 x2 + a1 x + a0. Исходными данными этой задачи являются значение b и значения коэффициентов полинома an, an – 1, …, a1, a0. Пусть для хранения каждого коэффициента и значения b достаточно одной условной единицы памяти. Проанализируем алгоритм A1 вычисления значения P(b) подстановкой x = b в P(x) и алгоритм A2 вычисления того же значения при помощи схемы Горнера. Считаем, что длина входа этих алгоритмов определяется значением n (точнее, равна n′ = n + 2). Выразим функции временной сложности этих алгоритмов через n. Для нахождения ai bi надо выполнить i операций умножения. Общее число операций умножения, которые выполняет алгоритм A1, определяет формула n + (n − 1) + ... + 2 + 1 = n(n + 1) . 2 Для вычисления суммы n + 1 слагаемых требуется выполнить n операций сложения. Таким образом, общее число операций алгоритма A1 равно t 1 ( n) = n(n + 1) n(n + 3) +n = = Θ(n 2 ). 2 2 Для вычисления значения P(b) по схеме Горнера P(b) = b(b(…(b(an b + an – 1) + an – 2) + … + a2) + a1) + a0 134 (П.7) достаточно выполнить n операций умножения и n операций сложения, так что общее число операций для алгоритма A2 составит t2(n) = 2n = Θ(n). (П.8) Таким образом, алгоритм A2 асимптотически быстрее алгоритма A1, поскольку lim n →∞ t 2 ( n) 2n 4 = lim = lim = 0, t1 (n) n→∞ n(n + 3) / 2 n→∞ n + 3 т. е. t2(n) t1(n). Заметим, что согласно утверждению П.3 переход от n к n′ не изменяет оценки (П.7), (П.8) и результат сравнительного анализа алгоритмов A1 и A2. Далее приведем известные оценки, которые часто используют в анализе алгоритмов [13]. Здесь i, n, k ∈ Z+. Оценки формул суммирования: n ∑i = i=1 n ∑i = 2 i=1 n(n + 1)(2n + 1) = Θ (n 3 ); 6 n 2 (n + 1) 2 = Θ (n 4 ); 4 n ∑i3 = i=1 n ∑k k n +1 − 1 = Θ (k n ), k ≠ 1; k −1 = i n(n + 1) = Θ (n2 ); 2 i=0 n ∑2 i = 2n +1 − 1 = Θ (2n ); i=0 n ∑ i2 i = (n − 1)2n +1 + 2 = Θ (n ⋅ 2n ); i=1 n ∑ i=1 1 = ln n + Θ (1); i 135 n n 1 ∑ i(i + 1) = n + 1 = Θ (1). i=1 Оценки комбинаторных чисел: n ⎛n⎞ n! = 2πn ⎜ ⎟ (1 + Θ (1 / n)), формула Стирлинга; ⎝e⎠ n! = o (n n ); C nk = ⎛ n! n nn = Θ⎜ ⋅ k ⎜ 2πk (n − k ) k (n − k ) n −k k! (n − k )! ⎝ ⎞ ⎟; ⎟ ⎠ ⎛ 2n ⎞ ⎟⎟, для четного n. Cnn 2 = Θ ⎜⎜ ⎝ πn / 2 ⎠ Оценки для экспоненты и логарифма: e n ≥ 1 + n; n ≤ ln (1 + n) ≤ n. 1+ n П.4. Полиномиальные и экспоненциальные алгоритмы Традиционно для классификации алгоритмов используют асимптотический подход, а в качестве сложностного класса рассматривают множество алгоритмов с характерным порядком роста их функций сложности. Выделяют два сложностных класса: низкозатратные (полиномиальные) алгоритмы и высокозатратные (экспоненциальные) алгоритмы. Алгоритм называют полиномиальным, если при длине входа n время его работы t(n) nc, т. е. t(n) = O (nc), где c – неотрицательное вещественное число. Значит, вычислительная сложность полиномиальных алгоритмов ограничена полиномом, быть может, очень большой, но конечной степени. 136 Полиномиальные алгоритмы принято считать эффективными, поскольку на практике они обычно работают довольно быстро. Между тем, если степень полинома значительная, то и полиномиальный алгоритм может работать неприемлемо долго. Поэтому при решении практических задач всегда отдается предпочтение полиномиальному алгоритму меньшей степени. Поскольку сумма, произведение и композиция полиномов есть снова полином, то классу полиномиальных алгоритмов присуще свойство замкнутости: – можно комбинировать полиномиальные алгоритмы (один полиномиальный алгоритм может вызывать другой полиномиальный алгоритм в качестве «подпрограммы»), при этом в целом время работы алгоритма остается полиномиальным; – можно выполнять полиномиальные преобразования входа алгоритма (например, переходить от одного алфавита к другому), оставаясь в границах полиномиальных затрат на вычисления. Второй класс алгоритмов – это алгоритмы, функция сложности которых растет быстрее любого полинома: для любого неотрицательного вещественного числа c всегда nc t(n). Такие алгоритмы принято называть экспоненциальными (или неполиномиальными). Строго говоря, функция сложности экспоненциальных алгоритмов может отличаться от экспоненты. Например, алгоритм с функцией сложности t(n) = Θ (n!) считают экспоненциальным, учитывая, что nc eλn n!, c > 0, λ > 0. Ясно, что при n → ∞ никакое быстродействие современных компьютеров пока не в силах справиться за реальное время с подобной экспоненциальной сложностью алгоритма. Что касается емкостной сложности, то с прикладной точки зрения наибольший интерес представляют полиномиальные оценки от длины входа. Ясно, что если алгоритм полиномиальный по времени, то 137 он так же является полиномиальным и по памяти: короткое вычисление не может использовать слишком большую память. Для установления принадлежности функции сложности алгоритма к одному из двух указанных сложностных классов полезны следующие две леммы, хорошо известные в математическом анализе [9]. Лемма П.1 (о логарифмическом пределе). Пусть lim u ( n) = ∞, n→∞ Тогда если ln u(n) lim w( n) = ∞. n→∞ ln w(n), то u(n) w(n). Лемма П.2. Для произвольных вещественных положительных констант ξ1, ξ2, ξ3, τ1, τ2, τ3 отношение n ξ1 (ln n) ξ 2 (ln ln n) ξ 3 ≺ n τ1 (ln n) τ 2 (ln ln n) τ 3 справедливо, если и только если ξ1 < τ1, или если ξ1 = τ1, ξ 2 < τ2, или если ξ1 = τ1, ξ2 = τ2, ξ3 < τ3. Пример П.5. Покажем, что алгоритм с функцией временной сложности t (n) = e a n ξ (ln n )1 − ξ , a > 0, 0 < ξ < 1 является экспоненциальным. Действительно, для произвольных констант a > 0, 0 < ξ < 1, λ > 0 всегда (1 − ξ) > 0 и справедливы пределы lim n →∞ ln( n c ) ln( e a n ξ 1− ξ (ln n ) ) = lim n →∞ c ln n c (ln n ) ξ = lim = 0. 1−ξ n →∞ an (ln n ) an ξ ξ Следовательно, по лемме П.1 n c ≺ t (n ). Это означает, что рассматриваемый алгоритм экспоненциальный по времени выполнения. 138 П.5. Немного о сложности задач В общем случае для алгоритмически разрешимой задачи может существовать несколько алгоритмов. С практической точки зрения важно не просто их существование, а поиск среди них наиболее эффективного. Под сложностью задачи принято понимать минимальную из сложностей алгоритмов, решающих эту задачу. Обширная алгоритмическая практика свидетельствует, что для некоторых задач удается построить эффективные алгоритмы − алгоритмы полиномиальной сложности. Такие задачи называют полиномиально разрешимыми (или эффективно разрешимыми). Для многих практически важных задач до сих пор эффективные алгоритмы не найдены, но и не доказана невозможность их существования. Их принято называть трудноразрешимыми (или труднорешаемыми). Для решения трудноразрешимых задач используют алгоритмы неполиномиальной сложности. Большинство таких алгоритмов – это варианты полного перебора. К классу трудноразрешимых задач относится большое число задач алгебры, математической логики, теории графов, теории автоматов и прочих разделов дискретной математики. В книге М. Гэри и Д. Джонсона [10] приведен список, включающий более чем 300 трудноразрешимых задач, а в настоящее время их известно в несколько раз больше. Возникает вопрос: неудача в нахождении для трудноразрешимой задачи полиномиального алгоритма является следствием неумения разработчиков или результатом особого свойства задачи. Ответ на этот вопрос дает теория сложности вычислений: трудноразрешимость – это свойство самой задачи. Приведенное здесь разбиение множества задач на полиномиально разрешимые и трудноразрешимые не является математически точным. В самом деле, используемое словосочетание «алгоритм не удается найти» допускает различное толкование: «алгоритм не существует вообще» или «алгоритм существует, но пока не найден». Формальное разделение задач на классы по достижимой эффективности их решения осуществлено только применительно к распознавательным задачам. При этом вместо РАМ использовано точное определение алгоритма в виде машины Тьюринга [1, 15]. Определены два основных класса сложности задач P и NP. 139 Класс P (Deterministic Polynomial, детерминированная полиномиальная сложность) – множество распознавательных задач, для которых существует алгоритм с полиномиальным временем работы. Существование такого алгоритма подразумевает наличие детерминированной машины Тьюринга полиномиальной временной сложности. Класс NP (Nondeterministic Polynomial, недетерминированная полиномиальная сложность) − множество распознавательных задач, которые могут быть решены за полиномиальное время с помощью недетерминированного алгоритма, т. е. алгоритма, представляемого недетерминированной машиной Тьюринга. В детерминированной машине Тьюринга вычислительный процесс не зависит от случайных факторов и всегда развивается по одной траектории, определяемой исходными данными. Поведение недетерминированной машины Тьюринга также не зависит от случайных факторов. В ней процесс вычислений распадается на два этапа: первый этап состоит в угадывании варианта решения, а второй – в проверке (верификации) решения, предложенного некоторым угадывающим устройством. Для задач класса NP оба этапа должны быть реализуемы за полиномиальное время. Разница между классом P и NP состоит в том, что в первом случае у нас имеются разрешающие полиномиальные алгоритмы, а во втором случае мы таких алгоритмов не знаем. Зато знаем, как за полиномиальное время проверить, удовлетворяет ли готовое решение условиям задачи. Поэтому задачи класса NP называют также полиномиально проверяемыми. Таким образом, интуитивно класс P можно представить себе как множество задач, которые можно «быстро» (за полиномиальное время) решить, а класс NP – множество задач, решения которых можно «быстро» проверить. Понятно, что решить задачу намного труднее, чем проверить готовое решение. Поэтому очевидно, что P ⊆ NP. К настоящему времени пока не удалось строго доказать или опровергнуть более сильные соотношения: P = NP, P ⊂ NP. Однако большинство специалистов полагают, что верно строгое включение: P ⊂ NP. Основанием для этого является существование в классе NP универсальных задач, которые названы NPполными. Универсальность этих задач состоит в том, что построение полиномиального алгоритма для любой такой задачи повлечет за собой возможность построения полиномиального алгоритма для всех остальных 140 задач класса NP. Заметим, что класс NP-полных задач обычно обозначают через NPC (NP-Complete). Приведем примеры некоторых задач из теории графов, для которых установлена принадлежность к классам P или NPC. Пример П.6. Рассмотрим распознавательный вариант оптимизационной задачи о кратчайшем пути в графе. КРАТЧАЙШИЙ ПУТЬ Условие. Заданы граф G = (V, E) с двумя выделенными вершинами s, t ∈ V, длины всех ребер w(e) ∈ Z+ и положительное целое число L. Вопрос. Существует ли в G путь от s до t, длина которого не превосходит L. Для решения данной задачи существует много полиномиальных алгоритмов. Наиболее известные из них алгоритм Дейкстры и алгоритм Флойда рассмотрены в задании 7 настоящего пособия. Таким образом, задача КРАТЧАЙШИЙ ПУТЬ принадлежит классу P. Между тем эта задача − частный случай следующей NP-полной задачи [10]. K КРАТЧАЙШИХ ПУТЕЙ Условие. Заданы граф G = (V, E) с двумя выделенными вершинами s, t ∈ V, длины всех ребер w(e) ∈ Z+ и положительные целые числа L и K. Вопрос. Существует ли в G не менее K различных путей от s до t, общая длина которых не превосходит L. Пример П.7. Полиномиально разрешима следующая задача. МИНИМАЛЬНЫЙ ОСТОВ Условие. Заданы граф G = (V, E), веса всех ребер w(e) ∈ Z+ и положительное целое число L. Вопрос. Существует ли в G остовное дерево, вес которого не превосходит L. Наиболее популярные полиномиальные алгоритмы решения этой задачи: алгоритм Прима и алгоритм Краскала (см. задание 6 в главе 2). Задача МИНИМАЛЬНЫЙ ОСТОВ − частный случай следующей задачи из класса NPC [10]. 141 K НАИЛУЧШИХ ОСТОВА Условие. Заданы граф G = (V, E), веса всех ребер w(e) ∈ Z+ и положительные целые числа L и K. Вопрос. Существует ли в G ровно K остовных дерева, общий вес которых не превосходит L. Другая задача, родственная с задачей МИНИМАЛЬНЫЙ ОСТОВ и принадлежащая NPC, формулируется так. ОСТОВ ОГРАНИЧЕННОЙ СТЕПЕНИ Условие. Заданы граф G = (V, E) и положительное целое число L. Вопрос. Существует ли в G остовное дерево, у которого степени всех вершин не превосходят L. Трудноразрешимые задачи не ограничиваются классом NPC. Определены и другие классы трудноразрешимых задач. Например, класс PSPACE. В него входят задачи, которые могут быть решены с помощью алгоритма, использующего полиномиальный относительно длины входа объем памяти (время может быть неполиномиальным). Несмотря на то, что PSPACE очевидным образом включает NP, пока не установлено, равны ли эти классы. Класс PSPACE, а также другие классы сложности трудноразрешимых задач описаны в [10]. Существует много трудноразрешимых задач, имеющих огромную практическую значимость. Поэтому специалисты в области математики и компьютерных наук в течение нескольких десятилетий пытаются продвинуться в их решении. На сегодняшний день здесь применяются следующие основные подходы [20]: – выделение полиномиально разрешимых подклассов экземпляров задач в их общей формулировке; – улучшение переборных алгоритмов за счет «разумной» организации стратегии перебора; – параметризированный подход; – нахождение приближенных решений. Первый подход основан на очевидном утверждении: чем более общей является задача, тем труднее ее решить. Он предполагает сужение NP-полной задачи с целью выделения легких частных случаев. Математически это выражается в нахождении достаточных условий существования решения задачи и полиномиальной сложности их 142 проверки. Продуктивность этого подхода сильно зависит от решаемой задачи. Многочисленные примеры показывают, что сужение задачи не всегда позволяет избавиться от NP-полноты. Сокращение перебора – наиболее широко применяемый подход решения трудноразрешимых задач. Реализуется он в основном с помощью методов декомпозиции. Например, таким методом является метод ветвей и границ, суть которого состоит в построении частичных решений, представленных деревом решений, и применении оценок, позволяющих опознать бесперспективные частичные решения [20]. Для ряда задач весьма результативно комплексное применение метода ветвей и границ и других декомпозиционных инструментов, таких как метод динамического программирования. Параметризированный подход направлен на получение точных решений трудноразрешимых задач. Основная идея параметризированного подхода состоит в том, чтобы с помощью некоторого числового параметра структурировать и измерить конечное пространство поиска. Роль параметра – выделить основной источник неполиномиальной сложности задачи и показать, «комбинаторный взрыв» какой величины можно ожидать при ее решении на тех или иных исходных данных [7]. Параметризированный подход позволяет решить трудноразрешимую задачу за приемлемое время при малых фиксированных значениях параметра. Приближенные алгоритмы применяются при решении трудноразрешимых задач оптимизации. Они нацелены на поиск полиномиального (по времени нахождения) приближения с гарантированной точностью – решения, отличающегося от действительного оптимума заведомо не более чем на заданную величину. Для построения приближенных решений (с гарантированной точностью или без нее) широко используются жадная стратегия, метод ветвей и границ, метод динамического программирования, различные вероятностные и эвристические приемы, методы искусственного интеллекта [13, 15, 20]. 143 Приложение 2 ВЫБОР ПРЕДСТАВЛЕНИЯ ДАННЫХ И ТЕСТОВ Данные, обрабатываемые программой, делятся на исходные, промежуточные и результирующие. Специфика исходных и результирующих данных заключается в том, что с ними имеет дело не только программа, но и пользователь программы. Поэтому различают два представления данных − внешнее и внутреннее (или машинное). Основное требование к внешнему представлению состоит в его максимальном удобстве, понятности и естественности для пользователя, чтобы он мог достаточно легко подготовить данные для ввода и оценить результат работы программы по выходным данным. Если выбор внешнего представления данных определяется естественностью для человека и обычно никак не зависит от выбора алгоритма решения задачи, то совершенно иначе дело обстоит с выбором внутреннего представления. Здесь рекомендуется исходить из правила: алгоритм и используемые в нем структуры данных должны разрабатываться одновременно. Неудачный выбор внутреннего представления данных может оказать значительное влияние на объем используемой памяти и на время работы программы. Очень часто внешнее и внутреннее представления исходных данных не совпадают. Тогда перевод из внешнего представления во внутреннее является частью процесса разработки программы. Например, один из способов внешнего представления графа − это задание графа множеством вершин и перечнем ребер. Однако для алгоритма обхода графа в глубину такой способ неудобен. В данном случае требуется перевод исходных данных во внутреннее представление – представление графа списками смежности. Умение выбрать подходящие для алгоритма представления данных является искусством программирования. Программист должен знать много разных способов представления одного и того же объекта и умело выбирать в каждой конкретной ситуации наиболее подходящий. 144 С разработкой алгоритма и программы тесно связано также построение набора тестов, на которых проверяется правильность работы программы во время ее отладки. Тест – некоторая совокупность исходных данных и точное описание результатов, которые должна выдать программа на этих исходных данных. Одно из основных требований, предъявляемых к набору тестов, – это его полнота: каждая ветвь, каждый оператор программы должны обязательно выполняться на одном из тестов набора. Обычно уже из формулировки задачи вытекает необходимость проверки работы программы хотя бы на двух тестах: один – когда решение задачи существует; другой – когда не существует. Другое важное требование к набору тестов – это учет количественных ограничений на размеры исходных данных программы и их значения. В программе должна быть предусмотрена проверка количественных ограничений, а в наборе тестов должны быть тесты, на которых данные ограничения нарушаются и в которых ожидаемый ответ содержит диагностическое сообщение об этом факте. 145 АЛФАВИТНЫЙ УКАЗАТЕЛЬ А В Алгоритм генерации перестановок 30 – – сочетаний 28 – Дейкстры 70 – декодирования префиксного кода 98 – Краскала 60 – неполиномиальный 137 – поиска в глубину или ширину 48 – полиномиальный 136 – порождения двоичных векторов 16 – построения кода Грея 18 – распознавания однозначности кода 101 – Прима 62 – Флойда 77 – Хаффмена 112 − экспоненциальный 137 Алфавит 92 – исходный 93 – кодирующий 93 Вектор степеней 38 – характеристический 8 Вершина графа 37 – висячая 38 – достижимая 56 – изолированная 38 Вложенность графов 40 Б Д Блок графа 54 Буква 92 Декодирование 93 Длина слова 92 – средняя элементарного кода 106 Дерево остовное 52 Дополнение графа 40 Г Граф 37 – ациклический 38 – безреберный 39 – взвешенный 39 – двудольный 39 – неориентированный 37 – несвязный 38 – ориентированный 41 – полный 39 − помеченный 39 – связный 38 Графы равные 39 146 З К Задача выбора проектов 23 – выделения компонент графа 51 – вычисления транзитивного замыкания 82 – квадратичная о назначениях 32 – нахождения всех источников орграфа 57 – определения дерева взаимопонимания 67 – – кратчайших контуров 81 − оптимального кодирования 110 – отыскания дерева кратчайших путей 75 – – – остовного 52 – – леса остовного 53 – о двух системах дорог 89 – – достижимости 56 – – доставке 24 – – кратчайшем пути 68 – – мелиорации рисовых полей 65 – – надежной вычислительной сети 66, 86 – – плотной системе дорог 88 – – поиске «узких» мест графа 53 – – сетевом планировании работ 84 − размещения баз 21 – распознавания однозначного Код алфавитный 93 − Грея бинарный 18 – неравномерный 94 – оптимальный 110 – префиксный 96 – равномерный 94 – Хаффмена 116 Кодирование 93 – алфавитное 93 – побуквенное 93 Компонента связности графа 38 Коэффициент сжатия 117 декодирования 94 И Инцидентность ребра и вершины 38 Л Лемма о рукопожатиях 49 Лес 38 – остовной 53 – – минимальный 62 М Маршрут 38 – замкнутый 38 – циклический 38 Матрица весов 44 – достижимости 83 − инцидентности графа 42 – – орграфа 42 – смежности графа 43 – – орграфа 43 Множество 6 – бесконечное 6 – конечное 6 – пустое 7 – универсальное 7 − элементарных кодов 93 Мощность множества 11 147 Н С Неравенство Макмиллана 99 Объединение графов 40 – – дизъюнктное 40 – множеств 9 Окрестность вершины 38 Окончание слова 96 Орграф 41 Остов минимальный 59 Отождествление вершин 40 Свойство префикса 96 Сжатие текстов 117 Слияние вершин 40 Слово 92 Сложность алгоритма 126 − задачи 139 Смежность вершин графа 37 – ребер графа 37 Сообщение 93 Сочетание 26 Степень вершины графа 38 Схема кодирования 93 П У Пересечение множеств 9 Перестановка 26 Подграф остовной 40 Поиск в глубину 48 – – ширину 48 Порядок графа 37 – лексикографический 27 Последовательность степенная 38 Постфикс слова 96 – – собственный 96 Префикс слова 96 – – собственный 96 Путь 41 – кратчайший 68 Удаление вершины графа 40 – ребра графа 40 О Р Равенство графов 39 – множеств 11 Равномощность множеств 11 Размещение 26 Разность множеств 9 – – симметрическая 10 Разрядность кода 94 Расстояние Хемминга 12 Ц Цепь графа 38 – – простая 38 Цикл графа 38 – – простой 38 Ч Число графа компонент связности 38 – – цикломатическое 52 – перестановок 26 – размещений 26 – сочетаний 26 Ш Шкала множества битовая 8 148 ОГЛАВЛЕНИЕ ПРЕДИСЛОВИЕ…………………………………………….…………….. ГЛАВА 1. ПЕРЕЧИСЛЕНИЕ ПРОСТЕЙШИХ КОМБИНАТОРНЫХ ОБЪЕКТОВ……...……………………………. З а дан и е 1. Множества: представления и операции………………..……………….…………….……………... 1.1. Основные понятия и обозначения…………........………………. 1.2. Битовая шкала множества…..……………….........………….….. 1.3. Теоретико-множественные операции и их реализация битовыми шкалами …………………………….....…............……….. 1.4. Отношения над множествами….………………………………... 1.5. Расстояние между множествами.……………………………...... 1.6. Порядок выполнения задания………………………………….... 1.7. Варианты.…………………………………………………………. З а дан и е 2. Генерация всех подмножеств конечного множества.…....................................................................................... 2.1. Формулировка задачи.........………………………..…………...... 2.2. Счет в двоичной системе счисления...…………..…………….... 2.3. Бинарные коды Грея....................................................................... 2.4. Порядок выполнения задания..………………………………….. 2.5. Варианты..………………………………………………………… З а дан и е 3. Пересчет и перечисление сочетаний и перестановок......................................................................................... 3.1. Определение комбинаторных объектов.......................……….… 3.2. Генерация сочетаний.........................................…………...…….. 3.3. Генерация перестановок...................……………………....……. 3.4. Порядок выполнения задания…………………………………… 3.5. Варианты………………………………………………………….. ГЛАВА 2. АЛГОРИТМЫ НА ГРАФАХ…………...............…..…….. З а дан и е 4. Графы: представления и операции.................... 4.1. Основные понятия и обозначения................................................. 4.2. Отношения и операции...……..…………………………....…….. 4.3. Родственные графам объекты..................……………………….. 4.4. Способы машинного представления ............................................ 4.5. Порядок выполнения задания...………………………………..... 4.6. Варианты………………………………………………………...... 149 3 6 6 6 7 9 11 12 13 14 16 16 16 17 20 21 26 26 27 30 31 32 37 37 37 39 41 41 47 47 З а дан и е 5 . Базовые задачи и алгоритмы на графах........ 9.1. Средняя длина элементарного кода ………………………...... 9.2. Формулировка задачи…..………………………….................... 9.3. Свойства оптимальных кодов……………………..………….. 9.4. Алгоритм Хаффмена………………………..………………….. 9.5. Сжатие текстов………………………..………………………... 9.6. Порядок выполнения задания…………………………………. 9.7. Варианты……………………………………………………… 48 48 51 57 58 59 59 59 62 62 64 64 68 68 69 75 76 81 83 84 92 92 92 94 96 103 104 106 106 110 111 112 117 118 119 Библиографический список............................................................ Приложение 1. Алгоритмы и сложность...................................... Приложение 2. Выбор представления данных и тестов.............. Алфавитный указатель………………………….………............... 120 122 144 146 5.1. Обход вершин графа в глубину или ширину..……………….. 5.2. Базовые задачи на графах …………………....……………… 5.3. Порядок выполнения задания…………………………………. 5.4. Варианты……………………………………………………… З а дан и е 6. Построение минимального остова................... 6.1. Формулировка задачи…..................…………………………… 6.2. Алгоритм Краскала..……………………………….................... 6.3. Алгоритм Прима…...................……………………..………….. 6.4. Некоторые замечания..……………………..………………….. 6.5. Порядок выполнения задания…………………………………. 6.6. Варианты ...................................................................................... З а дан и е 7. Построение кратчайших путей......................... 7.1. Формулировка задачи…..................…………………………… 7.2. Случай неотрицательных весов. Алгоритм Дейкстры............. 7.3. Дерево кратчайших путей........……………………..…………. 7.4. Случай произвольной матрицы весов. Алгоритм Флойда…... 7.5. Кратчайшие контуры и транзитивное замыкание..................... 7.6. Порядок выполнения задания…………………………………. 7.7. Варианты ...................................................................................... ГЛАВА 3. АЛФАВИТНОЕ КОДИРОВАНИЕ……………..……... З а дан и е 8 . Однозначность декодирования……….............. 8.1. Основные понятия и обозначения ….……………………….... 8.2. Формулировка задачи….....……………………………………. 8.3. Критерии однозначного декодирования….………………… 8.4. Порядок выполнения задания…....……………………………. 8.5. Варианты……………………………………………………… Задание 9. Оптимальное кодирование и сжатие текстов 150 , , . . - . . 15.12.2014 . . 60×84/16. . . . . 9,5. ! 500 " . # $ 14. % & ' - ( 660041, ) , . ( *&, 79 ./ (391) 206-21-49, e-mail: rio@lan.krasu.ru ' - ( 660041, ) , . ( *&, 82 ./ (391) 206-26-49; . (391) 206-26-67 E-mail: print_sfu@mail.ru; http://lib.sfu-kras.ru