Хотя некоторые детали представленных примеров и выходят за рамки настоящей книги, в ней приводятся основные методы, применяющиеся для их решения. В книге также показано, как решить многие конкретные задачи, в том числе перечисленные ниже. Пусть имеется карта дорог, на которой обозначены расстояния между каждой парой соседних перекрестков. Наша цель — определить кратчайший путь от одного перекрестка к другому. Количество возможных маршрутов может быть огромным, даже если исключить те из них, которые содержат самопересечения. Как найти наиболее короткий из всех возможных маршрутов? При решении этой задачи карта дорог (которая сама по себе является моделью настоящих дорог) моделируется в виде графа (мы подробнее познакомимся с графами в части VI и приложении Б). Задача будет заключаться в определении кратчайшего пути от одной вершины графа к другой. Эффективное решение этой задачи представлено в главе 24. У нас имеются две упорядоченные последовательности символов, X = (х1, Х2, … , хт) и Y = (y1,у2,..., уп), и нам надо найти длиннейшую общую подпоследовательность X и Y. Подпоследовательностью X является сама последовательность X с некоторыми (возможно, всеми или никакими) удаленными элементами. Например, одной из подпоследовательностей (А, В, С, D, Е, F, G) является (В, С, Е, G). Наидлиннейшая общая подпоследовательность X и У дает меру схожести двух последовательностей. Например, если этими двумя последовательностями являются базовые пары в цепочках ДНК, то мы можем считать их сходными, если они имеют длинную общую подпоследовательность. Если X имеет т символов, a Y — п символов, то X и Y имеют 2т и 2П возможных подпоследовательностей соответственно. Выбирая все возможные подпоследовательности X и У и сопоставляя их, можно решить поставленную задачу только за очень длительное время (конечно, если только т и п не оказываются очень малыми величинами). В главе 15 мы познакомимся с общим методом гораздо более эффективного решения этой задачи, известным как динамическое программирование. - Хотя некоторые детали представленных примеров и выходят за рамки настоящей книги, в ней приводятся основные методы, применяющиеся для их решения. В книге также показано, как решить многие конкретные задачи, в том числе перечисленные ниже. Пусть имеется карта дорог, на которой обозначены расстояния между каждой парой соседних перекрестков. Наша цель — определить кратчайший путь от одного перекрестка к другому. Количество возможных маршрутов может быть огромным, даже если исключить те из них, которые содержат самопересечения. Как найти наиболее короткий из всех возможных маршрутов? При решении этой задачи карта дорог (которая сама по себе является моделью настоящих дорог) моделируется в виде графа (мы подробнее познакомимся с графами в части VI и приложении Б). Задача будет заключаться в определении кратчайшего пути от одной вершины графа к другой. Эффективное решение этой задачи представлено в главе 24. У нас имеются две упорядоченные последовательности символов, X = (х1, Х2, … , хт) и Y = (y1,у2,..., уп), и нам надо найти длиннейшую общую подпоследовательность X и Y. Подпоследовательностью X является сама последовательность X с некоторыми (возможно, всеми или никакими) удаленными элементами. Например, одной из подпоследовательностей (А, В, С, D, Е, F, G) является (В, С, Е, G). Наидлиннейшая общая подпоследовательность X и У дает меру схожести двух последовательностей. Например, если этими двумя последовательностями являются базовые пары в цепочках ДНК, то мы можем считать их сходными, если они имеют длинную общую подпоследовательность. Если X имеет т символов, a Y — п символов, то X и Y имеют 2т и 2П возможных подпоследовательностей соответственно. Выбирая все возможные подпоследовательности X и У и сопоставляя их, можно решить поставленную задачу только за очень длительное время (конечно, если только т и п не оказываются очень малыми величинами). В главе 15 мы познакомимся с общим методом гораздо более эффективного решения этой задачи, известным как динамическое программирование.
- У нас имеется проект, который представлен в виде библиотеки составных частей, причем каждая часть может включать экземпляры из других частей. Нам требуется перечислить все части в таком порядке, чтобы каждая часть появлялась в списке до любой другой части, ее использующей. Если проект состоит из п частей, имеется п! возможных упорядочений, где n! — обозначение факториала. Поскольку факториал растет быстрее даже показательной функции, мы не можем просто сгенерировать все возможные упорядочения и для каждого из них проверить, соответствует ли расположение частей нашему требованию (если, конечно, частей достаточно много). Эта задача представляет собой экземпляр топологической сортировки, и в главе 22 мы узнаем о способе ее эффективного решения. Пусть имеется п принадлежащих плоскости точек, и нужно найти выпуклую оболочку этих точек. Выпуклой оболочкой точек называется минимальный выпуклый многоугольник, содержащий эти точки. Для решения этой задачи удобно воспользоваться такой наглядной картиной: если представить точки в виде вбитых в доску и торчащих из нее гвоздей, то выпуклую оболочку можно получить, намотав на них резинку таким образом, чтобы все гвозди вошли внутрь замкнутой линии, образуемой резинкой. Каждый гвоздь, вокруг которого обвивается резинка, становится вершиной выпуклой оболочки (рис. 33.6 на с. 1076). В качестве набора вершин может выступать любое из 2п подмножеств множества точек. Однако недостаточно знать, какие точки являются вершинами выпуклой оболочки, нужно еще указать порядок их обхода. Таким образом, чтобы найти выпуклую оболочку, придется перебрать множество вариантов. В главе 33 описаны два хороших метода поиска выпуклой оболочки. Приведенные выше случаи применения алгоритмов отнюдь не являются исчерпывающими, однако на их примере выявляются две общие характеристики, присущие многим интересным алгоритмам. Они имеют множество вариантов-кандидатов, подавляющее большинство которых решениями не являются. Поиск среди них работающего (или “наилучшего”) кандидата является довольно сложным делом. Они имеют практическое применение. Простейший пример среди перечисленных задач — определение кратчайшего пути, соединяющего два перекрестка. Любая компания, занимающаяся автомобильными или железнодорожными перевозками, финансово заинтересована в том, чтобы определить кратчайший маршрут. Это способствовало бы снижению затрат труда и расходов горючего. Маршрутизатору в Интернете также нужно иметь возможность находить кратчайшие пути в сети, чтобы как можно быстрее доставлять сообщения. Водитель, едущий из одного города в другой, может захотеть найти маршрут на веб-сайте или использовать в поездке GPS.
Структуры данных - Структуры данных
- В книге также представлен ряд структур данных. Структура данных (data structure) — это способ хранения и организации данных, облегчающий доступ к этим данным и их модификацию. Ни одна структура данных не является универсальной и не может подходить для всех целей, поэтому важно знать преимущества и ограничения, присущие некоторым из них.
- Методические указания
- Данную книгу можно рассматривать как “сборник рецептов” для алгоритмов. Правда, однажды вам встретится задача, для которой вы не сможете найти опубликованный алгоритм (например, таковы многие из приведенных в книге упражнений и задач). Данная книга научит вас методам разработки алгоритмов и их анализа. Это позволит вам разрабатывать корректные алгоритмы и оценивать их эффективность самостоятельно. В разных главах рассматриваются различные аспекты решения алгоритмических задач. Одни главы посвящены конкретным задачам, таким как поиск медиан и порядковых статистик в главе 9, вычисление минимальных остовных деревьев в главе 23 и определение максимального потока в сети в главе 26. Другие главы ориентированы на методы, такие как “разделяй и властвуй” в главе 4, динамическое программирование в главе 15 и амортизационный анализ в главе 17.
- Сложные задачи
- Большая часть этой книги посвящена эффективным алгоритмам. Обычной мерой эффективности алгоритма является скорость — время, в течение которого алгоритм выдает результат. Однако существуют задачи, для которых неизвестны эффективные методы решения. В главе 34 рассматривается интересный вопрос, имеющий отношение к подобным задачам, известным как NP-полные.
- Почему NP-полные задачи представляют такой интерес? Во-первых, несмотря на то что до сих пор не найден эффективный алгоритм их решения, также не доказано, что такого алгоритма не существует. Другими словами, никто не знает, существует ли эффективный алгоритм для NP-полных задач. Во-вторых, набор NP-полных задач обладает замечательным свойством. Оно заключается в том, что если эффективный алгоритм существует хотя бы для одной из этих задач, то его можно сформулировать и для всех остальных. Эта взаимосвязь между NP- полными задачами и отсутствие методов их эффективного решения вызывают еще больший интерес к ним. В-третьих, некоторые NP-полные задачи похожи (но не идентичны) на задачи, для которых известны эффективные алгоритмы. Ученых волнует вопрос о том, как небольшое изменение формулировки задачи может значительно ухудшить эффективность самого лучшего из всех известных алгоритмов.
- Вы должны знать об NP-полных задачах, поскольку в реальных приложениях некоторые из них возникают неожиданно часто. Если перед вами встанет задача найти эффективный алгоритм для NP-полной задачи, скорее всего, вы потратите много времени на безрезультатные поиски. Если же вы покажете, что данная задача принадлежит к разряду NP-полных, то можно будет вместо самого лучшего из всех возможных решений попробовать найти достаточно эффективное.
- В качестве конкретного примера рассмотрим компанию грузового автотранспорта, имеющую один центральный склад. Каждый день грузовики загружаются на этом складе и отправляются по определенному маршруту, доставляя груз в несколько мест. В конце рабочего дня грузовик должен вернуться на склад, чтобы на следующее утро его снова можно было загрузить. Чтобы сократить расходы, компании нужно выбрать оптимальный порядок доставки груза в различные точки. При этом расстояние, пройденное грузовиком, должно быть минимальным. Эта задача хорошо известна как “задача о коммивояжере”, и она является NP-пол- ной. Эффективный алгоритм решения для нее неизвестен, однако при некоторых предположениях можно указать такие алгоритмы, в результате выполнения которых полученное расстояние будет ненамного превышать минимально возможное. Подобные “приближенные алгоритмы” рассматриваются в главе 35.
Параллельные вычисления - Параллельные вычисления
- Многие годы можно было считать скорость работы процессоров устойчиво возрастающей. Однако физика накладывает свои фундаментальные ограничения на скорость работы: поскольку с ростом тактовой частоты выделение тепловой мощности растет более чем линейно, микросхемы могут просто начать плавиться. Для повышения количества вычислений, выполняемых за единицу времени, проектировщиками все активнее используется другой путь — разработка процессоров, содержащих несколько “ядер” Мы можем уподобить эти многоядерные компьютеры нескольким компьютерам на одном обычном процессоре; другими словами, это разновидность “параллельных компьютеров” Чтобы добиться более высокой производительности от многоядерных компьютеров, мы должны разрабатывать алгоритмы с учетом возможности параллельных вычислений. В главе 27 представлена модель для “многопоточных” (multithreaded) алгоритмов, которая использует преимущества многоядерности. Эта модель обладает рядом преимуществ с теоретической точки зрения и образует основу для ряда успешных программ, включая программу для игры в шахматы.
- Алгоритмы как технология
- Предположим, быстродействие компьютера и объем его памяти можно увеличивать до бесконечности. Была бы в таком случае необходимость в изучении алгоритмов? Была бы, но только для того, чтобы продемонстрировать, что метод решения имеет конечное время работы и что он дает правильный ответ. Если бы компьютеры были неограниченно быстрыми, подошел бы любой корректный метод решения задачи. Возможно, вы бы предпочли, чтобы реализация решения была выдержана в хороших традициях программирования (например, ваша реализация должна быть качественно разработана и аккуратно задокументирована), но чаще всего выбирался бы метод, который легче всего реализовать. Конечно же, сегодня есть весьма производительные компьютеры, но их быстродействие не может быть бесконечно большим. Память также дешевеет, но не может стать бесплатной. Таким образом, время вычисления — такой же ограниченный ресурс, как и объем необходимой памяти. Вы должны разумно распоряжаться этими ресурсами, чему и способствует применение алгоритмов, эффективных в плане расходов времени и памяти.
- Эффективность
- Различные алгоритмы, разработанные для решения одной и той же задачи, часто очень сильно различаются по эффективности. Эти различия могут быть намного значительнее тех, которые вызваны применением неодинакового аппаратного и программного обеспечения. В качестве примера можно привести два алгоритма сортировки, которые рассматриваются в главе 2. Для выполнения первого из них, известного как сортировка вставкой, требуется время, которое оценивается как с1п2, где п — количество сортируемых элементов, а с1 — константа, не зависящая от п. Таким образом, время работы этого алгоритма приблизительно пропорционально п2. Для выполнения второго алгоритма, сортировки слиянием, требуется время, приблизительно равное С2n lg n, где lg п — краткая запись log2 n, а С2 — некоторая другая константа, не зависящая от п. Типичная константа метода вставок меньше константы метода слияния, т.е. с1 < С2. Давайте убедимся, что постоянные множители намного меньше влияют на время работы алгоритма, чем множители, зависящие от п. Запишем время работы алгоритма сортировки вставкой как с1п* п, а сортировки слиянием — как С2n • lg п. Тогда мы увидим, что там, где сортировка вставкой имеет сомножитель п, сортировка слиянием содержит lgn, что существенно меньше. (Например, когда п = 1000, lgn приближенно равен 10, а когда п равно миллиону, lgn всего лишь около 20.) Хотя сортировка вставкой обычно работает быстрее сортировки слиянием для небольшого количества сортируемых элементов, когда размер входных данных п становится достаточно большим, все заметнее проявляется преимущество сортировки слиянием, возникающее благодаря тому, что для больших n незначительная величина lgn по сравнению с п полностью компенсирует разницу величин постоянных множителей. Не имеет значения, во сколько раз константа с1 меньше, чем С2. С ростом количества сортируемых элементов обязательно будет достигнут переломный момент, когда сортировка слиянием окажется более производительной.
В качестве примера можно привести два алгоритма сортировки, которые рассматриваются в главе 2. Для выполнения первого из них, известного как сортировка вставкой, требуется время, которое оценивается как с1п2, где п — количество сортируемых элементов, а с1 — константа, не зависящая от п. Таким образом, время работы этого алгоритма приблизительно пропорционально п2. Для выполнения второго алгоритма, сортировки слиянием, требуется время, приблизительно равное С2n lg n, где lg п — краткая запись log2 n, а С2 — некоторая другая константа, не зависящая от п. Типичная константа метода вставок меньше константы метода слияния, т.е. с1 < С2. Давайте убедимся, что постоянные множители намного меньше влияют на время работы алгоритма, чем множители, зависящие от п. Запишем время работы алгоритма сортировки вставкой как с1п* п, а сортировки слиянием — как С2n • lg п. Тогда мы увидим, что там, где сортировка вставкой имеет сомножитель п, сортировка слиянием содержит lgn, что существенно меньше. (Например, когда п = 1000, lgn приближенно равен 10, а когда п равно миллиону, lgn всего лишь около 20.) Хотя сортировка вставкой обычно работает быстрее сортировки слиянием для небольшого количества сортируемых элементов, когда размер входных данных п становится достаточно большим, все заметнее проявляется преимущество сортировки слиянием, возникающее благодаря тому, что для больших n незначительная величина lgn по сравнению с п полностью компенсирует разницу величин постоянных множителей. Не имеет значения, во сколько раз константа с1 меньше, чем С2. С ростом количества сортируемых элементов обязательно будет достигнут переломный момент, когда сортировка слиянием окажется более производительной. - В качестве примера можно привести два алгоритма сортировки, которые рассматриваются в главе 2. Для выполнения первого из них, известного как сортировка вставкой, требуется время, которое оценивается как с1п2, где п — количество сортируемых элементов, а с1 — константа, не зависящая от п. Таким образом, время работы этого алгоритма приблизительно пропорционально п2. Для выполнения второго алгоритма, сортировки слиянием, требуется время, приблизительно равное С2n lg n, где lg п — краткая запись log2 n, а С2 — некоторая другая константа, не зависящая от п. Типичная константа метода вставок меньше константы метода слияния, т.е. с1 < С2. Давайте убедимся, что постоянные множители намного меньше влияют на время работы алгоритма, чем множители, зависящие от п. Запишем время работы алгоритма сортировки вставкой как с1п* п, а сортировки слиянием — как С2n • lg п. Тогда мы увидим, что там, где сортировка вставкой имеет сомножитель п, сортировка слиянием содержит lgn, что существенно меньше. (Например, когда п = 1000, lgn приближенно равен 10, а когда п равно миллиону, lgn всего лишь около 20.) Хотя сортировка вставкой обычно работает быстрее сортировки слиянием для небольшого количества сортируемых элементов, когда размер входных данных п становится достаточно большим, все заметнее проявляется преимущество сортировки слиянием, возникающее благодаря тому, что для больших n незначительная величина lgn по сравнению с п полностью компенсирует разницу величин постоянных множителей. Не имеет значения, во сколько раз константа с1 меньше, чем С2. С ростом количества сортируемых элементов обязательно будет достигнут переломный момент, когда сортировка слиянием окажется более производительной.
- В качестве примера рассмотрим два компьютера — А и Б. Компьютер А более быстрый, и на нем работает алгоритм сортировки вставкой, а компьютер Б более медленный, и на нем работает алгоритм сортировки методом слияния. Оба компьютера должны выполнить сортировку множества, состоящего из десяти миллионов чисел. (Хотя десять миллионов чисел и могут показаться огромным количеством, если эти числа представляют собой восьмибайтовые целые числа, то входные данные занимают около 80 Мбайт памяти, что весьма немного даже для старых недорогих лэптопов.) Предположим, что компьютер А выполняет десять миллиардов команд в секунду (что быстрее любого одного последовательного омпьютера на момент написания книги), а компьютер Б — только десять миллионов команд в секунду, так что компьютер А в тысячу раз быстрее компьютера Б. Чтобы различие стало еще большим, предположим, что код для метода вставок (т.е. для компьютера А) написан самым лучшим в мире программистом на машинном языке и для сортировки п чисел надо выполнить 2п2 команд. Сортировка же методом слияния (на компьютере Б) реализована программистом-се- реднячком с помощью языка высокого уровня. При этом компилятор оказался не слишком эффективным, и в результате получился код, требующий выполнения 50n lg п команд. Для сортировки десяти миллионов чисел компьютеру А понадобится
Как видите, использование кода, время работы которого возрастает медленнее, даже при плохом компиляторе на более медленном компьютере требует более чем в 17 раз меньше процессорного времени! Если же нужно выполнить сортировку ста миллионов чисел, то преимущество метода слияния становится еще более очевидным: там, где для сортировки вставкой потребуется более 23 дней, сортировка слиянием справится за четыре часа. Общее правило таково: чем больше количество сортируемых элементов, тем заметнее преимущество сортировки слиянием. - Как видите, использование кода, время работы которого возрастает медленнее, даже при плохом компиляторе на более медленном компьютере требует более чем в 17 раз меньше процессорного времени! Если же нужно выполнить сортировку ста миллионов чисел, то преимущество метода слияния становится еще более очевидным: там, где для сортировки вставкой потребуется более 23 дней, сортировка слиянием справится за четыре часа. Общее правило таково: чем больше количество сортируемых элементов, тем заметнее преимущество сортировки слиянием.
- Алгоритмы и другие технологии
- Приведенный выше пример демонстрирует, что алгоритмы, как и аппаратное обеспечение компьютера, следует рассматривать как технологию. Общая производительность системы настолько же зависит от эффективности алгоритма, насколько и от мощности применяющегося аппаратного обеспечения. В области разработки алгоритмов происходит такое же быстрое развитие, как и в других компьютерных технологиях. Возникает вопрос, действительно ли так важны алгоритмы, работающие на современных компьютерах, если и так достигнуты выдающиеся успехи в других областях высоких технологий, таких как усовершенствованные архитектуры компьютеров и технологий их изготовления; легкодоступные, интуитивно понятные графические интерфейсы (GUI); объектно-ориентированные системы; интегрированные веб-технологии; быстрые сети, как проводные, так и беспроводные.
- Ответ — да, безусловно. Несмотря на то что некоторые приложения не требуют алгоритмического наполнения явно (такие, как некоторые простые веб-приложения), для большинства приложений оно необходимо. Например, рассмотрим вебслужбу, определяющую, как добраться из одного места в другое. В основе ее реализации лежит высокопроизводительное аппаратное обеспечение, графический интерфейс пользователя, глобальная сеть и, возможно, объектно-ориентированный подход. Кроме того, использование алгоритмов необходимо для определенных операций, выполняемых данной веб-службой, например таких, как поиск маршрутов (вероятно, использующий алгоритм поиска кратчайшего пути), визуализация карт и интерполяция адресов. Более того, даже приложение, не требующее алгоритмического наполнения на высоком уровне, сильно зависит от алгоритмов. Ведь известно, что работа приложения зависит от производительности аппаратного обеспечения, а при его разработке применяются разнообразные алгоритмы. Все мы также знаем, что приложение тесно связано с графическим интерфейсом пользователя, а для разработки любого графического интерфейса пользователя требуются алгоритмы. Вспомним приложения, работающие в сети. Чтобы они могли функционировать, необходимо осуществлять маршрутизацию, которая, как уже говорилось, основана на ряде алгоритмов. Чаще всего приложения составляются на языке, отличном от машинного. Их код обрабатывается компилятором или интерпретатором, который интенсивно использует различные алгоритмы. И таким примерам несть числа. Кроме того, ввиду постоянного роста вычислительных возможностей компьютеров, они применяются для решения все более и более сложных задач. Как мы уже убедились на примере сравнительного анализа двух методов сортировки, с ростом сложности решаемой задачи различия в эффективности алгоритмов проявляются все значительнее.
- Знание основных алгоритмов и методов их разработки — одна из характеристик, отличающих действительно умелого, опытного программиста от новичка. Располагая современными компьютерными технологиями, некоторые задачи можно решить и без основательного знания алгоритмов, однако знания в этой области позволяют достичь намного большего.
Функции сложности алгоритмов и их рост Анализ алгоритма и его сложности - Существует ряд важных практических причин, побуждающих нас заниматься анализом алгоритмов. Одной из них является потребность получения оценок или границ для объема памяти и времени работы, необходимых алгоритму для успешной обработки входных данных. Следует избегать разработки программы, которая за отведенное студенту машинное время не успеет обработать входные данные достаточно большого размера (например, следует признать неудовлетворительной программу обработки графов, если из-за нехватки времени она не дает ответа для графа, состоящего из десяти вершин). Лучше заранее (еще при разработке алгоритма) с помощью карандаша и бумаги оценить объем памяти и время, необходимые разрабатываемому алгоритму, а затем улучшать его (или разработать новый, более эффективный). Хороший анализ может выявить узкие места в ваших программах (например, те части программы, на выполнение которых расходуется большая часть времени), а также выбрать более подходящий алгоритм из широкого класса алгоритмов, решающих одно и то же задание.
- Для алгоритма мы определили функцию временной сложности ВРЕМЯ (n), дающую верхнюю границу для максимального времени его работы, т.е. максимального числа основных операций (сложения, сравнения и т.д.), которые должен выполнить алгоритм при решении задачи на входных данных размером . Аналогично можно определить функцию емкостной сложности ПАМЯТЬ (n), дающую границу для максимального числа одновременно существующих скалярных значений при выполнении на входных данных размером .
Do'stlaringiz bilan baham: |