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

[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi

.pdf
Скачиваний:
68
Добавлен:
25.04.2014
Размер:
3.16 Mб
Скачать

171

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

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