Глава E. Массивы Урок E10. Обработка двумерных массивов До

advertisement
Глава E. Массивы
Урок E10. Обработка двумерных массивов
Это была удивительная страна. Поперек бежали прямые
ручейки, а аккуратные живые изгороди делили
пространство между ручейками на равные квадраты.
Льюис Кэрролл
До сих пор в Главе E речь шла об алгоритмах обработки вектора. Лишь в
Уроке E1 мы остановились на вопросах размещения матриц и доступа к их
элементам. Пора вновь вернуться к этой структуре, которая встречается также
под именем прямоугольного (или: ортогонального) массива.
В том же Уроке E1 мы выяснили, что прямой доступ к элементам матрицы
обходится несколько дороже, чем при обработке вектора. Если условия задачи
требуют просмотра всей матрицы, то естественно обращаться к ее элементам в
порядке их размещения в памяти. Так мы поступали при инициализации вектора
(см. пример E1-1), так же поступим при начальном заполнении прямоугольного
массива.
Пример E10-1
В уроках A4 и A5 мы не раз обращались к таблице умножения. Сейчас этот объект
вновь оказывается полезным – для иллюстрации механизма последовательного
заполнения матрицы.
const
M = 9; {строки}
N = 9; {столбцы}
type
index1 = 1..N-1;
index2 = 1..M-1;
massive2 = array [index2, index1] of byte;
procedure InitMulTable (var MulTable: massive2);
var
i: index2; j: index1;
begin
for i:= 1 to M-1 do
for j:= 1 to N-1 do
MulTable[i, j]:= i*j
end; {InitMulTable}
Однако далеко не всегда представляет интерес последовательный обход
массива. Разумеется, вряд ли стоит перебирать все элементы в случайном
порядке, ведь таких способов можно указать (M×N)! (будьте внимательны: это
не просто восклицательный знак, а знак факториала!). Но существует несколько
специальных вариантов обхода, которые нередко предлагаются в учебниках
программирования в качестве упражнений. Не нарушим традицию и мы,
постаравшись одновременно осветить конкретную область применения.
Алгоритм E10-1. Обход «горизонтальной змейкой»
Порядок обхода предполагает обычный перебор строк «сверху вниз», а внутри
каждой строки перебор столбцов «слева направо» для строк с нечетным
номером и, соответственно, «справа налево» для четных строк.
Практические применения из реальной жизни вполне естественны. Скажем,
добросовестный экскурсовод, знакомя посетителей с экспозицией музея, вряд
ли предложит им, условно говоря, перейти из зала [1,N] в зал [2,1], минуя
остальные без всяких комментариев.
Другой пример имеет уже непосредственное отношение к интересующей нас
тематике. Речь идет о таблицах Карно – Вейча1 (рис. E10-1). В частности, они
удобны для построения одношагового четырехразрядного кода Грея, в котором
каждая очередная комбинация отличается от предыдущей (и последующей)
лишь в одном разряде.
Рис. E10-1. Варианты одношагового кода Грея
Того же рода обход «вертикальной змейкой», который моделирует другой
вариант кода Грея, отличающийся, впрочем, лишь последовательностью
перебора комбинаций.
Упражнения E10-1
a) Напишите программы, реализующие алгоритмы обхода прямоугольного массива
«горизонтальной змейкой» и «вертикальной змейкой».
b) Напишите программу для обхода по часовой стрелке «по спирали»: от элемента
[1,1] направо к [1,N], далее вниз до [M,N], затем налево к [M,1],.после него вверх
до [2,1], вновь направо и т.д.
Алгоритм E10-2. Обход «диагональной змейкой»
Проще всего проиллюстрировать порядок обхода картинкой – см. рис. E10-2.
Рис. E10-2. Обход «диагональной змейкой»
Механизм, демонстрируемый рисунком слева, находит применение в одном из
алгоритмов сжатия видеоизображения. Его правый «собрат» мало отличается от
левого, мы привели его для следующего упражнения.
1
Другие названия – диаграмма Вейча (A.Veitch) или карта Карно (M.Karnaugh).
Упражнение E10-2
Напишите программы, реализующие алгоритмы обхода прямоугольного массива
«диагональной змейкой» в соответствии с рис. E10-2.
Обратите внимание на следующий технический прием. Продвижение по любой
диагонали естественно реализовать как цикл однотипных шагов. При этом для
очередной диагонали, проходящей через соответствующие именно ей ячейки, не
меняется некоторое соотношение индексов строки i и столбца j этих ячеек. Так,
для левого рисунка неизменна сумма i+j=const, для правого – разность ij=const. Эти константы называются инвариантами цикла.2 При написании
программы их удобно использовать, поскольку во внешнем цикле нетрудно
организовать перебор диагоналей, подбирая константу в соответствии с
номером очередной диагонали.
Вот еще один типичный пример, связанный с полным систематическим обходом
прямоугольного массива, имеющий практическое значение в связи с
представлением графов.
Упражнение E10-3
Ориентированный граф можно определить (без строгой формализации) как два
множества. Первое – это множество точек (вершин) V; второе – дуги, каждая из
которых есть пара (vi, vj), составленная из начальной и конечной вершин дуги. Одной
из форм представления графа является матрица смежности: квадратный массив
Matrix [V×V] с элементами 0 или 1, в зависимости от отсутствия или наличия
соответствующей дуги, и нулевыми элементами на главной диагонали.
Напишите процедуру проверки симметричности матрицы смежности, то есть
выполнения условия Matrix[i, j] = Matrix[j, i] для всех строк и столбцов.
Очередной пример мы находим в линейной алгебре. Так, в связи с решением
систем линейных уравнений применение находят транспонированные
матрицы.
Упражнение E10-3
Для матрицы Matrix, имеющую M строк и N столбцов можно построить
транспонированную матрицу MatrixT, у которой строк, наоборот, N, а столбцов M,
причем выполняется условие Matrix [i, j] = MatrixT [j, i].
Напишите процедуру построения транспонированной матрицы MatrixT по заданной
матрице Matrix.
Очевидно, любой полный обход матрицы сопряжен с трудоемкостью O(M*N),
для квадратных матриц – O(M2).
Емкостная сложность та же. Однако в отдельных случаях удается ее уменьшить.
Например, вместо симметричной матрицы можно хранить только верхнюю
треугольную матрицу, которая будет представлена как вектор, «склеенный» из
строк с постепенно уменьшающейся длиной – от M у верхней до 1 у нижней, с
шагом 1. Но при этом нужно обеспечить пересчет координат (строка, столбец)
матрицы в индекс ячейки вектора и обратно.
Упражнение E10-4
Для заданной симметричной матрицы Matrix [M×M] напишите процедуры «упаковки»
ее треугольной верхней матрицы в вектор и обратную процедуру распаковки вектора
в матрицу.
2
invariant – утверждение, истинность которого не меняется при некотором преобразовании; в
данном случае – при выполнении цикла обработки диагонали
Хранение такого вектора вместо Matrix [M×M] обеспечивает почти двукратную
экономию памяти.
Еще бóльших успехов можно достичь, если симметричной является матрица
смежности. Достаточно воспользоваться тем обстоятельством, что все ее
элементы либо нули, либо единицы, а стало быть, их можно размещать побитно.
Упаковка по 8 элементов в байт позволяет «ужать» верхнюю треугольную
матрицу симметричной матрицы смежности в 8 раз, причем элементы главной
диагонали в вектор не переносятся.
Упражнение E10-5
Для заданной симметричной матрицы смежности Matrix [M×M] напишите процедуры
«упаковки» ее треугольной верхней матрицы в битовый вектор и обратную
процедуру «распаковки» вектора в матрицу.
В результате, экономия места оказывается более чем 16-кратной. К сожалению,
в расчетных задачах это не всегда удобно, так как замедляет обращение к
элементам матрицы смежности. Вообще, обратная зависимость между объемом
памяти и быстродействием должна рассматриваться как почти неизбежная дань,
которую программист платит в пользу повышения эффективности алгоритма.
Выше мы имели дело с алгоритмами обработки матриц, имеющими только
квадратичную временнýю сложность. Тем приятнее найти возможность, точнее
– примеры, когда удается ускорить вычисления. Разумеется, речь идет лишь о
специально сконструированных ситуациях.
Одна из них возникает в связи с уже знакомой нам матрицей смежности. В ряде
задач теории графов нужно среди его вершин обнаружить т.н. сток, то есть
вершину, из которой не выходит ни одна дуга. Ограничимся случаем, когда все
вершины и дуги графа связаны в одну сеть, иначе говоря, из любой вершины
можно «добраться» по дугам до любой другой, если игнорировать направления
дуг. В этом случае нетрудно доказать, что сток может быть лишь один, либо
вообще отсутствует. Само доказательство представляется весьма полезным
упражнением, но нас в настоящий момент занимает другой вопрос, который мы
и предлагаем читателю для самостоятельного решения. Небольшой подсказкой
послужит вытекающий из единственности существования стока вывод, а
именно: строка, соответствующая вершине–стоку, должна быть заполнена
только 0, и напротив, соответствующий столбец целиком состоит из 1, за
исключением единственного нулевого элемента на главной диагонали.
Упражнение E10-6
Задана симметричная матрица смежности Matrix [M×M], содержащая только нулевые
и единичные элементы. Напишите процедуру поиска в ней стока с трудоемкостью
обработки O(M).
В завершение – симпатичная задачка, которую мы почерпнули из известного
сборника.3 Она тоже служит примером заметного уменьшения временнóй
сложности алгоритма обработки матрицы по сравнению с интуитивно
ожидаемым решением.
3
Брудно А.Л., Каплан Л.И. Московские олимпиады по программированию. – 2-е изд., доп. и
перераб. – М.: Наука, 1990. – 208 с.
Упражнение E10-7
Массив чисел A [1:M, 1:N] упорядочен по неубыванию внутри каждой строки и
внутри каждого столбца. По заданному числу K найти элемент массива, имеющий то
же значение. (Количество шагов алгоритма должно быть порядка M+N.)
Download