
- •Структуры и алгоритмы обработки данных
- •Алгоритмы. Основные определения
- •От задачи к программе
- •Написание программы.
- •Типы данных, структуры данных и абстрактные типы данных
- •Указатели и курсоры
- •Время выполнения программ
- •Измерение времени выполнения программы
- •Степень (порядок) роста
- •Вычисление времени выполнения программ
- •Линейные абстрактные типы данных атд «Список»
- •Реализация списков посредством массивов
- •Реализация списков с помощью указателей
- •Атд «Стек»
- •Атд «Очередь»
- •Реализация очередей с помощью циклических массивов
- •Нелинейные абстрактные типы данных Деревья
- •Порядок узлов
- •Прямой, обратный и симметричный обходы дерева
- •Помеченные деревья и деревья выражений
- •Представление деревьев с помощью массивов
- •Специальные виды множеств атд “Дерево двоичного поиска”
- •Атд "Словарь", основанный
- •Представление ориентированных графов
- •Атд для ориентированных графов
- •Задача нахождения кратчайшего пути
- •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
Перемножение нескольких матриц
Пусть мы хотим найти произведение последовательности матриц
А1*А2*…*Аn. (*)
Будем пользоваться стандартным алгоритмом перемножения двух матриц в качестве подпрограммы. Но прежде надо расставить скобки в (*), чтобы указать порядок умножений. Будем говорить, что в произведении матриц полностью расставлены скобки, если это произведение либо состоит из одной единственной матрицы, либо является заключенным в скобки произведением двух произведений с полностью расставленными скобками. Поскольку умножение матриц ассоциативно, конечный результат вычислений не зависит от расстановки скобок. Например, в произведении А1*А2*А3*А4 можно полностью расставить скобки пятью разными способами:
(А1*(А2*(А3*А4)))
((А1*А2)*(А3*А4))
((А1*(А2*А3))*А4)
(((А1*А2)*А3)*А4)
(А1*((А2*А3)*А4))
во всех случаях ответ будет один и тот же.
Не влияя на ответ, способ расстановки скобок может сильно повлиять на стоимость перемножения матриц.
Посмотрим сначала, сколько операций требует простейший способ перемножения двух матриц.
typedef struct
{
int **matr;
int rows;
int columns;
} MATRIX;
// выделение памяти под матрицы a, b, c и заполнение матриц a, b
void MatrixMultiply(MATRIX a, MATRIX b, MATRIX c)
{
int i, j, k;
if (a.columns != b.rows)
{ //умножить нельзя, обработка ошибки
}
else
for(i=0;i<a.rows;i++)
for(j=0;j<b.columns;j++)
{
c.matr[i][j]=0;
for(k=0;k<a.columns;k++)
c.matr[i][j]+=a.matr[i][k]*b.matr[k][j];
}
}
Матрицы а и b можно перемножать, только если число столбцов матрицы а равно числу строк матрицы b. Если а – это (pq)-матрица, а b – это (qr)-матрица, то их произведение с является (pr)-матрицей. При выполнении этого алгоритма делается pqr умножений и примерно столько же сложений. Для простоты будем учитывать только умножения.
Чтобы увидеть, как расстановка скобок может повлиять на стоимость, рассмотрим последовательность из трех матриц А1,А2, А3 размеров 10100, 1005 и 550.
При вычислении ((А1*А2)* А3) нужно 10*100*5 + 10*5*50=5000+2500=7500 умножений.
При вычислении (А1*(А2* А3)) нужно 100*5*50 + 10*100*50=25000+50000=75000 умножений. Тем самым первый способ расстановки скобок в 10 раз выгоднее.
Задача об умножении последовательности матриц может быть сформулирована следующим образом: дана последовательность из n матриц А1,А2,…,Аn заданных размеров (матрица Аi имеет размер pi-1pi); требуется найти такую полную расстановку скобок в произведении А1*А2*…*Аn, чтобы вычисление произведения требовало наименьшего числа умножений.
Очевидно, что число расстановок скобок экспоненциально зависит от n, так что полный перебор неэффективен.
Построим алгоритм, основанный на динамическом программировании.
Шаг 1: строение оптимальной расстановки скобок
Обозначим для удобства через Аi…j матрицу, являющуюся произведением Аi*Аi+1*…*Аj.
Оптимальная расстановка скобок в произведении А1*А2*…*Аn разрывает последовательность между Аk и Аk+1 для некоторого k, удовлетворяющего неравенству 1 k < n. То есть, мы сначала вычисляем произведения А1…k и Аk+1…n, а затем перемножаем их и получаем окончательный ответ А1…n.
Следовательно, стоимость оптимальной расстановки равна стоимости вычисления матрицы А1…k плюс стоимость вычисления матрицы Аk+1…n плюс стоимость перемножения этих двух матриц.
То же самое происходит и с двумя подпоследовательностями.
Чем меньше умножений нам потребуется для вычисления А1…k и Аk+1…n, тем меньше будет общее число умножений. Следовательно, оптимальное решение задачи содержит оптимальные решения её подзадач, что позволяет применить метод динамического программирования.