Опубликован: 05.01.2015 | Доступ: свободный | Студентов: 1996 / 0 | Длительность: 63:16:00
Лекция 2:

Принципы анализа алгоритмов

Аннотация: Рассмотрены основные методы получения информации о количественных показателях производительности алгоритмов.

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

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

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

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

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

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

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

Реализация и эмпирический анализ

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

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

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

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

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

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

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

Один из первых шагов в понимании производительности алгоритмов - это эмпирический анализ. Если есть два алгоритма для решения одной задачи, то все естественно: мы запустим оба и увидим, который из них выполняется дольше! Это концепция может показаться слишком очевидной, чтобы о ней стоило говорить, но ее часто упускают из виду при сравнительном анализе алгоритмов. Трудно не заметить, что один алгоритм в 10 раз быстрее другого, если один выполняется 3 секунды, а другой 30 секунд, однако при математическом анализе эту разницу легко упустить из виду как небольшой постоянный множитель. При замерах производительности тщательно выполненных реализаций алгоритмов для типичных данных мы получаем результаты, которые не только являются прямым показателем эффективности, но и содержат информацию, необходимую для сравнения алгоритмов и обоснования прилагаемых математических результатов (см., например, таблица 1.1). Если эмпирическое изучение начинает поглощать значительное количество времени, на помощь приходит математический анализ. Вряд ли стоит ожидать завершения программы в течение часа или целого дня, чтобы убедиться, что она работает медленно - особенно если тот же результат может дать несложный анализ.

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

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

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

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

Возможно, наиболее распространенной ошибкой при выборе алгоритма является игнорирование характеристик производительности. Более быстрые алгоритмы, как правило, сложнее, чем прямые решения, и разработчики часто предпочитают более медленные алгоритмы, дабы избежать лишних сложностей. Однако, как было показано на примере алгоритмов объединение-поиск, можно добиться значительных улучшений с помощью даже нескольких строк кода. Пользователи удивительно большого числа компьютерных систем теряют много времени в ожидании решения задачи простыми квадратичными алгоритмами, в то время как доступные алгоритмы сложности N logN или линейные алгоритмы ненамного сложнее, но могут решить задачу значительно быстрее. Но когда мы имеем дело с большими задачами, приходится искать наилучший алгоритм, что и будет показано далее.

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

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

Упражнения

2.1. Переведите программу из "Введение" на другой язык программирования и ответьте на вопросы упражнения 1.22 для вашей реализации.

2.2. Сколько времени займет посчитать до 1 миллиарда (не учитывая переполнение)? Определите количество времени, необходимое программе

int i, j, k, count = 0;
for (i = 0; i < N; i ++)
  for (j = 0; j < N; j ++)
    for (k = 0; k < N; k ++)
      count ++;
      

для выполнения в вашей среде программирования для N = 10, 100 и 1000. Если в вашем компиляторе имеются средства оптимизации, предназначенные для повышения эффективности программ, проверьте, дают ли они какой-либо результат для этой программы.

Дмитрий Уколов
Дмитрий Уколов
Михаил Новопашин
Михаил Новопашин
Владимир Хаванских
Владимир Хаванских
Россия, Москва, Высшая школа экономики
Вадим Рычков
Вадим Рычков
Россия, Москва, МГТУ Станкин