
- •Структуры и алгоритмы обработки данных
- •Алгоритмы. Основные определения
- •От задачи к программе
- •Написание программы.
- •Типы данных, структуры данных и абстрактные типы данных
- •Указатели и курсоры
- •Время выполнения программ
- •Измерение времени выполнения программы
- •Степень (порядок) роста
- •Вычисление времени выполнения программ
- •Линейные абстрактные типы данных атд «Список»
- •Реализация списков посредством массивов
- •Реализация списков с помощью указателей
- •Атд «Стек»
- •Атд «Очередь»
- •Реализация очередей с помощью циклических массивов
- •Нелинейные абстрактные типы данных Деревья
- •Порядок узлов
- •Прямой, обратный и симметричный обходы дерева
- •Помеченные деревья и деревья выражений
- •Представление деревьев с помощью массивов
- •Специальные виды множеств атд “Дерево двоичного поиска”
- •Атд "Словарь", основанный
- •Представление ориентированных графов
- •Атд для ориентированных графов
- •Задача нахождения кратчайшего пути
- •Int **c, // массив стоимостей
- •Int *d) // массив кратчайших
- •Остовные деревья минимальной стоимости
- •Обход графов
- •Поиск в ширину
- •Поиск в глубину
- •Сортировка
- •Простые схемы сортировки Метод «пузырька»
- •Сортировка вставками
- •Быстрая сортировка
- •Пирамидальная сортировка
- •«Карманная» сортировка
- •Порядковые статистики
- •Методы разработки алгоритмов. Типы алгоритмов
- •Алгоритмы «разделяй и властвуй» (метод декомпозиции)
- •Баланс подзадач
- •Динамическое программирование
- •Перемножение нескольких матриц
- •Шаг 1: строение оптимальной расстановки скобок
- •Шаг 2: рекуррентное соотношение
- •Шаг 3: вычисление оптимальной стоимости
- •Void MatrixChainOrder(int n, // кол-во матриц
- •Int p[], // размеры матриц
- •Int **s) // оптимальное k
- •Шаг 4: построение оптимального решения
- •Int **s, // таблица, полученная
- •Int I, // индексы
- •Когда применимо динамическое программирование?
- •Оптимальность для подзадач
- •Перекрывающиеся подзадачи
- •«Жадные» алгоритмы
- •"Жадные" алгоритмы как эвристики
- •Когда применим жадный алгоритм?
- •Принцип жадного выбора
- •Оптимальность для подзадач
- •Поиск с возвратом
- •Функции выигрыша
- •Метод ветвей и границ
- •Структуры данных и алгоритмы для внешней памяти
- •Внешняя сортировка
- •Хранение данных в файлах
- •Простая организация данных
- •Хешированные файлы
- •Индексированные файлы
- •Содержание
- •Глава I. Линейные абстрактные типы данных 31
- •Глава II. Сортировка 108
Степень (порядок) роста
Итак, мы предполагаем, что программы можно оценить с помощью функций времени выполнения, пренебрегая при этом константами пропорциональности. Пойдем еще дальше и скажем, что время работы в худшем случае имеет порядок (степень) роста n2, отбрасывая члены меньших порядков. Это записывают так: T(n)=O(n2).
Подчеркнем, мы предполагаем, что все функции времени выполнения определены на множестве неотрицательных целых чисел и их значения также неотрицательны, но не обязательно целые. Df:
Будем говорить, что T(n) имеет порядок O(f(n)), если существуют константы с и n0 такие, что для всех nn0 выполняется неравенство T(n) c * f(n).
Для программ, у которых время выполнения имеет порядок O(f(n)), говорят, что они имеют порядок (или степень) роста f(n). Это подразумевает, что f(n)является верхней границей скорости роста T(n).
Чтобы указать нижнюю границу скорости роста T(n), используется обозначение T(n)=(g(n)), это подразумевает существование такой константы с, что бесконечно часто (для бесконечного числа значений n) выполняется неравенство T(n)cg(n).
Пример. Функция T(n)=3n^3+2*n^2 имеет степень роста О(n^3). Положим n0=0, с=5 легко видеть, что для всех целых n>=0 выполняется неравенство 3n^3+2*n^2<=5n^3. Можно конечно сказать, что Т(n) имеет порядок О(n^4), но это более слабое утверждение. Нижняя граница скорости роста этой функции также есть(n^3) достаточно положить с=1.
Итак, мы предполагаем, что программы можно оценить с помощью функций времени выполнения, пренебрегая при этом константами пропорциональности. С этой точки зрения программа с временем выполнения O(n2) лучше программы с временем выполнения O(n3). Константы пропорциональности зависят не только от используемых компилятора и компьютера, но и от свойств самой программы. Пусть при определенной комбинации компилятор-компьютер одна программа выполняется за 100n2 миллисекунд, а вторая – за 5n3 миллисекунд. Может ли вторая программа быть предпочтительнее чем первая?
Ответ на этот вопрос зависит от размера входных данных программ. Рассмотрим соотношение 5n3 / 100n2 = n/20. Откуда следует, что при n<20 программа с временем выполнения 5n3 завершится быстрее. Поэтому, если программы в основном выполняются с входными данными небольшого размера, предпочтение необходимо отдать второй программе. А при больших n или близких к 20 предпочтение необходимо отдать первой программе.
Другая причина предпочтения программ с меньшей степенью роста времени выполнения – чем меньше степень роста, тем больше размер задачи, которую можно решить на компьютере. Т.Е. если увеличивается скорость вычислений компьютера, то растет также и размер задач, решаемых на компьютере. Но не пропорционально.
Н
а
рисунке показаны функции времени
выполнения, измеренные в сек., для четырех
программ с различной временной сложностью
для одного и того же сочетания
компилятор-компьютер.
Примеры. 1) Посчитать эффект от 10-кратного увеличения быстродействия компьютера.
Время Выполнения |
Максимальный размер Задачи для 103 секунд |
Максимальный размер Задачи для 104 секунд |
Увеличение максимального размера задачи |
100n |
10 |
100 |
10 |
5n2 |
14 |
45 |
3.2 |
n3/2 |
12 |
27 |
2.3 |
2n |
10 |
13 |
1.3 |
2) Пусть имеется алгоритм, решающий задачу размера n на некотором компьютере за f(n) микросекунд. Каков максимальный размер задачи, которую он сможет решить за время t? Найти его для функций и времен, перечисленных в таблице.
|
1 сек |
1 мин |
1 час |
1 день |
1 месяц |
1 год |
1 век |
Log n |
10^10^6 |
|
|
|
|
|
|
n |
10^12 |
3.6*10^15 |
1.3*10^19 |
7.5*10^21 |
6.7*10^24 |
9.7*10*26 |
9.7*10^30 |
N |
10^6 |
6*10^7 |
3.6*10^9 |
8.64*10^10 |
2.59*10^12 |
3.11*10^13 |
3.11*10^15 |
n log n |
1.895*10^5 |
8.649*10^6 |
4.176*10^8 |
8.693*10^9 |
2.282*10^11 |
2.509*10^12 |
2.17*10^14 |
N2 |
1000 |
7746 |
6*10^4 |
2.9*10^5 |
1.61*10^6 |
5.577*10^6 |
5.577*10^7 |
N3 |
100 |
392 |
1533 |
4421 |
13740 |
31450 |
1.46*10^5 |
2n |
20 |
26 |
32 |
36 |
41 |
45 |
52 |
N! |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
Алгоритмы 2^n независимо от быстродействия компьютера могут решать только очень небольшие задачи.
Чем выше временная сложность алгоритма (чем более высокая степень роста функции времени выполнения), тем меньший эффект дает увеличение быстродействия компьютера. Следовательно, алгоритмы должны иметь как можно меньшую вычислительную сложность.
Поскольку машинное время все время дешевеет, а компьютеры становятся более быстродействующими, мы надеемся, что сможем решить все большие по размеру и более сложные задачи. Но вместе с тем забываем о том что наибольший эффект принесут алгоритмы с низкой степенью роста функции времени выполнения.
Замечания
-
Если создаваемая программа будет использована несколько раз, то стоимость написания и отладки программы будет доминировать в общей стоимости программы, т.е. фактическое время выполнения не окажет существенного влияния на общую стоимость. В этом случае следует выбрать алгоритм наиболее простой для реализации.
-
Если программа будет работать с малыми входными данными, то степень роста времени выполнения будет иметь меньшее значение, чем константа пропорциональности, присутствующая в формуле времени выполнения.
-
Необходимо учитывать, что эффективные, но «хитрые» алгоритмы могут быть не востребованы из-за их сложности и трудностей, возникающих при попытке в них разобраться.
-
Иногда эффективные алгоритмы требуют такой большой объем памяти, что предпочтительными становятся менее эффективные алгоритмы.
-
В численных алгоритмах точность и устойчивость алгоритмов не мене важны, чем их временная эффективность.