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

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

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

221

35:в порядке, заданном функцией Vergl}

36:procedure GemeineSort(p:pointer;Umf,Quant:longint;Vergl:Vergleich);

37:var

38:p1,p2:Pointer;

39:p3:pointer;

40:i,j:longint;

41:begin

42:writeln('Mem in Sort',MemAvail);

43:getMem(p3,Umf);

44:

45:for i:=Quant-2 downto 0 do

46:for j:=0 to i do

47:begin

48:p1:=pointer(longint(p)+j*Umf);

49:p2:=pointer(longint(p)+(j+1)*Umf);

50:if Vergl(p1,p2)=true then

51:begin

52:Kopiere(p3,p2,Umf);

53:Kopiere(p2,p1,Umf);

54:Kopiere(p1,p3,Umf);

55:end;

56:end;

57:

58:freemem(p3,Umf);

59:writeln('Mem in Sort',MemAvail);

60:end;

61:

62:{Целочисленное больше}

63:function Mehr(p1,p2:pointer):boolean;far;

64:begin

65:Mehr:=PInt(p1)^>PInt(p2)^;

66:end;

67:

68:{Строковое больше}

69:function StringMehr(p1,p2:pointer):boolean;far;

70:begin

71:StringMehr:=PStr(p1)^>PStr(p2)^;

72:end;

73: //////////основная программа//////////

74:begin

75:Clrscr;

76: { Сортируем массив из целых чисел} 77:

78:Umf:=sizeof(Integer);{Размер целой переменной}

79:n:=40;

80:writeln('Mem = ',Memavail);

81:getMem(p,n*Umf); {Резервируем память под весь массив}

82:writeln('Mem = ',Memavail);

83:

randomize;

{Заполняем элементы массива}

84:

 

 

85:

for i:=0 to n-1 do

{(он расположен в динамич. памяти)}

222

86: (pint(longint(p)+Umf*i))^:=random(100); 87:

88:writeln('исходный массив');

89:SchrMass(p,n);

90:

91:GemeineSort(p,Umf,n,Mehr); {Сортируем массив с ф-ей сравнения

Mehr}

92:writeln('отсортированный массив');

93:SchrMass(p,n);

94:

95:freeMem(p,n*Umf);{освобождаем память}

96:writeln('Mem = ',Memavail);

97:

98: {сортируем массив строк размера 30 байт}

99:Umf:=30;

100:n:=4;

101:writeln('Mem = ',Memavail);

102:getMem(p,n*Umf);{Выделяем память под 4 строки}

103:writeln('Mem = ',Memavail);

104:{Заполняем их данными}

105:(pstr(longint(p)+Umf*0))^:='Heinrich';

106:(pstr(longint(p)+Umf*1))^:='Ulrich';

107:(pstr(longint(p)+Umf*2))^:='Anders';

108:(pstr(longint(p)+Umf*3))^:='Harald';

109:

110:writeln('исходный массив');

111:for i:=0 to n-1 do

112:write(pstr(longint(p)+Umf*i)^,' ');

113:writeln;

114:GemeineSort(p,Umf,n,StringMehr); {Сортируем строки}

116:writeln('отсортированный массив');

117:for i:=0 to n-1 do

118:write(pstr(longint(p)+Umf*i)^,' ');

119:writeln;

120:freeMem(p,n*Umf);{освобождаем память}

121:writeln('Mem = ',Memavail);

122:readkey;

123:end.

Встроке 6 задан функциональный тип Vergleich – (vergleichen - сравнивать) - функция сравнения для двух указателей (точнее, для переменных, на которые они ссылаются).

Следующая процедура рассматривает р как указатель на массив целых чисел из Q элементов, и печатает элементы этого массива.

procedure SchrMass(p:pointer;Q:longint);

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

procedure Kopiere(p1,p2:pointer;Umf:longint);

223

Можно было бы сделать иначе, попросив у пользователя передавать в процедуру GemSort процедуру копирования как параметр, но мне кажется, что этот выход из положения не очень удачен.

