[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi
.pdf171
•Побочной диагональю квадратной матрицы называется диагональ, соединяющая правый верхний и левый нижний углы.
10.4. Действия над матрицами
1. Сложение матриц
a |
a |
... |
a |
|
b |
b |
... |
b |
|
a |
+ b |
a |
+ b |
... |
a |
+ b |
|
11 |
12 |
|
1m |
|
11 |
12 |
|
1m |
|
11 |
11 |
12 |
12 |
|
1m |
1m |
|
C = A + B = a21 |
a22 |
... |
a2m |
+ b21 |
b22 |
... b2m |
= a21 |
+ b21 |
a22 |
+ b22 |
... |
a2m + b2m |
|||||
... ... |
... |
... |
|
... ... |
... |
... |
|
... |
|
... |
... |
|
... |
|
|||
|
|
|
|
|
|
|
|
|
|
|
+ bn1 |
|
+ bn 2 |
|
|
|
|
an1 |
an 2 |
... |
anm |
bn1 |
bn 2 |
... |
bnm |
an1 |
an 2 |
... |
anm + bnm |
Т.е. для того, чтобы просуммировать 2 матрицы надо просто сложить соответствующие
элементы обеих матриц, т.е. cij |
= aij |
+ bij , где i = |
1, n |
, j = |
1, m |
. Например: |
|||||||||||||
|
1 2 |
|
3 5 4 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
+ |
|
= |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 3 |
|
4 10 8 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
Абсолютно аналогично, если надо найти разность матриц: С = А− В , то элементы |
||||||||||||||||||
матрицы С будут иметь вид: cij = aij − bij , где i = |
|
, j = |
|
. |
|||||||||||||||
1, n |
1, m |
||||||||||||||||||
2. Умножение матрицы на число: |
|
|
|
|
|
|
|
|
|
|
|
||||||||
|
a |
a ... |
a |
|
sa |
sa |
... |
sa |
|
||||||||||
|
|
11 |
12 |
1m |
|
|
11 |
12 |
|
1m |
|
||||||||
C |
= sA = s a21 |
a22 ... |
a2m = |
sa21 |
sa22 |
... |
sa2m |
||||||||||||
|
... |
... ... |
... |
|
... ... |
... |
... |
|
|||||||||||
|
|
|
an 2 ... |
|
|
|
|
|
... |
|
|
||||||||
|
an1 |
anm |
san1 |
san 2 |
sanm |
3. Умножение матрицы на вектор
a11
C = Ax = a21
...
an1
a |
... a |
x |
|
|
(a<1> , x) |
|
||||
12 |
1m |
|
1 |
|
|
(a<2 |
> |
|
|
|
a22 |
... a2m x2 |
|
= |
, x) |
, |
|||||
... |
... ... |
|
... |
|
|
... |
|
|
||
|
|
|
|
|
|
|
<n |
> |
|
|
an 2 |
... anm xm |
|
(a |
|
|
|||||
|
|
|
, x) |
|
где(a<i> , x) - скалярное произведение i -й строки матрицы на вектор x, т.е.:
(a<i> , x) = ai1x1 + ai 2 x2 + ... + aim xm
Например:
|
1 2 3 |
1 |
1 1+ 2 2 + 3 3 |
|||||
|
4 5 6 |
|
|
2 |
|
|
4 1+ 5 2 + 6 3 |
|
|
|
|
|
= |
|
|||
|
7 8 9 |
|
|
3 |
|
|
7 1+ 8 2 + 9 3 |
|
|
|
|
|
4. Умножение двух матриц
|
a |
a |
... |
a |
|
|
b |
b |
... b |
|
|
|
(a<1> ,b |
||
|
11 |
12 |
|
1m |
|
|
11 |
12 |
1s |
|
|
|
|
|
<1> |
C = A B = |
a21 |
a22 |
... a2m |
|
b21 |
b22 |
... b2s |
= |
|
(a<2> ,b<1> |
|||||
... ... |
... |
... |
|
... ... |
... ... |
|
|
|
... |
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<n> |
|
|
an1 |
an 2 |
... |
anm |
|
bm1 |
bm 2 |
... bms |
|
|
(a |
,b<1> |
|||
|
|
|
|
|
где (a<i> ,b< j > ) = ai1b1 j + ai 2b2 j + ... + aimbmj
) |
(a |
<1> |
,b<2> ) |
... (a |
<1> |
,b<s > ) |
|
|
|
|
|||||||
|
|
|
|
|||||
) |
(a<2> ,b<2> ) ... (a<2> ,b<s > ) |
, |
||||||
|
|
... |
... |
... |
|
|||
|
|
|
|
|||||
|
|
<n> |
|
|
<n> |
|
|
|
) |
(a |
,b<2> ) |
... (a |
|
|
|
||
|
|
,b<s > ) |
|
Например:
172 |
|
|
|
|
|
|
|
|
|
|
|
1 2 |
3 5 |
|
1 3 + 2 4 1 5 + 2 10 |
|
11 25 |
||||||
|
|
|
4 10 |
|
= |
4 3 + 3 4 4 5 |
+ 3 10 |
|
= |
24 50 |
|
4 3 |
|
|
|
|
|
|
|
5. Транспонирование матрицы
•Транспонированием матрицы называется замена ее столбцов на строки, а строк на столбцы. Полученная при этом матрица называется транспонированной матрицей.
Матрица, транспонированная к матрице А, обозначается AT .
|
|
a |
a ... |
a |
T |
|
a |
|
a |
|
|
... |
a |
|
|
|
|
|
11 |
12 |
1m |
|
|
|
11 |
|
21 |
|
|
n1 |
|
||
T |
= |
a21 |
a22 ... |
a2m |
= |
a12 |
a22 |
... |
an 2 |
|||||||
A |
... ... ... |
... |
|
... |
... |
... |
... |
|
||||||||
|
|
|
|
|||||||||||||
|
|
|
an 2 ... |
|
|
|
|
|
|
|
|
|
... |
|
|
|
|
|
an1 |
anm |
|
|
a1m |
a2m |
anm |
||||||||
Например: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
1 3 4 |
|
|
|
|
1 6 |
|
|
|
|
|
||||
1. |
|
|
|
B |
T |
|
3 2 |
|
|
|
|
|
||||
B = |
|
|
|
= |
|
|
|
|
|
|||||||
|
|
6 2 1 |
|
|
|
|
|
4 1 |
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Заметим, что для квадратной матрицы транспонирование эквивалентно отражению относительно главной диагонали.
Пусть вас не смущает то, что умножение матриц задается так хитро: в этом есть свой смысл. Например, следующая система линейных алгебраических уравнений
a11x1 + a12 x2 + ... + a1m xm = b1a21 x1 + a22 x2 + ... + a2m xm = b2
...
an1x1 + an 2 x2 + ... + anm xm = bn
может быть записана очень просто:
|
|
Ax = b , где |
|
|
|
|
||||
a |
a |
... |
a |
|
|
x |
|
|
b |
|
11 |
12 |
... |
1m |
|
|
1 |
|
|
1 |
|
A = a21 |
a22 |
a2m |
, |
x = x2 |
|
, |
b = b2 |
|||
... ... |
... |
... |
|
|
... |
|
... |
|||
|
|
... |
|
|
|
|
|
|
|
|
an1 |
an 2 |
anm |
|
xn |
|
|
bn |
|
Теперь, после краткого теоретического введения мы напишем программу с 2-мя процедурами: транспонирования матриц и их умножения.
Пример 4: Действия над матрицами.
1 : uses
2 : MatrArrU;
3 : const
4 : n=3; {количество строк матрицы}
5 : m=3; {количество столбцов матрицы}
6 : type
7 : {Объявление вещественной матрицы размера n*m}
8 : Matrix=array[1..n,1..m] of integer;
9 : var
10:A,B,C:Matrix;
173
11:
12:procedure Transpon(const A:Matrix;var B:Matrix);
13:var
14:i,j:integer;
15:begin
16:for i:=1 to n do
17:for j:=1 to m do
18:B[i,j]:=A[j,i];
19:end;
20:
21:{Внимание: процедура умножения матриц будет работать
22:только, если n=m}
23:procedure MultMat(const A,B:matrix;var C:Matrix);
24:var
25:i,j,k:word;
26:begin
27:for i:=1 to n do
28:for j:=1 to m do
29:begin
30:c[i,j]:=0; {обнуляем элемент матрицы С[i,j]}
31:for k:=1 to n do
32:c[i,j]:=c[i,j]+A[i,k]*b[k,j];
33:end;
34:end;
35:
36:begin
37:writeln('Введите матрицу А размера ',n,'*',m);
38:LeseMass(A,n,m);
39:writeln('Матрица А:');
40:SchrMass(A,n,m);
41:Transpon(A,B);
42:writeln('Матрица B = A^t:');
43:SchrMass(B,n,m);
44:
45:MultMat(A,B,C);
46:writeln('Матрица C = A*B:');
47:SchrMass(C,n,m);
48:end.
10.5. Множества
Напомню, что под множеством понимается некоторая совокупность элементов, четко отличимых друг от друга.
Для множеств в ТР создан специальный тип данных set. Количество элементов в типе set не может быть больше 256. Задать множество значений типа set можно так:
Var
A:set of byte;
B:set of 1..9
Первое определение будет означать, что значениями переменной А могут быть все подмножества множества {0, 1, …, 255}. А переменная В может быть любым подмножеством множества {1, 2, …, 9}.
174
|
|
Операции над множествами: |
Операция |
Выражение |
Описание выражения |
in |
х in A |
Принадлежит ли элемент х множеству А |
<= |
A<=B |
Проверяет, является ли А подмножеством В |
>= |
A>=B |
Проверяет, является ли В подмножеством А |
= |
A=B |
Проверяет, совпадают ли А и В |
<> |
A<>B |
Проверяет, не совпадают ли А и В |
+ |
C:=A+B |
Во множество С записывает объединение множеств А и В |
- |
C:=A-B |
Во множество С записывает разность множеств А и В |
*С:=A*B Во множество С записывает пересечение множеств А и В
Задавать константы множественного типа надо так: const
Alphabet=[‘a’..’z’,’A’..’Z’]; Ziffern=[‘0’..’9’];
Давайте разберемся, как представляются множества в памяти ПК. Для примера рассмотрим переменную A: set of 1..8.
На нее в памяти отводится 8 бит. Каждый бит отвечает за свой элемент множества: за единицу – 1-й бит, за 2-ку второй бит, и т.д. Если элемент принадлежит множеству, то соответствующий бит равен 1, в противном случае он равен 0.
Если А=[1,2,4,8], то в памяти эти 8 бит будут выглядеть так: 11010001. Если А=[1,4,5,8], то в памяти будет храниться 10011001.
Из такого представления сразу следует, что во множестве не может быть 2-х одинаковых элементов (тогда бит должен кодировать не только 0 или 1, но 0,1 или 2).
Хотя каждый элемент множества кодируется в памяти одним битом, но память, которая выделяется под переменную типа множество всегда должна быть кратна 8 битам (т.к. минимальной адресуемой единицей памяти является байт, а не бит). Это означает, например, что на переменную B: set of 1..9 будет выделено 2 байта.
К сожалению, в ТР максимальное количество элементов множества слишком мало, – всего 256 элементов; это значительно сокращает область их применения. Однако научившись работать с динамической памятью, вы сможете на основе битового массива создать собственный тип-множество, у которого не будет столь суровых ограничений на количество элементов.
10.6. Сортировка подсчетом
Рассмотрим пример применения множеств: пусть нам надо отсортировать массив, и при этом:
1.Элементы в массиве типа byte.
2.Все элементы массива различны.
3.Количество элементов достаточно велико
Если эти 3 условия выполнены, то можно написать очень эффективный и простой алгоритм сортировки:
1. Заносим все элементы массива во вспомогательное множество.
175
2. Проходим по всем числам от 0 до 256 и если встречаем число во множестве, то записываем его в массив и исключаем из множества.
В результате мы расставим все числа в порядке возрастания, причем всего за два прохода, т.е. число действий должно не порядка n2 , как в сортировке пузырем или выбором, и даже не n log n , как в сортировке слиянием, а только порядка n действий.
Пример 5: Сортировка подсчетом.
1 : const
2 : n=5;
3 : type
4 : Arr=array [1..n] of byte;
5 : var
6 : M:arr;
7 : i:byte;
8 :
9 : procedure MengeSort(var A:Array of byte);
10:var
11:i,j:byte;
12:S:set of byte;
13:begin
14:S:=[];
15:for i:=0 to High(A) do {Заполняем S элементами массива}
16:S:=S+[A[i]];
17:j:=0;
18:i:=0;
19:while (S<>[]) do {Заполняем А элементами из S}
20: |
begin |
{в порядке возрастания} |
21:if i in S then
22:begin
23:S:=S-[i]; {Убираем элемент из множества}
24: |
A[j]:=i; |
{и добавляем в массив} |
25:inc(j);
26:end;
27:inc(i);
28:end;
29:end;
30:
31:begin
32:for i:=1 to n do
33:read(M[i]);
34:writeln('Исходный массив');
35:for i:=1 to n do
36:write(M[i],' ');
37:MengeSort(M); {сортировка}
38:writeln('Отсортированный массив');
39:for i:=1 to n do
40:write(M[i],' ');
41:end.
176
То, что элементы должны быть типа byte – это просто ограничение на тип set. Естественно, мы могли бы применять сортировку подсчетом, используя вместо множества массив. Если массив состоял бы из элементов типа byte, то мы могли бы использовать сортировку массивов, в которых элементы могут повторяться не более, чем 255 раз. Тогда в каждом элемента массива будет храниться то, сколько раз в исходном массиве встретилось число (отсюда и название сортировки).
Поэтому требование того, чтобы в массиве не было повторяющихся элементов – не создает больших проблем. Главное – чтобы элементы находились довольно кучно. Ведь если мы знаем, что каждое число массива находится в пределах x ≤ A[i] ≤ y , то нам
нужен вспомогательный массив из y − x элементов для того, чтобы применять эту сортировку. Если количество элементов в массиве, который мы хотим отсортировать, значительно меньше, чем y − x , то сортировка подсчетом будет требовать много памяти
и работать очень долго. Если же кучность велика, то сортировка подсчетом будет весьма эффективна.
Самое жесткое ограничение, накладываемое на сортировку подсчетом – ограничение на тип элементов. Для целых чисел мы знаем заранее порядок следования элементов. Но если надо сортировать записи некоторого типа, порядок следования элементов которого заранее не известен, а дана лишь функция сравнения элементов, показывающая, какой из элементов должен стоять дальше, а какой ближе, то сортировка подсчетом не пройдет.
10.7. Перечисляемый тип
Описание перечисляемого типа – это список идентификаторов, заключенных в скобки, например:
Type
Jahreszeiten = (Lenz, Sommer, Herbst, Winter); {Времена года} Farben = (Piks, Kreuze, Karos, Herzen); {Масти}
Boolean = (False, True);
По большому счету, перечисленный тип – это массив констант. Для типа
Jahreszeiten они равны:
Lenz=0, Sommer=1, Herbst=2, Winter=3.
Использование перечисляемого типа ограничено следующими правилами:
1. В 2-х перечисленных типах не могут использоваться одинаковые идентификаторы.
Type
Typ1 = (el1,el2,el3); Typ2 = (el1,el2);
Если вы напишите эти объявления типов, то при компиляции будет выдана
ошибка: {Ошибка: Duplicate identifier (el1)}
2. С переменными перечисляемого типа нельзя использовать операции Writeln, Write,
readln, Read.
Использование перечисляемого типа улучшает иногда читаемость программы.
Пример 6: Тасование карт и их печать.
1 : uses Crt;
177
2 |
: const |
|
3 |
: |
n=36; {количество карт} |
4 |
: |
AFarb=4;{кол-во мастей} |
5 |
: |
AQual=9;{кол-во карт каждой масти} |
6 |
: type |
|
7 |
: |
Farben=(Piks,Kreuze,Karos,Herzen); {Масти} |
8 |
: |
Qualitat=(Sechs,Sieben,Acht,neun,zehn,Bube,Dame,Konig,As); |
{достоинство} |
||
9 |
: |
|
10:Karte=record{карта}
11:F:Farben;
12:Q:Qualitat;
13:end;
14:Kartenspiel=array[1..n] of Karte;{колода карт}
16:{Заполняет массив KS картами по порядку}
17:procedure FullKartSpiel(var KS:KartenSpiel);
18:var
19:i,j:integer;
20:begin
21:for i:=1 to AFarb do
22:for j:=1 to AQual do
23:begin
24:KS[(i-1)*AQual+j].F:=Farben(i-1);
25:KS[(i-1)*AQual+j].Q:=Qualitat(j-1);
26:end;
27:end;
28:
29:{Случайное заполнение колоды карт}
30:procedure ZufallKartSpiel(var KS:KartenSpiel);
31:var
32:i,tmp:integer;
33:Kx:Karte;
34:begin
35:FullKartSpiel(KS);
36:for i:=1 to n do{Меняем местами каждый элемент}
37: |
begin |
{с некоторым другим, выбранным случайно} |
38:tmp:=random(n)+1;
39:Kx:=KS[i];
40:KS[i]:=KS[tmp];
41:KS[tmp]:=Kx;
42:end;
43:end;
44:
45:{Печать колоды карт}
46:procedure DruckKartSpiel(var KS:KartenSpiel);
47:var
48:i:integer;
49:begin
50:for i:=1 to n do
51:begin
52:case KS[i].Q of
178
53:Bube: write('B');
54:Dame: write('D');
55:konig: write('K');
56:As:write('A');
57:else {Если карта - от шестерки до десятки}
58:write(byte(KS[i].Q)+6);
59:end;
60:write(chr(byte(Ks[i].F)+3),' ');{печатаем масть}
61:end;
62:end;
63:
64:var
65:Ksp:kartenSpiel;
66:begin
67:Clrscr;
68:randomize;
69:FullKartSpiel(KSp);
70:DruckKartSpiel(Ksp);
71:writeln;
72:ZufallKartSpiel(Ksp);
73:DruckKartSpiel(Ksp);
74:readkey;
75:end.
10.8.Магические квадраты
Взаключение главы рассмотрим более сложный пример – построение магических квадратов.
•Магическим квадратом порядка n называется квадрат, состоящий из n2 клеток, заполненный числами 1.. n2 и в котором сумма элементов каждой строки, каждого столбца и обоих диагоналей равны одному и тому же числу.
Существует только 1 магический квадрат 3-го порядка (с точностью до поворотов и отражений). Его знали еще в Древнем Китае.
4 |
9 |
2 |
|
|
|
3 |
5 |
7 |
|
|
|
8 |
1 |
6 |
|
|
|
Рис 10.3. Древний китайский магический квадрат Ло Шу
Просто генерировать все возможные квадраты, а потом решать, какой из них будет магическим, а какой – нет – задача весьма нелегкая, потому что всевозможных квадратов порядка n будет (n2 )! (проверьте это).
Поэтому мы будем последовательно перебирать все квадраты, при этом отбрасывая те, которые заранее не подходят.
179
Во-первых, можно сразу вычислить сумму элементов строки (а также столбца и
диагонали) магического квадрата. Очевидно, что она равна 1+ 2 + 3 + ... + n2 . В числителе n
– сумма элементов арифметической прогрессии, которую мы умеем находить. Т.е.
магическая сумма равна n(n2 +1) . Эта сумма всегда будет целым числом, т.к. при
2
любом n в числителе стоит четное число. Зная магическую сумму, мы легко сможем составить алгоритм генерирования квадратов.
Для того, чтобы мы могли на каждом шаге проверять, не нарушилось ли хотя бы одно из условий, надо хранить сумму элементов каждой строки, каждого столбца и обоих диагоналей. Кроме того, надо знать, какие числа были использованы, а какие нет. Алгоритм я написал рекурсивно.
Процедура StellNeu(i,j) пробегает все числа, которые еще не были использованы, и смотрит, какие из них можно поставить так, чтобы магические условия не нарушались. Когда функция находит такое число, она ставит его в квадрат (в матрицу А), и вызывает себя же, для того, чтобы искать подходящее число для следующего квадрата. Когда весь квадрат заполнен правильно, то процедура печатает его. Чтобы разобраться в деталях, смотрите комментарии в самом программном коде.
Пример 7: Генерация всех магических квадратов.
1 : uses Crt,Matrarru;
2 :
3 : const
4 : n=3;
5 : L=n*n;
6 : type
7 : Matrix=array [1..n,1..n] of integer;
8 : MasN=array[1..n] of integer;
9 : MasL=array [1..L] of integer; 10:
11:{Печатает все магические квадраты порядка n и возвращает
12:их количество}
13:function Magisch(n:integer):longint;
14:var
15:i,j:integer;
16:A:Matrix;
17:S:integer; {S - необходимая сумма элементов строки}
18:d1,d2:integer;{Сумма элементов на диагоналях (главной и
побочной}
19:Sp,R:MasN;{Sp[i] - сумма элем. i-го столбца, R[i] - с. эл. i-
строки}
20:M:MasL; {M[i]=0, если число использовано}
21:QuantMag:longint;{Храним кол-во магических квадратов}
23:procedure StellNeu(i,j:integer);{Локальная процедура}
24:var
25:k:word;
26:begin
27:for k:=1 to l do
28:if (M[k]<>0) and (R[i]+k<=S) and (Sp[j]+k<=S) then
180
29:begin
30:if (i=j) and (d1+k>S) then
31:continue;
32:if (n+1-i=j) and (d2+k>S) then
33:continue;
34:if (i=n) and (Sp[j]+k<>S) then
35:continue;
36:if (j=n) and (R[i]+k<>S) then
37:continue;
38:
39:if (i=n) and (j=n) then
40:if (d1+k<>S) or (d2<>S) then
41: continue;
42:{Если мы дошли до этого места, значит можно ставить число}
43:A[i,j]:=k;
44:Sp[j]:=Sp[j]+k;
45:R[i]:=R[i]+k;
46: |
M[k]:=0; |
{Число k теперь использовано} |
47:if (i=j) then
48:d1:=d1+k;
49:if (n+1-i=j) then
50:d2:=d2+k;
51:if (i=n) and (j=n) then {Заполнен весь квадрат}
52:begin
53:writeMass(A,n,n);
54:inc(QuantMag);
55:writeln;
56:end;
57: |
if j=n then |
{Здесь рекурсивный вызов} |
58:StellNeu(i+1,1)
59:else
60:StellNeu(i,j+1);
61: |
A[i,j]:=0; |
{Для дальнейшего поиска обнуляем} |
62:Sp[j]:=Sp[j]-k;
63:R[i]:=R[i]-k;
64:M[k]:=1;
65:if (i=j) then
66:d1:=d1-k;
67:if (n+1-i=j) then
68:d2:=d2-k;
69:end;
70:end;
71:
72:begin
73:ClrScr;
74:for i:=1 to n do
75:for j:=1 to n do
76:A[i,j]:=0;
77:for i:=1 to L do
78:M[i]:=1;
79:for i:=1 to n do
80:begin