Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
РП_31_АСД.pdf
Скачиваний:
166
Добавлен:
23.02.2016
Размер:
2.92 Mб
Скачать

Тема 16. ЧЕРГИ

Черга, як і стек, - це один із різновидів однозв’язного лінійного списку. Вона працює за принципом «першим прийшов – першим вийшов», що позначається абревіатурою FIFO (від англ. First In First Out), і характеризується такими властивостями:

Елементи додаються в кінець черги;

Елементи зчитуються та видаляються з початку (вершини) черги;

Покажчик в останньому елементі черги дорівнює nil;

Неможливо отримати елемент із середини черги, не вилучивши всі елементи, що

йдуть попереду.

Про використання черг в обчислювальній техніці свідчать наступні приклади. У мережній операційні системі процесор сервера обслуговує в певний момент часу тільки одного користувача. Запити інших користувачів записуються до черги. Під час обслуговування користувачів кожен запит просувається до початку черги. Перший в черзі запит підлягає «першочерговому» обслуговуванню. У комп’ютерній мережі за чергою обслуговуються інформаційні пакети. Черги застосовуються також для буферизації потоків даних, що виводяться на друк, якщо в комп’ютерній мережі використовується один принтер.

Для роботи з чергою потрібні: покажчик head на початок черги, покажчик last на кінець черги та допоміжний покажчик current. Елементи з черги видаляються за тим самим

алгоритмом, що і зі стеку.

Алгоритм вставки елементів до черги

1.Виділити пам'ять для нового елемента черги: new(current); (рис.1а).

2.Ввести дані до нового елемента: readln(current^.data); (рис.1.б).

3.Вважати новий елемент останнім у черзі: current^.next:=nil; (рис.1.в).

4.Якщо черга порожня, то ініціалізувати її вершину: head:=current.

5.Якщо черга не порожня, то зв’язати останній елемент черги із новоутвореним: last^.next:=current.

6.Вважати новий елемент черги останнім: last:=current; (рис.1г).

 

 

current

?a

?

 

 

 

 

 

 

current

data

?

current

data

next=nil

 

б

 

 

 

в

head

data

next

data

next

 

 

 

last

current

data

next=nil

 

 

 

 

 

 

 

 

г

Рис.1. Вставка елемента до черги: виділення пам'яті (а); введення даних (б);

встановлення ознаки кінця черги (в); переміщення кінця (г) Програмна реалізація вище наведених алгоритмів наступна:

program Yrok7;

{Створення, виведення та очищення черги} {$APPTYPE CONSOLE}

uses

47

SysUtils;

type

 

 

ptr=^Item;

{тип покажчика на елемент черги}

Item=record

{тип елементу черги}

data:string;

{інформаційне поле елемента}

next:ptr;

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

end;

 

 

var

 

 

head:

ptr;

{покажчик на початок черги}

current: ptr;

{допоміжний покажчик}

last:

ptr;

{покажчик на кінець черги}

str: string;

{значення, що додається до черги}

i:integer;

{параметр циклу}

n:integer;

{кількість елементів черги}

key:char;

{номер пункту меню програми}

{Додавання елемента до черги}

procedure add(value:string); {параметр - значення що додається}

begin

 

new(current);