Теперь рассмотрим основную подпрограмму. GemeineSort (gemein – общий (а также низкий, подлый)). Эта процедура рассматривает p как указатель на начало массива, состоящего из Q элементов и размер одного элемента (в байтах) равен Umf. Функция Vergl типа Vergleich используется в условном операторе для того, чтобы знать, в каком случае надо менять элементы массива.

procedure GemeineSort(p:pointer;Umf,Quant:longint;Vergl:Vergleich);

В основной программе сначала создается в ДП массив целых чисел, который сортируется по возрастанию (функцией Mehr (больше)). Потом этот массив удаляется из ДП, и создается массив строк, который затем сортируется в алфавитном порядке.

13.5. Указатели и ссылки

Ссылки, с помощью которых можно передавать параметры в подпрограммы, также могут указывать на определенные области памяти. Отличие их от указателей в том, что их нельзя разыменовывать (они разыменовываются автоматически). Благодаря этому можно не использовать нотацию указателей. В Delphi ссылки используются гораздо чаще, чем в ТР, т.к. работать с объектами можно лишь посредством ссылок.

13.6. Динамические структуры данных

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

– это массивы, которые могут изменять свой размер с течением времени.

Доступ

В массиве доступ к любому элементу можно получить за 1 операцию, т.к. элементы массива индексированы.

Удаление

Удаление элемента выполняется за n операций, где n – длина массива, т.к. каждый элемент, который находится справа от того, который надо удалить, надо сдвинуть влево.

Вставка элемента

Если массив статический, то для того, чтобы можно было вставить элемент, в нем должно быть зарезервировано дополнительное место. Тогда можно освободить место для элемента, сдвинув все элементы вправо, а затем вставить требуемый элемент. Если же места нет, то вставить элемент в массив нельзя.

Пусть массив динамический. Тогда возможны 2 случая:

c log n

224

1. После массива есть некоторое количество свободного места (зарезервированного). Тогда элемент можно вставить точно так же, как и в статический массив.

Память, занятая массивом

Свободное место

Другие переменные

 

 

 

2. Если места для размещения дополнительного элемента массива нет, то надо найти новый непрерывный участок памяти, в который поместился бы массив вместе с новым элементом. Если он есть, то надо скопировать в него все элементы, включая тот, который надо вставить в требуемом порядке и затем освободить участок памяти, который раньше занимал массив.

Память, занятая массивом

Другие переменные

 

 

Вставка элемента в массив в обоих случаях составляет cn операций, где n - длина массива. Причем выделение нового участка памяти под массив тоже может потребовать много времени (и копировать придется весь массив, а не его часть).

Поиск элемента в неотсортированном массиве составляет cn операций, а в отсортированном - операций (бинарный поиск).

Итак: доступ к элементам массива очень быстрый, однако пополнять или удалять элементы – довольно накладно. Кроме того, массиву требуется непрерывный участок памяти (который надо еще найти). Расширение динамического массива также проблема.

Часто массивами вообще нельзя пользоваться. Например, когда мы с вами моделировали многочлены (глава «Массивы»), мы отметили, что для хранения многочлена x1000 + x456 понадобится массив из не менее чем 1001 элемента (степени начинаются с 0).

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

Изучение динамических структур данных начнем с простейшей структуры –

стека.

13.7.Стеки

Стек (Stapel) – структура данных, в которой удалять и добавлять элементы можно только из вершины стека.

Стек используется в тех случаях, когда вам надо хранить некоторое количество

элементов, а доступ вам нужен лишь к тому, который вы добавили в стек последним. Например, в главе 11 мы разбирали пример, в котором надо было проверить правильность расстановки нескольких видов скобок в строке. Алгоритм сводился к тому, что как только мы нашли закрывающую скобку, надо было проверить, не совпадает ли она с последней свободной открывающей скобкой. Если для хранения открывающих скобок использовать стек, то алгоритм был бы следующий: если мы нашли открывающую скобку, добавляем ее в стек. Если нашли закрывающую скобку,

225

то если она того же типа. что скобка в вершине стека, убираем из стека открывающую скобку. В противном случае скобки расставлены неверно.

Рожок автомата Калашникова – тоже стек.

