Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Демидов Основы программирования в примерах на языке ПАСЦАЛ 2010

.pdf
Скачиваний:
128
Добавлен:
16.08.2013
Размер:
1.28 Mб
Скачать

for i:=3 to n do begin f3 := f1 + f2;

f1 := f2;

f2 := f3; end;

writeln('rabbits = ', f3); end.

Этому алгоритму, очевидно, свойственна линейная временная сложность и константная емкостная сложность, так как удалось обойтись без массива!

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

Многочлен Чебышёва

Рекуррентное соотношение для многочленов Чебышёва выглядит следующим образом:

T0(x) = 1; T1(x) = x;

Tn(x) = 2xTn-1(x) – Tn-2(x).

Согласно указанным соотношениям

T2(x) = –1 + 2x2;

T3(x) = 2x*(–1 + 2x2) – x = –3x + 4x3;

T4(x) = 2x*(–3x + 4x3) – (–1 + 2x2) = 1 – 8x2 +8x4;

Задача состоит в нахождении коэффициентов k-го многочлена Чебышёва Tk(x).

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

для k = 0 – [1, 0, …] для k = 1 – [0, 1, 0, …]

для k = 2 – [–1, 0, 2, 0, …] для k = 3 – [0, –3, 0, 4, 0, …] для k = 4 – [1, 0, –8, 0, 8, 0,…]

Задача сводится к вычислению элементов массива для очередного многочлена Tn на основе двух массивов Tn-1 и Tn-2, хранящих

71

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

1)умножение многочлена на константу равносильно умножению всех элементов массива на эту константу;

2)умножение многочлена на переменную х равносильно сдвигу каждого элемента вправо на 1 разряд;

3)разность многочленов равносильна разности соответствующих элементов в массивах коэффициентов.

Таким образом, Tn[i] = Tn-1[i-1]*2 – Tn-2[i] для всех i, кроме 0, а Tn[0] = –Tn-2[0]. Потребуются два цикла: в главном будет нарастать счетчик k, а во вложенном – рассчитываться по выведенной формуле коэффициенты многочлена Tk.

72

Глава 7. Операции над массивами и матрицами

Сдвиг элементов массива

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

В случае циклического сдвига крайний правый элемент переходит на освободившееся место в начале массива.

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

var

a: array [1..n] of integer; i: integer;

begin

for i:=n downto 2 do a[i] := a[i-1];

a[1] := 0;

end.

Для циклического сдвига необходимо сначала запомнить последний элемент:

var

a: array [1..n] of integer; buf,i: integer;

begin

buf := a[n];

for i:=n downto 2 do a[i] := a[i-1];

a[1] := buf;

end.

Для сдвига влево меняется направление прохода по массиву и граничные условия.

73

Циклический сдвиг элементов массива на k позиций

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

Чтобы не усложнять алгоритм лучше воспользоваться дополнительным массивом, если это не запрещается условием задачи. Необходимо построить взаимно-однозначное соответствие между элементами массивов, т.е. получить формулу для расчета индекса newi элемента в новом массиве по его индексу i в исходном массиве. Для обычного сдвига newi = i + k, но для циклического сдвига необходимо предусмотреть случай, когда newi становится больше N. Модифицируем формулу следующим образом: newi = (i + k) mod N, где i, newi [1..N]. Действительно, если i + k = N + 3, то (i + k) mod N + 1 = 4. Но если i + k = N, то должно получиться N, а у нас получается 0. Тогда модифицируем формулу следующим образом: newi = (i + k – 1) mod N + 1.

var

a,b: array [1..n] of integer; i,k: integer;

begin

for i:=1 to n do

b[(i+k-1) mod n + 1] := a[i];

end.

Вообще, если бы индекс массива i [lo..hi], то формула выглядела бы так:

newi = (i + k – lo) mod (hi – lo + 1) + lo.

Реверсирование массива

Рассмотрим произвольный массив. Необходимо построить другой массив, в котором элементы идут в обратном порядке по сравнению с порядком элементов в исходном массиве.

Если можно использовать дополнительный массив, то формула для расчёта индекса такова: newi = n – i + 1. В противном случае можно построить следующую процедуру: сначала обменять места-

74

ми первый и последний элементы массива, далее второй и предпоследний и т.д.

Однако если построить цикл от 1 до n, то элементы поменяются местами дважды и получится исходный массив, поэтому обмен следует вести только до середины массива, которая определяется как n div 2. Для нечетных n – это индекс элемента, предшествующего центральному элементу, который останется на своём месте:

var

a: array [1..n] of integer; i,buf: integer;

begin

for i:=1 to n div 2 do begin buf := a[i];

a[i] := a[n-i+1]; a[n-i+1] := buf;

end;

end.

Слияние массивов

Даны два массива чисел длины N, упорядоченных по возрастанию. Следует получить массив длины 2*N, также упорядоченный по возрастанию и состоящий из элементов исходных массивов.

Для решения этой задачи явно требуются операторы цикла. Введем три счетчика i, j, k, где i, j [1..N] – текущие позиции в исходных массивах, а k [1..2*N] – текущая позиция в новом массиве.

Для определения того, из какого массива копировать очередной элемент, на каждом витке цикла необходимо сравнивать текущие элементы исходных массивов и копировать наименьший. После этого необходимо увеличивать счетчик того массива, из которого скопирован элемент. Так как должны быть скопированы все элементы исходных массивов, то по окончании работы программы счетчики должны принять максимальные значения. Тогда конечное состояние работы программы можно записать как (i=n) and (j=n). Ясно, что в этом случае k = 2*n. Отрицание этого выражения можно считать условием выполнения цикла:

not ((i=n) and (j=n)) not (i=n) or not (j=n) (i<=n) or (j<=n)

75

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

