Комбинаторные алгоритмы для программистов

Целые

Целые являются основными объектами в вычислительной комбинаторике. В различных вычислительных теоретико-числовых исследованиях изучаются сами целые числа, но мы будем использовать их главным образом при подсчете и индексировании. В последнее время установлено, что полезны различные представления. В этой лекции обсудим общий класс позиционных представлений.
Мы будем рассматривать только неотрицательные целые. Кроме того, к любому представлению неотрицательных целых легко присоединить одиночный знаковый двоичный разряд.
Позиционные системы для представления целых чисел очень широко известны, поскольку они встречаются во многих разделах математики, начиная с "новой математики" и кончая углубленным курсом теории чисел. В системе счисления с основанием Целые каждое положительное целое число имеет единственное представление в виде конечной последовательности
Целые
(2.1)

в которой каждое Целые - целое, удовлетворяющее условию Целые и Целые . Нуль представляется последовательностью Целые . Целые называется основанием системы Целые . Целое, соответствующее последовательности (2.1), имеет вид
Целые
что принято выражать следующим образом:
Целые
На протяжении истории использовались различные значения Целые . Например, древние вавилоняне использовали Целые , а индейцы племени Майя — Целые . Сегодня наиболее широко используется Целые - десятичная система, которую мы унаследовали от арабов, и Целые - двоичная система, которая лежит в основе современных вычислительных устройств. В действительности она применяется лишь на самом низком уровне аппаратного оборудования; в сложных вычислительных устройствах и базисных языках удобнее использовать Целые или Целые .
Единственность этого представления можно доказать методом от противного. Числа Целые и Целые , очевидно, имеют единственное представление. Предположим, что представление не единственно, и пусть Целые будет наименьшим целым числом, имеющим два различных представления: Целые
Если Целые , то без потери общности предположим, что Целые . Тогда, поскольку
Целые
и поскольку Целые , мы заключаем, что
Целые
(2.2)

что невозможно. Таким образом, мы должны иметь Целые . Аналогично, если Целые , мы имели бы снова неравенство (2.2) и отсюда с необходимостью Целые .
Следовательно, число

Целые

имеет два различных представления, что противоречит предположению, что Целые - наименьшее из таких чисел.

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

Алгоритм 1. Преобразование числа Целые в его представление Целые в системе счисления с основанием Целые .

Он строит последовательность Целые путем повторения деления на Целые и записи остатков. Пусть на первом шаге при делении Целые на Целые остаток будет Целые . Частное, полученное в результате первого шага, делим на Целые , вновь полученное частное делим на Целые и так далее. Полученная в результате такого процесса последовательность остатков и будет требуемым представлением Целые по основанию Целые .

Важным обобщением систем счисления с основанием Целые являются смешанные системы счисления, в которых задается не единственное основание Целые , а последовательность оснований Целые и последовательность (2.2) соответствует целому Целые

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

Смешанные системы счисления могут вначале показаться странными, но в действительности в повседневной жизни они встречаются почти так же часто, как и десятичные.

Пример. Рассмотрим нашу систему измерения времени: секунды, минуты, часы, дни недели и годы. Это - в точности смешанная система с Целые .

Представление целого Целые в смешанной системе счисления Целые осуществляется с помощью алгоритма 2, который является простым обобщением алгоритма 1. Вместо того, чтобы для получения Целые в качестве делителя всегда использовалась Целые , в алгоритме 2 используется Целые .

Алгоритм 2. Преобразование числа Целые в его представление Целые в смешанной системе счисления Целые

Последовательности

Бесконечная последовательность Последовательности
формально определяется как функция Последовательности , областью определения которой является множество положительных целых чисел: Последовательности . Во многих случаях индексирование последовательности более удобно начинать с нуля; тогда областью определения Последовательности будет множество целых неотрицательных чисел. Аналогично определим конечную последовательность или список
Последовательности
как функцию, областью определения которой является множество Последовательности . Примером бесконечной последовательности являются простые числа
Последовательности
(2.3)

Последовательности
а перестановка
Последовательности
представляет собой пример конечной последовательности.
В комбинаторных алгоритмах часто приходится встречаться с представлениями конечных последовательностей (или начальных сегментов бесконечных последовательностей) и операциями над ними.

Различные способы представлений

Последовательное распределение. С вычислительной точки зрения простейшим представлением конечной последовательности является список ее членов, расположенных по порядку в последовательных ячейках памяти.
Различные способы представлений
Рис. 2.1.
  Последовательное распределение последовательности Различные способы представлений для каждого элемента которой требуется Различные способы представлений ячеек
Так, Различные способы представлений хранится, начиная с ячейки Различные способы представлений , Различные способы представлений хранится, начиная с ячейки Различные способы представлений , Различные способы представлений хранится, начиная с ячейки Различные способы представлений и так далее, где Различные способы представлений - число ячеек, требуемых для хранения одного элемента последовательности.
Описанное выше представление последовательности имеет ряд преимуществ. Во-первых, оно легко осуществимо и требует небольших расходов в смысле памяти. Кроме того, оно полезно и потому, что существует простое соотношение между Различные способы представлений и адресом ячейки, в которой хранится Различные способы представлений : Различные способы представлений
Это соотношение позволяет организовать прямой доступ к любому элементу последовательности. Наконец, последовательное представление имеет достаточно широкий диапазон и включает в себя в качестве специального случая представление многомерных массивов.
Например, чтобы представить массив размером Различные способы представлений
Различные способы представлений
(2.4)

будем рассматривать его как последовательность Различные способы представлений , в которой каждое Различные способы представлений в свою очередь является последовательностью из Различные способы представлений элементов Различные способы представлений -й строки нашей матрицы. Таким образом, число ячеек, требуемых для записи элемента Различные способы представлений (будем обозначать это число символом Различные способы представлений ), равно Различные способы представлений , где Различные способы представлений - число ячеек, требуемых для записи элемента Различные способы представлений . Поскольку последовательность Различные способы представлений начинается в ячейке
Различные способы представлений
ячейка для Различные способы представлений будет иметь следующий адрес:
Различные способы представлений
Это представление известно как построчная запись матрицы; постолбцовая запись получается, если (2.4) рассматривать как последовательность Различные способы представлений в которой каждое Различные способы представлений в свою очередь является последовательностью из элементов Различные способы представлений -го столбца матрицы.
Последовательное распределение, наряду с преимуществами, имеет значительные недостатки. Например, такое представление становится неудобным, если требуется изменить последовательность путем включения новых и исключения имеющихся там элементов. Включение между Различные способы представлений и Различные способы представлений нового элемента требует сдвига Различные способы представлений вправо на одну позицию; аналогично, исключение Различные способы представлений требует сдвига тех же элементов на одну позицию влево. С точки зрения времени обработки, такое передвижение элементов может оказаться дорогостоящим, и в случае динамических последовательностей лучше использовать технику связного распределения, рассматриваемую в следующей лекции.
Характеристические векторы. Важной разновидностью последовательного распределения является случай, когда такому представлению подвергается последовательность некоторой основной последовательности Различные способы представлений В этом случае можно представить последовательность более удобно, используя характеристический вектор - последовательность из нулей и единиц, где Различные способы представлений -й разряд равен единице, если Различные способы представлений принадлежит рассматриваемой последовательности, и нулю в противном случае.
Например, характеристический вектор начального сегмента последовательности (2.3) Различные способы представлений
характеристический вектор для простых чисел:
Различные способы представлений
Здесь основной последовательностью является последовательность целых положительных чисел. В ЭВМ с 32-разрядными словами для запоминания простых чисел, меньших Различные способы представлений , потребуется Различные способы представлений слов. Замечая далее, что для Различные способы представлений число Различные способы представлений не простое, можно сэкономить половину этого поля, выписывая разряды только для чисел видов Различные способы представлений , и запоминая, что простое число 2 отсутствует. Таким образом, простые числа, меньшие чем Различные способы представлений , можно записать только 15625 словами. Поскольку число простых чисел, меньших Различные способы представлений , равно 78498, последовательное представление, описанное ранее, потребовало бы поля в пять раз меньшего размера.
Характеристические векторы полезны в ряде случаев. Их полезность вытекает из их компактности, существования простого фиксированного соотношения между Различные способы представлений и адресом Различные способы представлений -го разряда и возможности при таком представлении очень легко исключать элементы.
Главное неудобство характеристических векторов состоит в том, что они не экономичны. Исключение составляют "плотные" последовательности последовательностей Различные способы представлений . Кроме того, их трудно использовать, если не существует простого соотношения между Различные способы представлений и Различные способы представлений .Если такое соотношение сложное, то использование характеристических векторов может быть очень не экономичным в смысле времени обработки. Если последовательности недостаточно плотные, то значительным может оказаться объем памяти. В случае простых чисел между Различные способы представлений и Различные способы представлений имеется простое соотношение: Различные способы представлений (или Различные способы представлений , если использовать только нечетные числа). Теорема о простых числах утверждает, что число простых чисел, меньших Различные способы представлений , приблизительно равно Различные способы представлений ; таким образом, простые числа относительно плотно распределены в множестве целых чисел. Различные способы представлений

Программы

Программа 1. Создание списка.
// Алгоритм реализован на языке программирования Turbo-C++. #include #include #include
struct List{int i; List*next; }; List*head=NULL;
void Hed(int i) {if(head==NULL){head=new List; head->i=1; head->next=NULL; }else { struct List*p,*p1; p=head; while(p->next!=NULL) p=p->next;
p1=new List; p1->i=i; p1->next=NULL; p->next=p1; } }
int s=0;
void Print(List*p) {cprintf(" %d",p->i); if(p->next!=NULL)Print(p->next); }
void delist() {List*p; while(head!=NULL) {p=head; head=head->next; delete(p); } } void Vstavka(int i1,int c) {List*p=head,*p1; while(p->i!=i1) p=p->next; p1=new List; p1->i=c; p1->next=p->next; p->next=p1; }
void main() { clrscr(); for(int i=1;i<=10;i++) Hed(i); textcolor(12); Print(head); textcolor(1); Vstavka(10,11); printf("\n"); Print(head); textcolor(11); Vstavka(3,12); printf("\n"); Print(head); textcolor(14); Vstavka(5,13); printf("\n"); Print(head); delist(); getch(); }
Программа 2. Создание стека и работа со стеком.
//Работа со стеком // Алгоритм реализован на языке программирования Turbo-C++. #include #include #include #include #include #include #define max_size 200 // char s[max_size]; //компоненты стека int s[max_size]; int next=0; // позиция стека int Empty() { return next==0; } int Full() { return next==max_size; } void Push() { if (next==max_size) { cout <<"Ошибка: стек полон"< else { next++;cout <<"Добавлен"< else { for(i=0;i Программы

Разновидности связанных списков

Тривиальной модификацией связанного списка, изображенного на рис 3.2, будет следующее несколько более гибкое представление последовательности: если Разновидности связанных списков указывает на Разновидности связанных списков, как показано на рис.3.5, то мы имеем так называемый циклический список. Такая форма списка дает возможность достигнуть любой элемент из любого другого элемента последовательности. Включение и исключение элементов здесь осуществляется так же, как и в нециклических списках, в то время как сцепление и разбиение реализуются несколько более сложно.
Разновидности связанных списков
Рис. 3.5.  Циклический список
Еще большая гибкость достигается, если использовать дважды связанный список, когда каждый элемент Разновидности связанных списков последовательности вместо одного имеет два связанных с ним указателя. Как показано на рис. 3.6, они указывают на элементы Разновидности связанных списков и Разновидности связанных списков. В таком списке для любого элемента имеется мгновенный прямой доступ к предыдущему и последующему элементам, в связи с чем облегчаются такие операции, как включение нового элемента перед Разновидности связанных списков и исключение элемента Разновидности связанных списков
без предварительного знания его предшественника. Если есть необходимость, дважды связанный список очевидным образом можно сделать циклическим.
Разновидности связанных списков
Рис. 3.6.  Дважды связанный список

Стеки и очередь

В комбинаторных алгоритмах особую важность представляют две структуры данных, основанные на динамических последовательностях, т.е. последовательностях, которые изменяются вследствие включения новых и исключения имеющихся элементов. В обоих случаях операции включения и исключения, которым подвергается последовательность, имеют ограниченный вид: они производятся только в концах последовательности. Стек есть последовательность, у которой все включения и исключения происходят только в ее правом конце, называемом вершиной стека (соответственно, левый конец последовательности называется основанием). Таким образом, элементы включаются в стек и исключаются из него в соответствии с правилом "Первым пришел - последним ушел". Очередь - это последовательность, в которой все включения производятся на правом конце списка (в конце очереди), в то время как все исключения производятся на левом конце (в начале очереди). В противоположность стеку очередь оперирует в режиме "Первым пришел - первым ушел".
Стеки и очереди имеют важное значение. Для выполнения какой-либо определенной задачи может потребоваться выполнение ряда подзадач. Каждая подзадача может также привести к другим требующим выполнения подзадачам. И стеки, и очереди являются механизмом, посредством которого запоминаются подзадачи, подлежащие выполнению, а также порядок, в котором они должны быть выполнены. В некоторых случаях порядок таков: "Первым пришел - последним ушел"; тогда удобно использовать стеки. Если порядок подчиняется правилу "Первым пришел - первым ушел", то подходящим инструментом являются очереди.

Связанное распределение

В лекции 11 даны примеры и программные реализации списков, стеков и очередей. Неудобство включения и исключения элементов при последовательном распределении происходит из-за того, что порядок следования элементов задается неявно требованием, чтобы смежные элементы последовательности находились в смежных ячейках памяти. В результате многие элементы последовательности во время включения или исключения должны передвигаться. Если это требование опущено, то можно выполнить операции включения и исключения без того, чтобы передвигать элементы последовательности. При связанном распределении последовательности (связанном списке) каждому Связанное распределение
поставлен в соответствии указатель (ссылка) Связанное распределение, отмечающий ячейку, в которой записаны Связанное распределение и Связанное распределение. Существует также указатель Связанное распределение, который указывает начальную ячейку последовательности, то есть ячейку с символами Связанное распределение и Связанное распределение. Все сказанное выше проиллюстрировано на рис. 3.1.
Связанное распределение
Рис. 3.1. 
Здесь каждый узел состоит из двух полей. Под узлом понимается одно или несколько последовательных слов в памяти машины, которые выступают как единое целое и разделены на части, именуемые полями. В поле Связанное распределение размещен сам элемент последовательности, а в поле Связанное распределение - указатель на следующий за ним элемент.
Linked list - список с использованием указателей: список, в котором каждый элемент содержит указатель на следующий элемент или два указателя - на следующий и предыдущий. Поскольку для Связанное распределение следующего элемента не существует, будем использовать обозначение Связанное распределение, где Связанное распределение- пустой , или нулевой указатель . Так как точные значения Связанное распределение для программиста не существенны, то в более общем виде связанное представление, показанное на рис. 3.1, можно изобразить так, как показано на рис.3.2.
Связанное распределение
Рис. 3.2.  Другой, более употребительный, способ представления списка
Связанное представление последовательностей облегчает операции включения элемента после некоторого Связанное распределение и исключения элемента Связанное распределение, если ячейка для Связанное распределение известна. Для этого необходимо лишь изменить значения некоторых указателей. Например, чтобы исключить элемент Связанное распределение из последовательности, изображенной на рис. 3.2, необходимо только положить Связанное распределение

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

Связанное распределение
Рис. 3.3.  Исключение элемента из связанного списка

Связанное распределение
Рис. 3.4.  Включение элемента в связанный список

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

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

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

Модифицированный список

Задача 1. Создать список, элементами которого являются числа: 1 2 3 4 5 6 7 8 9. Вывести список на экран терминала. Включить в связанный список элемент 2005 после каждого элемента, который делится на 3. Модифицированный список вывести на экран терминала.
Задача 2. Очередью с приоритетом называется линейный список, который оперирует в режиме "первым включается - с высшим приоритетом исключается"; иными словами, каждому элементу очереди сопоставлено некоторое число - приоритет. Включения производятся в конец очереди, а исключения - в любом месте очереди, поскольку исключаемый элемент - это всегда элемент с высшим приоритетом. Нужно описать алгоритм (и его реализацию) включения и исключения для очередей с приоритетом.

Деревья

Конечное корневое дерево Деревья формально определяется как непустое множество упорядоченных узлов, таких, что существует один выделенный узел, называемый корнем дерева, а оставшиеся узлы разбиты на Деревья
поддеревьев Деревья. Будем рассматривать только корневые деревья. Узлы, не имеющие поддеревьев, называются листьями; остальные узлы называются внутренними узлами.
Деревья
Рис. 4.1.
  Дерево с 11 узлами, помеченными буквами от Деревья до Деревья. Узлы с метками Деревья являются листьями; другие узлы внутренние. Узел с меткой Деревья - корень
В первой лекции уже было использовано дерево - при изучении необходимого числа взвешиваний в задаче о фальшивой монете с Деревья монетами. Так "рано" деревья появились в тексте не случайно, поскольку понятие дерева используется в различных важных аспектах данного курса. Посредством деревьев изображаются иерархические организации, поэтому они являются наиболее важными нелинейными структурами в комбинаторных алгоритмах.
В описании соотношений между узлами дерева используем терминологию, принятую в генеалогических деревьях. Так, говорят, что в дереве или поддереве все узлы являются потомками его корня, и наоборот, корень есть предок всех своих потомков. Корень именуют отцом корней его поддеревьев, которые в свою очередь будут сыновьями корня. Например, на рис. 4.1 узел Деревья является отцом узлов Деревья и Деревья; Деревья - сыновья Деревья, а Деревья - братья.
Все рассматриваемые нами деревья будут упорядочены, то есть для них будет важен относительный порядок поддеревьев каждого узла. Таким образом, деревья считаются различными.
Деревья
Рис. 4.2.  Различные деревья
Определим лес как упорядоченное множество деревьев; в связи с этим можно перефразировать определение дерева: дерево есть непустое множество узлов, такое, что существует один выделенный узел, называемый корнем дерева, а оставшиеся узлы образуют лес с Деревья
поддеревьями корня
. Важной разновидностью корневых деревьев является класс бинарных деревьев. Бинарное дерево Деревья либо пустое, либо состоит из выделенного узла, называемого корнем, и двух бинарных поддеревьев: левого Деревья и правого Деревья.
Бинарные деревья не являются подмножеством множества деревьев, они полностью отличаются по своей структуре, поскольку два следующих рисунка не изображают одно и то же бинарное дерево.

Деревья
Рис. 4.3.  Различные бинарные деревья

Как деревья, однако, они не отличаются от дерева, изображенного на рис. 4.4.

Деревья
Рис. 4.4.  Не бинарное дерево

Различие между деревом и бинарным деревом состоит в том, что дерево не может быть пустым, а каждый узел дерева может иметь произвольное число поддеревьев; в то же время, бинарное дерево может быть пустым. Каждая из вершин бинарного дерева может иметь 0, 1 или 2 поддерева, и существует различие между левым и правым поддеревьями.

Длина путей

Деревья можно использовать не только как способ представления структуры данных, но также как средство для анализа поведения определенных алгоритмов. В связи с этим возникает потребность в количественных измерениях различных характеристик деревьев и, в частности, бинарных деревьев.
Наиболее важные количественные характеристики деревьев связаны с уровнями узлов. Уровень Длина путей определяется рекурсивно и считается равным нулю, если Длина путей корень Длина путей; в противном случае уровень Длина путей определяется как Длина путей. Понятие уровня дает возможность определить высоту Длина путей дерева Длина путей: Длина путей
Другими словами, высота дерева есть максимальное число ребер, образующих путь от корня к листу дерева.

Представления

Почти все машинные представления деревьев основаны на связанных распределениях. Каждый узел состоит из поля Представления и нескольких полей для указателей. Например, представление, которое будет удобным для изложения множества и мультимножества, для каждого узла имеет единственное поле для указателя Представления, указывающего на отца данного узла. При этом приведенное на рис. 4.1 дерево будет выглядеть так, как показано на рис. 4.6.
Такое представление полезно, если необходимо подниматься по дереву от потомков к предкам. Такая операция встречается довольно редко. Чаще требуется опуститься по дереву от предков к потомкам.
Представление дерева (или леса) с использованием указателей, ведущих от предков к потомкам, довольно сложно, поскольку узел, имея не более чем одного отца, может в то же время иметь произвольно много сыновей. Другими словами, при таком представлении узлы должны различаться по размеру, что является определенным неудобством. Один из путей обхода этой трудности состоит в том, чтобы определить соответствие между деревьями и бинарными деревьями, поскольку бинарные деревья легко представить узлами фиксированного размера.
Представления
Рис. 4.5.  Дерево из рис. 4.1, представленное с помощью узлов с полем Представления и указателем Представления
Каждый узел в этом случае имеет три поля: Представления, указатель местоположения корня левого поддерева, Представления, содержимое узла, и Представления, указатель местоположения корня правого поддерева. Все сказанное выше проиллюстрировано на рис. 4.6.
Представления
Рис. 4.6.  Бинарное дерево и его представление с помощью узлов с тремя полями Представления, Представления, Представления
Можно представлять деревья как бинарные, используя узлы фиксированного размера, представляя каждый узел леса в виде узла, состоящего из полей Представления, Представления, Представления. При этом Представления
предназначается для указания самого левого сына данного узла, а поле Представления - для указания следующего брата данного узла.

Программa