Структура стека показана на рис. 13.2. Описывается такой стек следующем образом:

type

ZKnote =^Knote;

Knote = record {Узел стека}

 

 

 

 

 

 

 

 

 

 

 

 

Folg:ZKnote;

{указатель на следующий узел (folgend -

 

 

 

следующий)}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

info:integer; {полезная информация}

 

 

 

 

 

end;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Stapel = record {Стек}

 

 

 

 

 

 

 

 

 

 

 

 

Spitze:ZKnote; {Spitze - вершина (стека)}

 

 

 

 

 

end;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

nil

 

 

 

info

 

 

 

info

 

 

 

info

 

 

 

info

 

 

 

info

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Spitze

Spitze – указатель на вершину стека info – полезная информация узла стека

Рис. 13.2. Стек.

Кроме базовых операций стека – добавления элемента в стек и вытягивания элемента из него, мы напишем подпрограмму, которая будет печатать содержимое стека. Для того чтобы уничтожить стек из памяти, просто уничтожить его вершину недостаточно, т.к. вся остальная часть стека (кроме вершины) будет занимать место в динамической памяти, хотя получить к ней доступ в программе будет нельзя. Поэтому надо написать специальную процедуру (Tod – смерть), которая будет полностью уничтожать содержимое стека из ДП (для этого надо уничтожать по очереди каждый элемент, начиная с вершины стека).

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

Пример 6: Использование стека.

1 : type

2 : ZKnote =^Knote;

3 :

4 : Knote = record {Узел стека}

5 : Folg:ZKnote; {указатель на следующий узел (folgend -

следующий)}

6 : info:integer; {полезная информация}

7 : end;

8 :

9 : Stapel = record {Стек}

226

10:Spitze:ZKnote; {Spitze - вершина (стека)}

11:end;

12:

13:{добавление элемента в стек}

14:procedure Add(var S:Stapel;x:integer);

15:var

16:z:ZKnote;

17:begin

18:

new(z);

{создаем новый узел}

19:z^.folg:=S.Spitze; {он будет находиться перед вершиной}

20:z^.info:=x;

21:S.Spitze:=z; {Делаем z вершиной стека}

22:end;

23:

24:{Возвращает true, если стек пуст}

25:function IstFrei(var S:Stapel):boolean;

26:begin

27:Istfrei:= S.Spitze=nil;

28:end;

29:

30:{Возвращает число, записанное в вершине стека и

31:затем уничтожает вершину}

32:function Gib(var S:Stapel):integer;

33:var

34:z:ZKnote;

35:begin

36:if IstFrei(S)=true then

37:begin

38:Gib:=-1; {если стек пуст, то возвращаем -1}

39:exit;

40:end;

41:

42:z:=S.Spitze^.Folg; {запоминаем следующий за вершиной узел}

43:Gib:=S.Spitze^.Info; {вытаскиваем информацию из вершины}

44:dispose(S.Spitze); {уничтожаем вершину}

45: S.Spitze:=z; {делаем z вершиной} 46: end;

47:

48:{Печать элементов стека (начиная с головы стека)}

49:procedure Schr(var S:Stapel);

50:var

51:z:ZKnote;

52:begin

53:z:=S.Spitze;

54:while z<>nil do

55:begin

56:write(z^.info, ' ');

57:z:=z^.folg; {переходим к следующему элементу стека}

58:end;

59:end;

60:

61: {Уничтожает стек}

227

62:procedure Tod(var S:Stapel);

63:var

64:z:ZKnote;

65:begin

66:while S.Spitze<>nil do

67:begin

68:z:=S.Spitze;

69:S.Spitze:=S.Spitze^.folg;

70:dispose(z);

71:end;

72:end;

73:

74:var

75:S:stapel;

76:i,x:integer;

78:begin

79:s.Spitze:=nil;

80:writeln('MemAvail ',MemAvail);{Сколько памяти доступно в

начале программы}

81:for i:=1 to 10 do {добавляем элемены в стек}

82:Add(S,i);

83:

84:write('Наш стек: ');

85:Schr(S); {печатем стек}

86:writeln;