Здесь есть подводный камень: массивы не могут закончиться одновременно. Один из массивов всегда закончится раньше другого и тогда программа все же обратится к несуществующему элементу. Тогда необходимо модифицировать условие выполнения цикла на (i<=n) and (j<=n). В этом случае цикл прекратится, как только один из счетчиков достигнет n. Тогда останется просто скопировать оставшиеся элементы другого массива:

var

a,b: array [1..n] of integer; c: array [1..2*n] of integer; i,j,k: integer;

begin

i:=1;

j:=1;

k:=1;

while (i<=n) and (j<=n) do begin if a[i] < b[j] then begin

c[k] := a[i]; i := i+1;

end

else if a[i] > b[j] then begin c[k] := b[j];

j := j+1; end

else begin c[k] := a[i]; k := k+1;

c[k] := a[i]; // второй раз тоже самое i := i+1;

j := j+1; end;

k := k+1; end;

// дописываем остаток, если есть if i>n then while (j<=n) do begin

c[k] := b[j]; j := j+1;

76

k := k+1;

end

else while (i<=n) do begin c[k] := a[i];

i := i+1; k := k+1;

end;

end.

В общем случае программа копирует один или более элементов из одного массива, затем из другого, после опять из первого и т.д., сохраняя порядок чисел, пока оба массива не закончатся.

Работа с матрицами

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

Проверка свойств матрицы

Задача: определить, является ли матрица треугольной. Достаточно проверить, что все элементы ниже главной диагона-

ли равны 0. Если хотя бы один элемент ненулевой, то проверку остальных элементов можно не делать:

var

matrix: array[1..n,1..n] of integer; i,j: integer;

triangle: boolean; begin

triangle := true;

// начинаем сразу со второй строки for i:=2 to n do begin

// с первого столбца до диагонали for j:=1 to i-1 do begin

if matrix[i,j] <> 0 then begin triangle := false;

break; end;

77

end;

// break прерывает только тот цикл, в котором находится if triangle then break;

end;

end.

Генерация матрицы заданного вида

1. Дано целое число n>1 и действительное число х. Написать процедуру, заполняющую матрицу вида

1

x

x2

. . . xn-1 xn

x

0

0

. . . 0

xn-1

x2

0

0

. . . 0

xn-2

xn-1 0

0

.

.

.

0

x

xn

xn-1 xn-2

.

.

.

x

1

Элементы, расположенные по периметру образуют степенной ряд, при этом налицо симметрия данных элементов. Это даёт возможность заполнить их в одном цикле, используя рекуррентное соотношение:

var

matrix: array[0..n,0..n] of integer; i,j: integer;

x,p: real; begin

readln(x); p := 1;

// заполнение элементов по периметру матрицы for i:=0 to n do begin

matrix[i,0] := p;

// первый столбец

matrix[0,i] := p;

// первая строка

matrix[n-i,n] := p;

// последний столбец

matrix[n,n-i] := p;

// последняя строка

p := p*x;

 

end;

 

// остальное заполняется нулями for i:=1 to n-1 do

for j:=1 to n-1 do matrix[i,j]:=0;

end.

78

2. Используя целочисленную квадратную матрицу А, построить матрицу В того же типа, где bij определяется следующим образом. Через аij проведем в А диагонали, параллельные главной и побочной диагоналям матрицы; bij равен максимальному элементу в области матрицы А, ограниченной снизу проведенными диагоналями.

Пусть матрицы объявлены следующим образом:

var A,B: array [0..9, 0..9] of integer;

Если провести аналогию между матрицей и системой координат, то элемент с индексом [0, 0] будет центром координат, ось абсцисс соответствует столбцам, ось ординат – строкам (направлена вниз). Тогда диагонали в матрице для каждого элемента brs можно описать уравнениями прямых на плоскости:

i – r = j – s; i – r = – j + s.

Здесь r, s – координаты очередного элемента матрицы B. Каждый элемент brs выбирается из соответствующей области матрицы А как

brs = max{aij}, где i [0..n–1], j [0..n–1], i – r <= j – s, i – r <= –j+s.

При поиске максимального элемента для очередного brs имеет смысл инициализировать переменную max элементом, стоящим на пересечении соответствующих диагоналей матрицы A, т.е. ars. Понятно, что в область входят только элементы, находящиеся на строках от 0 до r, причем в строке r это единственный элемент ars. Тогда границами цикла по строкам будут 0 и r–1, так как элемент ars учтён при инициализации. Границами цикла по столбцам в общем случае будут 0 и n, а ограничения будут проверяться явно:

var

A,B: array[0..n,0..n] of integer; i,j,r,s,max: integer;

begin

// циклы для всех элементов матрицы B, счетчики r и s for r:=0 to n do

for s:=0 to n do begin max := A[r,s];

// выбор максимального из соответствующей области в А for i:=0 to r-1 do

for j:=0 to n do

if (i <= j-s+r) and (i <= -j+s+r)

79

and (A[i,j] > max) then max := A[i,j]; B[r,s] := max;

end;

end.

Этому алгоритму свойственна сложность O(n4), так как выполняются четыре вложенных цикла. Однако для матриц алгоритм не так уж плох – он не хуже, чем алгоритмы квадратичной сложности для одномерных массивов.

3. Умножение матриц. Пусть имеется матрица A[M, K] и матрица B[K, N]. Получить матрицу C[M, N], где

K

cij aikbkj ,i [1..M], j [1..N].

k 1

Идея для решения: для заполнения матрицы C потребуется два вложенных цикла по i для строк и по j для столбцов, а чтобы вычислить очередной элемент, нужен ещё один вложенный цикл по k.

Не страшно, если программа не сразу заработает. Помните: «Программа – зеркало глупости программиста».

80

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]