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

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

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

231

10:

11:ZyklListe = record

12:Anfang:ZKnoten; {Anfang - начало}

13:end;

14:

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

16:function IstFrei(var L:ZyklListe):boolean;

17:begin

18:IstFrei:=L.Anfang=nil;

19:end;

20:

21:{Добавляет узел, в котором будет записано число gerz}

22:procedure AddKnoten(var Zliste:ZyklListe; zahl:integer);

23:var

24:zeig:ZKnoten;

25:begin

26:New(zeig);

27:zeig^.z:=zahl;

28:if IstFrei(ZListe)=true then

29:begin

30:zeig^.folg:=zeig; {один элемент должен указывать на себя же}

31:ZListe.Anfang:=zeig;

32:exit;

33:end;

34:

35:zeig^.Folg:=Zliste.anfang^.folg;

36:Zliste.Anfang^.folg:=zeig;

37:end;

38:

39:{Печать списка}

40:procedure SchrListe(var ZListe:ZyklListe);

41:var

42:zeig:ZKnoten;

43:begin

44:if IstFrei(ZListe)=true then

45:exit;

46:

47:zeig:=ZListe.anfang^.folg;

48:repeat

49:write(zeig^.z,' ');

50:zeig:=zeig^.folg;

51:until zeig=ZListe.anfang^.folg;

52:end;

53:

54:{Освобождает память, занятую списком}

55:procedure Tod(var ZListe:ZyklListe);

56:var

57:zeig:ZKnoten;

58:begin

59:if ZListe.anfang=ZListe.anfang^.folg then {Если в списке один

элемент}

60:begin

232

61:dispose(ZListe.anfang);

62:ZListe.anfang:=nil;

63:exit;

64:end;

65:

66:repeat

67:zeig:=ZListe.anfang^.folg; {Сохраняем следующий за началом}

68:ZListe.anfang^.folg:=zeig^.folg; {Выбрасываем его из списка}

69:

dispose(zeig);

{освобождаем память, выделенную под zeig}

70:until ZListe.anfang^.folg=ZListe.anfang;

71:dispose(ZListe.anfang);

72:ZListe.anfang:=nil;

73:end;

74:

75:{Считалочка, в которой выбывает каждый s-й}

76:procedure Spiel(var LZ:ZyklListe;s:integer);

77:var

78:zeig:ZKnoten;

79:lauf:ZKnoten;

80:i,q:integer;

81:begin

82:zeig:=LZ.anfang;

83:q:=0;

84:writeln('!!! Считалочка !!!');

85:while zeig<>zeig^.folg do {Пока не остался один элемент в

списке}

86:begin

87:for i:=1 to s-1 do {Пробегаем s-1 человек}

88:zeig:=zeig^.folg;

89:

90:lauf:=zeig^.folg; {Запоминаем того, кто должен вылететь}

91:zeig^.folg:=zeig^.folg^.folg; {выбрасываем его из списка}

93:inc(q);

94:writeln(q,'-ым выбыл ',lauf^.z);

95:dispose(lauf); {удаляем из памяти того, кто выбыл}

96:end;

97:writeln('остался ',zeig^.z); {Пишем, кто же остался}

99:LZ.anfang:=zeig; {того, кто остался делаем началом}

100:end;

101:

102:var

103:ZL:ZyklListe;

104:i:integer;

106:begin

107:Clrscr;