87:

88:writeln('Извлекаем 5 элементов из стека');

89:for i:=1 to 5 do

90:write(Gib(S),' ');

91:writeln;

92:

93:write('Что осталось от стека ');

94:Schr(S);

95:writeln;

96:

 

 

97:

Tod(S);

{уничтожаем стек из динамической памяти}

98:writeln('MemAvail ',MemAvail); {Проверяем, вся ли память была

возвращена в кучу} 99: end.

13.8.Однонаправленные списки

Однонаправленный список – структура данных, состоящая из элементов, каждый из которых содержит помимо другой информации указатель на следующий элемент списка. Добавлять (и удалять) элементы можно в любую позицию списка.

Стек является частным случаем однонаправленного списка. Есть еще 2

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

228

info info info info info nil

Erste

Letzte

erste – указатель на первый элемент списка letzte - указатель на последний элемент списка

Рис 13.3. Однонаправленный список

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

Структура списка очень похожа на структуру стека (только хранить надо указатель и на начало, и на конец списка).

Пример 7: Работа со списком.

type

 

lstElp=^ListeEl;

 

ListeEl = record

 

Nachste:lstElp;

{Nachste - следующий}

zahl:integer;

{zahl - число}

end;

 

Liste = record

 

Erste,Letzte:lstElp; {erste - первый, letzte - последний} end;

function IstLeer(var L:Liste):boolean; begin

IstLeer:=L.Erste=nil;

end;

procedure ListeTod(var L:Liste); {Уничтожает список} var

x,y:lstElp; begin

with L do begin

if IstLeer(L) then exit;

x:=erste; {сохраняем указатель на 1-ый элемент списка} erste:=nil;

letzte:=nil;

while (x<>nil) do {пока не дошли до конца списка} begin

y:=x^.Nachste; {сохраняем указатель на следующий за х элем.} dispose(x);

x:=y;

{х теперь указывает на начало списка}

end;

 

end;

 

end;

 

229

{Добавляет число в начало списка}

procedure StellZuAnf(var L:Liste;n:integer); var

x:lstElp; begin

with L do begin new(x); x^.zahl:=n;

x^.Nachste:=nil; if IstLeer(L) then

begin Letzte:=x;

Letzte^.Nachste:=nil;

Erste:=Letzte;

exit;

end;

x^.Nachste:=Erste;

Erste:=x;

end;

end;

{Добавляет число в список в заданную позицию Platz} procedure StellZuPlatz(var L:Liste;n:integer;platz:integer); var

x,tmp:lstElp;

i:longint; begin

with L do begin

if (platz=1) then {Если надо вставить элемент в начало списка} begin

StellZuAnf(L,n);

exit;

end;

tmp:=Erste;

i:=1;

while (tmp<>nil) and (i<platz-1) do{пробегаем все элементы до (platz-1)-го}

begin tmp:=tmp^.Nachste; inc(i);

end;

if (tmp=nil) then {Если в списке меньше, чем platz-1 элементов}

exit;

{элемент вставлять не будем}

new(x);

{Выделяем место под узел списка}

x^.zahl:=n;

x^.Nachste:=tmp^.Nachste; {устанавливаем ссылку на сл. эл. списка} tmp^.Nachste:=x; {делаем, чтобы следующим за tmp элементом был х} end;

end;

230

13.9.Кольцевые списки

Кольцевым списком называется список, в котором его последний элемент ссылается на первый элемент.

Anfang

info

info

info

info

info

Anfang – указатель на «начало» списка.

Рис 13.4. Циклический односвязный список

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

23

1

 

4

 

 

 

7

5

 

6

 

 

Рис. 13.5. Считалочка

Например, если играют 7 участников (см. рисунок 13.5), n=3 и начальный элемент равен 1, то вылетать они будут в таком порядке:

3, 6, 2, 7, 5, 1.

Пример 8: Считалочка.

1

: uses

2

:

Crt;

3

: type

4

:

ZKnoten = ^Knoten;

5

:

 

6

:

Knoten = record

7

:

z:integer;

8

:

Folg:ZKnoten;

9

:

end;