- •1. Типы данных
- •Для записи в конец файла необходимо, чтобы была установлена ситуация Eof (логическая функция Eof возвращала true). Чтение файла возможно при отсутствии ситуации Eof.
- •2. Линейные списки
- •2.1. Стеки
- •2.2. Очереди и их применение
- •Продвижение очереди производится операторами
- •Пусть имеется n клиентов. Обозначим через m[I] момент прихода, а через t[I] время обслуживания I-го клиента. Рассмотрим алгоритм решения задачи.
- •2.3. Двусвязные списки и мультисписки
- •3. Деревья
- •3.1. Организация в памяти и рекурсивный обход
- •3. 2. Обход деревьев с помощью стека
- •3.3. Пример программы с обходами деревьев
- •4. Графы
- •4.1. Представление графа. Транзитивное замыкание
- •.2. Пример программы с использованием матрицы смежности
- •4.3. Обход графа в глубину. Поиск путей
- •4.4. Обход графа в ширину
- •4.5. Алгоритмы поиска кратчайших путей Дейкстры и Флойда
- •5. Поиск данных
- •5.1. Последовательный, индексно-последовательный, бинарный поиск
- •4.2. Бинарные деревья поиска
- •5.3. Балансировка деревьев поиска
- •5.5. Хеширование
- •6. Сортировка данных
- •6.1. Методы внутренней сортировки
- •6.2. Методы внешней сортировки
- •Заключение
- •Литература
- •Содержание
2.2. Очереди и их применение
Вторым распространенным типом линейных списков являются очереди. Это список типа FIFO (первым пришел – первым вышел) с двумя точками входа: начало и конец. Очередь пополняется с конца и продвигается с начала.
В жизни все мы сталкиваемся с очередями (за билетами, на дежурство и т.п.). В программировании часто приходится иметь дело с очередями на получение различных системных ресурсов.
Аналогично стекам очереди могут быть организованы на основе массивов или динамических структур. При использовании массива начало очереди находится в первом элементе, а конец задается индексом и меняется при изменении очереди. Впрочем, возможно расположить очередь в массиве и в противоположном направлении.
Пусть очередь описана Var Och[1..N] of T, где T – тип элементов очереди. Конец очереди задан индексом Endo.
Постановка в очередь производится командами
Endo:= Endo+1;
Och[Endo]:=NewElement;
Продвижение очереди выполняется команками
For I:=1 to Endo-1 do Och[I]:= Och[I+1];
Endo:= Endo-1;
Как и для стека, необходимо проверять выход за границы массива при постановке и непустоту очереди при удалении элементов.
При организации очереди на основе указателей используется следующее описание
Type
Ukaz=^Och;
Och=record
Info: …;
{ поля информационной части элемента }
Next: ukaz
end;
Var
Bego, Endo: ukaz; { начало и конец очереди }
Выгодно указатели Next ориентировать в направлении от начала очереди к концу, а не наоборот. В этом случае постановка в очередь выполняется командами
New(Kon);
Endo^.Next:=Kon;
Kon^.Next:=Nil;
и далее присвоение значений информационным полям.
Продвижение очереди производится операторами
If Bego <> Nil then
Begin
Kon:=Bego;
Bego:=Bego^.Next;
Dispose(Bego);
End;
В качестве примера рассмотрим следующую задачу, связанную с ведением очереди. Дан список клиентов, желающих посетить банк в некоторый период времени. Для каждого клиента заданы имя, момент прихода и время обслуживания. В банке работает один кассир. Если в момент прихода очередного клиента кассир занят, клиент становится в очередь. Обслуженный кассиром клиент покидает банк. Требуется промоделировать работу кассира, то есть последовательно выдать информацию о приходах и уходах клиентов из банка с указанием состояния очереди. Необходимо также найти среднее время обслуживания клиента.
Будем считать, что список упорядочен по возрастанию моментов прихода клиентов. При работе банка встречаются два класса событий:
приход клиента и постановка его в очередь (если кассир не занят, то очередь будет состоять только из пришедшего клиента);
продвижение очереди.
Пусть имеется n клиентов. Обозначим через m[I] момент прихода, а через t[I] время обслуживания I-го клиента. Рассмотрим алгоритм решения задачи.
1. S:=0 - общее время обслуживания клиентов.
2. R:=M[1]+T[1] - момент разгрузки очереди.
3. I:=1 - начало цикла по клиентам до I=N.
4. P:=M[I], где P - момент прихода очередного клиента.
5. Пока P>=R (приход клиента после ближайшей разгрузки очереди) и очередь не пуста, выполнить:
1) S:=S+R-M[J], где J-номер клиента из начала очереди;
2) разгрузить очередь, убрав клиента из ее начала, и выдать сообщение на экран;
3) если очередь не пуста, то R:=R+T[J+1] - следующий момент разгрузки очереди.
6. Если очередь пуста, то
R:=M[I]+T[I].
7. Постановка в очередь I-го клиента и выдача сообщения.
8. I:=I+1; если I<=N, то переход к шагу 4.
9. Если очередь не пуста, то ее полная разгрузка с выдачей сообщений.
10. H:=S/N - среднее время обслуживания клиента.
11. Выдача H; конец.
Приведем текст программы. Очередь реализована с помощью массива. Начало очереди соответствует первому элементу массива, а конец - последнему элементу.
Program Ochered;
Uses Crt;
Type
klient=record
Name: string; { имя }
M: integer; { момент прихода }
T: integer { время обслуживания }
end;
Var
H: real; { среднее время обслуживания клиента }
I, J, R, S, P, N, L: integer;
Och: array [1..100] of integer; { очередь }
Bego, Endo: integer; { начало и конец очереди }
Kli: array [1..100] of Klient;
{ список клиентов по возрастанию моментов прихода }
Procedure Razgruz;
Begin
J:=Och[Bego]; { номер клиента из начала очереди }
S:=S+R-Kli[J].M;
{ учет времени его нахождения в очереди }
Write('Время: ', R, ', обслужен клиент ', Kli[J].Name);
Endo:=Endo-1; { разгрузка очереди }
For L:=Bego to Endo do
Och[L]:=Och[L+1];
if Endo>0 then { очередь не пуста }
begin
WriteLn(', следующий клиент ', Kli[J+1].Name);
R:=R+Kli[J+1].T
{ следующий момент разгрузки }
end
else WriteLn(', очередь пуста !');
ReadLn { пауза }
End;
Begin
ClrScr; { очистка экрана }
For N:=1 to 100 do
begin
with Kli[N] do
Begin
WriteLn('Введите информацию об очередном клиенте:');
Write('Введите имя (к-признак конца): ');
ReadLn(Name);
if Name='к' then Break; { конец списка клиентов }
Write('Укажите момент прихода: ');
ReadLn(M);
Write('Сообщите время обслуживания: ');
ReadLn(T)
end
end;
WriteLn;
WriteLn(' ПРОТОКОЛ РАБОТА БАНКА');
N:=N-1; { число клиентов }
S:=0; { для общего времени обслуживания }
Bego:=1; { начало очереди всегда здесь ! }
Endo:=0; { критерий пустой очереди }
R:= Kli[1].M+ Kli[1].T; { ближайший момент разгрузки очереди }
For I:=1 to N do
begin
P:=Kli[I].M; { момент прихода следующего клиента }
While (P>=R) and (Endo>0) do
Razgruz;
{ очередь сокращается до прихода следующего клиента }
{ и больше не разгружается }
if Endo=0 then R:=Kli[I].M+Kli[I].T;
{ следующий момент разгрузки }
Endo:=Endo+1; { постановка в очередь i-го клиента }
Och[Endo]:=I;
Write('Время: ', Kli[I].M,', прибыл клиент ', Kli[I].Name);
if Endo=1 then
WriteLn(', очереди нет, ура !')
else
WriteLn(', встал в очередь за клиентом ',
Kli[Och[Endo-1]].Name);
ReadLn { пауза }
end;
While Endo>0 do
Razgruz;
H:=S/N;
WriteLn('Обслуживание закончено, перерыв на обед !');
WriteLn('Среднее время обслуживания клиента: ',h:5:2);
ReadLn { пауза }
End.
В этом примере продвижение очереди связано со сдвигом всей заполненной части массива, что может быть трудоемкой операцией при большой размерности и частом повторении. Для преодоления этого недостатка используют кольцевые списки, в которых последний элемент связан с первым. Это позволяет из любого элемента списка добраться до нужного элемента. Кольцевые списки легче восстанавливаются в случае разрушения какой-либо связи.
Рассмотрим кольцевую очередь, заданную массивом Och с N элементами. Начало и конец очереди заданы индексами Bego и Endo. Следующим после N-го элемента будем считать первый элемент массива. Тогда постановка в очередь выполняется командами
Endo:= Endo mod N + 1;
Och[Endo]:=NewElement;
а продвижение производит оператор Bego:=Bego mod N + 1. Индексы Bego и Endo как бы двигаются по кольцу вдогонку друг за другом. Необходимым условием для постановки в очередь в этом случае является NumElement < N, где NumElement – число элементов в очереди, а при продвижении очереди должно быть NumElement >0. Буфер клавиатуры в MS DOS организован в виде кольцевой очереди.