{виділення пам'яті для елемента}

current^.data:=value;

{ініціалізація інформаційного поля}

current^.next:=nil;

{встановлення ознаки кінця черги}

if last=nil

 

then head:=current

{ініціалізація покажчика на початок черги}

else last^.next:=current; {якщо новий елемент не є першим, то здійснюється

 

зв"язування колишнього кінця черги з новим елементом}

last:=current;

{новий елемент вважається останнім}

end;

 

{Видалення елемента з черги зі збереженням}

procedure getdel(value:string); {параметр - змінна, значення якої буде видалене} begin

current:=head; {збереження адреси вершини черги} value :=head^.data; {збереження значення, що видаляється} writeln(value); {Відображення значення, що видаляється}

head :=current^.next; {перенесення вершини черги на другий елемент}

if head=nil

{якщо черга стала порожньою,}

then last:=nil;

{порожнім має стати і кінець черги}

dispose(current);

{звільнення пам'яті}

end;

{Основна програма} begin

writeln('

Queue');

head:=nil;

{черга порожня}

last:=nil;

 

repeat

{виведення меню}

writeln('========================='); writeln('1.create queue');

48

writeln('2.output and delet e queue'); writeln('3.exit');

write('press key 1..3: '); re adln(key); {Вибір пункту меню}

case key of

 

'1': begin

{додавання елементів до черги}

write('enter queue length '); readln(n); for i:=1 to n do

begin

write('Enter data item ',i,': '); readln(str);

add(str); {виклік процедури додавання елемента до черги} end;

end;

'2': begin {вивести та видалити елемент} while head<>nil do getdel(str);

writeln;

writeln('queue is empty'); end;

end;

until key='3'; {продовженн я роботи програми до вибору 3-го пункту меню} end.

Результат роботи програ ми Yrok7 зображений на рис.2.

Рис.2. Результат роботи програми Yrok7. Робота з чергою

49

Тема 17. МАШИННЕ ПРЕДСТАВЛЕННЯ ЧЕРГИ. ЧЕРГИ З ПРІОРИТЕТАМИ. ДЕКИ.

При представленні черги вектором в статичній пам'яті на додаток до звичайних для дескриптора вектора параметрам в ньому повинні знаходитися два покажчика: на початок черги (на перший елемент у черзі) і на її кінець (перший вільний елемент в черзі). При включенні елемента в чергу елемент записується за адресою, що визначається покажчиком на кінець, після чого цей покажчик збільшується на одиницю. При виключення елемента з черги вибирається елемент, що адресується вказівником на початок, після чого цей покажчик зменшується на одиницю.

Очевидно, що з часом покажчик на кінець при черговому включенні елемента досягне верхньої межі тієї області пам'яті, яка виділена для черги. Однак, якщо операції включення чергувалися з операціями виключення елементів, то в початковій частині відведеної під чергу пам'яті є вільне місце. Для того, щоб місця, що займалися виключеними елементами, могли бути повторно використані, черга замикається в кільце: покажчики (на початок і на кінець), досягнувши кінця виділеної області пам'яті, перемикаються на її початок. Така організація черги в пам'яті називається кільцевої чергою. Можливі, звичайно, й інші варіанти організації: наприклад, всякий раз, коли вказівник кінця досягне верхньої межі пам'яті, зсувати всі непусті елементи черги до початку області пам'яті, але як цей, так і інші варіанти вимагають переміщення в пам'яті елементів черги і менш ефективні, ніж кільцева черга.

У початковому стані покажчики на початок і на кінець вказують на початок області пам'яті. Рівність цих двох покажчиків (при будь-якому їх значенні) є ознакою порожньої черги. Якщо в процесі роботи з кільцевої чергою число операцій включення перевищує число операцій виключення, то може виникнути ситуація, в якій покажчик кінця "наздожене" покажчик початку. Це - ситуація заповненої черги. Але якщо в цій ситуації покажчики зрівняються, ця вона буде відрізнити від ситуації порожньої черги. Для розрізнення цих двох ситуацій до кільцевої черги ставиться вимога, щоб між покажчиком кінця і покажчиком початку залишався "зазор" з вільних елементів. Коли цей "зазор" скорочується до одного елемента, черга вважається заповненою і подальші спроби запису в неї блокуються. Очищення черги зводиться до запису одного і того ж (не обов'язково початкового) значення в обидва покажчика. Визначення розміру полягає в обчисленні різниці покажчиків з урахуванням кільцевої природи черги.

Наступний програмний приклад ілюструє організацію черги.

unit Queue;

{ Черга FIFO - кільцева }

Interface

 

const SIZE=...;

{ граничний розмір черги }

type data = ...;

{ елементи можуть мати будь-який тип }

Procesure QInit;

 

Procedure Qclr;

 

Function QWrite(a: data) : boolean; Function QRead(var a: data) : boolean; Function Qsize : integer;

Implementation { Черга на кільце }

var QueueA : array[1..SIZE] of data; { дані черги }

50

top, bottom : integer;

{ початок та кінець }

Procedure QInit;

{** ініціалізація - початок=кінець=1 }

begin top:=1; bottom:=1; end;

Procedure Qclr;

{**очищення - початок=кінець }

begin top:=bottom; end;

Function QWrite(a : data) : boolean; {** запис у кінець } begin

if bottom mod SIZE+1=top then { черга повна } QWrite:=false else begin

{ запис, модифіикація покажчика кінця з переходом по кільцю } Queue[bottom]:=a; bottom:=bottom mod SIZE+1; QWrite:=true; end; end; { QWrite }

Function QRead(var a: data) : boolean; {** вибірка з початкуа } begin

if top=bottom then QRead:=false else

{ запис, модифіикація покажчика початку з переходом по кільцю } begin a:=Queue[top]; top:=top mod SIZE + 1; QRead:=true;

end; end; { QRead }

Function QSize : integer; {** визначення розміру } begin

if top <= bottom then QSize:=bottom-top else QSize:=bottom+SIZE-top;

end; { QSize } END.

Черги з пріоритетами

В реальних задачах іноді виникає необхідність у формуванні черг, відмінних від FIFO або LIFO. Порядок вибірки елементів з таких черг визначається пріоритетами елементів. Пріоритет в загальному випадку може бути представлений числовим значенням, яке обчислюється або на підставі значень яких-небудь полів елемента, або на підставі зовнішніх факторів. Так, і FIFO, і LIFO-черги можуть трактуватися як пріоритетні черги, в яких пріоритет елемента залежить від часу його включення в чергу. При вибірці елемента всякий раз вибирається елемент з найбільшим пріоритетом. Черги з пріоритетами можуть бути реалізовані на лінійних спискових структурах, в суміжному або зв'язаному представленні. Можливі черги з пріоритетним включенням - в яких послідовність елементів черги весь час підтримується впорядкованою, тобто кожен новий елемент включається на те місце в послідовності, що визначається його пріоритетом, а при виключенні завжди вибирається елемент з початку. Можливі й черги з пріоритетним виключенням - новий елемент включається завжди в кінець черги, а при виключенні в черзі шукається (цей пошук може бути тільки лінійним) елемент з максимальним пріоритетом і після вибірки видаляється з послідовності. І в тому, і в іншому варіанті потрібно пошук, а якщо чергу розміщується в статичній пам'яті - ще й переміщення елементів.

51

Черги в обчислювальних системах

Ідеальним прикладом кільцевої черги в обчислювальної системи є буфер клавіатури в базовій системі вводу-виводу ПЕОМ IBM PC. Буфер клавіатури займає послідовність байтів пам'яті за адресами від 40:1 E до 40:2 D включно. За адресами 40:1A і 40:1 C розташовуються покажчики на початок і кінець черги відповідно. При натисканні на будь-яку клавішу генерується переривання 9. Оброблювач цього переривання читає код натиснутої клавіші і розташовує його в буфері клавіатури - в кінці черги. Коди натиснутих клавіш можуть накопичуватися в буфері клавіатури, перш ніж вони будуть прочитані програмою. Програма при введенні даних з клавіатури звертається до переривання 16H. Оброблювач цього переривання вибирає код клавіші з буфера - з початку черги - і передає в програму.

Черга є одним з ключових понять в багатозадачних операційних системах (Windows NT, Unix, OS / 2, ЄС та ін.). Ресурси обчислювальної системи (процесор, оперативна пам'ять, зовнішні пристрої тощо використовуються усіма завданнями, одночасно виконуваними в середовищі такої операційної системи. Оскільки багато видів ресурсів не допускають реально одночасного використання різними завданнями, такі ресурси надаються задачам по черзі. Таким чином, завдання, які претендують на використання того чи іншого ресурсу, шикуються в чергу до цього ресурсу. Ці черги зазвичай пріоритетні, проте, досить часто застосовуються і FIFO-черги, так як це єдина логічна організація черги, яка гарантовано не допускає постійного витіснення завдання більш пріоритетними. LIFO-черги зазвичай використовуються операційними системами для обліку вільних ресурсів.

Також в сучасних операційних системах одним із засобів взаємодії між паралельно виконуваними завданнями є черги повідомлень, що називаються також поштовими скриньками. Кожне завдання має свою чергу – поштову скриньку, і всі повідомлення, відправлені їй від інших завдань, потрапляють в цю чергу. Завданнявласник черги вибирає з неї повідомлення, причому може керувати порядком вибірки -

FIFO, LIFO або по пріоритету.

Деки

Дек - особливий вид черги. Дек (від англ. Deq - double ended queue, тобто черга з двома кінцями) - це такий послідовний список, в якому як включення, так і виключення елементів може здійснюватися з будь-якого з двох кінців списку. Окремий випадок дека - дек з обмеженим входом і дек з обмеженим виходом. Логічна і фізична структури дека аналогічні логічної і фізичної структурі кільцевої FIFO-черзі. Однак, стосовно до деку доцільно говорити не про початок і кінець, а про лівий і правий кінці.

Операції над Деком:

включення елемента справа;

включення елемента зліва;

виключення елемента справа;

виключення елемента зліва;

визначення розміру;

очищення.

На рис. 1 як приклад показана послідовність станів дека при включенні і виключенні п'яти елементів. На кожному етапі стрілка вказує з якого кінця дека (лівого

52

або правого) здійснюється включення або виключення елемента. Е лементи відповідно позначені буквами A, B, C, D, E.

Рис. 1. Стани дека в процесі зміни.

Фізична структура дека в статичній пам'яті ідентична структурі кільцевої черги. Розробити програмний прикл ад, який ілюструє організацію дека і о перації над ним не складно за зразком вище н аведеного програмного прикладу. У цьому модулі повинні бути реалізовані процедури і функції:

Function DeqWrRight (a: data): boolean; - включення елемента справа; Function DeqWrLeft (a: data): boolean; - включення елемента зліва; Function DeqRdRight (var a: data): boolean; - виключення елеме нта справа; Function DeqRdLeft (var a: data): boolean; - виключення елемент а зліва; Procedure DeqClr; - очищення;

Function DeqSize: integer; - визначення розміру.

Задачі, що вимагають структури дека, зустрічаються в обчи слювальній техніці і програмуванні набагато рідш е, ніж завдання, реалізовані на структурі стека або черги. Як правило, вся організація дек а виконується програмістом без яких-небудь спеціальних засобів системної підтримки.

Однак, як приклад та кої системної підтримки розглянемо організацію буфера введення в мові REXX. У звичайному режимі буфер введення пов'яз аний з клавіатурою і працює як FIFO-чергу. Однак, в REXX є можливість призначити в якос ті буфера введення програмний буфер і напра вити в нього виведення програм і системних утиліт. У розпорядженні програміста є операції QUEUE - запис рядка в кінець буфера і PULL - вибірка рядки з початку буф ера. Додаткова операція PUSH - запис рядка в початок буфера - перетворює буфер в дек з обмеженим виходом. Така структура буфера введення дозволяє програмувати на REXX дуже гнучку конвеєрну обробку з напрямком виходу однієї програми на вхід інший і модифікацією перенаправляти потоки.

53