Программа 1. Обход бинарного дерева в глубину
//Обход ориентированных графов - поиск в глубину - //обобщение обхода дерева в прямом порядке #include #include #include #include
int matr_sm[50][50]; int mark[50]; int n;
void vvod () {int v1,v2; printf("Введите кол-во вершин в графе: "); do { scanf("%d",&n); if (n>51) printf("Ошибка!! Введите кол-во вершин в графе: "); } while (n>51);
for (int i=0;i <50;i++) for (int j=0;j <50;j++) matr_sm[i][j]=0;
printf("\nВведите связанные вершины : \n"); do { scanf("%d ",&v1); if (v1>n) //исходящая вершина { printf("ОШИБКА ВВОДА !!!!!!!!!"); abort(); } if (v1==0){break;} // конец ввода scanf(" %d ",&v2); if (v2>n) // входящая вершина { printf("ОШИБКА ВВОДА !!!!!!!!!"); abort(); } if (v2==0){break;} //конец ввода matr_sm[v2-1][v1-1]=1; } while(1); }
void vivod() { for (int j=0;j void dfs(int v) { int w; mark[v]=1; //посетили printf("%i ",v+1); //и выдали на экран for (w=0;w void main() { clrscr(); vvod(); // printf("\n"); printf("МАТРИЦА СМЕЖНОСТИ\n"); // vivod();
printf("\n РЕЗУЛЬТАТ ПОИСКА В ГЛУБИНУ \n"); for (int v=0;v<50;v++) mark[v]=0; for (v=0;v getch(); }
Программa

Прохождения

Во многих приложениях необходимо пройти лес, заходя в узлы, то есть обрабатывая их некоторым систематическим образом. Посещение каждого узла может быть связано с простой операцией, такой как печать содержимого, или со сложной, такой как вычисление функции. Будем предполагать, что при посещении узла структура леса не меняется. Рассмотрим четыре основных способа прохождения леса: в глубину, снизу вверх, в горизонтальном порядке и для бинарных деревьев - в симметричном порядке.
При прохождении в глубину, известном также как прохождение в прямом порядке, узлы леса проходятся в соответствии со следующей рекурсивной процедурой:
  • Посетить корень первого дерева.
  • пройти в глубину поддерева первого дерева (если оно есть).
  • Пройти в глубину оставшиеся деревья, если они есть.

  • Например, для леса, показанного на рис. 4.7, узлы будут проходиться в следующем порядке: Прохождения
    Прохождения
    Рис. 4.7.  Лес
    Название "в глубину" отражает тот факт, что после посещения некоторого узла мы продолжаем прохождение в глубь дерева всякий раз, когда это возможно. Такой порядок особенно полезен в процедурах поиска.
    Для бинарных деревьев эта процедура упрощается и выглядит следующим образом.
  • Посетить корень.
  • Пройти в глубину левое поддерево
  • Пройти в глубину правое поддерево.

  • Прохождение снизу вверх, известное также как обратный порядок или концевой порядок, осуществляется согласно следующей рекурсивной процедуре:
  • Пройти снизу вверх поддеревья первого дерева, если они есть.
  • Посетить корень первого дерева.
  • Пройти снизу вверх оставшиеся деревья, если они есть.

  • Название "снизу вверх" связано с тем, что в момент посещения произвольного узла его потомки оказываются уже пройденными. Такой порядок прохождения полезен, в частности, потому, что он позволяет вычислять рекурсивно определенные функции на лесах. При этом порядке прохождения узлы леса, показанного на рис. 4.7, проходятся в такой последовательности: Прохождения. Рекурсивная процедура прохождения снизу вверх применительно к бинарным деревьям имеет следующий вид:

  • Пройти снизу вверх левое дерево.
  • Пройти снизу вверх правое дерево.
  • Посетить корень.


  • Симметричный порядок для бинарных деревьев определяется рекурсивно следующим образом:

  • Пройти в симметричном порядке левое поддерево.
  • Посетить корень.
  • Пройти в симметричном порядке правое поддерево.


  • Такой способ прохождения известен также как лексикографический порядок или внутренний порядок. Заметим, что прохождение леса снизу вверх эквивалентно прохождению в симметричном порядке бинарного дерева, соответствующего этому лесу (при естественном соответствии).

    Сравнивая рекурсивные процедуры прохождения бинарных деревьев в глубину, снизу вверх и в симметричном порядке, можно обнаружить их значительное сходство:

    прохождение в глубинусимметричный порядокпрохождение снизу вверх
    посетить кореньлевое поддереволевое поддерево
    левое поддеревопосетить кореньправое поддерево
    правое поддеревоправое поддеревопосетить корень
    Это сходство позволяет построить общий нерекурсивный алгоритм, который может быть применен к каждому из этих порядков прохождения бинарных деревьев.

    Горизонтальный порядок прохождения. При таком способе узлы леса проходятся слева направо, уровень за уровнем от корня вниз. Таким образом, в соответствии с этой процедурой узлы леса, показанного на рис.4.7, будут проходиться в следующем порядке: Прохождения. Такое прохождение дерева полезно в определенных алгоритмах на графах.

    Построить алгоритм обхода

    Задача 1. Построить алгоритм обхода бинарного дерева (см. рис. 4.6,(а)) в глубину.

    Число сочетаний

    Рассмотрим подмножества множества, состоящего из пяти элементов, и подсчитаем их число. При этом записывать подмножества будем не с помощью букв, как обычно, а в виде последовательностей длиной пять, составленных из нулей и единиц. Каждая из единиц указывает на наличие в подмножестве соответствующего элемента. Например, подмножества, содержащие один элемент, будут изображаться следующими последовательностями: 10000, 01000, 00100, 00010, 00001. Пустое подмножество Число сочетаний будет соответствовать последовательности 00000. Подмножества, содержащие по два элемента из пяти, запишутся с помощью следующих последовательностей: 11000, 10100, 10010, 10001, 01100. 01010, 01001, 00110, 00101, 00011. Всего их Число сочетаний .
    Вообще, число сочетаний из Число сочетаний элементов по Число сочетаний равно числу всевозможных последовательностей из Число сочетаний единиц и Число сочетаний нулей.

    Деревья и перестановки из n элементов

    С помощью леса можно представить перестановки из Деревья и перестановки из n элементов элементов множества Деревья и перестановки из n элементов (множество мы определяем так: множество - это неупорядоченная совокупность различных объектов или структура данных, используемая для представления множества). Подсчитаем, сколько можно получить перестановок. Для Деревья и перестановки из n элементов такой лес изображен на рис. 5.1.
    Деревья и перестановки из n элементов
    Рис. 5.1.  Всевозможные перестановки прочитываются по этой схеме от корневой до висячей вершины соответствующего дерева. Ярус показывает номер места, на котором расположен элемент. Число висячих вершин леса равно числу перестановок

    Комбинаторные задачи теории информации

    Информация - сведения, неизвестные до их получения, или данные, или значения, приписанные данным.
    Теория информации - математическая дисциплина, изучающая количественные свойства информации.
    Задачу, похожую на только что решенную, приходится решать в теории информации. Предположим, что сообщение передается с помощью сигналов нескольких типов. Длительность передачи сигнала первого типа равна Комбинаторные задачи теории информации , второго типа - Комбинаторные задачи теории информации -го типа - Комбинаторные задачи теории информации единиц времени.
    Задача 6. Сколько различных сообщений можно передать с помощью этих сигналов за Комбинаторные задачи теории информации единиц времени? При этом учитываются лишь "максимальные" сообщения, то есть сообщения, к которым нельзя присоединить ни одного сигнала, не выйдя за рамки отведенного для передачи времени.
    Обозначим число сообщений, которые можно передать за время Комбинаторные задачи теории информации через Комбинаторные задачи теории информации . Рассуждая точно так же, как и в задаче о марках, получаем, что Комбинаторные задачи теории информации удовлетворяет соотношению
    Комбинаторные задачи теории информации
    (5.8)
    При этом снова Комбинаторные задачи теории информации , если Комбинаторные задачи теории информации и Комбинаторные задачи теории информации Комбинаторные задачи теории информации

    Разные статистики

    Задачи о раскладке предметов по ящикам весьма важны для статистической физики. Эта наука изучает, как распределяются по своим свойствам физические частицы; например, какая часть молекул данного газа имеет при данной температуре ту или иную скорость. При этом множество всех возможных состояний распределяют на большое число Разные статистики маленьких ячеек (фазовых состояний), так что каждая из Разные статистики частиц попадет в одну из ячеек.
    Вопрос о том, какой статистике подчиняются те или иные частицы, зависит от вида этих частиц. В классической статистической физике, созданной Максвеллом и Больцманом, частицы считаются различимыми друг от друга. Такой статистике подчиняются, например, молекулы газа. Известно, что Разные статистики различных частиц можно распределить по Разные статистики ячейкам Разные статистики способами. Если все эти Разные статистики способов при заданной энергии имеют равную вероятность, то говорят о статистике Максвелла-Больцмана.
    Оказалось, что этой статистике подчиняются не все физические объекты. Фотоны, атомные ядра и атомы, содержащие четное число элементарных частиц, подчиняются иной статистике, разработанной Эйнштейном и индийским ученым Бозе. В статистике Бозе-Эйнштейна частицы считаются неразличимыми друг от друга. Поэтому имеет значение лишь то, сколько частиц попало в ту или иную ячейку, а не то, какие именно частицы туда попали.
    Однако для многих частиц, например таких как электроны, протоны и нейтроны, не годится и статистика Бозе-Эйнштейна. Для них в каждой ячейке может находится не более одной частицы, причем различные распределения, удовлетворяющие указанному условию, имеют равную вероятность. В этом случае может быть Разные статистики различных распределений. Эта статистика называется статистикой Дирака-Ферми.

    Сколькими способами можно сделать такое

    Общая постановка этих задач:
    Задач 1. Раскладка по ящикам
    Даны Сколькими способами можно сделать такое различных предметов и Сколькими способами можно сделать такое ящиков. Надо положить в первый ящик Сколькими способами можно сделать такое предметов, во второй - Сколькими способами можно сделать такое предметов,..., в Сколькими способами можно сделать такое -й - Сколькими способами можно сделать такое предметов, где Сколькими способами можно сделать такое . Сколькими способами можно сделать такое распределение?
    Число различных раскладок по ящикам равно Сколькими способами можно сделать такое Эту формулу можно получить при решении следующей, на первый взгляд, совсем непохожей задачи:
    Задача 2. Перестановки с повторением.
    Имеются предметы Сколькими способами можно сделать такое различных типов. Сколько различных перестановок можно сделать из Сколькими способами можно сделать такое предметов первого типа, Сколькими способами можно сделать такое предметов второго типа, ..., Сколькими способами можно сделать такое предметов Сколькими способами можно сделать такое -го типа? Число элементов в каждой перестановке равно Сколькими способами можно сделать такое . Поэтому если бы все элементы были различны, то число перестановок равнялось бы Сколькими способами можно сделать такое . Но из-за того, что некоторые элементы совпадают, получится меньшее число перестановок. В самом деле, возьмем, например, перестановку
    Сколькими способами можно сделать такое (5.1)
    в которой сначала выписаны все элементы первого типа, потом все элементы второго типа, ..., наконец, все элементы Сколькими способами можно сделать такое -го типа. Элементы первого типа можно переставлять друг с другом Сколькими способами можно сделать такое способами. Но так как все эти элементы одинаковы, то такие перестановки ничего не меняют. Точно так же ничего не меняют Сколькими способами можно сделать такое перестановок элементов второго типа, ..., Сколькими способами можно сделать такое перестановок элементов Сколькими способами можно сделать такое -го типа.
    Перестановки элементов первого типа, второго типа и так далее можно делать независимо друг от друга. Поэтому элементы перестановки 5.1. можно переставлять друг с другом Сколькими способами можно сделать такое способами так, что она остается неизменной. То же самое верно и для любого другого расположения элементов. Поэтому множество всех Сколькими способами можно сделать такое перестановок распадается на части, состоящие из Сколькими способами можно сделать такое одинаковых перестановок каждая. Значит, число различных перестановок с повторениями, которые можно сделать из данных элементов, равно
    Сколькими способами можно сделать такое (5.2)
    где Сколькими способами можно сделать такое .
    Пользуясь формулой 5.2, можно ответить на вопрос: сколько перестановок можно сделать из букв слова "Миссисипи"? Здесь у нас одна буква "м", четыре буквы "и", три буквы "с" и одна буква "п", а всего 9 букв. Значит, по формуле 5.2 число перестановок равно Сколькими способами можно сделать такое Чтобы установить связь между этими задачами, занумеруем все Сколькими способами можно сделать такое мест, которые могут занимать наши предметы.
    Каждой перестановке соответствует распределение номеров мест на Сколькими способами можно сделать такое классов. В первый класс попадают номера тех мест, на которые попали предметы первого типа, во второй - номера мест предметов второго типа и так далее. Тем самым устанавливается соответствие между перестановками с повторениями и раскладкой номеров мест по "ящикам". Понятно, что формулы решения задач оказались одинаковыми.
    В рассмотренных задачах мы не учитывали порядок, в котором расположены элементы каждой части. В некоторых задачах этот порядок надо учитывать.
    Задача 3. Флаги на мачтах.
    Имеется Сколькими способами можно сделать такое различных сигнальных флагов и Сколькими способами можно сделать такое мачт, на которые их вывешивают. Значение сигнала зависит от того, в каком порядке развешены флаги. Сколькими способами можно развесить флаги, если все флаги должны быть использованы, но некоторые из мачт могут оказаться пустыми?
    Каждый способ развешивания флагов можно осуществить в два этапа. На первом этапе мы переставляем всеми возможными способами данные Сколькими способами можно сделать такое флагов. Это можно сделать Сколькими способами можно сделать такое способами. Затем берем один из способов распределения Сколькими способами можно сделать такое одинаковых флагов по Сколькими способами можно сделать такое мачтам (число этих способов Сколькими способами можно сделать такое ). Пусть этот способ заключается в том, что на первую мачту надо повесить Сколькими способами можно сделать такое флагов, на вторую - Сколькими способами можно сделать такое флагов, ..., на Сколькими способами можно сделать такое Сколькими способами можно сделать такое флагов, где Сколькими способами можно сделать такое . Тогда берем первые Сколькими способами можно сделать такое флагов данной последовательности и развешиваем в полученном порядке на первой мачте; следующие Сколькими способами можно сделать такое флагов развешиваем на второй мачте и т.д. Ясно, что используя все перестановки Сколькими способами можно сделать такое флагов и все способы распределения Сколькими способами можно сделать такое одинаковых флагов по Сколькими способами можно сделать такое мачтам, получим все способы решения поставленной задачи. По правилу произведения получаем, что число способов развешивания флагов равно
    Сколькими способами можно сделать такое
    (5.3)
    Вообще, если имеется Сколькими способами можно сделать такое различных вещей, то число способов распределения этих вещей по Сколькими способами можно сделать такое различным ящикам равно Сколькими способами можно сделать такое .

    Задачи на разбиение чисел

    Теперь мы переходим к задачам, в которых все разделяемые предметы совершенно одинаковы. В этом случае можно говорить не о разделении предметов, а о разбиении натуральных чисел на слагаемые (которые, конечно, тоже должны быть натуральными числами).
    Здесь возникает много различных задач. В одних задачах учитывается порядок слагаемых, в других - нет.
    Задача 4. Отправка бандероли.
    За пересылку бандероли надо уплатить 18 рублей. Сколькими способами можно оплатить ее марками стоимостью 4, 6, и 10 рублей, если два способа, отличающиеся порядком марок, считаются различными (запас марок различного достоинства считаем неограниченным)?
    Обозначим через Задачи на разбиение чисел число способов, которыми можно наклеить марки в 4, 6 и 10 рублей так, чтобы общая стоимость этих марок равнялась Задачи на разбиение чисел . Тогда для Задачи на разбиение чисел справедливо следующее соотношение:
    Задачи на разбиение чисел
    (5.4)
    Пусть имеется некоторый способ наклейки марок с общей стоимостью Задачи на разбиение чисел , и пусть последней наклеена марка стоимостью 4 рубля. Тогда все остальные марки стоят ( Задачи на разбиение чисел ) рубля. Наоборот, присоединяя к любой комбинации марок общей стоимостью ( Задачи на разбиение чисел ) рубля одну четырехрублевую марку, получаем комбинацию марок стоимостью Задачи на разбиение чисел рублей. При этом из разных комбинаций стоимостью ( Задачи на разбиение чисел ) рублей получается разные комбинации стоимостью Задачи на разбиение чисел рублей. Итак, число искомых комбинаций, где последней наклеена марка стоимостью 4 рубля, равно Задачи на разбиение чисел .
    Точно так же доказывается, что число комбинаций, оканчивающихся на на шестирублевую марку, равно Задачи на разбиение чисел , а на десятирублевую марку оканчиваются Задачи на разбиение чисел комбинацией. Поскольку любая комбинация оканчивается на марку одного из указанных типов, то по правилу суммы получаем соотношение 5.4.
    Соотношение 5.4 позволяет свести задачу о наклеивании марок на сумму Задачи на разбиение чисел рублей к задачам о наклеивании марок на меньшие суммы. Но при малых значениях Задачи на разбиение чисел задачу легко решить непосредственно. Простой подсчет показывает, что Задачи на разбиение чисел Равенство Задачи на разбиение чисел означает, что сумму в 0 рублей можно уплатить единственным образом: совсем не наклеивая марок. А сумму в 1,2,3,5,7 и 9 рублей вообще никак нельзя получить с помощью марок стоимостью 4, 6 и 10 рублей.
    Используя значения Задачи на разбиение чисел для Задачи на разбиение чисел , легко найти Задачи на разбиение чисел : Задачи на разбиение чисел После этого находим Задачи на разбиение чисел Задачи на разбиение чисел и т.д. Наконец, получаем значение Задачи на разбиение чисел . Таким образом, марки можно наклеить восемью способами. Эти способы таковы: Задачи на разбиение чисел ; Задачи на разбиение чисел ; Задачи на разбиение чисел ; Задачи на разбиение чисел ; Задачи на разбиение чисел ; Задачи на разбиение чисел ; Задачи на разбиение чисел ; Задачи на разбиение чисел . Отметим, что значения Задачи на разбиение чисел для Задачи на разбиение чисел можно было получить иначе, не приводя непосредственно проверки. Дело в том, что при Задачи на разбиение чисел имеем Задачи на разбиение чисел , поскольку отрицательную сумму нельзя уплатить, наклеивая неотрицательное количество марок. В то же время, как мы видели, Задачи на разбиение чисел . Поэтому Задачи на разбиение чисел .
    Точно так же получаем значение Задачи на разбиение чисел , а для Задачи на разбиение чисел имеем Задачи на разбиение чисел .
    Задача 5.Общая задача о наклейке марок.
    Разобранная задача является частным случаем следующей общей задачи:
    Имеются марки достоинством в Задачи на разбиение чисел . Сколькими способами можно оплатить с их помощью сумму в Задачи на разбиение чисел рублей, если два способа, отличающиеся порядком, считаются различными? Все числа Задачи на разбиение чисел различны, а запас марок неограничен. Здесь на первом месте мы будем указывать число слагаемых, на втором – разбиваемое число и на последнем - ограничения на величину слагаемых.
    В этом случае число Задачи на разбиение чисел способов удовлетворяет соотношению
    Задачи на разбиение чисел
    (5.5)
    При этом Задачи на разбиение чисел , если Задачи на разбиение чисел и Задачи на разбиение чисел . С помощью соотношения 5.5 можно найти Задачи на разбиение чисел для любого Задачи на разбиение чисел , последовательно вычисляя Задачи на разбиение чисел .
    Рассмотрим частный случай этой задачи, когда Задачи на разбиение чисел , Задачи на разбиение чисел . Мы получаем всевозможные разбиения числа Задачи на разбиение чисел на слагаемые Задачи на разбиение чисел , причем разбиения, отличающиеся порядком слагаемых, считаются различными. Обозначим число этих разбиений через Задачи на разбиение чисел . (На первом месте мы будем указывать число слагаемых, на втором - разбиваемое число и на последнем – ограничения на величину слагаемых.) Из соотношения 5.5 следует, что
    Задачи на разбиение чисел
    (5.6)
    При этом Задачи на разбиение чисел и Задачи на разбиение чисел ,если Задачи на разбиение чисел . Вычисление Задачи на разбиение чисел можно упростить, если заметить, что Задачи на разбиение чисел и потому
    Задачи на разбиение чисел
    (5.7)
    Ясно, что слагаемые не могут быть больше Задачи на разбиение чисел . Поэтому Задачи на разбиение чисел равно числу всех разбиений на Задачи на разбиение чисел на натуральные слагаемые (включая и "разбиение" Задачи на разбиение чисел . Если число слагаемых равно Задачи на разбиение чисел , то получаем Задачи на разбиение чисел разбиений. Поэтому Задачи на разбиение чисел Итак, мы доказали, что натуральное число Задачи на разбиение чисел можно разбить на слагаемые Задачи на разбиение чисел способами. Напомним, что при этом учитывается порядок слагаемых.Например, число 5 можно разбить на слагаемые Задачи на разбиение чисел способами.
    5 = 55 = 3 + 1 + 15 = 1 + 2 + 2
    5 = 4 + 15 = 1 + 3+ 15 = 2 + 1 + 1 + 1
    5 = 1 + 45 = 1 + 1 + 35 = 1 + 2 + 1 + 1
    5 = 2 + 35 = 2 + 2 + 15 = 1 + 1 + 2 + 1
    5 = 3 + 25 = 2 + 1 + 25 = 1 + 1 + 1 + 2
    5 = 1 + 1 + 1 + 1 + 1

    Формула включений и исключений

    Пусть имеется Формула включений и исключений предметов, некоторые из которых обладают свойствами Формула включений и исключений. При этом каждый предмет может либо не обладать ни одним из этих свойств, либо обладать одним или несколькими свойствами. Обозначим через Формула включений и исключений количество предметов, обладающих свойствами Формула включений и исключений (и быть может, еще некоторыми из других свойств). Если нужно взять предметы, не обладающие некоторым свойством, то эти свойства пишем со штрихом. Например, через Формула включений и исключений) обозначено количество предметов, обладающих свойствами Формула включений и исключений, но не обладающих свойством Формула включений и исключений (вопрос об остальных свойствах останется открытым). Число предметов, не обладающих ни одним из указанных свойств, обозначается по этому правилу через Формула включений и исключений. Общий закон состоит в том, что
    Формула включений и исключений
    (6.3)

    Здесь алгебраическая сумма распространена на все комбинации свойств Формула включений и исключений(без учета их порядка), причем знак + ставится, если число учитываемых свойств четно, и знак Формула включений и исключений, если это число нечетно. Например, Формула включений и исключений входит со знаком +, а Формула включений и исключений со знаком Формула включений и исключений. Формулу 6.3 называют формулой включений и исключений - сначала исключаются все предметы, обладающие хотя бы одним из свойств Формула включений и исключений, потом включаются предметы, обладающие, по крайней мере, двумя из этих свойств, затем исключаются имеющие, по крайней мере, три и т.д.

    Множества и мультимножества

    Не существует формального определения множества; считается что это понятие первичное и не определяется. Так, можно говорить, что множество есть объединение различных элементов, но при этом мы оставляем неопределяемыми понятия "объединение" и "элементы". Дадим следующее определение множеству: множество - это неупорядоченная совокупность различных объектов или структура данных, используемая для представления множества. Мультимножество есть объединение не обязательно различных элементов; его можно считать множеством, в котором каждому элементу поставлено в соответствие положительное целое число, называемое кратностью.
    Конечное множество Множества и мультимножества будем записывать в следующем виде: Множества и мультимножества
    где Множества и мультимножества - элементы Множества и мультимножества, обязательно различные! Мощность множества Множества и мультимножества обозначается как Множества и мультимножества, для выписанного выше множества мощность записывается так Множества и мультимножества. Если Множества и мультимножества - конечное мультимножество, то будем записывать его в следующем виде:
    Множества и мультимножества
    Здесь все Множества и мультимножества различны и Множества и мультимножества - кратность элемента Множества и мультимножества. В этом случае мощность Множества и мультимножества равна
    Множества и мультимножества
    Наиболее общими операциями на множествах и мультимножествах являются операции объединения и пересечения. Для множеств эти операции будем обозначать Множества и мультимножества и Множества и мультимножества, а для мультимножеств - Множества и мультимножества и Множества и мультимножества. Последовательное и связанное представление последовательностей можно использовать для множеств и мультимножеств очевидным способом. Индуцируя искусственный порядок элементов множества или используя собственный порядок, если он существует, можно рассматривать множество как последовательность. Аналогично, как последовательность можно рассматривать и мультимножество, или, для того чтобы сэкономить место, его можно рассматривать как последовательность пар, каждая из которых состоит из элемента и его кратности.
    Как и для последовательностей, наилучший метод представления множеств или мультимножеств существенно зависит от операций, которые выполняются над ними. Предположим, например, что имеем дело с непересекающимися подмножествами множества Множества и мультимножества и что над ними необходимо выполнить две следующие операции: объединение двух множеств и отыскание подмножества, содержащего данное Множества и мультимножества.
    Таким образом, в любой момент времени имеем разбиение Множества и мультимножества на непустые непересекающиеся подмножества. Рассмотрим эти операции в конце данной лекции.

    С целью идентификации считаем, что каждое из непересекающихся подмножеств множества Множества и мультимножества имеет имя. Имя - это просто один из элементов подмножества, или, иначе, - представитель подмножества. Когда мы будем ссылаться на имя подмножества, то будем под этим подразумевать его представителя. Рассмотрим, например, множествоМножества и мультимножества

    разбитое на четыре непересекающихся подмножества

    Множества и мультимножества

    (6.1)
    В каждом из подмножеств, взятый в скобки элемент является его именем. Если нам нужно найти подмножество, в котором содержится восьмерка, искомым ответом будет 7, то есть имя подмножества, содержащего восьмерку. Если нужно взять объединение подмножеств с именами 2 и 10, получим разбиение множества Множества и мультимножества

    следующего вида:

    Множества и мультимножества

    Именем множества Множества и мультимножестваМножества и мультимножества

    Множества и мультимножества может быть или 2, или 10.Предполагаем, что вначале имеется разбиение множества Множества и мультимножества

    на Множества и мультимножества подмножеств, каждое из которых состоит из одного элемента
    Множества и мультимножества

    (6.2)
    и имя каждого из них есть просто этот единственный элемент. Это разбиение преобразуется путем применения операций объединения вперемешку с операциями отыскания. Такая кажущаяся на первый взгляд надуманной задача чрезвычайно полезна в определенных комбинаторных алгоритмах; пример ее полезности виден в "жадном" алгоритме (лекция 16).

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

    и Множества и мультимножества образует новое подмножество, содержащее все элементы множеств Множества и мультимножества и Множества и мультимножества. Процедура (операция)Множества и мультимножества

    выдает имя множества, содержащего Множества и мультимножества. Например, если нужно множество,содержащее Множества и мультимножества, объединить с множеством, содержащим Множества и мультимножества, необходимо выполнить следующую последовательность операторов: Множества и мультимножества

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


    Такой структурой данных является представление в виде леса с указателями отца, как показано на рис. 4.5 лекции 4. Каждый элемент Множества и мультимножества множества будет узлом леса, а отцом его будет элемент из того же подмножества, что и Множества и мультимножества. Если элемент не имеет отца, то есть является корнем, то он будет именем своего подмножества. В соответствии с этим разбиение 5.1 может быть представлено так:

    Множества и мультимножества
    Рис. 6.1.  Представление разбиения

    При таком представлении процедура (операция) Множества и мультимножества состоит в переходах по указателям отцов от Множества и мультимножества до корня, то есть имени, его подмножества. Процедура (операция) Множества и мультимножества состоит в связывании вместе некоторым образом деревьев, имеющих корни Множества и мультимножества и Множества и мультимножества. Например, такую связь можно осуществить, сделав Множества и мультимножества отцом Множества и мультимножества.

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

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

    Если операция Множества и мультимножества выполняется путем назначения Множества и мультимножества отцом Множества и мультимножества, то после Множества и мультимножества операций объединения может получиться лес, показанный ниже.

    Множества и мультимножества

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

    Примеры программы

    Программа 1. Решето Эратосфена.
    {В примере, иллюстрирующем работу с множествами, реализуется алгоритм выделения из первой сотни натуральных чисел всех простых чисел. В основе алгоритма лежит прием "решета Эратосфена". Алгоритм написан на языке программирования Turbo-Pascal.}
    Uses crt; Const N=100; {количество элементов исходного множества} Type SetN=set of 1..N; var n1, next, I : word; {вспомогательные переменные } BeginSet, {исходное множество } PrimerSet: SetN; {множество простых чисел } Begin Clrscr; {почистить экран} BeginSet:=[2..N]; {создать исходное множество} PrimerSet:=[1]; {первое простое число} next:=2; {следующее простое число} while BeginSet <> [ ] do {начало основного цикла} begin n1:=next; {n1-число, кратное очередному простому (next)} while n1<=N do {цикл удаления из исходного множества непростых чисел} begin BeginSet:=BeginSet-[n1]; n1:=n1+next {следующее кратное} end; {конец цикла удаления} repeat {получить следующее простое число, которое есть первое не вычеркнутое из исходного множества} inc(next) until(next in BeginSet) or (next > N) end; {конец основного цикла} {вывод результата} textcolor(15); {задание цвета} for I:=1 to N do if i in PrimerSet then write(I:8); readlen; end.
    Программа 2. Простые числа в порядке убывания от 200.
    {Находит и пишет все простые числа в порядке убывания от 2 до 200. Алгоритм написан на языке программирования Turbo-Pascal.}
    Uses crt; const n=197; var
    i,q,w,e,r,t:integer; prost:array[1..n] of integer;
    begin clrscr; e:=1; r:=0; for q:=1 to n do begin r:=0; for w:=2 to n-1 do if (q<>w) and (q mod w = 0) then r:=1; {prost[e]:=q; e:=e+1;}
    if r=0 then begin{begin write(q,' ');}
    prost[e]:=q; e:=e+1; end; end;
    for i:=e downto 2 do begin write (prost[i],' '); if wherex>70 then writeln;
    end; readln; end.
    Программа 3. Поиск литер в строке.
    {Поиск числа вхождений в данную сроку литер a, c, e, h. Алгоритм написан на языке программирования Turbo-Pascal.}
    Uses crt; type liter_set = set of char;
    var c:integer; let: liter_set; a:char;
    begin clrscr; let:=['a','c','e','h'];
    repeat a:=readkey; write(a); if a in let then c:=c+1; until a = '.'; writeln; writeln('Общее число вхожений литер a,c,e,h в вашу запись:',c); readln;
    end.
    Примеры программы

    Решето Эратосфена

    Одной из самых больших загадок математики является расположение простых чисел в ряду всех натуральных чисел. Иногда два простых числа идут через одно, (например, 17 и 19, 29 и 31), а иногда подряд идет миллион составных чисел. Сейчас ученые знают уже довольно много о том, сколько простых чисел содержится среди Решето Эратосфена первых натуральных чисел. В этих подсчетах весьма полезным оказался метод, восходящий еще к древнегреческому ученому Эратосфену. Он жил в третьем веке до новой эры в Александрии.
    Эратосфен занимался самыми различными вопросами - ему принадлежат интересные исследования в области математики, астрономии и других наук. Впрочем, такая разносторонность привела его к некоторой поверхностности. Современники несколько иронически называли Эратосфена "во всем второй": второй математик после Евклида, второй астроном после Гиппарха и т.д.
    В математике Эратосфена интересовал как раз вопрос о том, как найти все простые числа среди натуральных чисел от 1 до Решето Эратосфена. (Эратосфен считал 1 простым числом. Сейчас математики считают 1 числом особого вида, которое не относится ни к простым, ни к составным числам.) Он придумал для этого следующий способ. Сначала вычеркивают все числа, делящиеся на 2 (исключая само число 2). Потом берут первое из оставшихся чисел (а именно 3). Ясно, что это число - простое. Вычеркивают все идущие после него числа, делящиеся на 3. Первым оставшимся числом будет 5. Вычеркивают все идущие после него числа, делящиеся на 5, и т.д. Числа, которые уцелеют после всех вычеркиваний, и являются простыми. Так как во времена Эратосфена писали на восковых табличках и не вычеркивали, а "выкалывали" цифры, то табличка после описанного процесса напоминала решето. Поэтому метод Эратосфена для нахождения простых чисел получил название "решето Эратосфена".
    Подсчитаем, сколько останется чисел в первой сотне, если мы вычеркнем по методу Эратосфена числа, делящиеся на 2, 3 и 5. Иными словами, поставим такой вопрос: сколько чисел в первой сотне не делится ни на одно из чисел 2, 3, 5? Эта задача решается по формуле включения и исключения.

    Обозначим через Решето Эратосфена свойство числа делиться на 2, через Решето Эратосфена - свойство делимости на 3 и через Решето Эратосфена - свойство делимости на 5. Тогда Решето Эратосфена

    означает, что число делится на 6, Решето Эратосфена означает, что оно делится на 10, и Решето Эратосфена - оно делится на 15. Наконец, Решето Эратосфена означает, что число делится на 30. Надо найти, сколько чисел от 1 до 100 не делится ни на 2, ни на 3, ни на 5, то есть не обладает ни одним из свойств Решето Эратосфена, Решето Эратосфена, Решето Эратосфена. По формуле 6.3 имеем Решето Эратосфена

    Но чтобы найти, сколько чисел от 1 до Решето Эратосфена делится на Решето Эратосфена, надо разделить Решето Эратосфена на Решето Эратосфена и взять целую часть получившегося частного. Поэтому

    Решето Эратосфена

    Решето Эратосфена

    и значит,

    Решето Эратосфена

    Таким образом, 32 числа от 1 до 100 не делятся ни на 2, ни на 3, ни на 5. Эти числа и уцелеют после первых трех шагов процесса Эратосфена. Кроме них останутся сами числа 2, 3 и 5. Всего останется 35 чисел.

    А из первой тысячи после первых трех шагов процесса Эратосфена останется 335 чисел. Это следует из того, что в этом случае Решето Эратосфена

    Решето Эратосфена

    Другой метод доказательства

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

    что и числа Фибоначчи. В самом деле, возьмем любую Другой метод доказательства-последовательность нулей и единиц, удовлетворяющую условию, что никакие две единицы не идут подряд. Она может оканчиваться или на 0, или на 1. Если она оканчивается на 0, то, отбросив его, получим Другой метод доказательства-последовательность, удовлетворяющую нашему условию. Если взять любую Другой метод доказательства- последовательность нулей и единиц, в которой подряд не идут две единицы, и приписать к ней нуль, то получим Другой метод доказательства-последовательность с тем же свойством. Таким образом доказано, что число последовательностей, оканчивающихся на нуль, равно Другой метод доказательства.
    Пусть теперь последовательность оканчивается на 1. Так как двух единиц подряд быть не может, то перед этой единицей стоит нуль. Иными словами, последовательность оканчивается на 01. Остающаяся же после отбрасывания 0 и 1 Другой метод доказательства-последовательность может быть любой, лишь бы в ней не шли подряд две единицы. Поэтому число последовательностей, оканчивающихся единицей, равно Другой метод доказательства. Но каждая последовательность оканчивается или на 0, или на 1. В силу правила суммы получаем, что Другой метод доказательства.
    Таким образом, получено то же самое рекуррентное соотношение. Отсюда еще не вытекает, что числа Другой метод доказательства и Другой метод доказательства совпадают.

    Перестановки

    При составлении размещений без повторений из Перестановки элементов по Перестановки мы получили расстановки, отличающиеся друг от друга и составом, и порядком элементов. Но если брать расстановки, в которые входят все Перестановки
    элементов, то они могут отличаться друг от друга лишь порядком входящих в них элементов. Такие расстановки называют перестановками из n элементов, или, короче, Перестановки-перестановками.

    Процесс последовательных разбиений

    Для решения комбинаторных задач часто применяют метод, использованный в предыдущем пункте: устанавливают для данной задачи рекуррентное соотношение и показывают, что оно совпадает с рекуррентным соотношением для другой задачи, решение которой нам уже известно. Если при этом совпадают и начальные члены последовательностей в достаточном числе, то обе задачи имеют одинаковые решения.
    Применим описанный прием для решения следующей задачи.
    Пусть дано некоторое множество из Процесс последовательных разбиений предметов, стоящих в определенном порядке. Разобьем это множество на две непустые части так, чтобы одна из этих частей лежала левее второй (то есть, скажем, одна часть состоит из элементов от первого до Процесс последовательных разбиений- го, а вторая - из элементов от Процесс последовательных разбиений-го до Процесс последовательных разбиений-го). После этого каждую из частей таким же образом разобьем на две непустые части (если одна из частей состоит уже из одного предмета, она не подвергается дальнейшим разбиениям). Этот процесс продолжается до тех пор, пока не получим части, состоящие из одного предмета каждая. Сколько существует таких процессов разбиения (два процесса считаются различными, если хотя бы на одном шагу они приводят к разным результатам)?
    Обозначим число способов разбиения для множества из Процесс последовательных разбиений
    предметов через Процесс последовательных разбиений. На первом шагу это множество может быть разбито Процесс последовательных разбиений способами (первая часть может содержать один предмет, два предмета,…,Процесс последовательных разбиений предметов). В соответствии с этим множество всех процессов разбиений распадается на Процесс последовательных разбиений классов - в Процесс последовательных разбиений- класс входят процессы, при которых первая часть состоит из Процесс последовательных разбиений предметов.
    Подсчитаем число процессов в Процесс последовательных разбиений-м классе. В первой части содержится Процесс последовательных разбиений элементов. Поэтому ее можно разбивать далее Процесс последовательных разбиений различными процессами. Вторая же часть содержит Процесс последовательных разбиений элементов, и ее можно разбивать далее Процесс последовательных разбиений
    процессами. По правилу произведения получаем, что Процесс последовательных разбиений- класс состоит из Процесс последовательных разбиений различных процессов. По правилу суммы отсюда вытекает, что
    Процесс последовательных разбиений
    (7.6)

    Таким образом получено рекуррентное соотношение для Процесс последовательных разбиений. Двоичный поиск, поиск делением пополам. Поиском по числам Фибоначчи называется поиск, основанный на том, что область поиска делится в точках, являющихся числами Фибоначчи.

    Размещения без повторений

    Имеется Размещения без повторений различных предметов. Сколько из них можно составить Размещения без повторений-расстановок? При этом две расстановки считаются различными, если они либо отличаются друг от друга хотя бы одним элементом, либо состоят из одних и тех же элементов, но расположенных в разном порядке. Такие расстановки называют размещениями без повторений, а их число обозначают Размещения без повторений. При составлении Размещения без повторений-размещений без повторений из Размещения без повторений предметов нам надо сделать Размещения без повторений выборов. На первом шагу можно выбрать любой из имеющихся Размещения без повторений предметов. Если этот выбор уже сделан, то на втором шагу приходится выбирать из оставшихся Размещения без повторений предметов. На Размещения без повторений- м шагу Размещения без повторений предметов. Поэтому по правилу произведения получаем, что число Размещения без повторений-размещений без повторения из Размещения без повторений предметов выражается следующим образом: Размещения без повторений

    Рекуррентные соотношения

    При решении многих комбинаторных задач пользуются методом сведения данной задачи к задаче, касающейся меньшего числа предметов. Метод сведения к аналогичной задаче для меньшего числа предметов называется методом рекуррентных соотношений (от латинского "recurrere" - "возвращаться").
    Понятие рекуррентных соотношений проиллюстрируем классической проблемой, которая была поставлена около 1202 года Леонардо из Пизы, известным как Фибоначчи. Важность чисел Фибоначчи для анализа комбинаторных алгоритмов делает этот пример весьма подходящим.
    Фибоначчи поставил задачу в форме рассказа о скорости роста популяции кроликов при следующих предположениях. Все начинается с одной пары кроликов. Каждая пара становится фертильной через месяц, после чего каждая пара рождает новую пару кроликов каждый месяц. Кролики никогда не умирают, и их воспроизводство никогда не прекращается.
    Пусть Рекуррентные соотношения - число пар кроликов в популяции по прошествии Рекуррентные соотношения месяцев, и пусть эта популяция состоит из Рекуррентные соотношения пар приплода и Рекуррентные соотношения "старых" пар, то есть Рекуррентные соотношения. Таким образом, в очередном месяце произойдут следующие события: Рекуррентные соотношения. Старая популяция в Рекуррентные соотношения-й момент увеличится на число родившихся в момент времени Рекуррентные соотношения. Рекуррентные соотношения. Каждая старая пара в момент времени Рекуррентные соотношения производит пару приплода в момент времени Рекуррентные соотношения. В последующий месяц эта картина повторяется: Рекуррентные соотношения
    Рекуррентные соотношения
    Объединяя эти равенства, получим следующее рекуррентное соотношение: Рекуррентные соотношения
    Рекуррентные соотношения
    (7.1)

    Выбор начальных условий для последовательности чисел Фибоначчи не важен; существенное свойство этой последовательности определяется рекуррентным соотношением. Будем предполагать Рекуррентные соотношения (иногда Рекуррентные соотношения).
    Рассмотрим эту задачу немного иначе.
    Пара кроликов приносит раз в месяц приплод из двух крольчат (самки и самца), причем новорожденные крольчата через два месяца после рождения уже приносят приплод. Сколько кроликов появится через год, если в начале года была одна пара кроликов?
    Из условия задачи следует, что через месяц будет две пары кроликов. Через два месяца приплод даст только первая пара кроликов, и получится 3 пары.
    А еще через месяц приплод дадут и исходная пара кроликов, и пара кроликов, появившаяся два месяца тому назад. Поэтому всего будет 5 пар кроликов. Обозначим через Рекуррентные соотношения

    количество пар кроликов по истечении Рекуррентные соотношения месяцев с начала года. Ясно, что через Рекуррентные соотношения

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

    (7.2)
    Так как, по условию, Рекуррентные соотношения и Рекуррентные соотношения, то последовательно находимРекуррентные соотношения и т.д.

    В частности, Рекуррентные соотношения.

    Числа Рекуррентные соотношения называются числами Фибоначчи. Они обладают целым рядом замечательных свойств. Теперь выведем выражение этих чисел через Рекуррентные соотношения. Для этого установим связь между числами Фибоначчи и следующей комбинаторной задачей.

    Найти число Рекуррентные соотношения последовательностей,состоящих из нулей и единиц, в которых никакие две единицы не идут подряд.

    Чтобы установить эту связь, возьмем любую такую последовательность и сопоставим ей пару кроликов по следующему правилу: единицам соответствуют месяцы появления на свет одной из пар "предков" данной пары (включая и исходную), а нулями - все остальные месяцы. Например, последовательность 010010100010 устанавливает такую "генеалогию": сама пара появилась в конце 11-го месяца, ее родители - в конце 7-го месяца, "дед" - в конце 5-го месяца и "прадед" - в конце второго месяца. Исходная пара кроликов тогда зашифровывается последовательностью 000000000000.

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

    Установленная связь показывает, что число Рекуррентные соотношения-последовательностей, обладающих указанным свойством, равно Рекуррентные соотношения.

    Докажем теперь, что
    Рекуррентные соотношения

    (7.3)
    где Рекуррентные соотношения, если Рекуррентные соотношения нечетно, и Рекуррентные соотношения, если Рекуррентные соотношения четно.


    Иными словами, Рекуррентные соотношения - целая часть числа Рекуррентные соотношения ( в дальнейшем будем обозначать целую часть числа Рекуррентные соотношения через Рекуррентные соотношения; таким образом, Рекуррентные соотношения).

    В самом деле, Рекуррентные соотношения - это число всех Рекуррентные соотношения- последовательностей из 0 и 1, в которых никакие две единицы не стоят рядом. Число же таких последовательностей, в которые входит ровно Рекуррентные соотношения единиц и Рекуррентные соотношения нулей, равно Рекуррентные соотношения. Так как при этом должно выполняться неравенство Рекуррентные соотношения, то Рекуррентные соотношения изменяется от 0 до Рекуррентные соотношения. Применяя правило суммы, приходим к соотношению (7.3).

    Равенство (7.3) можно доказать и иначе. Положим Рекуррентные соотношения

    где Рекуррентные соотношения. Из равенства Рекуррентные соотношения

    легко следует, что
    Рекуррентные соотношения

    (7.4)
    Кроме того, ясно, что Рекуррентные соотношенияиРекуррентные соотношения. Так как обе последовательности Рекуррентные соотношения и Рекуррентные соотношения

    удовлетворяют рекуррентному соотношению Рекуррентные соотношения, то имеем Рекуррентные соотношения

    и, вообще, Рекуррентные соотношения.

    Сочетания

    В тех случаях, когда нас не интересует порядок элементов в комбинации, а интересует лишь ее состав, говорят о сочетаниях. Итак, Сочетания-сочетаниями из Сочетания
    элементов называют всевозможные Сочетания- расстановки, составленные из этих элементов и отличающиеся друг от друга составом, но не порядком элементов. Число Сочетания-сочетаний, которое можно составить из Сочетания элементов, обозначают через Сочетания.
    Формула для числа сочетаний получается из формулы для числа размещений. В самом деле, составим сначала все Сочетания-сочетания из Сочетания
    элементов, а потом переставим входящие в каждое сочетание элементы всеми возможными способами. При этом получается, что все Сочетания-размещения из Сочетания элементов, причем каждое только по одному разу. Но из каждого Сочетания- сочетания можно сделать Сочетания перестановок, а число этих сочетаний равно Сочетания. Значит справедлива формула Сочетания Из этой формулы находим, что Сочетания

    Затруднение мажордома"

    Бывают комбинаторные задачи, в которых приходится составлять не одно рекуррентное соотношение, а систему соотношений, связывающую несколько последовательностей. Эти соотношения выражают Затруднение мажордома-у члены последовательностей через предыдущие члены не только данной, но и остальных последовательностей.
    Задача: "Затруднение мажордома". Однажды мажордом короля Артура обнаружил, что к обеду за круглым столом приглашено 6 пар враждующих рыцарей. Сколькими способами можно рассадить их так, чтобы никакие два врага не сидели рядом?
    Если мы найдем какой-то способ рассадки рыцарей, то, пересаживая их по кругу, получим еще 11 способов. Мы не будем сейчас считать различными способы, получающиеся друг из друга такой циклической пересадкой.
    Введем следующие обозначения. Пусть число рыцарей равно Затруднение мажордома. Через Затруднение мажордома обозначим число способов рассадки, при которых никакие два врага не сидят рядом. Через Затруднение мажордома обозначим число способов, при которых рядом сидит ровно одна пара врагов, и через Затруднение мажордома - число способов, при которых есть ровно две пары враждующих соседей.
    Выведем сначала формулу, выражающую Затруднение мажордома через Затруднение мажордома. Пусть Затруднение мажордома пар рыцарей посажены так, что никакие два врага не сидят рядом. Мы будем считать, что все враждующие пары рыцарей занумерованы. Попросим встать из-за стола пару рыцарей с номером Затруднение мажордома. Тогда возможны три случая: среди оставшихся за столом нет одной пары соседей- врагов, есть одна такая пара и есть две такие пары (ушедшие рыцари могли разделять эти пары). Мы считаем, что Затруднение мажордома. При Затруднение мажордома
    последующие рассуждения теряют силу.
    Выясним теперь, сколькими способами можно снова посадить ушедших рыцарей за стол так, чтобы после этого не было одной пары соседей-врагов.
    Проще всего посадить их, если за столом рядом сидят две пары врагов. В этом случае один из вновь пришедших садится между рыцарями первой пары, а другой – между рыцарями второй пары. Это можно сделать двумя способами. Но так как число способов рассадки Затруднение мажордома рыцарей, при которых две пары соседей оказались врагами, равно Затруднение мажордома, то всего получилось Затруднение мажордома способов.
    Пусть теперь рядом сидит только одна пара врагов.
    Один из вернувшихся должен сесть между ними. Тогда за столом окажутся Затруднение мажордома рыцарей, между которыми есть Затруднение мажордома мест. Из них два места - рядом с только что севшим гостем – запретны для второго рыцаря, и ему остается Затруднение мажордома мест. Так как первым может войти любой из двух вышедших рыцарей, то получается Затруднение мажордома способов рассадки. Но число случаев, когда Затруднение мажордома рыцарей сели так, чтобы ровно одна пара врагов оказалась соседями, ровно Затруднение мажордома. Поэтому мы получаем Затруднение мажордома способов посадить гостей требуемым образом.
    Наконец, пусть никакие два врага не сидели рядом. В этом случае первый рыцарь садится между любыми двумя гостями - это он может сделать Затруднение мажордома способами. После этого для его врага останется Затруднение мажордома мест - он может занять любое место, кроме двух мест, соседних с только что севшим рыцарем. Таким образом, если Затруднение мажордома рыцарей уже сидели нужным образом, то вернувшихся гостей можно посадить Затруднение мажордома
    способами. Как уже отмечалось, разработанными случаями исчерпываются все возможности. Поэтому имеет место рекуррентное соотношение
    Затруднение мажордома
    (7.7)

    Этого соотношения пока недостаточно, чтобы найти Затруднение мажордома для всех значений Затруднение мажордома. Надо еще узнать, как выражаются Затруднение мажордома через Затруднение мажордома.
    Предположим, что среди Затруднение мажордома рыцарей оказалась ровно одна пара врагов-соседей. Мы знаем, что это может произойти в Затруднение мажордома случаях. Во избежание ссоры попросим их удалиться из-за стола. Тогда останется Затруднение мажордома рыцарей, причем возможно одно из двух: либо среди оставшихся нет врагов-соседей, либо есть ровно одна пара таких врагов - до ухода покинувших зал они сидели по обе стороны от них и теперь оказались рядом. Во втором случае ушедших можно посадить обратно только на старое место - иначе появится вторая пара враждующих соседей. Но так как Затруднение мажордома рыцарей можно посадить Затруднение мажордома способами так, чтобы была только одна пара враждующих соседей, то мы получаем Затруднение мажордома вариантов (возвратившихся рыцарей можно поменять местами). В первом же случае можно посадить ушедших между любыми двумя рыцарями, то есть Затруднение мажордома
    способами, а так как их еще можно поменять местами, то получится Затруднение мажордома способов. Комбинируя их со всеми способами посадки Затруднение мажордома пар рыцарей, при которых нет соседей врагов, получаем Затруднение мажордома способов.


    Наконец, номер ушедшей и вернувшейся пары рыцарей мог быть любым от 1 до Затруднение мажордома. Отсюда вытекает, что рекуррентное соотношение для Затруднение мажордома имеет вид
    Затруднение мажордома
    (7.8)

    Наконец, разберем случай, когда среди Затруднение мажордома рыцарей было две пары врагов-соседей. Номера этих пар можно выбрать Затруднение мажордома способами. Заменим каждую пару одним новым рыцарем, причем будем считать новых двух рыцарей врагами. Тогда за столом будут сидеть Затруднение мажордома
    рыцарей, причем среди них либо не будет ни одной пары врагов-соседей (если новые рыцари не сидят рядом), либо только одна такая пара.
    Первый вариант может быть в Затруднение мажордома случаях. Вернуться к исходной компании мы можем 4 способами благодаря возможности изменить порядок рыцарей в каждой паре. Поэтому первый вариант приводит к Затруднение мажордома
    способами.
    Второй же вариант может быть в Затруднение мажордома случаях. Имеется Затруднение мажордома случаев, когда какая-нибудь пара врагов сидит рядом. Если указать, какая именно пара должна сидеть рядом, получим в Затруднение мажордома раз меньше случаев.
    Здесь тоже можно вернуться к исходной компании 4 способами, и мы получаем всего Затруднение мажордома способов. Отсюда вытекает, что при Затруднение мажордома
    Затруднение мажордома
    (7.9)

    Мы получили систему рекуррентных соотношений
    Затруднение мажордома
    (7.10)

    Затруднение мажордома
    (7.11)

    Затруднение мажордома
    (7.12)

    Они справедливы при Затруднение мажордома. Но простой подсчет показывает, что Затруднение мажордома. Поэтому из соотношений 7.10-7.12 вытекает, что Затруднение мажордома. Продолжая далее, находим, что гостей можно посадить за стол требуемым образом Затруднение мажордома способами. Затруднение мажордома

    Линейные рекуррентные соотношения с постоянными коэффициентами

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

    где Линейные рекуррентные соотношения с постоянными коэффициентами- некоторые числа. Такие соотношения называют линейными рекуррентными соотношениями с постоянными коэффициентами.
    Сначала рассмотрим, как решаются такие соотношения при Линейные рекуррентные соотношения с постоянными коэффициентами, то есть изучим соотношение вида
    Линейные рекуррентные соотношения с постоянными коэффициентами
    (8.4)

    Решение этих соотношений основано на следующих двух утверждениях.
  • Если Линейные рекуррентные соотношения с постоянными коэффициентами и Линейные рекуррентные соотношения с постоянными коэффициентами являются решениями рекуррентного соотношения (8.4), то при любых числах Линейные рекуррентные соотношения с постоянными коэффициентами и Линейные рекуррентные соотношения с постоянными коэффициентами
    последовательность Линейные рекуррентные соотношения с постоянными коэффициентами также является решением этого соотношения.
    В самом деле, по условию, имеем Линейные рекуррентные соотношения с постоянными коэффициентами
    Линейные рекуррентные соотношения с постоянными коэффициентами
    Умножим эти равенства на Линейные рекуррентные соотношения с постоянными коэффициентами и Линейные рекуррентные соотношения с постоянными коэффициентами соответственно и сложим полученные тождества. Получим, что Линейные рекуррентные соотношения с постоянными коэффициентами
    А это означает, что Линейные рекуррентные соотношения с постоянными коэффициентами является решением соотношения(8.4).
  • Если Линейные рекуррентные соотношения с постоянными коэффициентами является корнем квадратного уравнения
    Линейные рекуррентные соотношения с постоянными коэффициентамито последовательность Линейные рекуррентные соотношения с постоянными коэффициентами
    является решением рекуррентного соотношения Линейные рекуррентные соотношения с постоянными коэффициентами
    В самом деле, если Линейные рекуррентные соотношения с постоянными коэффициентами, то Линейные рекуррентные соотношения с постоянными коэффициентами и Линейные рекуррентные соотношения с постоянными коэффициентами. Подставляя эти значения в соотношение (8.4), получаем равенство Линейные рекуррентные соотношения с постоянными коэффициентами
    Оно справедливо, так как по условию имеем Линейные рекуррентные соотношения с постоянными коэффициентами. Заметим, что наряду с последовательностью Линейные рекуррентные соотношения с постоянными коэффициентами любая последовательность вида Линейные рекуррентные соотношения с постоянными коэффициентами
    также является решением соотношения (8.4). Для доказательства достаточно использовать утверждение (8.4), положив в нем Линейные рекуррентные соотношения с постоянными коэффициентами

  • Из утверждений 1 и 2 вытекает следующее правило решения линейных рекуррентных соотношений второго порядка с постоянными коэффициентами.
    Пусть дано рекуррентное соотношение
    Линейные рекуррентные соотношения с постоянными коэффициентами
    (8.5)

    Составим квадратное уравнение
    Линейные рекуррентные соотношения с постоянными коэффициентами
    (8.6)

    которое называется характеристическим для данного соотношения. Если это уравнение имеет два различных корня Линейные рекуррентные соотношения с постоянными коэффициентами, то общее решение соотношения (8.5) имеет вид Линейные рекуррентные соотношения с постоянными коэффициентами
    Чтобы доказать это правило, заметим сначала, что по утверждению 2 Линейные рекуррентные соотношения с постоянными коэффициентами являются решениями нашего соотношения. А тогда по утверждению 1 и Линейные рекуррентные соотношения с постоянными коэффициентами является его решением. Надо только показать, что любое решение соотношения (8.5) можно записать в этом виде. Но любое решение соотношения второго порядка определяется значениями Линейные рекуррентные соотношения с постоянными коэффициентами. Поэтому достаточно показать, что система уравнений Линейные рекуррентные соотношения с постоянными коэффициентами
    Линейные рекуррентные соотношения с постоянными коэффициентами
    имеет решение при любых Линейные рекуррентные соотношения с постоянными коэффициентами.
    Этими решениями являются Линейные рекуррентные соотношения с постоянными коэффициентами

    Линейные рекуррентные соотношения с постоянными коэффициентами

    (Случай, когда оба корня уравнения (8.6) совпадут друг с другом, разберем в следующем пункте.)

    Пример на доказанное правило.

    При изучении чисел Фибоначчи мы пришли к рекуррентному соотношению
    Линейные рекуррентные соотношения с постоянными коэффициентами

    (8.7)
    Для него характеристическое уравнение имеет вид Линейные рекуррентные соотношения с постоянными коэффициентами

    Корнями этого квадратного уравнения являются числа Линейные рекуррентные соотношения с постоянными коэффициентами

    Поэтому общее решение соотношения Фибоначчи имеет вид
    Линейные рекуррентные соотношения с постоянными коэффициентами

    (8.8)
    (Мы воспользовались сделанным выше замечанием и взяли показатели Линейные рекуррентные соотношения с постоянными коэффициентами

    вместо Линейные рекуррентные соотношения с постоянными коэффициентами). Мы называли числами Фибоначчи решения соотношения (8.7), удовлетворяющее начальным условиям Линейные рекуррентные соотношения с постоянными коэффициентами( то есть последовательность Линейные рекуррентные соотношения с постоянными коэффициентами). Часто бывает более удобно добавить к этой последовательности вначале числа 0 и 1, то есть рассматривать последовательность Линейные рекуррентные соотношения с постоянными коэффициентами Ясно, что эта последовательность удовлетворяет тому же самому рекуррентному соотношению (8.6) и начальным условиям Линейные рекуррентные соотношения с постоянными коэффициентами. Полагая в формуле (8.6) Линейные рекуррентные соотношения с постоянными коэффициентами, получаем для Линейные рекуррентные соотношения с постоянными коэффициентами

    систему уравнений Линейные рекуррентные соотношения с постоянными коэффициентами

    Линейные рекуррентные соотношения с постоянными коэффициентами

    Отсюда находим, что Линейные рекуррентные соотношения с постоянными коэффициентами и потому
    Линейные рекуррентные соотношения с постоянными коэффициентами

    (8.9)
    На первый взгляд кажется удивительным, что это выражение при всех натуральных значениях Линейные рекуррентные соотношения с постоянными коэффициентами принимает целые значения.

    Производящие функции

    В комбинаторных задачах на подсчет числа объектов при наличии некоторых ограничений искомым решением часто является последовательность Производящие функции, где Производящие функции- число искомых объектов "размерности" Производящие функции. Например, если мы ищем число разбиений числа, то можем принять Производящие функции, если ищем число подмножеств Производящие функции-элементного множества, то Производящие функции и т.д. В этом случае удобно последовательности Производящие функции, поставить в соответствие формальный ряд
    Производящие функции
    (8.12)

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

    операцию умножения на число (действительное или комплексное):
    Производящие функции
    (8.14)

    и произведение Коши
    Производящие функции
    (8.15)

    где
    Производящие функции
    (8.16)

    Если Производящие функции для Производящие функции, то ряд (8.12) будем отождествлять с многочленом Производящие функции. Из математического анализа известно, что если ряд (8.12) сходится в некоторой окрестности нуля, то его сумма Производящие функции является аналитической функцией в этой окрестности и
    Производящие функции
    (8.17)

    (Производящие функции обозначает значение Производящие функции-й производной функции Производящие функции для Производящие функции; ряд 8.12 - это не что иное, как ряд Маклорена функции Производящие функции). Более того, когда Производящие функции являются аналитическими функциями в окрестности нуля, то формулы (8.13)-(8.16) будут справедливы, если Производящие функции трактовать как значения функций Производящие функции в точке Производящие функции, а ряды понимать в обычном смысле, т.е. так, как в математическом анализе. Это сохраняющее операции взаимно однозначное соответствие между рядами, сходящимися в окрестности нуля, и функциями, аналитическими в окрестности нуля, позволяет отождествить формальный ряд (8.12) с определенной через него аналитической функцией в случае рядов, сходящихся в окрестности нуля (несмотря на то, что ряды мы будем трактовать всегда как формальные ряды, то есть только как формальную запись их коэффициентов). Таким образом, будем писать, например, Производящие функции
    Производящие функции
    и т.д. Производящие функции

    Решение рекуррентных соотношений

    Будем говорить, что рекуррентное соотношение имеет порядок Решение рекуррентных соотношений, если оно позволяет выразить Решение рекуррентных соотношений через Решение рекуррентных соотношений. Например, Решение рекуррентных соотношений
    рекуррентное соотношение второго порядка, а Решение рекуррентных соотношений- рекуррентное соотношение третьего порядка. Если задано рекуррентное соотношение Решение рекуррентных соотношений-го порядка, то ему удовлетворяет бесконечно много последовательностей. Дело в том, что первые Решение рекуррентных соотношений
    элементов последовательности можно задать совершенно произвольно - между ними нет никаких соотношений. Но если первые Решение рекуррентных соотношений элементов заданы, то все остальные элементы определяются совершенно однозначно - элемент Решение рекуррентных соотношений выражается в силу рекуррентного соотношения через Решение рекуррентных соотношений элемент Решение рекуррентных соотношений - через Решение рекуррентных соотношений и т.д.
    Пользуясь рекуррентным соотношением и начальными членами, можно один за другим выписывать члены последовательности, причем рано или поздно получим любой ее член. Однако при этом придется выписать и все предыдущие члены - ведь не узнав их, мы не узнаем и последующих членов. Но во многих случаях нужно узнать только один определенный член последовательности, а остальные не нужны. В этих случаях удобнее иметь явную формулу для Решение рекуррентных соотношений-го члена последовательности. Некоторая последовательность является решением данного рекуррентного соотношения, если при подстановке этой последовательности соотношение тождественно выполняется. Например, последовательность Решение рекуррентных соотношений
    является одним из решений рекуррентного соотношения Решение рекуррентных соотношений
    В самом деле, общий член этой последовательности имеет вид Решение рекуррентных соотношений. Значит, Решение рекуррентных соотношений. Но при любом Решение рекуррентных соотношений имеет место тождество Решение рекуррентных соотношений. Поэтому Решение рекуррентных соотношений является решением указанного соотношения.
    Решение рекуррентного соотношения Решение рекуррентных соотношений-го порядка называется общим, если оно зависит от Решение рекуррентных соотношений произвольных постоянных Решение рекуррентных соотношений
    и путем подбора этих постоянных можно получить любое решение данного соотношения. Например, для соотношения
    Решение рекуррентных соотношений
    (8.1)

    общим решением будет
    Решение рекуррентных соотношений
    (8.2)

    В самом деле, легко проверить, что последовательность (8.2) обращает (8.1) в тождество. Поэтому нам надо только показать, что любое решение нашего соотношения можно представить в виде (8.2). Но любое решение соотношения (8.1) однозначно определяется значениями Решение рекуррентных соотношений и Решение рекуррентных соотношений. Поэтому нам надо доказать, что для любых чисел Решение рекуррентных соотношений и Решение рекуррентных соотношений

    найдутся такие значения Решение рекуррентных соотношений и Решение рекуррентных соотношений, что Решение рекуррентных соотношенийи Решение рекуррентных соотношений
    Но легко видеть, что при любых значениях Решение рекуррентных соотношений и Решение рекуррентных соотношений
    система уравненийРешение рекуррентных соотношений
    Решение рекуррентных соотношений имеет решение. Поэтому (8.2) действительно является общим решением соотношения (8.1).

    Случай равных корней характеристического уравнения

    Рассмотрим случай, когда оба корня характеристического уравнения совпадают: Случай равных корней характеристического уравнения. В этом случае выражение Случай равных корней характеристического уравнения уже не будет общим решением. Ведь из-за того, что Случай равных корней характеристического уравнения, это решение можно записать в виде Случай равных корней характеристического уравнения
    Остается только одно произвольное постоянное ; и выбрать его так, чтобы удовлетворить двум начальным условиям Случай равных корней характеристического уравнения, вообще говоря, невозможно.Поэтому надо найти какое-нибудь второе решение, отличное от Случай равных корней характеристического уравнения. Таким решением является Случай равных корней характеристического уравнения. В самом деле, если квадратное уравнение Случай равных корней характеристического уравнения имеет два совпадающих корня Случай равных корней характеристического уравнения, то по теореме Виета Случай равных корней характеристического уравнения. Поэтому уравнение записывается так: Случай равных корней характеристического уравнения
    А тогда рекуррентное соотношение имеет такой вид:
    Случай равных корней характеристического уравнения
    (8.10)

    Проверим, что Случай равных корней характеристического уравнения действительно являются его решением. Имеем Случай равных корней характеристического уравнения, а Случай равных корней характеристического уравнения. Подставляя эти значения в соотношение (8.10), получаем очевидное тождество Случай равных корней характеристического уравнения
    Значит, Случай равных корней характеристического уравнения- решение рассматриваемого соотношения.
    Итак, имеются два решения Случай равных корней характеристического уравнения и Случай равных корней характеристического уравнения заданного соотношения. Его общее решение запишется так:Случай равных корней характеристического уравненияТеперь уже путем подбора Случай равных корней характеристического уравнения можно удовлетворить любым начальным условиям.
    Линейные рекуррентные соотношения с постоянными коэффициентами, порядок которых больше двух, решаются таким же способом. Пусть соотношение имеет вид
    Случай равных корней характеристического уравнения
    (8.11)

    Составим характеристическое уравнение Случай равных корней характеристического уравнения
    Если все корни Случай равных корней характеристического уравнения этого алгебраического уравнения Случай равных корней характеристического уравнения-й степени различны, то общее решение соотношения (8.3) имеет вид Случай равных корней характеристического уравнения
    Если же, например, Случай равных корней характеристического уравнения, то этому корню соответствуют решения Случай равных корней характеристического уравнения
    Случай равных корней характеристического уравнения
    рекуррентного соотношения (8.11). В общем решении этому корню соответствует часть Случай равных корней характеристического уравнения
    Составляя такое выражение для всех корней и складывая их, получаем общее решение соотношения (8.3).
    Например, решим рекуррентное соотношение Случай равных корней характеристического уравнения
    Характеристическое уравнение в этом случае имеет вид Случай равных корней характеристического уравнения
    Решая его, получим корни Случай равных корней характеристического уравнения
    Значит, общее решение нашего соотношения имеет следующий вид: Случай равных корней характеристического уравнения

    Алгебраические дроби и степенные ряды

    При делении многочлена Алгебраические дроби и степенные ряды на многочлен Алгебраические дроби и степенные ряды мы получаем бесконечный степенной ряд. Возникает вопрос: как связан этот ряд с алгебраической дробью Алгебраические дроби и степенные ряды, то есть какой смысл можно придать записи
    Алгебраические дроби и степенные ряды
    (9.2)

    Рассмотрим, например, разложение
    Алгебраические дроби и степенные ряды
    (9.3)

    Мы не пишем здесь знака равенства, так как не знаем, какой смысл имеет стоящая справа сумма бесконечного числа слагаемых. Чтобы выяснить это, попробуем подставлять в обе части соотношения (9.3) различные значения Алгебраические дроби и степенные ряды. Сначала положим Алгебраические дроби и степенные ряды. Тогда левая часть соотношения примет значение Алгебраические дроби и степенные ряды, а правая превратится в бесконечный числовой ряд Алгебраические дроби и степенные ряды
    Так как мы не умеем складывать бесконечно много слагаемых, попробуем взять сначала одно слагаемое, потом - два, потом - три и так далее слагаемых. Мы получим такие суммы: Алгебраические дроби и степенные ряды
    Алгебраические дроби и степенные ряды. Ясно, что с возрастанием Алгебраические дроби и степенные ряды
    эти суммы приближаются к значению Алгебраические дроби и степенные ряды которое приняла левая часть соотношения (9.3) при Алгебраические дроби и степенные ряды.
    То же самое получится, если вместо Алгебраические дроби и степенные ряды подставить в обе части (9.3) число Алгебраические дроби и степенные ряды. Левая часть равенства примет значение 2, а правая превратится в бесконечный числовой ряд Алгебраические дроби и степенные ряды Беря последовательно одно, два, три, четыре, слагаемых, мы получим числа 1; Алгебраические дроби и степенные ряды; Алгебраические дроби и степенные ряды; Алгебраические дроби и степенные ряды,…, Алгебраические дроби и степенные ряды. Ясно, что с возрастанием Алгебраические дроби и степенные ряды эти числа стремятся к числу 2.
    Однако, если взять Алгебраические дроби и степенные ряды, то левая часть (9.3) примет значение Алгебраические дроби и степенные ряды, а в правой получим ряд Алгебраические дроби и степенные ряды
    Если последовательно складывать члены этого ряда, то получаются суммы 1; 5; 21; 85; … Эти суммы неограниченно увеличиваются и не приближаются к числу Алгебраические дроби и степенные ряды.
    Мы встретились, таким образом, с двумя случаями. Чтобы их различать, введем общее понятие о сходимости и расходимости числового ряда. Пусть задан бесконечный числовой ряд
    Алгебраические дроби и степенные ряды
    (9.4)

    Говорят, что бесконечный числовой ряд сходится к числу Алгебраические дроби и степенные ряды, если разность Алгебраические дроби и степенные ряды
    стремится к нулю при неограниченном увеличении Алгебраические дроби и степенные ряды. Иными словами, какое бы число Алгебраические дроби и степенные ряды мы ни указали, отклонение суммы Алгебраические дроби и степенные ряды от Алгебраические дроби и степенные ряды, начиная с некоторого номера Алгебраические дроби и степенные ряды, окажется меньше Алгебраические дроби и степенные ряды:
    Алгебраические дроби и степенные ряды
    В этом случае число Алгебраические дроби и степенные ряды называют суммой бесконечного ряда Алгебраические дроби и степенные ряды и пишут
    Алгебраические дроби и степенные ряды
    Если не существует числа Алгебраические дроби и степенные ряды, к которому сходится данный ряд (9.4), то этот ряд называют расходящимся.
    Проведенное выше исследование показывает, что Алгебраические дроби и степенные ряды

    Алгебраические дроби и степенные ряды

    в то время как ряд Алгебраические дроби и степенные ряды... расходится. Более тщательное исследование показывает, что если Алгебраические дроби и степенные ряды, то ряд Алгебраические дроби и степенные ряды сходится к Алгебраические дроби и степенные ряды, а если Алгебраические дроби и степенные ряды, то он расходится. Чтобы доказать это утверждение, достаточно заметить, что

    Алгебраические дроби и степенные ряды

    и что при Алгебраические дроби и степенные ряды выражение Алгебраические дроби и степенные ряды стремится к нулю, если Алгебраические дроби и степенные ряды, и к бесконечности, если Алгебраические дроби и степенные ряды. При Алгебраические дроби и степенные ряды

    получаем расходящиеся числовые ряды Алгебраические дроби и степенные ряды

    и Алгебраические дроби и степенные ряды.Итак, если Алгебраические дроби и степенные ряды, то

    Алгебраические дроби и степенные ряды

    (9.5)
    Отметим, что равенство (9.5) - это известная из школьного курса математики формула для суммы бесконечно убывающей геометрической прогрессии.

    Мы выяснили, таким образом, смысл записи Алгебраические дроби и степенные ряды

    Она показывает, что для значений Алгебраические дроби и степенные ряды, лежащих в некоторой области, а именно при Алгебраические дроби и степенные ряды, стоящий справа ряд сходится к Алгебраические дроби и степенные ряды. Говорят, что функция Алгебраические дроби и степенные ряды при Алгебраические дроби и степенные ряды разлагается в степенной ряд Алгебраические дроби и степенные ряды.Теперь уже можно выяснить и более общий вопрос.

    Пусть при делении многочлена Алгебраические дроби и степенные ряды на многочлен Алгебраические дроби и степенные ряды получился степенной ряд
    Алгебраические дроби и степенные ряды

    (9.6)
    Оказывается, что тогда при достаточно малых значениях Алгебраические дроби и степенные ряды ряд (9.6) сходится к Алгебраические дроби и степенные ряды. Размеры области сходимости зависят от корней знаменателя, то есть чисел, при которых знаменатель обращается в нуль. Именно, если эти числа равны Алгебраические дроби и степенные ряды и Алгебраические дроби и степенные ряды - наименьшее из чисел Алгебраические дроби и степенные ряды, то ряд сходится в области Алгебраические дроби и степенные ряды.

    Иными словами, всегда есть область Алгебраические дроби и степенные ряды, в которой выполняется равенство
    Алгебраические дроби и степенные ряды

    (9.7)
    В степенные ряды можно разлагать не только алгебраические дроби, но и многие другие функции. В математическом анализе доказывают, например, что

    Алгебраические дроби и степенные ряды

    Алгебраические дроби и степенные ряды

    Алгебраические дроби и степенные ряды

    Отметим еще следующее важное утверждение: функция Алгебраические дроби и степенные ряды

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


    Действия над степенными рядами

    Перейдем теперь к действиям над степенными рядами. Пусть функции Действия над степенными рядами и Действия над степенными рядами разложены в степенные ряды
    Действия над степенными рядами
    (9.12)

    Действия над степенными рядами
    (9.13)

    Тогда
    Действия над степенными рядами
    Оказывается, что слагаемые в правой части равенства можно переставить и сгруппировать вместе члены с одинаковыми степенями Действия над степенными рядами. Это утверждение совсем не так очевидно, как кажется на первый взгляд. Ведь в правой части равенства у нас бесконечные суммы, а в бесконечных суммах переставлять слагаемые можно далеко не всегда. После этой перегруппировки мы получим
    Действия над степенными рядами
    (9.14)

    Ряд, стоящий в правой части равенства (9.14), называется суммой степенных рядов (9.12) и (9.13).
    Посмотрим теперь, как разлагается в степенной ряд произведение функций Действия над степенными рядами и Действия над степенными рядами. Мы имеем
    Действия над степенными рядами
    (9.15)

    Оказывается, что как и в случае многочленов, ряды, стоящие в правой части равенства (9.15), можно почленно перемножать. Мы опускаем доказательство этого утверждения. Найдем ряд, получающийся после почленного перемножения. Свободный член этого ряда равен Действия над степенными рядами. Члены, содержащие Действия над степенными рядами, получатся дважды: при умножении Действия над степенными рядами на Действия над степенными рядами и при умножении Действия над степенными рядами на Действия над степенными рядами. Они дают
    Действия над степенными рядами
    Точно так же вычисляются члены, содержащие Действия над степенными рядами. Таким образом,
    Действия над степенными рядами
    (9.16)

    Ряд, стоящий в правой части равенства (9.16), называется произведением рядов(9.12) и (9.13).
    В частности, возводя ряд (9.12) в квадрат, получаем
    Действия над степенными рядами
    (9.17)

    Посмотрим теперь, как делят друг на друга степенные ряды. Пусть свободный член ряда (9.13) отличен от нуля. Покажем, что в этом случае существует такой степенной ряд
    Действия над степенными рядами
    (9.18)

    что
    Действия над степенными рядами
    (9.19)

    Для доказательства перемножим ряды в левой части этого равенства. Мы получим ряд
    Действия над степенными рядами
    Для того чтобы этот ряд совпадал с рядом (9.12), необходимо и достаточно, чтобы выполнялись равенства Действия над степенными рядами
    Действия над степенными рядами
    Действия над степенными рядами
    Действия над степенными рядами
    Действия над степенными рядами
    Эти равенства дают бесконечную систему уравнений для отыскания коэффициентов Из первого уравнения системы получаем Действия над степенными рядами. Подставим полученное значение во второе уравнение. Мы получим уравнение
    Действия над степенными рядами
    из которого находим, что Действия над степенными рядами. Вообще, если уже найдены коэффициенты Действия над степенными рядами, то для отыскания Действия над степенными рядами имеем уравнение
    Действия над степенными рядами
    Это уравнение разрешимо, поскольку Действия над степенными рядами.Итак, мы доказали существование ряда (9.18), удовлетворяющего соотношению (9.19). Ряд (9.18) называют частным при делении рядов (9.12) и (9.13). Можно доказать, что он получается при разложении функции Действия над степенными рядами. Таким образом, степенные ряды можно складывать, умножать и делить (последнее - при условии, что свободный член делителя отличается от нуля). Эти действия соответствуют действиям над разлагаемыми функциями.
    Действия над степенными рядами

    Деление многочленов

    Если заданы два многочлена Деление многочленов и Деление многочленов, то всегда существуют многочлены Деление многочленов (частное) и Деление многочленов (остаток), такие, что Деление многочленов, причем степень Деление многочленов меньше степени Деление многочленов или Деление многочленов. При этом Деление многочленов называется делимым, а Деление многочленов- делителем. Если же мы хотим, чтобы деление выполнялось без остатка, то придется допустить в качестве частного не только многочлены, но и бесконечные степенные ряды. Для получения частного надо расположить многочлены по возрастающим степеням Деление многочленов и делить "углом", начиная с младших членов. Рассмотрим, например, деление Деление многочленов на Деление многочленов
    Деление многочленов
    Ясно, что процесс деления никогда не закончится ( так же, например, как при обращении числа Деление многочленов в бесконечную десятичную дробь). С помощью индукции легко убедиться, что все коэффициенты частного равны единице. Поэтому в качестве частного получается бесконечный ряд Деление многочленов. Вообще, если Деление многочленов и Деление многочленов - два многочлена
    Деление многочленов
    Деление многочленов
    причем свободный член Деление многочленов многочлена Деление многочленов
    отличен от нуля, Деление многочленов, то при делении Деление многочленов
    на Деление многочленов получается бесконечный ряд
    Деление многочленов
    (9.1)

    Лишь в случае, когда Деление многочленов делится без остатка на Деление многочленов, ряд (9.1) обрывается и мы получаем многочлен.

    Бином Ньютона

    Получим производящую функцию для конечной последовательности чисел Бином Ньютона. Известно, что Бином Ньютона
    и
    Бином Ньютона
    Эти равенства являются частными случаями более общей формулы, дающей разложение для Бином Ньютона. Запишем Бином Ньютона в виде
    Бином Ньютона
    (10.4)

    Раскроем скобки в правой части этого равенства, причем будем записывать все множители в том порядке, в котором они нам встретятся. Например, Бином Ньютона запишем в виде
    Бином Ньютона
    (10.5)

    а Бином Ньютона - в виде
    Бином Ньютона
    (10.6)

    Видно, что в формулу (10.5) входят все размещения с повторениями, составленные из букв Бином Ньютона и Бином Ньютона по две буквы в каждом размещении, а в формулу (10.6) - размещения с повторениями из тех же букв, но состоящие из трех букв каждое. То же самое и в общем случае — после раскрытия скобок в формуле (10.4) мы получим всевозможные размещения с повторениями букв Бином Ньютона и Бином Ньютона, состоящие из Бином Ньютона элементов. Приведем подобные члены. Подобными будут члены, содержащие одинаковое количество букв Бином Ньютона (тогда и букв Бином Ньютона в них будет поровну). Найдем, сколько будет членов, в которые входит Бином Ньютона
    букв Бином Ньютона и, следовательно, Бином Ньютона букв Бином Ньютона. Эти члены являются перестановками с повторениями, составленными из Бином Ньютона букв Бином Ньютона
    и Бином Ньютона букв Бином Ньютона. Поэтому их число равно
    Бином Ньютона
    Отсюда вытекает, что после приведения подобных членов выражение Бином Ньютона войдет с коэффициентом Бином Ньютона. Итак, мы доказали, что
    Бином Ньютона
    (10.7)

    Равенство (10.7) принято называть формулой бинома Ньютона. Если положить в этом равенстве Бином Ньютона, то получим
    Бином Ньютона
    (10.8)

    Мы видим, что Бином Ньютона является производящей функцией для чисел Бином Ньютона, Бином Ньютона. С помощью этой производящей функции можно сравнительно просто доказать многие свойства чисел Бином Ньютона.

    Об едином нелинейном рекуррентном соотношении

    При решении задачи о разбиении последовательности мы пришли к рекуррентному соотношению
    Об едином нелинейном рекуррентном соотношении
    (10.14)

    где Об едином нелинейном рекуррентном соотношении.Покажем, как решить соотношение (10.14). Для этого составим производящую функцию.
    Об едином нелинейном рекуррентном соотношении
    (10.15)

    Положим
    Об едином нелинейном рекуррентном соотношении
    (10.16)

    и возведем Об едином нелинейном рекуррентном соотношении в квадрат. Мы получим, что
    Об едином нелинейном рекуррентном соотношении
    Об едином нелинейном рекуррентном соотношении
    Но по рекуррентному соотношению (10.14), Об едином нелинейном рекуррентном соотношении
    Значит,
    Об едином нелинейном рекуррентном соотношении
    Полученный ряд есть не что иное, как Об едином нелинейном рекуррентном соотношении; поскольку Об едином нелинейном рекуррентном соотношении, он равен
    Об едином нелинейном рекуррентном соотношении
    Для функции Об едином нелинейном рекуррентном соотношении получилось квадратное уравнение (10.17). Решая его, находим, что
    Об едином нелинейном рекуррентном соотношении
    Мы выбрали перед корнем знак минус, так как в противном случае при Об едином нелинейном рекуррентном соотношении
    мы имели бы Об едином нелинейном рекуррентном соотношении, а из разложения (10.16) видно, что Об едином нелинейном рекуррентном соотношении.
    Об едином нелинейном рекуррентном соотношении

    Применение степенных рядов для доказательства тождеств

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

    Если заменить здесь Применение степенных рядов для доказательства тождеств на –Применение степенных рядов для доказательства тождеств, то получим, что
    Применение степенных рядов для доказательства тождеств
    (10.2)

    Перемножив разложения (10.1) и (10.2), выводим, что
    Применение степенных рядов для доказательства тождеств
    (10.3)

    Очевидно, что коэффициенты при нечетных степенях Применение степенных рядов для доказательства тождеств обращаются в нуль, каждое слагаемое дважды входит в эти коэффициенты с противоположными знаками. Коэффициент же Применение степенных рядов для доказательства тождеств равен Применение степенных рядов для доказательства тождеств
    Но функцию Применение степенных рядов для доказательства тождеств можно разложить в степенной ряд и иным образом. Мы имеем
    Применение степенных рядов для доказательства тождеств
    А разложение для Применение степенных рядов для доказательства тождеств получается из разложения (10.1), если заменить в нем Применение степенных рядов для доказательства тождеств на Применение степенных рядов для доказательства тождеств:
    Применение степенных рядов для доказательства тождеств
    (10.4)

    Мы знаем, что никакая функция не может иметь двух различных разложений в степенные ряды. Поэтому коэффициент при Применение степенных рядов для доказательства тождеств
    разложении (10.3) должен равняться коэффициенту при Применение степенных рядов для доказательства тождеств
    в разложении (10.4). Отсюда вытекает следующее тождество:
    Применение степенных рядов для доказательства тождеств

    Производящие функции

    Пусть дана некоторая последовательность чисел Производящие функции. Образуем степенной ряд Производящие функции
    Если этот ряд сходится в какой-то области к функции Производящие функции, то эту функцию называют производящей для последовательности чисел Производящие функции Например, из формулы
    Производящие функции
    вытекает, что функция Производящие функции является производящей для последовательности чисел. А формула (10.1) показывает, что для последовательности чисел Производящие функции
    производящей является функция Производящие функции.
    Нас будут интересовать производящие функции для последовательностей Производящие функции, так или иначе связанных с комбинаторными задачами. С помощью таких функций удается получить самые разные свойства этих последовательностей. Кроме того, мы рассмотрим, как связаны производящие функции с решением рекуррентных соотношений.

    Производящие функции и рекуррентные соотношения

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

    то
    Производящие функции и рекуррентные соотношения
    Производящие функции и рекуррентные соотношения
    Раскроем в правой части этого равенства скобки и сравним коэффициенты при одинаковых степенях Производящие функции и рекуррентные соотношения слева и справа. Сначала мы получим Производящие функции и рекуррентные соотношения соотношений такого вида:
    Производящие функции и рекуррентные соотношения
    (10.11)

    (если Производящие функции и рекуррентные соотношения, то мы считаем, что Производящие функции и рекуррентные соотношения). А дальше все соотношения имеют один и тот же вид:
    Производящие функции и рекуррентные соотношения
    (10.12)

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

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

    Ряд Ньютона

    Мы назвали, как это обычно делают, формулу Ряд Ньютона биномом Ньютона. Это наименование с точки зрения истории математики неверно. Формулу для Ряд Ньютона хорошо знали среднеазиатские математики Омар Хайям, Гиясэдди и другие. В Западной Европе задолго до Ньютона она была известна Блэзу Паскалю. Заслуга же Ньютона была в ином - ему удалось обобщить формулу Ряд Ньютона на случай нецелых показателей. Именно, он доказал, что если Ряд Ньютона - положительное число и Ряд Ньютона, то для любого действительного значения Ряд Ньютона имеет место равенство
    Ряд Ньютона
    (10.9)

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

    Двоичные деревья

    Основные определения и понятия о графах даются в лекции 12.
    В лекции 16 и 17 рассматриваются комбинаторные алгоритмы на графах. В данной лекции приведены несколько понятий, необходимых для описания абстрактной структуры данных, - двоичное дерево.
    При решении многих задач математики используется понятие графа.Граф - набор точек на плоскости (эти точки называются вершинами графа), некоторые из которых соединены отрезками (эти отрезки называются ребрами графа). Вершины могут располагаться на плоскости произвольным образом. Причем неважно, является ли ребро, соединяющее две вершины, отрезком прямой или кривой линией. Важен лишь факт соединения двух данных вершин графа ребром. Примером графа может служить схема линий железных дорог. Если требуется различать вершины графа, их нумеруют или обозначают разными буквами. В изображении графа на плоскости могут появляться точки пересечения ребер, не являющиеся вершинами.
    Говорят, что две вершины графа соединены путем, если из одной вершины можно пройти по ребрам в другую вершину. Путей между двумя данными вершинами может быть несколько. Поэтому обычно путь обозначают перечислением вершин, которые посещают при движении по ребрам. Граф называется связным, если любые две его вершины соединены некоторым путем.
    Состоящий из различных ребер замкнутый путь называется циклом. Связный граф, в котором нет циклов, называется деревом. Одним из основных отличительных свойств дерева является то, что в нем любые две вершины соединены единственным путем. Дерево называется ориентированным, если на каждом его ребре указано направление. Следовательно, о каждой вершине можно сказать, какие ребра в нее входят, а какие - выходят. Точно так же о каждом ребре можно сказать, из какой вершины оно выходит, и в какую - входит. Двоичное дерево - это такое ориентированное дерево, в котором:
  • Имеется ровно одна вершина, в которую не входит ни одного ребра. Эта вершина называется корнем двоичного дерева.
  • В каждую вершину, кроме корня, входит одно ребро.
  • Из каждой вершины (включая корень) исходит не более двух ребер.

  • Граф задается аналогично спискам через записи и указатели.
    Программа 4. Создание и работа с деревом.
    //Алгоритм реализован на языке Turbo-C++. //Вершины дерева задаются структурой: поле целых, //поле для размещения адреса левого "сына" и поле для размещения //адреса правого "сына" //Значение целого выбирается случайным образом из интервала 0..99. //Число уровней дерева равно N. В примере N = 5. #include #include #include #include #define N 5 struct tree{int a; tree* left; tree* right;};
    void postr(tree* root,int h) { root->a=random(100); if (h!=0){ if (random(3)){ root->left=new(tree); postr(root->left,h-1);} else root->left=NULL; if (random(3)) {root->right=new(tree);postr(root->right,h-1);} else root->right=NULL;} else {root->right=NULL;root->left=NULL;} }
    void DFS(tree* root) {printf("%d ",root->a); if (root->left!=NULL) DFS(root->left); if (root->right!=NULL) DFS(root->right);}
    void main() {clrscr(); randomize(); tree* root1; root1=new(tree); postr(root1,N); DFS(root1); getch(); }
    Двоичные деревья

    Очереди

    Очередь - одномерная структура данных, для которой загрузка или извлечение элементов осуществляется с помощью указателей начала извлечения (head) и конца (tail) очереди в соответствии с правилом FIFO ("first-in, first-out" - "первым введен, первым выведен").
  • Начальная установка:

  • Head:=1; tail:=1;
  • Добавление элемента x:

  • Queue[tail]:=x; tail:=tail+1; If tail>qd then tail:=1; Здесь qd - размерность очереди.
  • Исключение элемента x:

  • x:=queue[head]; head:=head+1; if head>qd then head:=1;
  • Проверка переполнения очереди и включение в нее элемента:

  • Temp:=tail+1; If temp>qd then temp:=1; If temp=head then \{переполнение\} Else btgin queue[tail]:=x; tail:=temp end;
  • Проверка элементов и исключение элемента:

  • If head:=tail then \{очередь пуста\} else begin x:=queue[head]; head:=head+1; if yead>qd then head:=1; end;
    Отметим, что при извлечении элемента из очереди все элементы могут также перемещаться на один шаг к ее началу.

    Стеки

    Стеком называется одномерная структура данных, загрузка или увеличение элементов для которой осуществляется с помощью указателя стека в соответствии с правилом LIFO ("last-in, first-out" "последним введен,первым выведен").
    Указатель стека sp (stack pointer) содержит в любой момент времени индекс (адрес) текущего элемента, который является единственным элементом стека, доступным в данный момент времени для обработки.
    Существуют следующие основные базисные операции для работы со стеком (для случая, когда указатель стека всегда задает ячейку, находящуюся непосредственно над его верхним элементом).
  • Начальная установка:

  • Sp:=1;
  • Загрузка элемента x в стек:

  • Stack[sp]:=x; Sp:=sp+1;
  • Извлечение элемента из стека:

  • Sp:=sp-1; X:=stack[sp];
  • Проверка на переполнение и загрузка элемента в стек:

  • If sp<=sd then Begin stack[sp]:=x; sp:=sp+1 end Else \{ переполнение \}; Здесь sd - размерность стека.
  • Проверка наличия элементов и извлечение элемента стека:

  • If sp>1 then Begin sp:=sp-1; x:=stack[sp] end Else \{ антипереполнение \}
  • Чтение данных из указателя стека без извлечения элемента:

  • x:=stack[sp-1].
    Программа 1. Работа со стеком.
    {Реализованы основные базисные операции для работы со стеком. Программа написана на языке программирования Turbo-Pascal }
    uses crt,graph; type PEl=^El; El=record n:byte; next:PEl; end;
    var ster:array[1..3] of PEl; number: byte; p:PEl; th,l: integer; i:integer; nhod:word; s:string;
    procedure hod(n,f,t:integer); begin if n>1 then begin hod(n-1,f,6-(f+t)); hod(1,f,t); hod(n-1,6-(f+t),t); end else begin p:=ster[f]; ster[f]:=ster[f]^.next; p^.next:=ster[t]; ster[t]:=p; inc(nhod); str(nhod,s); {**********************************************************} setfillstyle(1,0);bar(0,0,50,10); setcolor(2);outtextxy(0,0,s); setfillstyle(1,0);setcolor(0);p:=ster[f];i:=1; while p<>nil do begin p:=p^.next;inc(i);end; fillellipse(160*f,460-(i-1)*th,(number-ster[t]^.n+1)*l,10); setfillstyle(1,4);setcolor(4);p:=ster[t];i:=1; while p<>nil do begin fillellipse(160*t,460-(i-1)*th,(number- ster[t]^.n+1)*l,10);inc(i);p:=p^.next;end; {**********************************************************} { readkey;}{delay(50);} end; end;

    procedure start; var i:integer;grD,grM: Integer; begin clrscr;write(' Enter the number of rings, please.');readln(number); for i:=1 to 3 do ster[i]:=nil; for i:=1 to number do begin new(p);p^.n:=i;p^.next:=ster[1];ster[1]:=p;end; nhod:=0; grD:=Detect;{InitGraph(grD,grM,'');}InitGraph(grD,grM,'c:\borland\tp\bgi'); th:=20;l:=round(50/number); setfillstyle(1,4);setcolor(4); for i:=1 to number do begin fillellipse(160,460-(i-1)*th,(number- i+1)*l,10);end; end;

    begin start; {readkey;} hod(number,1,3); {closegraph;} end.

    Программа 2. Ханойская башня.

    На стержне Стеки в исходном порядке находится Стеки

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

    Программа реализована с помощью абстрактного типа данных – стек для произвольного числа дисков.

    {Программа написана на языке программирования Turbo-Pascal}

    uses crt,graph; type PEl=^El; El=record n:byte; next:PEl; end;

    var ster:array[1..3] of PEl; number: byte; p:PEl; th,l: integer; i:integer; nhod:word; s:string;

    procedure hod(n,f,t:integer); begin if n>1 then begin hod(n-1,f,6-(f+t)); hod(1,f,t); hod(n-1,6-(f+t),t); end else begin p:=ster[f]; ster[f]:=ster[f]^.next; p^.next:=ster[t]; ster[t]:=p; inc(nhod); str(nhod,s); {**********************************************************} setfillstyle(1,0);bar(0,0,50,10); setcolor(2);outtextxy(0,0,s); setfillstyle(1,0);setcolor(0);p:=ster[f];i:=1; while p<>nil do begin p:=p^.next;inc(i);end; fillellipse(160*f,460-(i-1)*th,(number-ster[t]^.n+1)*l,10); setfillstyle(1,4);setcolor(4);p:=ster[t];i:=1; while p<>nil do begin fillellipse(160*t,460-(i-1)*th,(number- ster[t]^.n+1)*l,10);inc(i);p:=p^.next;end; {**********************************************************} { readkey;}{delay(50);} end; end;

    procedure start; var i:integer;grD,grM: Integer; begin clrscr;write('Enter the number of rings, please.');readln(number); for i:=1 to 3 do ster[i]:=nil; for i:=1 to number do begin new(p);p^.n:=i;p^.next:=ster[1];ster[1]:=p;end; nhod:=0; grD:=Detect;{InitGraph(grD,grM,'');}InitGraph(grD,grM,'c:\borland\tp\bgi'); th:=20;l:=round(50/number); setfillstyle(1,4);setcolor(4); for i:=1 to number do begin fillellipse(160,460-(i-1)*th,(number- i+1)*l,10);end; end;

    begin start; {readkey;} hod(number,1,3); {closegraph;} end.

    Связанные списки

    Связанный список представляет собой структуру данных, которая состоит из узлов (как правило, записей), содержащих указатели на следующий узел. Указатель, который ни на что не указывает, снабжается значением nil. Таким образом, в каждый элемент связанного списка добавляется указатель (звено связи).
    Приведем основные базисные операции для работы с однонаправленным связанным списком.
  • Включение элемента после элемента:

  • Link[q]:=link[p]; Link[p]:=q;
    Здесь q – индекс элемента, который должен быть вставлен в список после элемента с индексом p.
  • Исключение преемника элемента x:

  • If link[x]<>null then Link[x]:=[link[x]] else \{Элемент x не имеет преемника\};
    Отметим, что элемент, следующий в списке за элементом x, называется преемником элемента x, а элемент, pасположенный перед элементом x, называется предшественником элемента x. Если элемент x не имеет преемника, то содержащемуся в нем указателю присваивается значение nil.
  • Включение элемента y перед элементом x:

  • Prev:=0; While(link[prev]<>nil)and(link[prev]<>x)do Prev:=link[prev]; If link[prev]=x then Btgin link[prev]:=y; link[y]:=x end Else \{Элемент x не найден\}; Здесь link[0]является началом списка.
    Отметим, что исключение последнего элемента из однонаправленного списка связано с просмотром всего списка.
    В двунаправленном связанным списке каждый элемент имеет два указателя (succlink - описывает связь элемента с преемником, predlink - с предшественником).
    Приведем основные базисные операции для работы с двунаправленным связанным списком.
    Ответ 1Включение y перед элементом x:
    Succlink[y]:=x; Predlink[y]:=predlink[x]; Succlink[predlink[x]]:=y; Predlink[x]:=y;
    Ответ 2Включение элемента y после элемента x:
    Succlink[y]:=succlink[x]; Predlink[y]:=x; Predlink[succlink[x]]:=y; Succlink[x]:=y;
    Ответ 3Исключение элемента x.
    Predlink[succlink[x]]:=predlink[x]; Succlink[predlink[x]]:=succlink[x];
    Программа 3.Список целых чисел.
    {Создается список целых чисел. Числа выбираются случайным образом из интервала 0..9999, затем он упорядочивается, сначала - по возрастанию, затем - по убыванию.
    Программа написана на языке программирования Turbo-Pascal}

    uses crt; type TLink=^Link; Link=record v : integer; p, n : TLink end;

    var i : integer; p, q, w : TLink; s1,s2,rs : TLink;

    procedure Sort( sp : TLink; t : integer ); var temp : integer; begin q:=sp; while q^.n<>nil do begin q:=q^.n; p:=sp; while p^.n<>nil do begin if (p^.v-p^.n^.v)*t>0 then begin temp:=p^.v; p^.v:=p^.n^.v; p^.n^.v:=temp; end; p:=p^.n; end; end; end;

    function CreatRndSpis(deep : integer):TLink; begin new(q); for i:=1 to deep do begin if i=1 then begin p:=q;q^.p:=nil; end; q^.v:=random(9999); new(q^.n); q^.n^.p:=q; q:=q^.n; end; q^.p^.n:=nil; dispose(q); CreatRndSpis:=p; end;

    function CreatSortDawnSpis(deep : integer):TLink; begin if deep<9999 then begin new(q); for i:=1 to deep do begin if i=1 then begin q^.p:=nil;p:=q; end; q^.v:=random(round(9999/deep))+round(9999*(1-i/deep)); new(q^.n); q^.n^.p:=q; q:=q^.n; end; q^.p^.n:=nil; dispose(q); end else p:=nil; CreatSortDawnSpis:=p; end;

    procedure Show( s : TLink; sp: integer ); var i : integer; begin p:=s; i:=1; while p<>nil do begin gotoxy(sp,i);write(' ' : 5); gotoxy(sp,i);writeln(p^.v); p:=p^.n; inc(i); end; end;

    function min( c1, c2 : integer) : integer; begin case c1
    function CreatConcSortUpSpis( sp1, sp2 : TLink ) : TLink; begin q:=sp1;while q^.n<>nil do q:=q^.n; w:=sp2;while w^.n<>nil do w:=w^.n; new(p);

    CreatConcSortUpSpis:=p; p^.p:=nil; while(w<>nil)and(q<>nil)do begin if(w<>nil)and(q<>nil)then begin p^.v:=min(q^.v,w^.v); case p^.v=q^.v of true : q:=q^.p; false: w:=w^.p; end; new(p^.n); p^.n^.p:=p; p^.n^.n:=nil; p:=p^.n; end; if(w=nil)and(q<>nil)then begin while q<>nil do begin p^.v:=q^.v;q:=q^.p; new(p^.n); p^.n^.p:=p; p^.n^.n:=nil; p:=p^.n; end; end; if(w<>nil)and(q=nil)then begin while w<>nil do begin p^.v:=w^.v;w:=w^.p; new(p^.n); p^.n^.p:=p; p^.n^.n:=nil; p:=p^.n; end; end; end; p^.p^.n:=nil; dispose(p); end;

    begin clrscr; randomize; s1:=CreatRndSpis(15);Sort(s1,-1); s2:=CreatRndSpis( 5);Sort(s2,-1); rs:=CreatConcSortUpSpis(s1,s2); Show(s1,10); Show(s2,20); Show(rs,30); Sort(rs,-1); Show(rs,40); readln; end.

    Изоморфизм

    Два графа Изоморфизм называются изоморфными, если существует взаимно однозначное соответствие Изоморфизм, такое, что Изоморфизм тогда и только тогда, если Изоморфизм, то есть существует соответствие между вершинами графа Изоморфизм и вершинами графа Изоморфизм, сохраняющее отношение смежности. Например, на рис. 12.2 показаны два изоморфных орграфа: вершины Изоморфизм в орграфе Изоморфизм соответствуют вершинам 2, 3, 6, 1, 4, 5 в указанном порядке в орграфе Изоморфизм. Вообще говоря, между Изоморфизм и Изоморфизм может быть более чем одно соответствие, и на рис. 12.3 графы имеют на самом деле второй изоморфизм: Изоморфизм соответствуют в указанном порядке вершинам 2, 3, 6, 1, 5, 4. Изоморфные графы отличаются только метками вершин, в связи с чем задача определения изоморфизма возникает в ряде практических ситуаций, таких, как информационный поиск и определение химических соединений.
    Заметим, что можно ограничится орграфами. Любой неориентированный граф превращается в орграф заменой каждого ребра двумя противоположно направленными ребрами. Два полученные таким образом орграфа, очевидно, изоморфны тогда и только тогда, если изоморфны исходные графы.
    Изоморфизм
    Рис. 12.3.  Изоморфные орграфы

    Клики

    Максимальный полный подграф графа Клики называется кликой графа Клики ; другими словами, клика графа Клики есть подмножество его вершин, такое, что между каждой парой вершин этого подмножества существует ребро и, кроме того, это подмножество не принадлежит никакому большому подмножеству с тем же свойством. Например, на рис. 12.2 показан граф и его клики.
    Клики
    Рис. 12.2.  Граф G и все его клики
    Клики графа представляют "естественные" группировки вершин, и определение клик графа полезно в кластерном анализе в таких областях, как информационный поиск и социология.

    Остовные деревья

    Связный неориентированный ациклический граф называется деревом, множество деревьев называется лесом. В связном неориентированном графе Остовные деревья существует по крайней мере один путь между каждой парой вершин; отсутствие циклов в Остовные деревья означает, что существует самое большее один такой путь между любой парой вершин в Остовные деревья. Поэтому, если Остовные деревья - дерево, то между каждой парой вершин в Остовные деревья существует в точности один путь. Рассуждение легко обратимо, и поэтому неориентированный граф Остовные деревья будет деревом тогда и только тогда, если между каждой парой вершин в Остовные деревья существует в точности один путь. Так как наименьшее число ребер, которыми можно соединить Остовные деревья вершин, равно Остовные деревья и дерево с Остовные деревья вершинами содержит в точности Остовные деревья ребер, то деревья можно считать минимально связными графами. Удаление из дерева любого ребра превращает его в несвязный граф, разрушая единственный путь между по крайней мере одной парой вершин.
    Особый интерес представляют остовные деревья графа Остовные деревья, то есть деревья, являющиеся подграфами графа Остовные деревья и содержащие все его вершины. Если граф Остовные деревья несвязен, то множество, состоящее из остовных деревьев каждой компоненты называется остовном лесом графа. Для построения остовного дерева (леса) данного неориентированного графа Остовные деревья, мы последовательно просматриваем ребра Остовные деревья, оставляя те, которые не образуют циклов с уже выбранными.
    Во взвешенном графе Остовные деревья часто интересно определить остовное дерево (лес) с минимальным общим весом ребер, то есть дерево (лес), у которого сумма весов всех его ребер минимальна. Такое дерево называется минимумом оставных деревьев или минимальное остовное дерево. Другими словами, на каждом шаге мы выбираем новое ребро с наименьшим весом (наименьшее ребро), не образующее циклов с уже выбранными ребрами; этот процесс продолжаем до тех пор, пока не будет выбрано Остовные деревья ребер, образующих остовное дерево Остовные деревья. Этот процесс известен как жадный алгоритм.
    Жадный алгоритм может быть выполнен в два этапа. Сначала ребра сортируются по весу и затем строится остовное дерево путем выбора наименьших из имеющихся в распоряжении ребер.
    Существует другой метод получения минимума остовных деревьев, который не требует ни сортировки ребер, ни проверки на цикличность на каждом шаге, - так называемый алгоритм ближайшего соседа.
    Мы начинаем с некоторой произвольной вершины Остовные деревья в заданном графе. Пусть Остовные деревья - ребро с наименьшим весом, инцидентное Остовные деревья ; ребро Остовные деревья включается в дерево. Затем среди всех ребер, инцидентных либо Остовные деревья, либо Остовные деревья, выбираем ребро с наименьшим весом и включаем его в частично построенное дерево. В результате этого в дерево добавляется новая вершина, например, Остовные деревья. Повторяя процесс, ищем наименьшее ребро, соединяющее Остовные деревья, Остовные деревья или Остовные деревья с некоторой другой вершиной графа. Процесс продолжается до тех пор, пока все вершины из Остовные деревья не будут включены в дерево, то есть пока дерево не станет остовным.

    Наихудшим для этого алгоритма будет случай, когда Остовные деревья - полный граф (то есть когда каждая пара вершин в графе соединена ребром); в этом случае для того, чтобы найти ближайшего соседа, на каждом шаге нужно сделать максимальное число сравнений. Чтобы выбрать первое ребро, мы сравниваем веса всех Остовные деревья ребер, инцидентных вершине Остовные деревья, и выбираем наименьшее; этот шаг требует Остовные деревья сравнений. Для выбора второго ребра мы ищем наименьшее среди возможных Остовные деревья ребер (инцидентных Остовные деревья или Остовные деревья ) и делаем для этого Остовные деревья сравнений. Таким образом, ясно, что для выбора Остовные деревья -го ребра требуется Остовные деревья сравнений, и поэтому в сумме потребуется Остовные деревья сравнений для построения минимума остовных деревьев.

    Планарность

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

  • Определение планарности графа отличается от других рассмотренных нами задач, поскольку при изображении точек и линий на плоскости приходится больше иметь дело с непрерывными, а не с дискретными величинами. Взаимосвязь между дискретными и непрерывными аспектами планарности заинтересовала математиков и привела к различным характеристикам планарных графов. С точки зрения математики эти характеристики изящны, но эффективных алгоритмов определения планарности они не дают. Наиболее успешный подход к определению планарности состоит просто в том, что граф разбивается на подграфы и затем делается попытка разместить его на плоскости, добавляя подграфы один за другим и сохраняя при размещении планарность.
    Вначале сделаем несколько простых, но полезных наблюдений. Поскольку орграф планарен тогда и только тогда, если планарен соответствующий неориентированный граф, полученный игнорированием направления ребер, то достаточно рассматривать только неориентированные графы, поскольку неориентированный граф планарен тогда и только тогда, если все его двусвязные компоненты планарны. Поэтому если неориентированный граф является разделимым, мы можем разложить его на двусвязные компоненты и рассматривать их отдельно. Наконец, поскольку параллельные ребра и петли всегда можно добавить к графу или удалить из него без нарушения свойства планарности, нам достаточно рассматривать только простые графы. Поэтому при определении планарности будем предполагать, что граф неориентированный, простой и двусвязный.
    Наша основная стратегия состоит прежде всего в том, чтобы в графе Планарность найти цикл Планарность, разместить Планарность на плоскости в виде простой замкнутой кривой, разложить оставшуюся часть Планарность на непересекающиеся по ребрам пути и затем попытаться разместить каждый из этих путей либо целиком внутри Планарность, либо целиком вне Планарность. Если нам удалось разместить так весь граф Планарность, то он планарен, в противном случае он непланарен. Трудность этого способа заключается в том, что при размещении путей можно выбирать либо внутренность, либо внешность Планарность, и мы должны проконтролировать, чтобы неправильный выбор области размещения на ранней стадии не устранял возможности размещения последующих путей, - это могло бы привести нас к неверному заключению, что планарный граф непланарен. Планарность

    Представления

    Наиболее известный способ представления графа на бумаге состоит в геометрическом изображении точек и линий. В ЭВМ граф должен быть представлен дискретным способом, причем возможно много различных представлений. Простота использования, так же как и эффективность алгоритмов на графе, зависит от подходящего выбора представления графа. Рассмотрим различные структуры данных для представления графов.
    Матрица смежностей. Одним из наиболее распространенных машинных представлений простого графа является матрица смежностей или соединений. Матрица смежностей графа Представления есть Представления -матрица Представления, в которой Представления, если в Представления существует ребро, идущее из Представления -й вершины в Представления -ю, и Представления в противном случае. Орграф и его матрица смежностей представлены на рис. 12.1.
    Представления
    Рис. 12.1.  Ориентированный граф и его матрица смежностей
    Представления
    Заметим, что в матрице смежностей петля может быть представлена соответствующим единичным диагональным элементом. Кратные ребра можно представить, позволив элементу матрицы быть больше 1, но это не принято, так как обычно удобно представлять каждый элемент матрицы одним двоичным разрядом.
    Для задания матрицы смежностей требуется Представления двоичных разрядов. У неориентированного графа матрица смежностей симметрична, и для ее представления достаточно хранить только верхний треугольник. В результате экономится почти 50% памяти, но время вычислений может при этом немного увеличиться, потому что каждое обращение к Представления должно быть заменено следующим: if Представления then Представления else Представления. В случае представления графа его матрицей смежностей для большинства алгоритмов требуется время вычисления, по крайней мере пропорциональное Представления.
    Матрица весов. Граф, в котором ребру Представления сопоставлено число Представления, называется взвешенным графом, а число Представления называется весом ребра Представления. В сетях связи или транспортных сетях эти веса представляют некоторые физические величины, такие как стоимость, расстояние, эффективность, емкость или надежность соответствующего ребра. Простой взвешенный граф может быть представлен своей матрицей весов Представления, где Представления есть вес ребра, соединяющего вершины Представления и Представления.
    Веса несуществующих ребер обычно полагают равными Представления или 0 в зависимости от приложений. Когда вес несуществующего ребра равен 0, матрица весов является простым обобщением матрицы смежностей.

    Список ребер. Если граф является разреженным, то возможно, что более эффектно представлять ребра графа парами вершин. Это представление можно реализовать двумя массивами Представления и Представления. Каждый элемент в массиве есть метка вершины, а Представления -е ребро графа выходит из вершины Представления и входит в вершину Представления. Например, орграф, изображенный на рис. 12.1, будет представляться следующим образом: Представления Представления Ясно, что при этом легко представимы петли и кратные ребра.

    Структура смежности. В ориентированном графе вершина Представления называется последователем другой вершины Представления, если существует ребро, направленное из Представления в Представления. Вершина Представления называется тогда предшественником Представления. В случае неориентированного графа две вершины называются соседями, если между ними есть ребро. Граф может быть описан его структурой смежности, то есть списком всех последователей (соседей) каждой вершины; для каждой вершины Представления задается Представления - список всех последователей (соседей) вершины Представления. В большинстве алгоритмов на графах относительный порядок вершин, смежных с вершиной Представления в Представления, не важен, и в таком случае удобно считать Представления мультимножеством (или множеством, если граф является простым) вершин, смежных с Представления. Структура смежности орграфа, представленного на рис. 12.1, такова:

    Adj(v) 1: 6 2: 1, 3, 4, 6 3: 4, 5 4: 5 5: 3, 6, 7 6: 7: 1, 6

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

    Структуры смежности могут быть удобно реализованы массивом из Представления линейно связанных списков, где каждый список содержит последователей некоторой вершины. Поле данных содержит метку одного из последователей, и поле указателей указывает следующего последователя. Хранение списков смежности в виде связанного списка желательно для алгоритмов, в которых в графе добавляются или удаляются вершины.


    Матрица инцидентности - Представления задает граф : Представления если ребро Представления выходит из вершины Представления, Представления, если ребро Представления входит в вершину Представления, и Представления в остальных случаях.

    Связность и расстояние

    Говорят, что вершины Связность и расстояние и Связность и расстояние в графе смежны, если существует ребро, соединяющее их. Говорят, что два ребра смежны, если они имеют общую вершину. Простой путь, или для краткости, просто путь, записываемый иногда как Связность и расстояние, - это последовательность смежных ребер Связность и расстояние, в которой все вершины Связность и расстояние различны, исключая, возможно, случай Связность и расстояние. В орграфе этот путь называется ориентированным из Связность и расстояние в Связность и расстояние, в неориентированном графе он называется путем между Связность и расстояние и Связность и расстояние. Число ребер в пути называется длиной пути. Путь наименьшей длины называется кратчайшим путем. Замкнутый путь называется циклом. Граф, который не содержит циклов, называется ациклическим.
    Подграф графа Связность и расстояние есть граф, вершины и ребра которого лежат в Связность и расстояние. Подграф Связность и расстояние, индуцированный подмножеством Связность и расстояние множества Связность и расстояние вершин графа Связность и расстояние, - это подграф, который получается в результате удаления всех вершин из Связность и расстояние и всех ребер, инцидентных им.
    Неориентированный граф Связность и расстояние связен, если существует хотя бы один путь в Связность и расстояние между каждой парой вершин Связность и расстояние и Связность и расстояние. Ориентированный граф Связность и расстояние связен, если неориентированный граф, получающийся из Связность и расстояние путем удаления ориентации ребер, является связным. Граф, состоящий из единственной изолированной вершины, является (тривиально) связным. Максимальный связный подграф графа Связность и расстояние называется связной компонентой или просто компонентой Связность и расстояние. Несвязный граф состоит из двух или более компонент. Максимальный сильно связный подграф называется сильно связной компонентой.
    Иногда недостаточно знать, что граф связен; нас может интересовать, насколько "сильно связен" связный граф. Например, связный граф может содержать вершину, удаление которой вместе с инцидентными ей ребрами разъединяет оставшиеся вершины. Такая вершина называется точкой сочленения или разделяющей вершиной. Граф, содержащий точку сочленения, называется разделимым. Граф без точек сочленения называется двусвязным или неразделимым. Максимальный двусвязный подграф графа называется двусвязной компонентой или блоком.
    Большинство основных вопросов о графах касается связности, путей и расстояний. Нас может интересовать вопрос, является ли граф связным; если он связен, то может оказаться нужным найти кратчайшее расстояние между выделенной парой вершин или определить кратчайший путь между ними. Если граф несвязен, то может потребоваться найти все его компоненты. В нашем курсе строятся алгоритмы для решения этих и других подобных вопросов.

    Бинарный поиск

    Когда ячейки таблицы последовательно распределены в памяти с произвольным доступом и имена хранятся в таблице в их естественном порядке, возможен бинарный поиск - один из наиболее широко используемых методов поиска. Идея этого метода состоит в том, чтобы искать имя Это утверждение очевидно первый раз, в интервале, крайними точками которого являются два заданных указателя Это утверждение очевидно первый раз, (для "низа") и Это утверждение очевидно первый раз, (для "верха"). Новый указатель Это утверждение очевидно первый раз, (для "средней точки") устанавливается где-то около середины интервала, и либо Это утверждение очевидно первый раз, с именем в этой ячейке сводит интервал поиска к одному из интервалов Это утверждение очевидно первый раз, или Это утверждение очевидно первый раз, . Если интервал становится пустым, поиск завершается безуспешно.
    Для получения логарифмического времени поиска существенно устанавливать указатель Это утверждение очевидно первый раз, за время, не зависящее от длины интервала; это требование делает непригодным бинарный поиск на большинстве вспомогательных запоминающих устройств. Требование, чтобы Это утверждение очевидно первый раз, помещалось точно в середине интервала, несущественно, хотя выбор средней точки в качестве Это утверждение очевидно первый раз, обычно дает самый эффективный алгоритм. В некоторых частных случаях полезно разбить интервал на подинтервалы длины Это утверждение очевидно первый раз, и Это утверждение очевидно первый раз, для фиксированного значения Это утверждение очевидно первый раз, , отличного от Это утверждение очевидно первый раз, . Когда таблица размещена не последовательно, а хранится в виде списка древовидной структуры, доля Это утверждение очевидно первый раз, должна, вероятно, меняться от интервала к интервалу.
    Бинарный поиск по идее прост, но с деталями условия завершения поиска нужно обращаться осторожно. Частные случаи Это утверждение очевидно первый раз, и Это утверждение очевидно первый раз, требуют пристального внимания в любой программе бинарного поиска. В алгоритме 13.4 эти случаи обрабатываются тем же кодом, что и в общем случае, и поучительно посмотреть, как это делается, проследив за выполнением алгоритма для Это утверждение очевидно первый раз, и Это утверждение очевидно первый раз, .
    Это утверждение очевидно первый раз,
    Алгоритм 13.4. Бинарный поиск имени Это утверждение очевидно первый раз, в таблице Это утверждение очевидно первый раз, , хранящейся в естественном порядке.
    Корректность алгоритма 13.4 следует из утверждения, данного в комментарии в начале тела цикла. Он устанавливает, что если Это утверждение очевидно первый раз,
    находится где-либо в таблице, то оно должно находиться в интервале Это утверждение очевидно первый раз, ; иначе говоря, при нашем предположении, что имя появляется в таблице не больше одного раза, утверждается, что Это утверждение очевидно первый раз, не встречается ни в интервале Это утверждение очевидно первый раз, , ни в интервале Это утверждение очевидно первый раз, .

    Это утверждение очевидно первый раз,

    Это утверждение очевидно первый раз, когда мы входим в цикл при Это утверждение очевидно первый раз, и Это утверждение очевидно первый раз, , и непосредственно по индукции проверяется, что оно выполняется при каждом проходе через цикл. Когда мы выходим из цикла, то должно быть Это утверждение очевидно первый раз, , и поэтому утверждение принимает вид Это утверждение очевидно первый раз, и Это утверждение очевидно первый раз, , откуда следует, что Это утверждение очевидно первый раз, .

    Логарифмический поиск в динамических таблицах

    Здесь мы рассмотрим организацию в виде деревьев для таблиц, в которых часто встречаются включения и исключения. Что происходит с временем поиска в дереве, которое модифицировалось путем включения и исключения? Если включенные и исключенные имена выбраны случайно, то оказывается, что в среднем время поиска мало изменяется; но в худшем случае поведение плохое - деревья могут вырождаться в линейные списки, поиск в которых нужно осуществлять последовательно. Проблема вырождения дерева в линейный список, приводящая к времени поискаЛогарифмический поиск в динамических таблицахвместоЛогарифмический поиск в динамических таблицахв практических применениях выражена более резко, чем это указывается теоретическим анализом. Такой анализ обычно предполагает, что включения и исключения появляются случайным образом, но на практике часто это не так.
    Случайные деревья бинарного поиска. Как ведут себя деревья бинарного поиска без ограничений в качестве динамических деревьев? Другими словами, предположим, что дерево бинарного поиска меняется при случайных включениях и исключениях.
    Для включенияЛогарифмический поиск в динамических таблицахмы используем незначительную модификацию алгоритма 13.5. ЕслиЛогарифмический поиск в динамических таблицахне было найдено, мы получаем дляЛогарифмический поиск в динамических таблицахновую ячейку и связываем ее с последним узлом, пройденным во время безуспешного поискаЛогарифмический поиск в динамических таблицах. Соответствующее предложение нельзя однако просто добавить в конце алгоритма 13.5, поскольку при нормальном окончании циклаЛогарифмический поиск в динамических таблицахуказательЛогарифмический поиск в динамических таблицахне указывает больше на последний пройденный узел, а вместо этого имеет значениеЛогарифмический поиск в динамических таблицах. В связи с этим мы должны производить включение до того, как выполняется предположениеЛогарифмический поиск в динамических таблицахилиЛогарифмический поиск в динамических таблицах; мы можем осуществить это, сделав процедуру включения рекурсивной. ПроцедураЛогарифмический поиск в динамических таблицахвыдает в качестве значения указатель на дерево, в которое добавленоЛогарифмический поиск в динамических таблицах. Таким образом,Логарифмический поиск в динамических таблицахиспользуется для включенияЛогарифмический поиск в динамических таблицахвЛогарифмический поиск в динамических таблицах.
    Логарифмический поиск в динамических таблицах
    Алгоритм 13.6. Включение в дерево бинарного поиска
    Исключение гораздо сложнее включения, и мы изложим здесь только основную идею. Если подлежащее удалению имя Логарифмический поиск в динамических таблицах имеет самое большее одного сына, то при исключении Логарифмический поиск в динамических таблицах его сын (если он вообще есть) объявляется сыном отца Логарифмический поиск в динамических таблицах. Если Логарифмический поиск в динамических таблицах имеет двух сыновей, его прямо удалить нельзя.
    Вместо этого мы находим в таблице либо имя Логарифмический поиск в динамических таблицах, которое непосредственно предшествует Логарифмический поиск в динамических таблицах, либо имя Логарифмический поиск в динамических таблицах, которое непосредственно следует за Логарифмический поиск в динамических таблицах в естественном порядке. Оба имени принадлежат узлам, которые имеют не больше одного сына, и, таким образом, Логарифмический поиск в динамических таблицах можно исключить заменой его либо именем Логарифмический поиск в динамических таблицах, либо Логарифмический поиск в динамических таблицах, и затем исключением узла, который содержал Логарифмический поиск в динамических таблицах или Логарифмический поиск в динамических таблицах, соответственно.

    Логарифмический поиск в статических таблицах

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

    Оптимальные деревья бинарного поиска

    Бинарный поиск в последовательно распределенной таблице ( алгоритм 13.4.) обеспечивает очень быстрое нахождение имен, которые являются средними точками на раннем этапе процесса деления пополам, именно имен, близких к вершине дерева Оптимальные деревья бинарного поиска. Таким образом, любое имя в таблице можно выбрать примерно за Оптимальные деревья бинарного поиска сравнений.
    Оптимальные деревья бинарного поиска
    Рис. 13.1.  Четыре дерева бинарного поиска над множеством имен {A,B,C,D}
    На практике в большинстве таблиц встречаются имена, к которым обращаются гораздо чаще, чем к другим, и "привилегированные" места в таблице разумно постараться использовать для наиболее часто вызываемых имен, а не для имен, выбранных для этих мест в результате бинарного поиска. Это невозможно осуществить для последовательно распределенных таблиц, поскольку место имени определяется его положением относительно естественного порядка имен в таблице. Введем структуру данных, легко приспосабливаемую как к месту бинарного поиска, так и к возможности выделять точки, в которых таблица делится на две части.
    Деревом бинарного поиска над именами Оптимальные деревья бинарного поиска
    называется расширенное бинарное дерево, все внутренние узлы которого помечены различными именами из списка Оптимальные деревья бинарного поиска таким образом, что симметричный порядок узлов совпадает с естественным порядком. Каждый из Оптимальные деревья бинарного поиска внешних узлов соответствует промежутку в таблице. На рис. 13.1 показаны четыре различных дерева бинарного поиска на множестве имен Оптимальные деревья бинарного поиска. Деревья (а) и (b) - вырожденные, поскольку они по существу являются линейными списками, которые должны просматриваться последовательно.
    Поиск имени Оптимальные деревья бинарного поиска в дереве бинарного поиска осуществляется путем сравнения Оптимальные деревья бинарного поиска с именем, стоящим в корне. Тогда
  • Если корня нет (дерево пусто), то Оптимальные деревья бинарного поиска в таблице отсутствует и поиск завершается безуспешно.
  • Если Оптимальные деревья бинарного поиска совпадает с именем в корне, поиск завершается успешно.
  • Если Оптимальные деревья бинарного поиска предшествует имени в корне, поиск продолжается ниже в левом поддереве корня.
  • Если Оптимальные деревья бинарного поиска следует за именем в корне, поиск продолжается ниже в правом поддереве корня.

  • Пусть бинарное дерево имеет вид, в котором каждый узел представляет собой тройку (LEFT, NAME, RIGHT), где LEFT и RIGHTсодержат указатели левого и правого сыновей соответственно, и NAME содержит имя, хранящееся в узле.
    Указатели могут иметь значение Оптимальные деревья бинарного поиска, означающее, что поддерево, на которое они указывают пусто. Если указатель корня дерева есть Оптимальные деревья бинарного поиска, то само дерево пусто. Как и следовало ожидать, успешный поиск завершается во внутреннем узле дерева бинарного поиска и безуспешный поиск завершается во внешнем узле.

    Эта процедура нахождения имени Оптимальные деревья бинарного поиска в таблице, организованная в виде дерева бинарного поиска Оптимальные деревья бинарного поиска, показана в алгоритме 13.5. Отметим его сходство с алгоритмом 13.4 (бинарный поиск).

    Оптимальные деревья бинарного поиска
    Алгоритм 13.5. Поиск в дереве бинарного поиска

    Поиск и другие операции над таблицами

    Любой способ поиска оперирует с элементами, которые будем называть именами, взятыми из множества имен Поиск и другие операции над таблицами - оно называется пространством имен. Это пространство имен может быть конечным или бесконечным. Самыми распространенными пространствами имен являются множества целых чисел с их числовым порядком (нумерацией), и множества последовательностей символов над некоторым конечным алфавитом с их лексикографическим (то есть словарным) порядком. Каждый из алгоритмов поиска, обсуждаемых в этой лекции, основан на одном из трех следующих предположений о пространстве Поиск и другие операции над таблицами.
    Предположение 1. На Поиск и другие операции над таблицами определен линейный порядок, называемый естественным порядком и обозначаемый знаком <. Такой порядок имеет следующие свойства.
  • Любые два элемента Поиск и другие операции над таблицами сравнимы, то есть должно выполняться в точности одно из трех условий: Поиск и другие операции над таблицами, Поиск и другие операции над таблицами, Поиск и другие операции над таблицами.
  • Порядок обладает транзитивностью, то есть если Поиск и другие операции над таблицами и Поиск и другие операции над таблицами, то Поиск и другие операции над таблицами для любых элементов Поиск и другие операции над таблицами. Мы используем обозначения Поиск и другие операции над таблицами, Поиск и другие операции над таблицами, Поиск и другие операции над таблицами в очевидном смысле. При анализе эффективности алгоритма поиска полагаем, что исход (Поиск и другие операции над таблицами, Поиск и другие операции над таблицами или Поиск и другие операции над таблицами) зависит от сравнения.

  • Предположение 2. Каждое имя в Поиск и другие операции над таблицами есть последовательность символов или цифр над конечным линейно упорядоченным алфавитом Поиск и другие операции над таблицами. Естественным порядком на Поиск и другие операции над таблицами является лексикографический порядок, индуцированный линейным порядком на Поиск и другие операции над таблицами. Мы полагаем, что исход ( Поиск и другие операции над таблицами, Поиск и другие операции над таблицами или Поиск и другие операции над таблицами) сравнения двух символов (не имен) получается за время, не зависящее от Поиск и другие операции над таблицами или Поиск и другие операции над таблицами.
    Предположение 3. Имеется функция Поиск и другие операции над таблицами, которая равномерно отображает пространство имен Поиск и другие операции над таблицами в множество Поиск и другие операции над таблицами, то есть все целые Поиск и другие операции над таблицами, приблизительно с одинаковой частотой являются образами имен из Поиск и другие операции над таблицами
    при отображении Поиск и другие операции над таблицами. Мы полагаем, что функция Поиск и другие операции над таблицами не зависела от Поиск и другие операции над таблицами, это с теоретической точки зрения выглядит шатко, но с практической - довольно реально.
    Как уже было отмечено, поиск производится не в самом пространстве Поиск и другие операции над таблицами имен, а в конечном подмножестве Поиск и другие операции над таблицами множества Поиск и другие операции над таблицами, называемом таблицей. На большинстве таблиц, которые мы рассматриваем, определен линейный порядок, называемый табличным порядком: ему соответствует нижний индекс имени (то есть Поиск и другие операции над таблицами есть первое имя в таблице, Поиск и другие операции над таблицами - второе и тому подобное).
    Табличный порядок часто совпадает с естественным порядком, определенным на пространстве имен, однако такое совпадение не обязательно.

    Мощность таблицы Поиск и другие операции над таблицами обычно намного меньше, чем мощность пространства имен Поиск и другие операции над таблицами, даже если Поиск и другие операции над таблицами конечно.

    Представление о таблице как об упорядоченном множестве имен существенно для многих целей. Но иногда бывает необходимо рассматривать таблицу как множество ячеек, каждая из которых может содержать одно имя. Например, если процесс включения нового имени рассматривать более подробно, то обнаружится, что сначала нужно расширить таблицу добавлением ячейки, для того чтобы заготовить место для новой записи: только потом туда заносится новое имя.

    Мы будем предполагать, что имена появляются в таблице не больше одного раза (исключение составляет переходный период, в течение которого заносится новое имя; в таблице допускается два вхождения одного имени). В большинстве случаев вследствие такого предположения таблица с Поиск и другие операции над таблицами именами имеет ровно Поиск и другие операции над таблицами ячеек. Однако важный класс алгоритмов, основанных на вычислении адреса, опирается на предположение о том, что таблица содержит больше ячеек, чем имен. Эти алгоритмы должны четко принимать во внимание наличие пустых ячеек.

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


    В таком типе организации файлов нашему понятию таблицы соответствует указатель или справочник.

    Мы рассматриваем только четыре табличные операции: поиск, включение, исключение и распечатка. Подробное определение того, что должны делать эти операции над таблицей Поиск и другие операции над таблицами, зависит от структуры данных, использованной для реализации таблицы.

    поиск Поиск и другие операции над таблицами: если Поиск и другие операции над таблицами, то отметить его указателем, то есть переменной Поиск и другие операции над таблицами присвоить такое значение, что Поиск и другие операции над таблицами; в противном случае указать, что Поиск и другие операции над таблицами;

    включение Поиск и другие операции над таблицами: если Поиск и другие операции над таблицами, то поместить его на соответствующее место.

    Включение в общем случае предполагает прежде всего поиск соответствующего места, поэтому иногда удобно разделить операцию на две фазы. Сначала используем процедуру поиска для отыскания места, куда должно быть помещено Поиск и другие операции над таблицами, и затем помещаем z на это место.

    включить Поиск и другие операции над таблицами на Поиск и другие операции над таблицами-е место: включить Поиск и другие операции над таблицами сразу после имени Поиск и другие операции над таблицами. До включения Поиск и другие операции над таблицами.

    исключить Поиск и другие операции над таблицами: если Поиск и другие операции над таблицами, то исключить его.

    Как и включение, исключение иногда реализуется процедурой поиска для получения места Поиск и другие операции над таблицами и последующей процедурой:

    исключить с Поиск и другие операции над таблицами-го места: исключить Поиск и другие операции над таблицами из Поиск и другие операции над таблицами.До исключения Поиск и другие операции над таблицами\\ & После исключения Поиск и другие операции над таблицами

    Таблица, в которой осуществляются включения или исключения, называется динамической; в противном случае она носит название статической.

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

    распечатка: & напечатать все имена из Поиск и другие операции над таблицами в их естественном порядке

    Среди всех операций, которые можно производить над таблицами, четыре, рассматриваемые в этой лекции (поиск, включение, исключение и распечатка), и сортировка (лекции 14, 15) - наиболее важные.

    Последовательный поиск

    Под последовательным поиском мы подразумеваем исследование имен в том порядке, в котором они встречаются в таблице. При таком поиске в таблице в худшем случае получается просмотр всей таблицы; даже в среднем последовательный поиск имеет тенденцию к использованию числа операций, пропорционального Последовательный поиск. Для больших таблиц его не следует относить к методам быстрого поиска, поскольку последовательный поиск асимптотически гораздо медленнее других алгоритмов, описанных в этой лекции. Несмотря на его низкие асимптотические возможности, имеется ряд причин, по которым этот метод следует обсудить вначале. Во-первых, хотя идея его проста, он позволяет нам ввести важные понятия и методы, применимые к поиску вообще. Во-вторых, последовательный поиск является единственным методом поиска, применимым к отдельным устройствам памяти и к тем таблицам, которые строятся на пространстве имен без линейного порядка. Наконец, последовательный поиск является быстрым для достаточно малых таблиц и для больших таблиц, организованных иерархическим способом: более быстрый метод используется для исследования окрестности верхушки иерархии, а последовательный поиск – для подтаблицы на нижнем уровне иерархии.
    Для последовательного поиска по таблице Последовательный поиск мы предполагаем, что имеется указатель Последовательный поиск, значение которого принадлежит отрезку Последовательный поиск или, возможно, Последовательный поиск. Над этим указателем разрешается производить только следующие операции; первоначальное присваивание ему значения 1 или Последовательный поиск(или, если удобнее, 0 или Последовательный поиск, увеличение и /или уменьшение его на единицу и сравнение его с 0, 1, Последовательный поиск или Последовательный поиск. При таких соглашениях наиболее очевидный алгоритм поиска в таблице Последовательный поиск первого вхождения данного имени Последовательный поиск имеет вид алгоритма 13.1. Здесь, как и во всех других алгоритмах поиска, изложенных в настоящей лекции, мы полагаем, что алгоритм останавливается немедленно по отыскании Последовательный поиск или установлении, что Последовательный поиск в таблице нет.
    Последовательный поиск Последовательный поиск Последовательный поиск Последовательный поиск
    Последовательный поиск Последовательный поиск Последовательный поиск Последовательный поиск Последовательный поиск найдено: Последовательный поиск указывает на Последовательный поиск
    Последовательный поиск не найдено: Последовательный поиск не входит в Последовательный поиск

    Алгоритм 13.1. Последовательный поиск Последовательный поиск Последовательный поиск Последовательный поиск Последовательный поиск
    Последовательный поиск Последовательный поиск Последовательный поиск Последовательный поиск Последовательный поиск найдено: Последовательный поиск указывает на Последовательный поиск
    Последовательный поиск не найдено: Последовательный поиск не входит в Последовательный поиск
    Рассмотрим некоторые аспекты эффективности последовательного поиска, начиная со стандартных методов программирования.
    В программе, построенной в виде одного цикла, как алгоритм 13.1, любое значительное ускорение должно быть следствием улучшения кода в цикле. Для того чтобы увидеть, какие операции выполняются внутри цикла, необходимо переписать алгоритм 13.1 в форме, близкой к языку машины:

    iПоследовательный поиск1 цикл: if z = xi then найдено if i = n then не найдено iПоследовательный поискi + 1 goto цикл

    За каждую итерацию выполняется до четырех команд: два сравнения, одна операция увеличения и одна передача управления.

    Для ускорения внутреннего цикла общим приемом является добавление в таблицу специальных строк, которые делают необязательной явную проверку того, достиг ли указатель границ таблицы. Это можно сделать в алгоритме 13.1. Если перед поиском мы добавим искомое имя Последовательный поиск в конце таблицы, то цикл всегда будет завершаться отысканием вхождения Последовательный поиск; таким образом, нам не нужно в цикле каждый раз делать проверку Последовательный поиск. В конце цикла проверка условия Последовательный поиск, выполняемая лишь однажды, говорит о том, является ли найденное вхождение Последовательный поиск истинным или специальным элементом таблицы. Это демонстрируется в алгоритме 13.2.

    Последовательный поиск

    iПоследовательный поиск1 while z Последовательный поискxi do i Последовательный поискi+1 if iПоследовательный поискn then{найдено: i указывает на z} else{не найдено}

    Алгоритм 13.2. Улучшенный последовательный поиск

    Улучшение алгоритма 13.1 будет наиболее очевидным, если мы перепишем алгоритм 13.2 в тех же близких к языку машины обозначениях, которые использовались раньше:

    Последовательный поиск iПоследовательный поиск1 цикл: if z = xi then goto возможно iПоследовательный поискi+1 goto цикл возможно: if iПоследовательный поискn then {найдено:i указывает на z} else{не найдено}

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

    Печально то, что фокус с добавлением Последовательный поиск в конец таблицы перед поиском удается, только если мы имеем прямой непосредственный доступ к концу таблицы. Это возможно, если таблица хранится в памяти с произвольным доступом, но невозможно в общем случае, когда используется связанное размещение или память с последовательным доступом.


    Единственным недостатком алгоритма 13. 2 является то, что при безуспешном поиске (поиске имен, которых нет в таблице) всегда просматривается вся таблица. Если такой поиск возникает часто, то имена надо хранить в естественном порядке; это позволяет завершать поиск, как только при просмотре попалось первое имя, большее или равное аргументу поиска. В этом случае в конец таблицы следует добавить фиктивное имя Последовательный поиск для того, чтобы гарантировать выполнение условия завершения (Последовательный поиск - это новое имя, которое по предположению больше любого имени из пространства имен Последовательный поиск). Таким образом получаем алгоритм 13.3.

    Последовательный поиск

    iПоследовательный поиск1 while z > xi do i Последовательный поиск i+1 if z=xi then{найдено:i указывает на z} else{не найдено}

    Алгоритм 13.3.Последовательный поиск по таблице, хранимой в естественном порядке

    Сбалансированные сильно ветвящиеся деревья

    Деревья бинарного поиска естественным образом обобщаются до Сбалансированные сильно ветвящиеся деревья-арных деревьев поиска, в которых каждый узел имеет Сбалансированные сильно ветвящиеся деревья
    сыновей и содержит Сбалансированные сильно ветвящиеся деревья имен. Имена в узле делят множество имен на Сбалансированные сильно ветвящиеся деревья подмножеств, каждое подмножество соответствует одному из Сбалансированные сильно ветвящиеся деревья
    поддеревьев узла. На рис. 13.2 показано полностью заполненное 5-арное дерево с двумя уровнями. Заметим, что мы не можем требовать, чтобы каждый узел Сбалансированные сильно ветвящиеся деревья-арного дерева имел ровно Сбалансированные сильно ветвящиеся деревья сыновей и включал равно Сбалансированные сильно ветвящиеся деревья имен; если мы захотим включить Сбалансированные сильно ветвящиеся деревья в дерево на рисунке 13.2, то должны будем создать узлы с меньше чем Сбалансированные сильно ветвящиеся деревья сыновьями и меньше чем Сбалансированные сильно ветвящиеся деревья именами. Таким образом, определение Сбалансированные сильно ветвящиеся деревья-арного дерева утверждает только, что каждый узел имеет не более Сбалансированные сильно ветвящиеся деревья сыновей и содержит не более Сбалансированные сильно ветвящиеся деревья имен. Ясно, что на Сбалансированные сильно ветвящиеся деревья-арных деревьях можно осуществлять поиск так же, как и на бинарных деревьях.
    Как и в деревьях бинарного поиска, полезно различать внутренние узлы и листья. Внутренний узел содержит Сбалансированные сильно ветвящиеся деревья имен, записанных в естественном порядке, и имеет Сбалансированные сильно ветвящиеся деревья сыновей, каждый из которых может быть либо внутренним узлом, либо листом. Лист не содержит имен (разве что временно в процессе включения), и, как раньше, в листьях - завершаются безуспешные поиски. Обычно за очевидностью мы на рисунке их опускаем.
    Сбалансированные сильно ветвящиеся деревья
    Рис. 13.2.  Абсолютно сбалансированное, полностью заполненное 5-арное дерево поиска
    Сбалансированное сильно ветвящееся дерево порядка Сбалансированные сильно ветвящиеся деревья есть Сбалансированные сильно ветвящиеся деревья-арное дерево в котором:
  • Все листья расположены на одном уровне.
  • Корень имеет Сбалансированные сильно ветвящиеся деревья сыновей, Сбалансированные сильно ветвящиеся деревья.
  • Другие внутренние узлы имеют Сбалансированные сильно ветвящиеся деревья сыновей, Сбалансированные сильно ветвящиеся деревья.

  • Сбалансированные сильно ветвящиеся деревья

    Обменная сортировка

    Обменная сортировка некоторым систематическим образом меняет местами пары имен, не отвечающие порядку, до тех пор, пока такие пары существуют. Фактически алгоритм 14.1 можно рассматривать как обменную сортировку, в которой имя Обменная сортировка меняется местами со своим соседом слева, пока не оказывается на правильном месте. В этом разделе мы обсуждаем два типа обменных сортировок: хорошо известную, но относительно неэффективную пузырьковую сортировку и быструю сортировку — один из лучших со всех точек зрения алгоритмов внутренней сортировки.
    Пузырьковая сортировка. Наиболее очевидный метод систематического обмена местами имен с неправильным порядком состоит в просмотре пар смежных имен последовательно слева направо и перемене мест тех имен, которые не отвечают порядку.
    Обменная сортировка
    Рис. 14.2.  Пузырьковая сортировка, примененная к таблице. Показан вектор инверсии таблицы после каждого прохода
    Эта техника получила название пузырьковой сортировки, так как большие имена "пузырьками всплывают" вверх (то есть на правый конец) таблицы. В алгоритме 14.2 эта простая идея реализуется с одним небольшим усовершенствованием: ясно, что не имеет смысла продолжать просмотр для больших имен (в правом конце таблицы), про которые известно, что они находятся на своих окончательных позициях. В алгоритме 14.2 используется переменная Обменная сортировка, значение которой в начале цикла Обменная сортировка
    равно наибольшему индексу Обменная сортировка, такому, что про имя Обменная сортировка еще не известно, стоит ли оно в окончательной позиции. На рис. 14.2 показана работа алгоритма на примере таблицы с Обменная сортировка именами.
    Обменная сортировка
    Алгоритм 14.2. Пузырьковая сортировка
    Анализ пузырьковой сортировки зависит от трех факторов: числа проходов (то есть числа выполнений тела цикла Обменная сортировка), числа сравнений Обменная сортировка и числа обменов Обменная сортировка. Число обменов равно, как в алгоритме 14.1, числу инверсий: 0 в лучшем случае, Обменная сортировка в худшем случае и Обменная сортировка - в среднем. Рисунок 14.2
    дает возможность предположить, что каждый проход пузырьковой сортировки, исключая последний, уменьшает на единицу каждый ненулевой элемент вектора инверсий и циклически сдвигает вектор на одну позицию влево; легко доказать, что это верно в общем случае, и поэтому число проходов равно единице плюс наибольший элемент вектора инверсий. В лучшем случае имеется всего один проход, в худшем случае - Обменная сортировка проходов и в среднем - Обменная сортировка проходов, где Обменная сортировка - вероятность того, что наибольшим элементом вектора инверсии является Обменная сортировка. Общее число сравнений имен трудно определить, но можно показать, что оно равно Обменная сортировка в лучшем случае, Обменная сортировка в худшем случае и Обменная сортировкаОбменная сортировка- в среднем.
    Пузырьковую сортировку можно несколько улучшить, но при этом она все еще не сможет конкурировать с более эффективными алгоритмами сортировки. Ее единственным преимуществом является простота.
    Как в простой сортировке вставками, так и в пузырьковой сортировке (алгоритм 14.2) основной причиной неэффективности является тот факт, что обмены дают слишком малый эффект, так как в каждый момент времени имена сдвигаются только на одну позицию. Такие алгоритмы непременно требуют порядка Обменная сортировка
    операций, как в среднем, так и в худшем случаях.
    Быстрая сортировка. Идея метода быстрой сортировки состоит в том, чтобы выбрать одно из имен в таблице и использовать его для разделения таблицы на две подтаблицы, составленные соответственно из имен меньших и больших выбранного, которые затем рекурсивно сортируются с использованием быстрой сортировки. Разделение можно реализовать, одновременно просматривая таблицу и слева направо, и справа налево, меняя местами имена в неправильных частях таблицы. Имя, используемое для расщепления таблицы, затем помещается между двумя подтаблицами, и две подтаблицы сортируются рекурсивно.
    В алгоритме 14.3 показаны детали быстрой сортировки для сортировки таблицы Обменная сортировка, где Обменная сортировка
    используется для разбиения таблицы на подтаблицы. На рис. 14.3 показано, как алгоритм 14.3 использует два указателя Обменная сортировка и Обменная сортировка для просмотра таблицы во время разбиения. В начале цикла "Обменная сортировка Обменная сортировка" Обменная сортировка и Обменная сортировка указывают соответственно на первое и последнее имена, о которых известно, что они находятся не в тех частях файла, в которых требуется. Когда Обменная сортировка
    встречаются, то есть когда Обменная сортировка, все имена находятся в соответствующих частях таблицы и Обменная сортировка помещается между двумя частями, меняясь при этом местами с Обменная сортировка, алгоритм предполагает, что имя Обменная сортировка определено и больше, чем Обменная сортировка.
    Обменная сортировка
    Алгоритм 14.3. Рекурсивный вариант быстрой сортировки, использующий первое имя для расщепления таблицы. Предполагается, что имя Обменная сортировка
    определено и больше или равно Обменная сортировка "
    Обменная сортировка
    Рис. 14.3.  Фаза разбиения быстрой сортировки, использующей первое имя для разбиения таблицы. Значение Обменная сортировка не показано, оно предполагается большим, чем другие показанные значения
    Алгоритм 14.3 изящен, но непрактичен. Проблема состоит в том, что рекурсия используется для записи подтаблиц, которые рассматриваются на более поздних этапах, и в худших случаях (когда таблица уже отсортирована) глубина рекурсии может равняться Обменная сортировка. Следовательно, для стека, реализующего рекурсию, необходима память, пропорциональная Обменная сортировка; для больших Обменная сортировка такое требование становится неприемлемым. Кроме того, второе рекурсивное обращение к быстрой сортировке в алгоритме 14.3 может быть легко исключено. По этим причинам мы предлагаем алгоритм 14.4, итерационный вариант быстрой сортировки, в которой стек ведется явно. Элементом стека является пара Обменная сортировка: когда пара находится в стеке, это значит, что нужно сортировать соответствующие Обменная сортировка. Алгоритм 14.4 помещает в стеке большую из двух подтаблиц и немедленно применяет алгоритм к меньшей подтаблице. Это уменьшает глубину стека в худшем случае примерно до Обменная сортировка. Заметим, что подтаблицы длины 1 игнорируются и что расщепление подтаблицы делается с использованием случайно выбранного имени в этой подтаблице.
    Обменная сортировка
    увеличить изображение
    Алгоритм 14.4.Итерационный вариант быстрой сортировки Обменная сортировка

    Внутренняя сортировка

    Существует по крайней мере пять широких классов алгоритмов внутренней сортировки.
  • Вставка. На Внутренняя сортировка-м этапе Внутренняя сортировка-е имя помещается на надлежащее место между Внутренняя сортировка
    уже отсортированным именами:

  • Внутренняя сортировка

  • Обмен. Два имени, расположение которых не соответствует порядку, меняются местами (обмен). Эти процедуры повторяются до тех пор, пока остаются такие пары.

  • Внутренняя сортировка

  • Выбор. На Внутренняя сортировка-м этапе из неотсортированных имен выбирается Внутренняя сортировка-е наибольшее (наименьшее) имя и помещается на соответствующее место.

  • Внутренняя сортировка

  • Распределение. Имена распределяются по группам, и содержимое групп затем объединяется таким образом, чтобы частично отсортировать таблицу; процесс повторяется до тех пор, пока таблица не будет отсортирована полностью.
  • Слияние. Таблица делится на подтаблицы, которые сортируются по отдельности и затем сливаются в одну.

  • Внутренняя сортировка

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

    В каждом из рассматриваемых алгоритмов будем считать, что имена нужно сортировать на месте. Другими словами, переразмещение имен должно происходить внутри последовательности Внутренняя сортировка; при этом существуют одна или две дополнительные ячейки, в которых временно размещается значение имени. Ограничение "на месте" основано на предположении, будто число имен настолько велико, что во время сортировки не допускается перенос их в другую область памяти. Если в распоряжении имеется память, достаточная для такого переноса, то некоторые из обсуждаемых алгоритмов можно значительно ускорить. Эти рассмотрения заставляют нас в алгоритмах распределяющей сортировки и сортировки слиянием реализовать последовательность Внутренняя сортировка как связанный список.

    Вставка

    Вставка - простейшая сортировка вставками проходит через этапы Вставка: на этапе Вставка имя Вставка
    вставляется на свое правильное место среди Вставка.
    Вставка
    Рис. 14.1.  Простая сортировка вставками, используемая на таблице из n = 5 имен. Пунктирные вертикальные линии разделяют уже отсортированную часть таблицы и еще не отсортированную
    При вставке имя Вставка временно размещается в Вставка, и просматриваются имена Вставка; они сравниваются с Вставка и сдвигаются вправо, если обнаруживается, что они больше Вставка. Имеется фиктивное имя Вставка, значение которого Вставка служит для остановки просмотра слева. На рис. 14.1 работа этого алгоритма проиллюстрирована на примере таблицы из пяти имен.
    Вставка
    Алгоритм 14.1. Простая сортировка вставками
    Эффективность этого алгоритма, как и большинства алгоритмов сортировки, зависит от числа сравнений имен и числа пересылок данных, осуществляемых в трех случаях : худшем, среднем (в предположении, что все Вставка перестановок равновероятны) и лучшем.

    Частичная сортировка (слияние)

    Вторым направлением исследования частичной сортировки является задача слияния двух отсортированных таблиц Частичная сортировка (слияние) и Частичная сортировка (слияние)
    в одну отсортированную таблицу Частичная сортировка (слияние). Существует очевидный способ это сделать: таблицы, подлежащие слиянию, просматривать параллельно, выбирая на каждом шаге меньшее из двух имен и помещая его в окончательную таблицу. Этот процесс немного упрощается добавлением имен-сторожей Частичная сортировка (слияние), как в алгоритме 15.5. В этом алгоритме Частичная сортировка (слияние) и Частичная сортировка (слияние) указывают, соответственно, на последние имена в двух входных таблицах, которые еще не были помещены в окончательную таблицу.
    Частичная сортировка (слияние)
    Алгоритм 15.5. Прямое слияние Частичная сортировка (слияние)

    Частичная сортировка (выбор)

    Как при данных именах Частичная сортировка (выбор) можно найти Частичная сортировка (выбор)-е из наибольших в порядке убывания? Задача, очевидно, симметрична: отыскание Частичная сортировка (выбор)-го наибольшего (Частичная сортировка (выбор)-го наименьшего) имени можно осуществить, используя алгоритм отыскания Частичная сортировка (выбор)-го наибольшего, но меняя местами действия, предпринимаемые при результатах < и >сравнения имен. Таким образом, отыскание наибольшего имени Частичная сортировка (выбор) эквивалентно отысканию наименьшего имени Частичная сортировка (выбор); отыскание второго наибольшего имени Частичная сортировка (выбор) эквивалентно отысканию второго наименьшего Частичная сортировка (выбор) и т.д.
    Конечно, все перечисленные варианты задачи выбора можно решить, используя любой из методов полной сортировки имен и затем тривиально обращаясь к Частичная сортировка (выбор)-му наибольшему. Такой подход потребует порядка Частичная сортировка (выбор) сравнений имен независимо от значений Частичная сортировка (выбор).
    При использовании алгоритма сортировки для выбора наиболее подходящим будет один из алгоритмов, основанных на выборе: либо простая сортировка выбором (алгоритм 15.1) либо пирамидальная сортировка (алгоритм 15.3). В каждом случае мы можем остановиться после того, как выполнены первые Частичная сортировка (выбор) шагов. Для простой сортировки выбором это означает использование Частичная сортировка (выбор)
    сравнений имен, а для пирамидальной — использование Частичная сортировка (выбор) сравнений имен. В обоих случаях мы получаем больше информации, чем нужно, потому что мы полностью определяем порядок Частичная сортировка (выбор) наибольших имен.

    Частичная сортировка

    Ранее мы изучали проблему полного упорядочения множества имен, не имея a priori информации об абстрактном порядке имен. Имеется два очевидных уточнения этой проблемы. Вместо полного упорядочения требуется только определить Частичная сортировка-е наибольшее имя (то есть Частичная сортировка-е имя в порядке убывания) (выбор) или, вместо того чтобы начинать процесс, не располагая информацией о порядке, начинать с двух отсортированных подтаблиц (слияние). Мы рассматриваем обе проблемы частичной сортировки.

    Цифровая распределяющая сортировка

    Цифровая распределяющая сортировка основана на наблюдении, что если имена уже отсортированы по младшим разрядами Цифровая распределяющая сортировка, то их можно полностью отсортировать, сортируя только по старшим разрядам Цифровая распределяющая сортировка при условии, что сортировка осуществляется таким образом, чтобы не нарушить относительный порядок имен с одинаковыми цифрами в старших разрядах. Заметим, что к самой таблице обращаются по правилу "первым включается – первым исключается", и поэтому лучшим способом представления являются очереди. В частности, предположим, что с каждым ключом Цифровая распределяющая сортировка ассоциируется поле связи Цифровая распределяющая сортировка; тогда эти поля связи можно использовать для сцепления всех имен в таблице вместе во входную очередь Цифровая распределяющая сортировка. При помощи полей связи можно также сцеплять имена в очереди Цифровая распределяющая сортировка, используемые для представления стопок. После того как имена распределены по стопкам, очереди, представляющие эти стопки, связываются вместе для получения вновь таблицы Цифровая распределяющая сортировка. Алгоритм 15.4. представляет эту процедуру в общих чертах (очереди описаны в лекции 11). В результате применения алгоритма очередь Цифровая распределяющая сортировка будет содержать имена в порядке возрастания; то есть имена будут связаны в порядке возрастания полями связи, начиная с головы очереди Цифровая распределяющая сортировка.
    Использовать поля связи Цифровая распределяющая сортировка для формирования Цифровая распределяющая сортировка во входную очередь Цифровая распределяющая сортировка
    Цифровая распределяющая сортировка
    Алгоритм 15.4.Цифровая распределяющая сортировка

    Распределяющая сортировка

    Обсуждаемый здесь алгоритм сортировки отличается от рассматривавшихся до сих пор тем, что он основан не на сравнениях между именами, а на представлении имен. Мы полагаем, что каждое из имен Распределяющая сортировка имеет вид Распределяющая сортировка
    и их нужно отсортировать в возрастающем лексикографическом порядке, то есть
    Распределяющая сортировка
    тогда и только тогда, если для некоторого Распределяющая сортировка
    имеем Распределяющая сортировка для Распределяющая сортировка и Распределяющая сортировка. Для простоты будем считать, что Распределяющая сортировка, и поэтому имена можно рассматривать как целые, представленные по основанию Распределяющая сортировка, так что каждое имя имеет Распределяющая сортировкаРаспределяющая сортировка-ичных цифр. Более короткие имена дополняются нулями.

    Внешняя сортировка

    В методах сортировки, обсуждавшихся в предыдущем разделе, мы полагали, что таблица умещается в быстродействующей внутренней памяти. Хотя для большинства реальных задач обработки данных это предположение слишком сильно, оно, как правило, выполняется для комбинаторных алгоритмов. Сортировка обычно используется только для некоторого сокращения времени работы алгоритмов, в которых сортировка применяется только для некоторого сокращения времени работы алгоритмов, когда оно недопустимо велико даже для задач "умеренных" размеров. Например, часто бывает необходимо сортировать отдельные предметы во времени исчерпывающего поиска (лекция 13), но поскольку такой поиск обычно требует экспоненциального времени, маловероятно, что подлежащая сортировке таблица будет настолько большой, чтобы потребовалось использование запоминающих устройств. Однако задача сортировки таблицы, которая слишком велика для основной памяти, служит хорошей иллюстрацией работы с данными большого объема, и поэтому в этом разделе мы обсудим важные идеи внешней сортировки. Более того, будем рассматривать только сортировку таблицы путем использования вспомогательной памяти с последовательным доступом. Условимся называть эту память лентой.
    Общей стратегией в такой внешней сортировке является использование внутренней памяти для сортировки имен из ленты по частям так, чтобы производить исходные отрезки (известные также как цепочки) имен в возрастающем порядке. По мере порождения эти отрезки распределяются по Внешняя сортировка
    рабочим лентам, и затем производится их слияние по Внешняя сортировка отрезков обратно на исходную Внешняя сортировка-ю ленту так, что она будет содержать меньшее число более длинных отрезков. Затем отрезки снова распределяются по остальным Внешняя сортировка лентам, и снова производится их слияние по Внешняя сортировка штук обратно на Внешняя сортировка-ю ленту. Процесс продолжается до тех пор, пока не получится один отрезок, то есть пока таблица не будет полностью отсортирована. Имеются, таким образом, две отдельные проблемы: как порождать исходные отрезки и как осуществлять слияние.
    Самый очевидный метод для получения исходных отрезков состоит в том, что можно просто считывать Внешняя сортировка-ю ленту Внешняя сортировка имен, рассортировывать их во внутренней памяти и записывать их на ленту в виде отрезка, продолжая процесс до тех пор, пока не будут исчерпаны все имена.
    Все полученные таким образом исходные отрезки содержат Внешняя сортировка имен (исключая, возможно, последний отрезок). Поскольку число исходных отрезков в конце концов определяет время слияния, мы хотели бы найти некоторый метод образования более длинных исходных отрезков и, следовательно, меньшего их количества. Это можно сделать, используя для сортировки идею турнира (пирамидальную сортировку). При этом подходе Внешняя сортировка имен, которые умещаются в памяти, хранятся в виде такой пирамиды, что сыновья узла больше узла (вместо того, чтобы быть меньше его). Этот метод отвечает определению "победителя" при сравнении имен в сортировках типа турнира как меньшего из двух имен, и это позволяет нам следить за наименьшим именем.

    Порождение исходных отрезков продолжается следующим образом. Из входной ленты считываются первые Внешняя сортировка имен, и затем из них формируется пирамида, как описано выше. Наименьшее имя выводится как первое в первом отрезке и заменяется в пирамиде следующим именем из входной ленты в соответствии с алгоритмом 15.2. модифицированным так, чтобы для восстановления пирамиды следить за наименьшим, а не за наибольшим именем. Процесс, известный как выбор с замещением, продолжается таким образом, что к текущему отрезку всегда добавляется наименьшее в пирамиде имя, большее или равное имени, которое последним добавлено к отрезку; при этом добавленное имя заменяется на следующее из входной ленты и восстанавливается пирамида. Когда в пирамиде нет имен, больших, чем последнее имя в текущем отрезке, отрезок обрывается и начинается новый. Этот процесс продолжается до тех пор, пока все имена не сформируются в отрезки.

    Разумный путь реализации этой процедуры состоит в том, чтобы рассматривать каждое имя Внешняя сортировка как пару Внешняя сортировка, где Внешняя сортировка есть номер отрезка, в котором находится Внешняя сортировка. Иначе говоря, считается, что пирамида состоит из пар Внешняя сортировка; сравнения между парами осуществляются лексикографически. Когда считывается имя, меньшее последнего имени в текущем отрезке, оно должно быть в следующем отрезке, и благодаря наличию номера отрезка это имя будет ниже всех имен пирамиды, которые входят в текущий отрезок.


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

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

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

    Выбор

    В сортировке посредством выбора основная идея состоит в том, чтобы идти по шагам Выбор, находя Выбор-е наибольшее (наименьшее) имя и помещая его на его место на Выбор-ом шаге. Простейшая форма сортировки выбором представлена алгоритмом 15.1: Выбор-е наибольшее имя находится очевидным способом просмотром оставшихся Выбор
    имен. Число сравнений имен на Выбор-ом шаге равно Выбор, что приводит к общему числу сравнений имен Выбор
    независимо от входа, поэтому ясно, что это не очень хороший способ сортировки.
    Выбор
    Алгоритм 15.1. Простая сортировка выбором
    Несмотря на неэффективность алгоритма 15.1, идея выбора может привести и к эффективному алгоритму сортировки. Весь вопрос в том, чтобы найти более эффективный метод определения Выбор-го наибольшего имени, чего можно добиться, используя механизм турнира с выбыванием. Суть его такова: сравниваются Выбор, затем сравниваются "победители" (то есть большие имена) этих сравнений и т.д.; эта процедура для Выбор показана на рис. 15.1.
    Заметим, что для определения наибольшего имени этот процесс требует Выбор сравнений имен; но, определив наибольшее имя, мы обладаем большим объемом информации о втором по величине (в порядке убывания) имени: оно должно быть одним из тех, которые "потерпели поражение" от наибольшего имени. Таким образом, второе по величине имя теперь можно определить, заменяя наибольшее имя на Выбор и вновь осуществляя сравнение вдоль пути от наибольшего имени к корню. На рис. 15.2 эта процедура показана для дерева из рис. 15.1.
    Идея турнира с выбыванием прослеживается при сортировке весьма отчетливо, если имена образуют пирамиду. Пирамида - это полностью сбалансированное бинарное дерево высоты Выбор, в котором все листья находятся на расстоянии Выбор или Выбор от корня и все потомки узла меньше его самого; кроме того, в нем все листья уровня максимально смещены влево. На рис. 15.3 показано множество имен, организованных в виде пирамиды. Чтобы получить удобное линейное представление дерева, пирамиду можно хранить по уровням в одномерном массиве: сыновья имени из Выбор-ой позиции есть имена в позициях Выбор и Выбор.
    Таким образом, пирамида, представленная на рисунке 15.3, принимает вид

    Выбор

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

    Выбор

    Это общее описание пирамидальной сортировки.

    Выбор
    Рис. 15.1.  Использование турнира с выбыванием для отыскания наибольшего имени.Путь наибольшего имени показан жирной линией

    Выбор
    Рис. 15.2.  Отыскание второго наибольшего имени путем замены наибольшего имени на Выбор. Проведение повторного сравнения имен, побежденных наибольшим именем

    Процедура RESTOREВыбор восстановления пирамиды из последовательности Выбор в предположении, что все поддеревья суть пирамиды, такова:

    Выбор

    Переписывая это итеративным способом и дополняя деталями, мы получим алгоритм 15.2.

    Выбор
    Алгоритм 15.2.Восстановление пирамиды из дерева, поддеревья которого суть пирамиды

    Выбор
    Рис. 15.3.  Пирамида, содержащая 12 имен

    Выбор
    Алгоритм 15.3.Пирамидальная сортировка

    Алгоритм Дейкстры нахождения кратчайшего пути

    Рассмотрим алгоритм нахождения путей в ориентированном графе. Пусть есть ориентированный граф Алгоритм Дейкстры нахождения кратчайшего пути, у которого все дуги имеют неотрицательные метки (веса дуг), а одна вершина определена как источник. Задача состоит в нахождении весов кратчайших путей от источника ко всем другим вершинам граф Алгоритм Дейкстры нахождения кратчайшего пути. Здесь длина пути определяется как сумма весов дуг, составляющих путь. Эта задача часто называется задачей нахождения кратчайшего пути с одним источником. Отметим, что мы будем говорить о длине пути даже тогда, когда она измеряется в других, не линейных, единицах измерения, например, во временных единицах или в денежном эквиваленте.
    Можно представить орграф Алгоритм Дейкстры нахождения кратчайшего пути в виде карты маршрутов рейсовых полетов из одного города в другой. Каждая вершина соответствует городу, а ребро (дуга) Алгоритм Дейкстры нахождения кратчайшего пути - рейсовому маршруту из города Алгоритм Дейкстры нахождения кратчайшего пути в город Алгоритм Дейкстры нахождения кратчайшего пути. Вес дуги Алгоритм Дейкстры нахождения кратчайшего пути - это время полета из города Алгоритм Дейкстры нахождения кратчайшего пути в город Алгоритм Дейкстры нахождения кратчайшего пути. В этом случае решение задачи нахождения кратчайшего пути с одним источником для ориентированного графа трактуется как минимальное время перелета между различными городами.
    Для решения поставленной задачи будем использовать "жадный" алгоритм, который называют алгоритмом Дейкстры (Dijkstra). Алгоритм строит множество Алгоритм Дейкстры нахождения кратчайшего пути вершин, для которых кратчайшие пути от источника уже известны. На каждом шаге к множеству Алгоритм Дейкстры нахождения кратчайшего пути добавляется та из оставшихся вершин, расстояние до которой от источника меньше, чем для других оставшихся вершин. Если веса всех дуг неотрицательны, то можно быть уверенным, что кратчайший путь от источника к конкретной вершине проходит только через вершины множество Алгоритм Дейкстры нахождения кратчайшего пути. Назовем такой путь особым. На каждом шаге алгоритма используется также массив Алгоритм Дейкстры нахождения кратчайшего пути, в который записываются длины кратчайших особых путей для каждой вершины. Когда множество Алгоритм Дейкстры нахождения кратчайшего пути будет содержать все вершины орграфа, то есть для всех вершин будут найдены особые пути, тогда массив Алгоритм Дейкстры нахождения кратчайшего пути будет содержать длины кратчайших путей от источника к каждой вершине.

    Алгоритм Флойда нахождения кратчайших путей между парами вершин

    Предположим, что мы имеем помеченный орграф, который содержит время полета по маршрутам, связывающим определенные города, и~мы хотим построить таблицу, где приводилось бы минимальное время перелета из одного (произвольного) города в любой другой. В этом случае мы сталкиваемся с общей задачей нахождения кратчайших путей, то есть нахождения кратчайших путей между всеми парами вершин орграфа.
    Формулировка задачи.Есть ориентированный граф Алгоритм Флойда нахождения кратчайших путей между парами вершин, каждой дуге (ребру) Алгоритм Флойда нахождения кратчайших путей между парами вершин этого графа сопоставлен неотрицательный вес Алгоритм Флойда нахождения кратчайших путей между парами вершин. Общая задача нахождения кратчайших путей заключается в нахождении для каждой упорядоченной пары вершин Алгоритм Флойда нахождения кратчайших путей между парами вершин любого пути от вершины Алгоритм Флойда нахождения кратчайших путей между парами вершин в вершину Алгоритм Флойда нахождения кратчайших путей между парами вершин, длина которого минимальна среди всех возможных путей от Алгоритм Флойда нахождения кратчайших путей между парами вершин к Алгоритм Флойда нахождения кратчайших путей между парами вершин.
    Можно решать эту задачу, последовательно применяя алгоритм Дейкстры для каждой вершины, объявляемой в качестве источника. Но мы для решения поставленной задачи воспользуемся алгоритмом, предложенным Флойдом (R.W. Floyd). Пусть все вершины орграфа последовательно пронумерованы от 1 до Алгоритм Флойда нахождения кратчайших путей между парами вершин. Алгоритм Флойда использует матрицу Алгоритм Флойда нахождения кратчайших путей между парами вершин, в которой находятся длины кратчайших путей:
    Алгоритм Флойда нахождения кратчайших путей между парами вершин, если Алгоритм Флойда нахождения кратчайших путей между парами вершин;
    Алгоритм Флойда нахождения кратчайших путей между парами вершин, если Алгоритм Флойда нахождения кратчайших путей между парами вершин;
    Алгоритм Флойда нахождения кратчайших путей между парами вершин если отсутствует путь из вершины Алгоритм Флойда нахождения кратчайших путей между парами вершин
    в вершину Алгоритм Флойда нахождения кратчайших путей между парами вершин.
    Над матрицей Алгоритм Флойда нахождения кратчайших путей между парами вершин выполняется Алгоритм Флойда нахождения кратчайших путей между парами вершин итераций. После Алгоритм Флойда нахождения кратчайших путей между парами вершин-й итерации Алгоритм Флойда нахождения кратчайших путей между парами вершин содержит значение наименьшей длины пути из вершины Алгоритм Флойда нахождения кратчайших путей между парами вершин в вершину Алгоритм Флойда нахождения кратчайших путей между парами вершин, причем путь не проходит через вершины с номерами большими Алгоритм Флойда нахождения кратчайших путей между парами вершин.
    Вычисление на Алгоритм Флойда нахождения кратчайших путей между парами вершин-ой итерации выполняется по формуле: Алгоритм Флойда нахождения кратчайших путей между парами вершин
    Верхний индекс Алгоритм Флойда нахождения кратчайших путей между парами вершин обозначает значение матрицы Алгоритм Флойда нахождения кратчайших путей между парами вершин
    после Алгоритм Флойда нахождения кратчайших путей между парами вершин-ой итерации.
    Для вычисления Алгоритм Флойда нахождения кратчайших путей между парами вершин проводится сравнение величины Алгоритм Флойда нахождения кратчайших путей между парами вершин (то есть стоимость пути от вершины Алгоритм Флойда нахождения кратчайших путей между парами вершин к вершине Алгоритм Флойда нахождения кратчайших путей между парами вершин без участия вершины Алгоритм Флойда нахождения кратчайших путей между парами вершин или другой вершины с более высоким номером) с величиной Алгоритм Флойда нахождения кратчайших путей между парами вершин (стоимость пути от вершины Алгоритм Флойда нахождения кратчайших путей между парами вершин к вершине Алгоритм Флойда нахождения кратчайших путей между парами вершин плюс стоимость пути от вершины Алгоритм Флойда нахождения кратчайших путей между парами вершин до вершины Алгоритм Флойда нахождения кратчайших путей между парами вершин). Если путь через вершину Алгоритм Флойда нахождения кратчайших путей между парами вершин дешевле, чем Алгоритм Флойда нахождения кратчайших путей между парами вершин, то величина Алгоритм Флойда нахождения кратчайших путей между парами вершин
    изменяется. Рассмотрим орграф:
    Алгоритм Флойда нахождения кратчайших путей между парами вершин
    Рис. 16.1.  Помеченный орграф
    Матрица A(3 * 3) на нулевой итерации (k = 0)
    Алгоритм Флойда нахождения кратчайших путей между парами вершин
    Матрица A(3 * 3) после первой итерации (k = 1)
    Алгоритм Флойда нахождения кратчайших путей между парами вершин
    Матрица A(3 * 3) после второй итерации (k = 2)
    Алгоритм Флойда нахождения кратчайших путей между парами вершин
    Матрица A(3 * 3) после третьей итерации (k = 3)
    Алгоритм Флойда нахождения кратчайших путей между парами вершин

    Поиск в глубину

    При решении многих задач, касающихся ориентированных графов, необходим эффективный метод систематического обхода вершин и дуг орграфов. Таким методом является метод поиск в глубину. Метод поиска в глубину является основой многих эффективных алгоритмов работы с графами. Предположим, что есть ориентированный граф Поиск в глубину, в котором первоначально все вершины помечены меткой "Поиск в глубину". Поиск в глубину начинается с выбора начальной вершины Поиск в глубину орграфа Поиск в глубину, для этой вершины метка "Поиск в глубину" меняется на метку "Поиск в глубину". Затем для каждой вершины, смежной с вершиной Поиск в глубину и не посещаемой раньше, рекурсивно применяется поиск в глубину. Когда все вершины, которых можно достичь из вершины Поиск в глубину, будут рассмотрены, поиск заканчивается. Если некоторые вершины остались не посещенными, то выбирается одна из них и алгоритм повторяется. Этот процесс продолжается до тех пор, пока не будут обойдены все вершины орграфа Поиск в глубину.
    Метод получил свое название - поиск в глубину, поскольку поиск не посещенных вершин идет в направлении вглубь до тех пор, пока это возможно. Например, пусть Поиск в глубину является последней посещенной нами вершиной. Выбираем очередную дугу Поиск в глубину(ребро), выходящую из вершины Поиск в глубину. Возможна следующая альтернатива: вершина Поиск в глубину
    помечена меткой "Поиск в глубину"; вершина Поиск в глубину
    помечена меткой "Поиск в глубину". Если вершина Поиск в глубину уже посещалась, то отыскивается другая вершина, смежная с вершиной Поиск в глубину; иначе вершина Поиск в глубину метится меткой "Поиск в глубину" и поиск начинается заново от вершины Поиск в глубину. Пройдя все пути, которые начинаются в вершине Поиск в глубину, возвращаемся в вершину Поиск в глубину, то есть в ту вершину, из которой впервые была достигнута вершина Поиск в глубину. Затем процесс повторяется, то есть продолжается выбор нерассмотренных дуг, исходящих из вершины Поиск в глубину, и так до тех пор, пока не будут исчерпаны все эти дуги.

    Программы

    Программа 1. Построение матрицы инциндентности.
    //Построение матрицы инциндентности //Программа реализована на языке программирования Turbo-C++
    \begin{verbatim} #include #include #include #include #include
    struct elem { int num; /* Номер вершины */ int suns; /* Количество сыновей */ char str[20]; /* Строка с номерами сыновей */ elem *next; /* Указатель на следующую вершину */ } *head, *w1, *w2;
    int Connected(int i, int j) { int k; char *str1; w2 = head; if(i == j) return 0; for(k=1; knext; if( strchr(w2->str, j) ) return 1; return 0; }
    void main() {
    int tops; int i,j,k,l; char *str1;
    clrscr(); printf("Введите количество вершин \n"); scanf("%d", &tops);
    head = (elem *)malloc(sizeof(elem)); head->num = 1; head->suns = 0; head->str[0] = '\0'; head->next = NULL;
    w1 = head;
    for(i=2;i<=tops;i++) { w2 = (elem *)malloc(sizeof(elem)); w2->num = i; w2->suns = 0; w2->str[0] = '\0'; w2->next = NULL;
    w1->next = w2; w1 = w2; }
    w1 = head;
    for(i=1; i<=tops; i++) { // clrscr(); printf("Введите количество путей из вершины %d\n", i); scanf("%d", &k);
    for(j=1; j<=k; j++) { printf("Введите связь %d\n", j); scanf("%d", &l); if((l<=0) || (l > tops)) { printf("Такой вершины нет, повторите попытку\n"); l = 0; j--; continue; } w1->str[w1->suns++] = l; w1->str[w1->suns] = '\0'; if(w1->suns == 49) { printf("Слишком много связей !"); exit(1); } } w1 = w1->next; } clrscr(); printf("\n\n Матрица инциндентности :\n"); for(i=1; i<=tops; i++) { printf("\n %d) ", i); for(j=1; j<=tops; j++) { printf("%d ", Connected(i, j)); } } printf("\n\n Нажмите любую клавишу\ldots "); getch(); }
    Программа 2.Поиск вершин, недостижимых из заданной вершины графа.
    //Поиск вершин, недостижимых из заданной вершины графа. //Программа реализована на языке программирования Turbo-C++
    \begin{verbatim} #include #include //- - - - - - - - - - - - - - - - - - - - - - int n,s; int c[20][20]; int r[20]; //- - - - - - - - - - - - - - - - - - - - - - int load(); int save(); int solve(); //- - - - - - - - - - - - - - - - - - - - - - int main(){ load(); solve(); save(); return 0; } //- - - - - - - - - - - - - - - - - - - - - - int load(){ int i,j; ifstream in("input.txt"); in"n"s; s--; for (i=0; i int save(){ int i; ofstream out("output.txt"); for (i=0; i0)&(r[i]==0)){ q[t]=i; t++; r[i]=1; } h++; } return 0; }
    Программа 3. Поиск циклов в графе.
    {> Реализация на Turbo-Pascal. Поиск циклов в графе <}
    {$R-,I-,S-,Q-}
    const MAXN = 40; QUERYSIZE = 600;
    type vert = record x: integer; s: array [1..MAXN] of integer; end;
    var c : array [1..MAXN,1..MAXN] of integer; n : integer;
    wr : vert;
    res : array [1..MAXN] of string; resv: integer; ss : string;
    procedure load; var i,j: integer; begin assign(input,'input.txt'); reset(input); read(n); for i:=1 to n do for j:=1 to n do read(c[i][j]); close(input); end;
    function saveway(i:integer):string; var e:string; begin str(i,e); if (wr.s[i]=-1) then saveway:=e+' ' else saveway:=saveway(wr.s[i])+e+' '; end;
    p>function findss(s: string): boolean; var i : integer; l1,l2,rs : string; i1,i2,i22 : integer;
    begin findss:=false; l2:=copy(s,1,pos(' ',s)-1); i2:=length(l2); i22:=length(s); for i:=1 to resv do begin l1:=copy(res[i],1,pos(' ',res[i])-1); i1:=length(l1); rs:=copy(res[i],1,length(res[i])-i1)+res[i]; if (length(res[i])+i2=i22+i1)and(pos(s,rs)>0) then begin findss:=true; exit; end; end; end;
    procedure solve; var h,t,i,j: integer; q : array [1..QUERYSIZE] of vert; e : string; begin resv:=0; fillchar(res,sizeof(res),0);
    for i:=1 to n do begin fillchar(q[i],sizeof(q[i]),0); q[i].x:=i; q[i].s[i]:=-1; end;
    t:=n+1; h:=1; while h0) then begin if (q[h].s[i]=-1) then begin wr:=q[h]; str(i,e); ss:=saveway(q[h].x)+e; if (not findss(ss)) then begin inc(resv); res[resv]:=ss; end; end; if (q[h].s[i]=0) then begin q[t]:=q[h]; q[t].x:=i; q[t].s[i]:=q[h].x; inc(t); end; end; inc(h); end;
    close(output); end;
    procedure save; var i: integer; begin assign(output,'output.txt'); rewrite(output); for i:=1 to resv do writeln(res[i]); close(output); end;
    begin load; solve; save; end.
    Программы

    Автоматическое построение лабиринтов

    Тезей должен был найти выход из Критского лабиринта или погибнуть, убитый Минотавром. Но что поразительно: найти вход в лабиринт - задача не менее трудная.
    Здесь не представляется возможным описать все мыслимые лабиринты, да это и не требуется. Мы займемся простыми лабиринтами, построенными на прямоугольникеАвтоматическое построение лабиринтов, где Автоматическое построение лабиринтов — положительные целые числа. Внутри и на границах прямоугольника поставлены стенки по ребрам покрывающей его единичной квадратной сетки. Чтобы построить из прямоугольника лабиринт, выбьем одну единичную стенку на одной из сторон прямоугольника (получится вход в лабиринт); выбьем одну единичную стенку на противоположной стороне (получится выход) и еще удалим какое-то число строго внутренних стенок. Говорят, что лабиринт имеет решение, если между входом и выходом внутри лабиринта есть путь в виде ломаной, не имеющей общих точек со стенками. Решение единственно, если любые два таких пути проходят через одни и те же внутренние ячейки сетки. На рис. 17.1 приведен пример лабиринтаАвтоматическое построение лабиринтов.
    Автоматическое построение лабиринтов
    Рис. 17.1.  Пример лабиринта
    Один из возможных подходов к решению таков. Выбираем вход; затем, начав от него, добавляем по одной ячейке к главному пути-решению, пока он не достигнет выходной стороны. После этого удаляем некоторые внутренние стенки так, чтобы все клетки оказались соединенными с главным путем. Чтобы главный путь не получился прямым коридором, следует при его построении предусмотреть случайные повороты. Программа должна также следить за тем, чтобы при построении главного пути или при открытии боковых ячеек не нарушалась единственность решения. Наблюдательный читатель заметит, что определение единственности решения не годится в случае, когда путь заходит в боковой тупик и затем возвращается.
    Программу можно написать почти на любом из процедурных языков.
    Программа 1. Лабиринт.
    {Программно задаются вход и выход. Нажимая на клавишу "Enter", перебираем всевозможные пути от входа до выхода в лабиринте. Выход из программы по клавише "Esc". Алгоритм реализован на языке программирования Turbo-Pascal}

    program Maze; uses Graph, Crt;

    var m,n: Integer; Matrix: array [1..100,1..100] of Boolean; Start,Finish: Integer;

    procedure PrepareGraph; var Driver,Mode: Integer; begin Driver:=VGA; Mode:=VGAHi; InitGraph(Driver,Mode,'c:\borland\tp\bgi'); end;

    procedure DisplayMaze(x1,y1,x2,y2: Integer); var i,j: Integer; dx,dy: Real; begin SetFillStyle(1,8); SetColor(15); dx:=(x2-x1)/m; dy:=(y2-y1)/n; for i:=1 to n do for j:=1 to m do if not Matrix[i,j] then Rectangle(Round(x1+(i-1)*dx),Round(y1+(j-1)*dy), Round(x1+i*dx),Round(y1+j*dy)); end;

    function CreatesPath(i,j: Integer): Boolean; var Result: Boolean; Count: Integer; ii,jj: Integer; begin Count:=0; if (i>1) and Matrix[i-1,j] then Inc(Count); if (i1) and Matrix[i,j-1] then Inc(Count); if (j1 then Result:=true else Result:=false; CreatesPath:=Result; end;

    function DeadEnd(i,j: Integer): Boolean; var Result: Boolean; Count: Integer; begin Count:=0; if (i=2) or CreatesPath(i-1,j) then Inc(Count); if (i=m-1) or CreatesPath(i+1,j) then Inc(Count); if (j=2) or CreatesPath(i,j-1) then Inc(Count); if (j=n-1) or CreatesPath(i,j+1) then Inc(Count); if Count=4 then Result:=true else Result:=false; DeadEnd:=Result; end;

    function CreateMaze: Boolean; var i,j: Integer; di,dj: Integer; Result: Boolean; begin Randomize; for i:=1 to n do for j:=1 to m do Matrix[i,j]:=false; Start:=Random(m-2)+2; i:=Start; j:=2; Matrix[Start,1]:=true; repeat Matrix[i,j]:=true; di:=0; dj:=0; while (di=0) and (dj=0) do begin di:=1-Random(3); if (i+di=1) or (i+di=m) then di:=0; if di=0 then dj:=1-Random(3); if j+dj=1 then dj:=0; if CreatesPath(i+di,j+dj) then begin di:=0; dj:=0; end; end; i:=i+di; j:=j+dj; until DeadEnd(i,j) or (j=n); Finish:=i; Matrix[Finish,n]:=true; if j
    begin m:=6; n:=6;

    PrepareGraph; repeat ClearDevice; repeat until CreateMaze; DisplayMaze(120,40,520,440); repeat until KeyPressed; until ReadKey=#27; CloseGraph; end.


    Программа 2. Лабиринт.

    { Лабиринт реализуется автоматически, без участия пользователя. Алгоритм реализован на языке программирования Turbo-Pascal } uses graph,crt; var mpos,npos,m,n,delx,x,y,t,gd,gm,i,k:integer;

    begin randomize; writeln('Input labyrint size (x and y)'); readln(m,n); writeln('Input entrance&exit coordinates (mpos
    while y>1 do begin delx:=random(m)-x+1; if y=2 then delx:=mpos-x; i:=91+x*10; if i<90+(x+delx)*10 then begin while i<>90+(x+delx)*10 do begin i:=i+1; line(i,91+y*10,i,99+y*10); end; end;

    if i>91+(x+delx)*10 then begin while i<>91+(x+delx)*10 do begin i:=i-1; line(i,91+y*10,i,99+y*10); end; end;

    x:=x+delx; line(91+10*x,90+y*10,99+10*x,90+y*10);

    y:=y-1;

    end;

    readln; end.

    Бинарное дерево

    Строится бинарное дерево. В узлы дерева засылаются целые положительные числа, выбранные случайным образом. После задания значений вершин на каждом уровне эти значения выводятся на экран, начиная с корневой. Пользователю предлагается сделать выбор номера уровня, в нем определяется максимальное значение и выводится на экран.
    Программа 3. Поиск максимального элемента.
    { Алгоритм реализован на языке программирования Turbo-Pascal} uses crt; type sp=^tree; tree=record val:integer; l:sp; r:sp; end; var t:sp; nh, max,h,i:integer; procedure find(t:sp; h,nh:integer); begin if t=nil then exit; if h=nh then begin if t^.val> max then max:=t^.val; end else begin find(t^.l,h+1,nh); find(t^.r,h+1,nh); end; end; procedure zadtree(var t:sp; h,nh:integer); begin if h=5 then begin new(t); t^.l:=nil; t^.r:=nil; t^.val:=random(100); end else begin new(t); zadtree(t^.l, h+1,nh); zadtree(t^.r, h+1,nh); t^.val:=random(100); end; end; procedure writetree(t:sp; h,nh:integer); begin if t=nil then exit; if h=nh then begin write(t^.val,' '); end else begin writetree(t^.l,h+1,nh); writetree(t^.r,h+1,nh); end; end; begin clrscr; randomize; t:=nil; zadtree(t,1,nh); for i:=1 to 5 do begin writetree(t,1,i); writeln; end; max:=0; write('vvedite uroven '); readln(nh); find(t,1,nh); write('max= ',max); readln; end.

    Ханойская башня

    В лекции 11 дана реализация алгоритма “Ханойская башня”. Здесь используется другой подход. В программе реализован пользовательский интерфейс с развитым эргономическим компонентом. Пользователю предоставляется возможность самому решить поставленную задачу. В программе использована работа с видеостраницами.
    Постановка задачи.
    На стержне в исходном порядке находится дисков, уменьшающихся по размеру снизу вверх. Диски должны быть переставлены на стержень в исходном порядке при использовании в случае необходимости промежуточного стержня для временного хранения дисков. В процессе перестановки дисков обязательно должны соблюдаться правила: одновременно может быть переставлен только один самый верхний диск (с одного из стержней на другой); ни в какой момент времени диск не может находиться на другом диске меньшего размера.
    Программа 12. Ханойская башня.
    {Программа реализована с помощью абстрактного типа данных – стек для произвольного числа дисков. Число колец задается константой maxc. Программа написана на языке программирования Turbo-Pascal}
    program Tower; uses Crt, Graph;
    const maxc = 4;{Максимальное число колец на одной башне}
    type TTower = record num: byte; sizes: array[1..maxc] of byte; up: byte; end;
    var Towers: array[1..3] of TTower; VisVP, ActVP: byte; {видимая и активная видеостраницы} ActTower: byte; Message: String; Win: boolean; font1: integer;
    function CheckEnd: boolean; var res: boolean; i: byte; begin res:=False; if (Towers[2].num=maxc) or (Towers[3].num=maxc) then res:=true; CheckEnd:=res; end;
    procedure BeginDraw; begin SetActivePage(ActVP); end;
    procedure EndDraw; begin
    VisVP:=ActVP; SetVisualPage(VisVP); if VisVP=1 then ActVP:=0 else ActVP:=1; end;
    procedure Init; var grDr, grM: integer;
    ErrCode: integer; i: integer; begin grDr:=VGA; grM:=VGAMed; InitGraph(grDr, grM, 'c:\borland\tp\bgi'); ErrCode:=GraphResult; if ErrCode <> grOk then begin Writeln('Graphics error:', GraphErrorMsg(ErrCode)); Halt; end;
    Towers[1].num:=maxc; Towers[1].up:=0; for i:=0 to maxc-1 do Towers[1].sizes[i+1]:=maxc-i;
    Towers[2].num:=0; Towers[2].up:=0; for i:=0 to maxc-1 do Towers[2].sizes[i+1]:=0;
    p>Towers[3].num:=0; Towers[3].up:=0; for i:=0 to maxc-1 do Towers[2].sizes[i+1]:=0;
    ActTower:=1; VisVP:=0; ActVP:=1; SetVisualPage(VisVP); SetActivePage(ActVP); Message:=''; Win:=False; end;
    procedure Close; begin closegraph; end;
    procedure DrawTower(x, y: integer; n: integer); var i: integer; begin if n=ActTower then SetColor(yellow); Line(x, y, x, y+15+maxc*15); for i:=1 to Towers[n].num do begin Rectangle(x-10*Towers[n].sizes[i], y+15+15*(maxc-i+1), x+10*Towers[n].sizes[i], y+15+15*(maxc-i)) end;
    if Towers[n].up<>0 then begin Rectangle(x-10*Towers[n].up, y-15, x+10*Towers[n].up, y-30); end;
    SetColor(White); end;
    procedure DrawInfo; begin OutTextXY(50, 20, 'Ханойская башня.); OutTextXY(80, 40, 'Работа с программой: стрелки влево-вправо - выбор башни'); OutTextXY(130, 60, 'текущая башня выделяется желтым цветом'); OutTextXY(60, 80, 'стрелка вверх - поднять кольцо, стрелка вниз - положить кольцо'); OutTextXY(80, 100, '(две последние операции выполняются для активной башни)');
    end;
    procedure Draw; begin BeginDraw; ClearDevice; OutTextXY(180, 140, Message); Message:=''; DrawTower(150, 200, 1); DrawTower(300, 200, 2); DrawTower(450, 200, 3);
    if win then begin SetTextStyle(GothicFont, HorizDir, 8); SetColor(Red); Outtextxy(70, 0, 'Congratulations'); Outtextxy(160, 70, 'You win'); SetTextStyle(DefaultFont, HorizDir, 1); SetColor(White); OutTextXY(250, 330, 'Press any key'); end else DrawInfo; EndDraw; end;
    procedure MainCycle; var ch: char; ex: boolean; up: byte; begin ex:=False; repeat if KeyPressed then begin ch:=ReadKey; case ch of #27: begin Ex:=True; end; #77: begin up:=Towers[ActTower].up; Towers[ActTower].up:=0; inc(ActTower); if ActTower>3 then ActTower:=1; Towers[ActTower].up:=up; end; #75: begin up:=Towers[ActTower].up; Towers[ActTower].up:=0; dec(ActTower); if ActTower<1 then ActTower:=3; Towers[ActTower].up:=up; end; #80: begin {вниз} if Towers[ActTower].up<>0 then begin if Towers[ActTower].num=0 then begin Towers[ActTower].num:=Towers[ActTower].num+1;
    Towers[ActTower].sizes[Towers[ActTower].num]:=Towers[ActTower].up; Towers[ActTower].up:=0; end else begin if Towers[ActTower].sizes[Towers[ActTower].num]>Towers[ActTower].up then begin Towers[ActTower].num:=Towers[ActTower].num+1;
    Towers[ActTower].sizes[Towers[ActTower].num]:=Towers[ActTower].up; Towers[ActTower].up:=0; end else Message:='Это кольцо сюда опускать нельзя'; end; end; end; #72: begin {вверх} if Towers[ActTower].num<>0 then begin Towers[ActTower].num:=Towers[ActTower].num-1;
    Towers[ActTower].up:=Towers[ActTower].sizes[Towers[ActTower].num+1]; Towers[ActTower].sizes[Towers[ActTower].num+1]:=0; end; end; end; if CheckEnd then begin Win:=True; ex:=True; end; Draw; end; until ex; end;
    begin Init; Draw; MainCycle; if win then repeat until keypressed; Close; end.
    Ханойская башня

    Сортировки

    В лекциях 14 и 15
    мы рассматривали более подробно различные способы сортировки. Здесь мы напоминаем некоторые из них и приводим пример программ.
    Сортировка упорядочивает совокупность объектов в соответствии с заданным отношением порядка. Ключ сортировки - поле или группа полей элемента сортировки, которые используются при сравнении во время сортировки. Сортирующая последовательность - схема упорядочивания. Например, можно взять последовательность символов алфавита, задающую способ упорядочения строк этого алфавита.
    Способ сортировки: сортировка по возрастанию, сортировки по убыванию. Методы сортировки:
  • метод прямого выбора;
  • метод пузырька;
  • метод по ключу;
  • сортировки слиянием;
  • сортировки Батчера.

  • Сортировка по возрастанию - это сортировка, при которой записи упорядочиваются по возрастанию значений ключевых полей.
    Сортировка по убыванию - это сортировка, при которой записи упорядочиваются по убыванию значений ключевых полей.
    Сортировка методом пузырька (пузырьковая сортировка) - способ сортировки, заключающийся в последовательной перестановке соседних элементов сортируемого массива.
    Сортировка по ключу - это сортировка записей с упорядочением по значению указанного поля или группы полей.
    Сортировка слиянием - это внешняя сортировка, при которой на первом этапе группы записей сортируются в оперативной памяти и записываются на несколько внешних носителей; на втором этапе упорядоченные группы сливаются с нескольких внешних носителей на один носитель данных. Носитель данных - материальный объект, предназначенный для хранения данных, или среда передачи данных.
    Сортировка Батчера - это сортировка, внутренний алгоритм которой работает за время Сортировки.
    Программа 5. Сортировка массива по возрастанию методом пузырька.
    //Данные, которые нужно отсортировать, берутся из файла "massiv.txt", //результат записывается в массив int mas['K'] и выводится на экран // Алгоритм реализован на Turbo C++. #include #include #define K 1000; //Размер массива

    int mas['K']; int n; void puzirek()// функция сортирует массив по возрастанию методом пузырька { int i,j,t; for(i=0;i
    int main() { clrscr(); FILE *filePointer=fopen("massiv.txt","r"); int i=0; while (!feof(filePointer)) { fscanf(filePointer,"%d",&mas[i]); i++; } n=i; puzirek(); for(i=0;i
    Программа 6. Пузырьковая сортировка и сортировка методом прямого выбора.

    {Сортировка. Алгоритм реализован на языке программирования Turbo-Pascal} uses crt; var M, N : array[0..10] of integer; i:integer;

    procedure Input; begin for i := 0 to 10 do begin writeln('Число'); readln(M[i]); {Ввод массива} end; end;

    Procedure Sort1; {Пузырьковый метод сортировки} var q,i,x:integer; begin

    for i:=10 downto 0 do begin for q:=0 to 10 do if M[q]
    procedure Sort2; {Метод прямого выбора} var i,j,k,x:integer; begin for i:=0 to 9 do begin k:=i; x:=M[i]; for j:=i+1 to 10 do if M[j] >x then begin k:=j; x:=M[k]; end; M[k]:= M[i]; M[i]:=x; end; end;

    {---------------------------------------------} begin clrscr; input; {Ввод исходного массива} writeln('Исходный массив'); for i:=0 to 10 do write(M[i],' '); {Вывод исходного массива} writeln; Sort1;{Сортировка массива методом пузырька} writeln ('Сортированный массив'); for i:=0 to 10 do write(M[i],' '); {Вывод отсортированного массива} input; {Ввод исходного массива} writeln('Исходный массив'); for i:=0 to 10 do write(M[i],' '); {Вывод исходного массива} writeln;

    sort2; writeln ('Сортированный массив методом прямого выбора'); for i:=0 to 10 do write(M[i],' '); {Вывод отсортированного массива} readln; end.

    Программа 7. Сестры.

    //Две строки матрицы назовем сестрами, если совпадают //множества чисел, встречающихся в этих строках. Программа //определяет всех сестер матрицы, если они есть, //и выводит номера строк.


    Алгоритм реализован на Turbo C++. //#include #include //#include #include #include //#include #include #include #include

    const n=4,//кол-во строк m=4; //столбцов

    int m1[n][m]; //исходный массив struct mas{int i,i1;}; //i-индекс сравниваемой строки с i1 строкой mas a[n*2]; // массив типа mas, где будут лежать сестры, пример) a[1].i и a[1].i1 - сестры

    void main() {clrscr(); int i,j; randomize(); for( i=0;i
    for(i=0;i=min) {m1[i][p]=m1[i][s];m1[i][s]=min;} //меняем местами s-й и p-й элемент,если s-й>p-го(минимального) }

    }

    printf("\n"); for(i=0;i
    printf("\nСестры :"); for(i=0;i

    Программа 8. Поиск узоров из простых чисел.

    //Построить матрицу А(15 Х 15)таким образом: А(8,8)=1, затем //по спирали против часовой стрелки, //увеличивая значение очередного элемента на единицу //и выделяя все простые числа красным цветом, заполнить матрицу //Алгоритм реализован на Turbo C++.

    #include #include

    void main(void) { clrscr(); int mas[15][15]; int n=1,x=6,y=6,k=1; int i,j; while(1){ mas[x][y]=k++; switch(n){ case 1: x++;break; case 2: y--;break; case 3: x--;break; case 4: y++;break; } if(x==15) break;

    if(x==y && x<6) n=4; else if(x+y==12 && x<6) n=1; else if(x+y==12 && x>6) n=3; else if(x==y+1 && x>6) n=2;

    }

    for(i=0;i<15;i++) { for(j=0;j<15;j++) { textcolor(12); if(mas[j][i]>2) for(k=2;k
    getch(); }

    Программа 9. Сортировка строк матрицы.

    //Cортировка строк матрицы. В каждой строке подсчитывается сумма //простых чисел. Полученный вектор упорядочивается по возрастанию. //Строки матрицы переставляются по новому вектору. //Алгоритм реализован на Turbo C++. #include #include

    #define n 5

    struct summa { int value; int idx; } sum,massum[n],a;

    void main(void){ clrscr(); int mas1[n][n],mas[n][n]={{1,1,1,1,1}, {3,16,11,6,4}, {8,10,15,23,1}, {3,8,10,15,3}, {7,3,20,15,10}};

    int i,j,k,flag;

    for(i=0;i2) for(k=2;k
    for(i=0;imassum[j+1].value){ a=massum[j]; massum[j]=massum[j+1]; massum[j+1]=a; } }

    for(i=0;i
    for(i=0;i
    printf("\n\n\n");

    for(i=0;i

    Задача о назначениях (задачи выбора)

    Эта задача состоит в следующем. Пусть имеется Задача о назначениях (задачи выбора) работ и Задача о назначениях (задачи выбора) кандидатов для выполнения этих работ. Назначение кандидата Задача о назначениях (задачи выбора) на работу Задача о назначениях (задачи выбора) связано с затратами Задача о назначениях (задачи выбора) Задача о назначениях (задачи выбора). Требуется найти назначение кандидатов на все работы, дающее минимальные суммарные затраты; при этом каждого кандидата можно назначить только на одну работу и каждая работа может быть занята только одним кандидатом.
    Иначе говоря, решение этой задачи представляет собой перестановку (Задача о назначениях (задачи выбора)) чисел Задача о назначениях (задачи выбора); каждое из производимых назначений описывается соответствием Задача о назначениях (задачи выбора)
    (Задача о назначениях (задачи выбора)). Указанные условия единственности при этом автоматически выполняются, и нашей целью является минимизация суммы
    Задача о назначениях (задачи выбора)
    (17.1)

    по всем перестановкам (Задача о назначениях (задачи выбора)).
    Перед нами типичная экстремальная комбинаторная задача. Ее решение путем прямого перебора, то есть вычисления значений функции 17.1 на всех перестановках и сравнения, практически невозможно при сколько-нибудь больших Задача о назначениях (задачи выбора), поскольку число перестановок равно Задача о назначениях (задачи выбора). Попытаемся свести дело к линейному программированию.
    Конечное множество, на котором задана целевая функция 17.1, представляет собой множество всех перестановок чисел Задача о назначениях (задачи выбора). Как известно, каждая такая перестановка может быть описана точкой в Задача о назначениях (задачи выбора)-мерном евклидовом пространстве; эту точку удобнее всего представить в виде Задача о назначениях (задачи выбора)-матрицы Задача о назначениях (задачи выбора). Элементы Задача о назначениях (задачи выбора) интерпретировать следующим образом:
    Задача о назначениях (задачи выбора), если i-й кандидат назначается на j-ю работу,
    Задача о назначениях (задачи выбора), в противном случае.
    Элементы матрицы должны быть подчинены двум условиям:
    Задача о назначениях (задачи выбора)
    (17.3)

    Задача о назначениях (задачи выбора)
    (17.4)

    Условия 17.3 и 17.4 говорят о том, что в каждой строке и в каждом столбце матрицы Задача о назначениях (задачи выбора) имеется ровно по одной единице. Говоря неформально, условие 17.3 означает, что каждый кандидат может быть назначен только на одну работу, а условие 17.4 — что каждая работа предназначена только для одного кандидата. (Матрицу перестановок можно получить из единичной матрицы путем некоторой перестановки ее строк.)
    Теперь задача заключается в нахождении чисел Задача о назначениях (задачи выбора), удовлетворяющих условиям 17.2, 17.3, 17.4 и минимизирующих суммарные затраты 17.1, которые теперь можно переписать в виде
    Задача о назначениях (задачи выбора)
    (17.5)

    Казалось бы, что к полученной задаче методы линейного программирования непосредственно применить нельзя, ибо в силу условий 17.2 она формально является целочисленной.
    Заменим условие 17.2 на условие неотрицательности переменных
    Задача о назначениях (задачи выбора)
    (17.6)

    Тем самым мы получаем обычную задачу линейного программирования. В нашем случае требование целочисленности 17.2 будет выполняться автоматически.
    Программа 10.Назначение на работу.
    program one;{Назначение на работу. Рассматривается случай: 10 работ и 10 желающих. реализовано на Turbo-Pascal} uses crt; const n=10; var C : array [1..n,1..n] of integer; T : array [1..n] of integer; M : array [1..n,1..4] of integer; Sum,tmj,z,min,i,j,tmp:integer;
    begin clrscr; randomize; write('work - '); for i:=1 to n do write(i:2,' '); for i:=1 to n do begin writeln; write(i:2,' man '); for j:=1 to n do begin C[i,j]:=random(100); {if M[i,j]>max then max:=M[i,j];} {if C[i,j] end; end; writeln;
    for j:=1 to n do T[j]:=0; Sum:=0; for i:=1 to n do begin writeln; write(i:2,' man '); min:=100; for j:=1 to n do begin if (C[i,j] end;
    write(C[i,j]:2,' '); end; T[tmj]:=1; {M[i,3]:=min;} Sum:=Sum+M[i,3]; write('=',M[i,3]:2,' man=',M[i,1],' job=',M[i,2]); end; writeln; {for i:=1 to n do begin for j:=1 to n do begin if (i<>j) and (M[i,2]=M[j,2]) then begin M[j,3]:=C[j,1]; for z:=1 to n do begin if (M[j,3]>C[j,z]) and (z<>M[j,2]) then begin M[j,3]:=C[j,z]; M[j,2]:=z; end; end; end; end; writeln('=',M[i,3]:2,' man=',M[i,1],' job=',M[i,2]); end; } write('sum=',Sum); readln; end.
    Программа 11.Назначение на работу.
    /* Назначение на работу. Рассматривается случай: 6 работ и 6 желающих. */
    //Назначение на работу. Реализовано на Turbo C++. #include #include #include #include #define k 6 int Sum,tmj,i,j,zj,min,tmp,min2,tmj2,p,q,ki; int M[k][4], C[k][k], T[k][2], Temper[k][2]; char a; /*struct myst {int cel; float rac; }; myst ctpyk[k];*/ main() {
    Sum=0; min=100; for(i=1;i

    for(i=1;i if(C[i][j] min=C[i][j]; //m[i][1] - 4el, m[i][2] -job, m[i][3] - stoimost. M[i][1]=i; M[i][2]=j; M[i][3]=C[i][j]; tmj=j; } /* else { if(C[i][j] } T[tmj2][2]=ki; T[tmj2][1]=min2; }
    */
    }
    printf(" %d ", C[i][j]); }
    T[tmj][2]=i; T[tmj][1]=min; // na4alo mega funkcii /* if(C[i][j] } */ //konec.
    Sum=Sum+M[i][3]; printf(" $= %d, man= %d, job= %d ",M[i][3],M[i][1],M[i][2]);
    }
    /* for(i=0;i */ scanf("%d",a); return 0; }

    Задача о восьми ферзях

    Условие задачи. Найти все такие расстановки восьми ферзей на шахматной доске, при которых ферзи не бьют друг друга.
    Анализ задачи. Пусть Задача о восьми ферзях - множество искомых расстановок (конфигураций). Рассмотрим следующий подход к решению задачи. Будем искать множество конфигураций Задача о восьми ферзях со следующими свойствами:
  • Задача о восьми ферзях.
  • Имеется условие, позволяющее по элементу из Задача о восьми ферзях определить, принадлежит ли он Задача о восьми ферзях.
  • Имеется процедура, генерирующая все элементы из Задача о восьми ферзях.

  • С помощью процедуры из пункта 3 будем генерировать по очереди все элементы из Задача о восьми ферзях; для элементов из Задача о восьми ферзях проверяем (см. пункт 2) принадлежит ли он Задача о восьми ферзях: в результате в силу 1 свойства будут порождены все элементы Задача о восьми ферзях.
    Заметим теперь, что ферзи, которые не бьют друг друга, должны располагаться на разных горизонталях. Поэтому можно упорядочить ферзи и всегда ставить Задача о восьми ферзях-го ферзя на Задача о восьми ферзях-ю горизонталь. Тогда в качестве Задача о восьми ферзях можно взять множество конфигураций, в которых на каждой из первых Задача о восьми ферзях горизонталей стоит ровно по одному ферзю, причем никакие два ферзя не бьют друг друга.
    Программа 4. Расстановка восьми ферзей на шахматной доске.
    { Программа выдает все комбинации ферзей, которые не бьют друг друга. Алгоритм реализован на языке программирования Turbo-Pascal } program ferz; uses crt; const desksize=8; type sizeint=1..desksize; unuses=set of sizeint; combinates=array[shortint] of sizeint; var num:byte; combinate:combinates; unuse:unuses;
    function attack(combinate:combinates):boolean; var i,j:byte; rightdiag,leftdiag:combinates; begin attack:=false; for i:=1 to desksize do begin leftdiag[i]:=i+combinate[i]; rightdiag[i]:=i-combinate[i]; end; for j:=1 to desksize do for i:=1 to desksize do begin if (i<>j) and ((leftdiag[i]=leftdiag[j])or(rightdiag[i]=rightdiag[j])) then begin attack:=true; exit; end; end; end;
    procedure output(combinate:combinates); var i,j:byte; begin
    for i:=1 to desksize do for j:=1 to desksize do begin gotoxy(i,j); if(combinate[i]=j) then write(#2) else write(' '); end; readln; end;
    procedure create(num:byte; unuse:unuses; combinate:combinates); var i:byte; begin if num<=desksize then for i:=1 to desksize do begin if i in unuse then begin combinate[num]:=i; create(num+1,unuse-[i],combinate); end; end else if not attack(combinate) then output(combinate);
    end;
    begin textmode(c40);
    clrscr; unuse:=[1..desksize]; create(1,unuse,combinate); end.

    Анализ алгоритмов

    В процессе разработки и реализации алгоритма естественным образом раскрываются некоторые его свойства. По мере того как алгоритмы становятся все более и более сложными, все менее и менее вероятно, что их важные свойства проявятся на стадиях разработки и реализации. Как правило, некоторые важные аспекты поведения алгоритма, такие как его корректность, необходимое число операций или объем памяти, определить трудно. Поэтому обычно глубокое понимание нового алгоритма предваряется очень длинной стадией его анализа.
    Из-за трудностей анализа им зачастую просто пренебрегают. Вместо этого программа выполняется для того, чтобы увидеть, что получается (например, измеряется время работы). Такой подход можно признать удовлетворительным, если есть основание полагать, что тестовые задачи достаточно хорошо характеризуют работу алгоритма в общем случае; если же это не так, то описанный подход даст мало ценной информации. Даже если тест прекрасно характеризует работу алгоритма, он никогда не даст ответ на придирчивый вопрос, могут ли существовать лучшие алгоритмы для решения той же самой задачи. Проблему оптимальности алгоритма можно решить только путем его анализа.
    В анализе алгоритмов существуют две фундаментальные проблемы:
  • Какими свойствами обладает данный алгоритм?
  • Какие свойства должен иметь любой алгоритм, решающий данную проблему?

  • Фундаментальная разница между этими двумя вопросами состоит в подходе к ответу на них. В первом случае алгоритм задан и заключения выводятся путем изучения свойств, присущих ему. Во втором случае задается проблема и точно определяется структура алгоритма. Заключения выводятся на основе изучения существа проблемы по отношению к данному классу алгоритмов.

    Классы алгоритмов

    Одной из причин быстрого прогресса комбинаторных вычислений является усиление внимания к исследованию классов алгоритмов в противоположность изучению отдельных из них. Для того чтобы утверждать, например, что "все алгоритмы, предназначенные для выполнения того-то и того-то, должны обладать такими-то и такими-то свойствами" или "не существует алгоритма, удовлетворяющего тому-то и тому-то", необходимо иметь дело с четко определенным классом алгоритмов. Именно при таком определении становится возможным говорить, что данный алгоритм является оптимальным по отношению к некоторому свойству, если он работает по крайней мере так же хорошо (относительно этого свойства), как любой другой алгоритм из рассматриваемого класса.
    Как можно строго определить, возможно, бесконечный, класс алгоритмов? Исследуем этот вопрос на примере задачи о фальшивой монете. Рассматриваемый в этом примере класс алгоритмов порождает более обширный и более важный класс алгоритмов - так называемые деревья решений.
    Задача. Имеется Классы алгоритмов монет, о которых известно, что Классы алгоритмов из них являются настоящими и не более чем одна монета, фальшивая (легче или тяжелее остальных монет). Дополнительно к группе из Классы алгоритмов сомнительных монет дается еще одна монета, причем заведомо известно, что она настоящая. Имеются также весы, с помощью которых можно сравнить общий вес любых Классы алгоритмов монет с общим весом любых других Классы алгоритмов монет и тем самым установить, имеют ли две группы по Классы алгоритмов монет одинаковый вес либо одна из групп легче другой. Задача состоит в том, чтобы найти фальшивую монету, если она есть, за наименьшее число взвешиваний, или сравнений.
    Решение Пусть сомнительные монеты занумерованы числами Классы алгоритмов. Монете, о которой известно, что она настоящая, поставим в соответствие номер 0. Пусть Классы алгоритмов - множество монет. Если Классы алгоритмов — непересекающиеся непустые подмножества множества Классы алгоритмов, то через Классы алгоритмов обозначим операцию сравнения весов множества Классы алгоритмов. При сравнении возможны три исхода, которые обозначим следующим образом: Классы алгоритмов в зависимости от того, является ли вес Классы алгоритмов меньшим, равным или большим веса Классы алгоритмов.

    Рассматриваемые алгоритмы можно представить в форме дерева решений.

    Классы алгоритмов
    Рис. 1.1.  Дерево решений для задачи о фальшивой монете с четырьмя монетами

    Корень дерева на рисунке изображен полой окружностью и помечен отношением Классы алгоритмов. Это означает, что алгоритм начинает работу сравнением весов монет с номерами 1 и 2. Три исходящие из корня ветви ведут к поддеревьям, определяющим продолжение работы алгоритма после каждого из трех возможных исходов первого сравнения. Окружности, залитые черной краской, называются листьями дерева и означают, что работа алгоритма заканчивается. Метки соответствуют исходам: "1л" - монета 1 легкая, "1т" - монета 1 тяжелая, "н" - все монеты настоящие. Непомеченная вершина дерева означает, что при наших предположениях этот случай возникнуть не может.

    Алгоритм, приведенный на рис. 1.1, требует двух сравнений в одних случаях и трех - в других. Скажем, что он требует "трех сравнений в худшем случае". Обычно важно знать, сколько работы требует алгоритм в среднем, однако для этого требуется задать вероятности различных исходов. Если предположим, что все девять исходов 1л, 1т, 2л, 2т, 3л, 3т, 4л, 4т, - равновероятны, то тогда этот алгоритм требует в среднем 7/3 сравнений.

    На одну чашку весов можем положить больше одной монеты. Например, можно начать сравнения, положив на одну чашку весов монеты 1 и 2, а на другую - монеты 3 и 4 (рис. 1.2).

    Классы алгоритмов
    Рис. 1.2.  Корень другого дерева решений для задачи о четырех монетах

    Если посчастливится, задачу можно решить за одно сравнение - это может произойти, когда все монеты настоящие. Независимо от того, как дополняется это дерево решений, в худшем случае задача все равно потребует тех же трех сравнений, поскольку единственное тернарное решение не может идентифицировать один из четырех исходов, которые возможны на ветви, помеченной символом "Классы алгоритмов", так же как и один из четырех исходов на ветви, помеченной символом "Классы алгоритмов". К тому же, независимо от того, как дополняется это дерево решений, оно потребует в среднем по крайней мере 7/3 сравнений, и в этом случае оно не лучше, чем дерево на рис.1.1.


    Используя монету 0, о которой известно, что она настоящая, можно получить приведенное на рис. 1.3 дерево решений (полное двухъярусное тернарное дерево), которое и в худшем, и в среднем случае требует двух сравнений.

    Классы алгоритмов
    Рис. 1.3.  Оптимальное дерево решений для задачи о четырех монетах

    Рассматриваемый класс алгоритмов решения задачи о фальшивой монете есть множество тернарных деревьев решений (примеры на рис.1.1, рис.1.2,

    рис.1.3), обладающих следующими свойствами:

  • каждый узел помечен сравнением Классы алгоритмов, где Классы алгоритмов и Классы алгоритмов - непересекающиеся непустые подмножества множества Классы алгоритмов всех монет;
  • каждый лист либо не помечен, что соответствует невозможному исходу в предположении существования не более чем одной фальшивой монеты, либо помечен одним из исходов iл, iT, н, означающим соответственно, что все монеты настоящие. Четко определив подлежащий дальнейшему рассмотрению класс алгоритмов, можно исследовать свойства, которыми должно обладать каждое дерево из этого класса, и определить, как найти алгоритмы, являющиеся в некотором смысле оптимальными. Решим эту проблему в начале для четырех монет, а затем перейдем к общему случаю.


  • Поскольку в задаче о четырех монетах требуется различить девять возможных исходов, любое дерево решений для этой задачи должно иметь, по крайней мере, девять листьев и, следовательно, не менее двух ярусов. Поэтому дерево на рис.1.3 является оптимальным и для худшего случая, и для среднего. Существуют ли другие оптимальные деревья? Для ответа на этот вопрос нужно рассмотреть множество всех деревьев решений для задачи о четырех монетах. Попытаемся исключить из дальнейшего рассмотрения какую-либо часть этого множества. Прежде всего видно, что путем любой перестановки множества Классы алгоритмов

    сомнительных монет из одного дерева, приведенного на рис.1.3, можно получить другие оптимальные деревья. Все они будут изоморфны дереву на рис.1.3. Исходя из этого, уточним постановку задачи и будем интересоваться попарно неизоморфными деревьями.

    Рассмотрим затем, существует ли оптимальное дерево среди тех, у которых в корне не используется монета с номером n.


    При таком ограничении в корне дерева можно сделать только два различных сравнения, а именно Классы алгоритмов и Классы алгоритмов. Рассмотрим разбиение исходов по трем ветвям, выходящим из корня, как показано на рис.1.2. Для получения такого, как на рис.1.3, полного двухъярусного тернарного дерева, девять возможных исходов должны были бы быть разбиты в отношении (3, 3, 3). Они же вместо этого разбиваются, соответственно, в отношении (2, 5, 2) и (4,1,4). Таким образом, заключаем, что задачу для четырех монет нельзя решить за два сравнения, не используя дополнительную настоящую монету.

    Наконец, рассмотрим те деревья решений, которые используют монету 0 в корне. В этом случае видно, что в корне фактически возможны только два сравнения: Классы алгоритмов и Классы алгоритмов. Для первого сравнения набор исходов будет (1, 7, 1), в связи с чем все алгоритмы, начинающиеся таким способом, для нас непригодны. Набор исходов (3, 3, 3) приводит к оптимальному дереву, показанному на рис.1.3. Аналогичным образом устанавливается, что для оптимального дерева сравнения в первом от корня ярусе определяются единственным образом. Отсюда заключаем, что для задачи о четырех монетах фактически существует только одно оптимальное дерево.

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

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

    Проблема представления: коды, сохраняющие разности

    Чрезвычайно важной проблемой в комбинаторных вычислениях является задача эффективного представления объектов, подлежащих обработке. Она возникает потому, что обычно имеется много возможных способов представления сложных объектов более простыми структурами, которые можно заложить в языки программирования, но не все такие представления в одинаковой степени эффективны с точки зрения времени и памяти. Более того, идеальное представление зависит от вида производимых операций.
    Приведем примеры, в которых задаются весьма специфические операции над целыми. Целые определяются как данные простейшего типа почти во всех вычислительных устройствах и языках программирования. Таким образом, проблема представления, как правило, не возникает. Имеющееся представление почти всегда наилучшее. Однако существуют некоторые заслуживающие внимания исключения, когда выгодно или даже необходимо использовать представление целых в вычислительном устройстве иным способом. Эти исключения появляются в следующих случаях:
  • Необходимы целые, больше имеющихся непосредственно в аппаратном оборудовании.
  • Необходимы только небольшие целые, и требуется сэкономить память, упаковывая их по несколько в одну ячейку.
  • Действия с целыми производятся не общепринятыми арифметическими операциями.
  • Целые используются для представления других типов объектов, и необходимо иметь возможность легко обращать целое в соответствующий ему объект и обратно.

  • Проблемы кодов, сохраняющих разности, касаются случаев 2 и 3. В задачах распознавания образов и классификации для решения вопроса, будут ли два объекта Проблема представления: коды, сохраняющие разности эквивалентными,стандартной является следующая процедура. Проблема представления: коды, сохраняющие разности представляются векторами признаков Проблема представления: коды, сохраняющие разности соответственно, где каждая компонента означает признак объекта, выраженный целым значением. Считается, что Проблема представления: коды, сохраняющие разности эквиваленты тогда и только тогда, если Проблема представления: коды, сохраняющие разности где Проблема представления: коды, сохраняющие разности — целое, называемое порогом.

    Программа

    Программа 1.Поиск фальшивой монеты
    //Монета ищется простым перебором. //Алгоритм реализован на языке программирования Turbo-C++. #include #include #include int main() { clrscr(); int k, n, flag = -1; int *mas; printf("Введите число монет: "); scanf("%i", &n); mas = (int*)malloc(sizeof(n)); randomize(); k = random (10) + 1; for (int i = 0; i < n; i++) { mas[i] = k; } for (i = k; i == k; i = random (20)); mas[random (n)] = i; printf("Масса монет: "); for (i = 0; i < n; i++) { printf("%5i", mas[i]); if (mas[i] != k) { flag = i; } } printf("\nФальшивая монета под номером %i ее вес %i", flag+1, mas[flag]); getch(); free(mas); return 0; }
    Программа

    Комбинаторная математика является старой дисциплиной.

    Комбинаторная математика является старой дисциплиной. Она получила свое наименование в 1666 г. от Лейбница в его "Dissertation de Arte Combinatori". Комбинаторные алгоритмы с их акцентом на разработку, анализ и реализацию практических алгоритмов являются продуктом века вычислительных машин.
    Предмет теории комбинаторных алгоритмов - вычисления на дискретных математических структурах. Это новое направление исследований. Лишь в последние несколько лет из наборов искусных приемов и разрозненных алгоритмов сформировалась система знаний о разработке, реализации и анализе алгоритмов.
    Комбинаторные вычисления находятся в таком же отношении к комбинаторной математике (дискретной, конечной математике), как численные методы анализа - к анализу. Комбинаторные вычисления развиваются в следующем направлении:
  • интенсивно изобретаются новые алгоритмы;
  • происходит быстрый прогресс (главным образом в математическом плане) в понимании алгоритмов, их разработки и анализа;
  • происходит переход от изучения отдельных алгоритмов к исследованию свойств, присущих классам алгоритмов.

  • В отличие от некоторых других разделов математики, комбинаторные вычисления не имеют "ядра", то есть некоторого количества "фундаментальных теорем", составляющих суть предмета, из которых выводится большинство результатов. Сначала может показаться, что в целом эта область состоит из наборов специальных методов и хитрых приемов. Однако после того, как было исследовано достаточно много комбинаторных алгоритмов, стали вырисовываться некоторые общие принципы. Именно эти принципы делают комбинаторные вычисления связной областью знаний и позволяют изложить ее в систематизированном виде.

    

        Программирование: Языки - Технологии - Разработка