[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi
.pdf231
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