108:writeln('Доступная память в начале работы программы

',MemAvail);

109:for i:=7 downto 1 do

110:AddKnoten(ZL,i);

233

111:SchrListe(ZL);

112:writeln;

113:writeln('Доступная память после выделения памяти под список

',MemAvail);

114:Spiel(ZL,3);

115:

116:Tod(ZL);

117:writeln('Доступная память после выхода из программы

',MemAvail);

118:readln;

119:end.

13.10. Характеристика списков

Точно так же, как мы охарактеризовали массивы, мы проанализируем списки.

Доступ

Добраться к произвольному элементу можно лишь за cn операций. Быстрый доступ возможен лишь к 1-му и последнему элементу списка.

Удаление и вставка элементов

Удаление элемента из середины списка требует cn операций. Но удалять элементы из начала (и конца для двунаправленных списков) списка можно за c операций ( c - не зависит от длины списка). Аналогичные свойства выполняются и для вставки элемента.

Поиск

Найти элемент можно лишь за cn операций независимо от того, является ли список отсортированным или нет.

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

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

Фактически, преимущества массивов перед списками – скорость доступа к произвольному элементу и быстрый поиск в отсортированном массиве.

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

13.11. Деревья

Все структуры данных, которые мы рассматривали до этого, имели линейную структуру. Дерево – это одна из простейших нелинейных структур данных.

234

Элемент дерева называется листом (Blatt).

Дерево (Baum) – структура данных, состоящая из корня («главного листа»), который ссылается на несколько листьев, каждый из листьев также может ссылаться на несколько листьев и т.д.

Дерево, у которого каждый лист ссылается на 2 других листа, называется бинарным

деревом (см. рис. 13.6).

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

 

 

 

 

 

 

 

 

 

Wurzel

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Info

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Info

 

 

 

 

 

 

 

 

 

 

 

 

Info

 

 

 

 

 

 

 

nil

 

 

 

 

 

 

 

 

 

 

 

 

 

nil

 

 

Info

 

 

 

 

 

 

 

 

Info

 

 

 

 

 

 

nil

 

 

 

 

 

nil

 

 

nil

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Info

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

nil

 

 

 

 

 

nil

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис 13.6. Вид бинарного дерева (Wurzel (корень) – указатель на корень дерева)

13.12. Разбор формулы

Чтобы показать сам принцип разбора формулы, и не загромождать код, мы ограничим множество допустимых формул следующими:

1.Переменные, задающиеся одной латинской буквой и константы.

2.Выражения вида s1 o s2 , где s1 и s2 - формулы из пункта 1, а o - некоторая операция из

заранее заданного множества.

3. Выражения вида (выражение) операция (выражение), где: (выражение) – любое выражение из пункта 1 или 2. (операция) – операция из заранее заданного множества. Например:

Допустимые выражения: x+y, (x+1)*(r+s), ((s+r)*2)+(r+(s/2)) Недопустимые выражения: (x-3)*x+3, (x+4)*(xy+4)

Целью будет построить т.н. дерево формулы.

Строится оно так: сначала выбирается самая старшая операция (у формул, которые мы разбираем, она определяется единственным образом). Если таковая есть, то она становится вершиной, а листы заполняются соответственно правым и левым подвыражением, если операций нет, значит формула является элементарной (из пункта 1), следовательно, та переменная или константа становится вершиной, а листы получают значение nil.

235

Например, для выражения ((s+r)*2)+(r+(s/2)) дерево формулы приведено на рис.

13.7.

 

 

+

 

 

*

+

 

 

+

2 r

/

s

r

s

2

Рис 13.7. Дерево формулы для выражения ((s+r)*2)+(r+(s/2)).

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

Алгоритм построения дерева по строковому выражению реализован в следующем примере в подпрограмме BaumAusZeile.

Нам понадобится и обратный алгоритм – печать дерева формулы в виде строки: Алгоритм - рекурсивный:

если левый лист текущего поддерева – операция, то печатаем ‘(‘, печатаем левое поддерево печатаем ‘)’

иначе

печатаем левое поддерево печатаем операцию в вершине дерева

если правый лист текущего поддерева – операция, то печатаем ‘(‘, печатаем правое поддерево печатаем ‘)’

иначе

печатаем правое поддерево

Эта подпрограмма в следующем примере называется BaumZurZeile.

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

{Ищет закрывающую скобку (Klammer) для скобки, индекс которой = n} function Finde2Klammer(const z:string;n:integer):integer; {Проверяет, правильно ли расставлены скобки в строке}

function IstKorrekt (const z:string):boolean; {Ищет первый символ операции в строке}

function FindeOper(const z:string;s:charset):integer;

Пример 9: Распознавание формул с помощью бинарного дерева.

236

 

1

: const

2

:

Menge = ['/','*','-','+']; {Множество всех операций}

3

:

 

4

: type

5

:

Monom = record {одночлен}

6

:

koef:real;

7

:

c:char;

8

:

end;

9

:

 

10:

ZBlatt=^Blatt;

11:

 

12:Blatt = record {лист}

13:m:Monom;

14:linke:ZBlatt; {левый лист}

15:recht:ZBlatt; {правый лист}

16:end;

17:

18:Baum = record {дерево}

19:

Wurzel:ZBlatt;

{Корень}

20:

end;

 

21:

 

 

22:

charset = set of char;

23:

 

 

24:

 

 

 

 

108:{Формирует дерево формулы из строки}

109:function BaumAusZeile(const z:string):ZBlatt;

110:var

111:Bl:ZBlatt;

112:op,kl:integer;

113:kode:integer;

114:begin

115:new(Bl);

116:if z[1]='(' then

117:op:=Finde2Klammer(z,1)+1 {Где операция}

118:else

119:op:=FindeOper(z,Menge); {ищем первую из операций}

121:Bl^.m.c:=z[op];

122:Bl^.m.koef:=1;

124:if z[op-1]=')' then {Если перед операцией стоит выраж. в

скобках}

125:BL^.linke:=BaumAusZeile(copy(z,2,op-2))

126:

else

{Строим элемент}

127:begin

128:new(Bl^.linke); {Выделяем место под лист}

129:Bl^.linke^.linke:=nil; {Надо обязательно обнулить}

130:

Bl^.linke^.recht:=nil;

{Ссылки листа}

131:

 

 

132:if (ord('0')<=ord(z[op-1]))and(ord(z[op-1])<=ord('9')) then

133:

begin

{если цифра}

237

134:val(copy(z,1,op-1),Bl^.linke^.m.koef,kode); {в коэффиц.

записываем все число}

135:Bl^.linke^.m.c:='?';

136:end

137:

else

{если переменная}

138:begin

139:Bl^.linke^.m.koef:=1;

140:Bl^.linke^.m.c:=z[op-1];

141:end;

142:end;

143:

144:if z[op+1]='(' then {Если после операции стоит выраж. в

скобках}

145:BL^.recht:=BaumAusZeile(copy(z,op+2,length(z)-(op+1)))

146:else

147:begin

148:new(Bl^.recht); {Выделяем место под лист}

149:Bl^.recht^.linke:=nil; {Надо обязательно обнулить}

150: Bl^.recht^.recht:=nil; {Ссылки листа} 151:

152:

153:if (ord('0')<=ord(z[op+1]))and(ord(z[op+1])<=ord('9')) then

154:

begin

{если цифра}

155:val(copy(z,op+1,length(z)-op),Bl^.recht^.m.koef,kode);

коэффиц. записываем все число}

156:Bl^.recht^.m.c:='?';

157:end

158:

else

{если переменная}

159:begin

160:Bl^.recht^.m.koef:=1;

161:Bl^.recht^.m.c:=z[op+1];

162:end;

163:end;

164:

165:BaumAusZeile:=Bl;

166:end;

167:

168:{Перевод дерева формулы в строковое представление}

169:function BaumZurZeile(ZB:ZBlatt):string;

170:var

171:tmp,tmp2:string;

172:begin

173:if ZB=nil then

174:begin

175:BaumZurZeile:='';

176:exit;

177:end;

178:

179:if not(ZB^.m.c in ['+','-','/','*']) then {Если в узле не

операция}

180:

if ZB^.m.c<>'?' then

{если переменная}

181:

if ZB^.m.koef=1 then

{коэффициент = 1}

238

182:BaumZurZeile:=ZB^.m.c

183:else

184:begin

185:str(ZB^.m.koef,tmp);

186:BaumZurZeile:=tmp+'*'+ZB^.m.c;

187:end

188:

else

{если число}

189:begin

190:str(ZB^.m.koef,tmp);

191:BaumZurZeile:=tmp;

192:end

193:

else

{в узле операция}

194:begin

195:if ZB^.linke<>nil then

196:if not(ZB^.linke^.m.c in Menge) then {не операция}

197:tmp:=BaumZurZeile(ZB^.linke)

198:else

199:tmp:='('+BaumZurZeile(ZB^.linke)+')';

200:

201:if ZB^.recht<>nil then

202:if not(ZB^.recht^.m.c in Menge) then {не операция}

203:tmp2:=BaumZurZeile(ZB^.recht)

204:else

205:tmp2:='('+BaumZurZeile(ZB^.recht)+')';

206:

207:BaumZurZeile:=tmp+ZB^.m.c+tmp2;

208:end;

209:end;

211:procedure Totebaum(ZB:ZBlatt); {Уничтожение дерева и

возвращение памяти в кучу} 212: begin

213:

214:if ZB^.linke<>nil then

215:ToteBaum(ZB^.linke);

216:if ZB^.recht<>nil then

217:ToteBaum(ZB^.recht);

218:dispose(ZB);

219:ZB:=nil;

220:end;

221:

222:var

223:z:string;

224:B:Baum;

226:begin

227: writeln('FreiSpeicher ',MemAvail); 228:

229:z:='(x+y)*((1+r)+r)';

230:writeln(IstKorrekt(z));

232: B.Wurzel:=BaumAusZeile(z); {Строим дерево}

 

 

 

239

233:

writeln(BaumZurZeile(B.Wurzel));

{Печатаем, что построили}

234:

 

 

 

235:

ToteBaum(B.Wurzel);

{Уничтожаем дерево}

236:writeln('FreiSpeicher ',MemAvail);

237:readln;

238:end.

13.13.Сортировка с помощью дерева

Спомощью дерева можно написать простой алгоритм сортировки, который в

среднем работает за время порядка n log n (n – длина исходного массива). Чтобы отсортировать массив, достаточно просто по очереди вставить все элементы массива в дерево.

Алгоритм вставки числа m такой: Если дерево пустое, то

вставляем число его в вершину Иначе

Если число m больше, чем число, которое записано в вершине, то Вставляем число m в правое поддерево

Иначе

Вставляем число m в левое поддерево

Можно доказать, что в среднем для

случайных массивов длины n высота

полученного дерева пропорциональна log n .

Отсюда и следует, что в среднем этот

алгоритм отсортирует массив за порядка n log n операций.

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

1.Добавить в дерево новый элемент так, чтобы не нарушить упорядоченности, можно в среднем за log n операций (а в массив вставка элемента требует порядка

nопераций).

2.Поиск элемента можно проводить в среднем за c log n операций ( c - некоторая константа).

Эти свойства делают деревья такого вида (называются они деревьями поиска)

полезными, в случае если требуется часто вставлять элементы в уже отсортированную последовательность (например, вставлять новые слова в словарь).

Пример 10: Построение дерева поиска.

В примере в целях экономии места не печатались процедуры для работы с массивами: RandMass – заполнение массива случайными числами

Schrmass – печать массива

ZeroMass – заполнение массива нулями

1 : const

2 : n=25;

3 :

240

4 : type

5 : Mas=array[1..n] of integer;

6 :

7 : ZBlatt = ^Blatt;

8 :

9 : Blatt = record

10:zahl:integer;

11:linke,recht:ZBlatt;

12:end;

13:

14:Baum = record

15:Wurzel:ZBlatt;

16:end;

{-------------

Процедуры для работы с деревом ------------

}

46:

 

 

47:{Записывает элементы дерева в массив}

48:procedure BaumZumArr(var B:Baum;var A:array of integer);

49:var

50:k:integer;

51:

 

52: procedure SchrBlatt(ZB:ZBlatt);

{Вложенная процедура}

53:begin

54:if ZB<>nil then

55:begin

56:SchrBlatt(ZB^.linke); {Записываем левую ветку дерева}

57:A[k]:=ZB^.zahl;

58:inc(k);

59:SchrBlatt(ZB^.recht); {Записываем правую ветку дерева}

60:end;

61:end;

62:

63:begin

64:k:=0;

65:SchrBlatt(B.Wurzel);

66:end;

67:

68:{Находит для числа int место в массиве и записывает его туда}

69:procedure StellInt(int:integer;zb:ZBlatt);

70:begin

71:if int<=zb^.zahl then

72:if zb^.linke=nil then

73:begin

74:new(zb^.linke);

75:

76:zb^.linke^.linke:=nil;

77:zb^.linke^.recht:=nil;

79:zb^.linke^.zahl:=int;

80:end

81:else

82:StellInt(int,zb^.linke)

83:else