133
Динамічні структури даних (мова Паскаль) © К.Ю. Поляков, 2008-2010 Переклад: Р. М. Васильчик 1. Вказівники 2. Динамічні масиви 3. Структури 4. Списки 5. Стеки, черги, деки 6. Дерева 7. Графи

Pascal (динамічні структури даних)

  • Upload
    -2

  • View
    5.010

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Pascal (динамічні структури даних)

Динамічні структури даних

(мова Паскаль)

© К.Ю. Поляков, 2008-2010

Переклад: Р. М. Васильчик

1. Вказівники2. Динамічні

масиви3. Структури4. Списки

5. Стеки, черги, деки

6. Дерева

7. Графи

Page 2: Pascal (динамічні структури даних)

Тема 1. Вказівники

Динамічні структури даних

(мова Паскаль)

© К.Ю. Поляков, 2008-2010

Переклад: Р. М. Васильчик

Page 3: Pascal (динамічні структури даних)

3

Статині дані

• змінна (масив) має ім'я, за яким до неї можна звертатися

• розмір наперед відомий (задається при написанні програми)

• пам'ять виділяється при оголошенні

• розмір не можна збільшити під час роботи програми

var x, y: integer; z: real; A: array[1..10] of real; str: string;

var x, y: integer; z: real; A: array[1..10] of real; str: string;

Page 4: Pascal (динамічні структури даних)

4

Динамічні дані

• розмір наперед невідомий, визначається під час роботи програми

• пам'ять виділяється під час роботи програми• немає імені?

Проблема: як звертатися до даних, якщо немає імені?

Рішення: використовувати адресу в пам'яті

Наступна проблема: в яких змінних може міститися адреса? як працювати з адресами?

Page 5: Pascal (динамічні структури даних)

5

ВказівникиВказівник – це змінна, в яку можна записати адресу

другої змінної (або блоку пам'яті).Оголошення:

Як записати адресу:

var pC: ^char; // адреса символу pI: ^integer; // адреса цілої змінної pR: ^real; // адреса дійсн. змінної

var pC: ^char; // адреса символу pI: ^integer; // адреса цілої змінної pR: ^real; // адреса дійсн. змінної

var m: integer; // ціла змінна pI: ^integer; // вказівник

A: array[1..2] of integer; // масив...pI:= @ m; // адреса змінної mpI:= @ A[1]; // адреса елемента масиву A[1]pI:= nil; // нульова адреса

var m: integer; // ціла змінна pI: ^integer; // вказівник

A: array[1..2] of integer; // масив...pI:= @ m; // адреса змінної mpI:= @ A[1]; // адреса елемента масиву A[1]pI:= nil; // нульова адреса

@

^

nil

вказівниквказівник

адреса коміркиадреса комірки

Page 6: Pascal (динамічні структури даних)

6

Звернення до даних через вказівник

program qq;

var m, n: integer;

pI: ^integer;

begin

m := 4;

pI := @m;

writeln('Адрес m = ', pI);//виведення адреси

writeln('m = ', pI^); // виведення значення

n := 4*(7 - pI^); // n = 4*(7 - 4) = 12

pI^ := 4*(n - m); // m = 4*(12 – 4) = 32

end.

program qq;

var m, n: integer;

pI: ^integer;

begin

m := 4;

pI := @m;

writeln('Адрес m = ', pI);//виведення адреси

writeln('m = ', pI^); // виведення значення

n := 4*(7 - pI^); // n = 4*(7 - 4) = 12

pI^ := 4*(n - m); // m = 4*(12 – 4) = 32

end.

^

«витягнути»значення за адресою

«витягнути»значення за адресою

Page 7: Pascal (динамічні структури даних)

7

Звернення до даних (масиви)

program qq;var i: integer; A: array[1..4] of integer; pI: ^integer;begin for i:=1 to 4 do A[i] := i; pI := @A[1]; // адреса A[1] while ( pI^ <= 4 ) // while( A[i] <= 4 ) do begin pI^ := pI^ * 2; // A[i] := A[i]*2; pI := pI + 1; // до наступного елемента end;end.

program qq;var i: integer; A: array[1..4] of integer; pI: ^integer;begin for i:=1 to 4 do A[i] := i; pI := @A[1]; // адреса A[1] while ( pI^ <= 4 ) // while( A[i] <= 4 ) do begin pI^ := pI^ * 2; // A[i] := A[i]*2; pI := pI + 1; // до наступного елемента end;end.

переміститися до наступного елементу = змінити адресу на sizeof(integer)

переміститися до наступного елементу = змінити адресу на sizeof(integer)

Не працює в PascalABC.NET!

Не працює в PascalABC.NET!!!

Page 8: Pascal (динамічні структури даних)

8

Що потрібно знати про вказівники

• вказівник – це змінна, в якій можна зберігати адресу іншої змінної;

• при оголошенні вказівника потрібно вказувати тип змінної, на який він буде вказувати, а перед типом поставити знак ^ ;

• знак @ перед іменем змінної означає її адресу;

• запис p^ означає значення комірки, на яку вказує вказівник p;

• nil – це нульовий вказівник, він нікуди не показує

• при зміні значення вказівника на n він насправді зміщується до n-го наступного числа даного типу (для вказівника на цілі числа – на n*sizeof(integer) байт).

Не можна використовувати вказівники, які показують невідомо куди (буде збій або зависання)!

Page 9: Pascal (динамічні структури даних)

Тема 2. Динамічні масиви

Динамічніструктури даних(мова Паскаль)

© К.Ю. Поляков, 2008-2010

Переклад: Р. М. Васильчик

Page 10: Pascal (динамічні структури даних)

10

Де потрібні динамічні масиви?Задача. Ввести розмір масиву, потім – елементи масиву.

Відсортувати масив і вивести на екран.

Проблема: розмір масиву наперед невідомий.

Шляхи розв'язання: 1) виділити пам’ять «з запасом»;2) виділяємо пам’ять тоді, коли розмір став відомий.

Алгоритм: 1) ввести розмір масиву;2) виділити пам’ять;3) ввести елементи масиву;4) відсортувати і вивести на екран;5) знищити масив.

виділити пам’ятьвиділити пам’ять

знищити масивзнищити масив

Page 11: Pascal (динамічні структури даних)

11

Використовування вказівників (Delphi)

program qq;type intArray = array[1..1] of integer;var A: ^intArray; i, N: integer;begin writeln('Розмір масиву>'); readln(N); GetMem(pointer(A), N*sizeof(integer)); for i := 1 to N do readln(A[i]); ... { сортування } for i := 1 to N do writeln(A[i]); FreeMem(pointer(A));end.

виділяємо пам’ятьвиділяємо пам’ять

вивільняємо пам’ятьвивільняємо пам’ять

працюємо так само, як з звичайним масивом!

працюємо так само, як з звичайним масивом!

деякий масив цілих чиселдеякий масив цілих чисел

Page 12: Pascal (динамічні структури даних)

12

Використання вказівників

• для виділення пам’яті використовується процедура GetMem

GetMem( вказівник, розмір в байтах );• вказівник повинен бути прив'язаний до типу pointer–

вказівник без типу, просто адреса деякого байта в пам'яті;

• з динамічним масивом можна працювати так само, як і з звичайним (статичним);

• для вивільнення блоку пам'яті потрібно застосувати процедуру FreeMem:

FreeMem ( вказівник );

Page 13: Pascal (динамічні структури даних)

13

Помилки при роботі з пам’ятюЗапис в «чужу» область пам'яті:

пам'ять не була виділена, а масив використовується.Що робити: так не робити.

Вихід за границі масиву:звернення до елементу масиву з неправильним номером, призапису псуються дані в «чужій» пам’яті.Що робити: якщо дозволяє транслятор, включати перевірку

виходу за границі масиву.

Вказівник знищується другий раз:структура пам’яті порушена, може бути все, що завгодно.

Що робити : в видалений вказівник краще записати nil, помилка виявиться швидше.

Витік пам'яті:непотрібна пам’ять не вивільняється.Що робити : прибирайте «сміття»

(в середовищі .NET є збирач сміття!)

Page 14: Pascal (динамічні структури даних)

14

Динамічні масиви (Delphi)

program qq;var A: array of integer; i, N: integer;begin writeln(‘Розмір масиву>'); readln(N); SetLength ( A, N ); for i := 0 to N-1 do readln(A[i]); ... { сортування } for i := 0 to N-1 do writeln(A[i]); SetLength( A, 0 );end.

виділяємо пам’ятьвиділяємо пам’ять

вивільняємо пам’ятьвивільняємо пам’ять

деякий масив цілих чисел

деякий масив цілих чисел

нумеруємо з НУЛЯ!нумеруємо з НУЛЯ!

Page 15: Pascal (динамічні структури даних)

15

Динамічні масиви (Delphi)

• при оголошенні масиву вказується тільки його тип, пам’ять не виділяється:

var A: array of integer;• для виділення пам’яті використовується процедура SetLength (встановити довжину)

SetLength ( масив, розмір );• номери елементів починаються з НУЛЯ!• для вивільнення блоку пам’яті потрібно встановити

нульову довжину через процедуру SetLength:

SetLength ( масив, 0 );

Page 16: Pascal (динамічні структури даних)

16

Динамічні матриці (Delphi)Задача. Ввести розмір матриці і виділити для неї місце в

пам’яті під час роботи програми.

Проблема: розміри матриці наперед невідомі

Розв'язання:

var A: array of array of integer; N, M: integer;begin writeln(‘Кількість рядків і стовпців>'); readln(N, M); SetLength ( A, N, M ); ... // працюємо, як з звичайною матрицею SetLength( A, 0, 0 );end.

var A: array of array of integer; N, M: integer;begin writeln(‘Кількість рядків і стовпців>'); readln(N, M); SetLength ( A, N, M ); ... // працюємо, як з звичайною матрицею SetLength( A, 0, 0 );end.

Page 17: Pascal (динамічні структури даних)

Тема 3. Структури (записи)

Динамічніструктури даних(мова Паскаль)

© К.Ю. Поляков, 2008-2010

Переклад: Р. М. Васильчик

Page 18: Pascal (динамічні структури даних)

18

Структури (в Паскалі – записи)

Структура (запис) – це тип даних, які можуть включати в себе декілька полів – елементів різних типів (в том числі і інші структури).

Властивості:• автор (рядок)• назва (рядок)• рік видання (ціле число)• кількість сторінок (ціле число)

Задача: об'єднати ці дані в одне цілеЗадача: об'єднати ці дані в одне ціле

Розміщення в пам’яті

автор назварік

виданнякількість сторінок

40 символів 80 символів ціле ціле

Page 19: Pascal (динамічні структури даних)

19

Один запис

readln(Book.author); // введенняreadln(Book.title);Book.year := 1998; // присвоєнняif Book.pages > 200 then // порівняння writeln(Book.author,'.',Book.title);// виведення

readln(Book.author); // введенняreadln(Book.title);Book.year := 1998; // присвоєнняif Book.pages > 200 then // порівняння writeln(Book.author,'.',Book.title);// виведення

Оголошення (виділення пам’яти):

var Book: record author: string[40]; // автор, рядок title: string[80]; // назва, рядок year: integer; // рік видання, ціле pages: integer; // кількість сторінок, ціле end;

var Book: record author: string[40]; // автор, рядок title: string[80]; // назва, рядок year: integer; // рік видання, ціле pages: integer; // кількість сторінок, ціле end;

названазва записзапис поляполя

Звернення до полів: Для звернення до поля запису використовується крапка!

Для звернення до поля запису використовується крапка!

!!

Page 20: Pascal (динамічні структури даних)

20

Масив записів

Оголошення (виділення пам’яті):

const N = 10;var aBooks: array[1..N] of record author: string[40]; title: string[80]; year: integer; pages: integer; end;

const N = 10;var aBooks: array[1..N] of record author: string[40]; title: string[80]; year: integer; pages: integer; end;

Books[1] ... Books[10]

author title year pages

Page 21: Pascal (динамічні структури даних)

21

Масив записів

for i:=1 to N do begin readln(aBooks[i].author); readln(aBooks[i].title); ...end;for i:=1 to N do if aBooks[i].pages > 200 then writeln(aBooks[i].author, '.', aBooks[i].title);

for i:=1 to N do begin readln(aBooks[i].author); readln(aBooks[i].title); ...end;for i:=1 to N do if aBooks[i].pages > 200 then writeln(aBooks[i].author, '.', aBooks[i].title);

Звернення до поля:

aBooks[i].author – звернення до поля author запису aBooks[i]

aBooks[i].author – звернення до поля author запису aBooks[i]!!

Page 22: Pascal (динамічні структури даних)

22

Новий тип даних – запис

const N = 10;var Book: TBook; // один запис aBooks: array[1..N] of TBook; // масив

const N = 10;var Book: TBook; // один запис aBooks: array[1..N] of TBook; // масив

Оголошення типу:type TBook = record author: string[40]; // автор, рядок title: string[80]; // назва, рядок year: integer;// рік видання, ціле pages : integer; // кількість сторінок, ціле end;

type TBook = record author: string[40]; // автор, рядок title: string[80]; // назва, рядок year: integer;// рік видання, ціле pages : integer; // кількість сторінок, ціле end;

Пам'ять не виділяється! Пам'ять не виділяється!!!

Оголошення змінних і масивів:

TBook – Type Book («тип книга») – зручно!

Page 23: Pascal (динамічні структури даних)

23

Записи в процедурах і функціях

Book.author := ‘О.С. Пушкін';ShowAuthor ( Book );Book.year := 1800;writeln( IsOld(Book) );

Book.author := ‘О.С. Пушкін';ShowAuthor ( Book );Book.year := 1800;writeln( IsOld(Book) );

Процедура:procedure ShowAuthor ( b: TBook );begin writeln ( b.author );end;

procedure ShowAuthor ( b: TBook );begin writeln ( b.author );end;

Основна програма:

function IsOld( b: TBook ): boolean;begin IsOld := b.year < 1900;end;

function IsOld( b: TBook ): boolean;begin IsOld := b.year < 1900;end;

Функція:

Page 24: Pascal (динамічні структури даних)

24

Файли записів

Оголошення вказівника на файл:

var F: file of TBook;var F: file of TBook;

Assign(F, 'books.dat');{ зв'язати з вказівником }

Rewrite(F); { відкрити файл для запису }

writeln(F, Book); { запис }

for i:=1 to 5 do

writeln(aBook[i]); { запис }

Close(F); { закрити файл }

Assign(F, 'books.dat');{ зв'язати з вказівником }

Rewrite(F); { відкрити файл для запису }

writeln(F, Book); { запис }

for i:=1 to 5 do

writeln(aBook[i]); { запис }

Close(F); { закрити файл }

Запис у файл:

Page 25: Pascal (динамічні структури даних)

25

Читання з файлу

Відома кількість записів:

Assign(F, 'books.dat');{ зв'язати з вказівником }Reset(F); { відкрити для читання }Read(F, Book); { читання }for i:=1 to 5 do Read(F, aBook[i]); { читання }Close(F); { закрити файл }

Assign(F, 'books.dat');{ зв'язати з вказівником }Reset(F); { відкрити для читання }Read(F, Book); { читання }for i:=1 to 5 do Read(F, aBook[i]); { читання }Close(F); { закрити файл }

«Поки не закінчується»:

count := 0;while not eof(F) do begin count := count + 1; { лічильник } Read(F, aBook[count]); { читання }end;

count := 0;while not eof(F) do begin count := count + 1; { лічильник } Read(F, aBook[count]); { читання }end;

В чому може бути проблема! В чому може бути проблема!??

поки не дійшли до кінця файлу FEOF = end of file

поки не дійшли до кінця файлу FEOF = end of file

Page 26: Pascal (динамічні структури даних)

26

Приклад програмиЗадача: в файлі books.dat записані дані про книги у

вигляді масиву структур типу TBook (не більше 100). Встановити для всіх 2008 рік видання і записати назад в той самий файл.

type Tbook … ;

const MAX = 100;var aBooks: array[1..MAX] of TBook; i, N: integer; F: file of TBook;begin { прочитати записи з файлу, N - кількість } for i:=1 to N do aBooks[i].year := 2008; { зберегти у файл }end.

type Tbook … ;

const MAX = 100;var aBooks: array[1..MAX] of TBook; i, N: integer; F: file of TBook;begin { прочитати записи з файлу, N - кількість } for i:=1 to N do aBooks[i].year := 2008; { зберегти у файл }end.

type TBook … ;повне описання

структуриповне описання

структури

Page 27: Pascal (динамічні структури даних)

27

Приклад програми

Читання «поки не закінчиться»:

Assign(f, 'books.dat'); Reset(f); N := 0; while not eof(F) and (N < MAX) do begin N := N + 1; read(F, aBooks[N]); end; Сlose(f);

Assign(f, 'books.dat'); Reset(f); N := 0; while not eof(F) and (N < MAX) do begin N := N + 1; read(F, aBooks[N]); end; Сlose(f);

Assign(f, 'books.dat'); { можна без цього } Rewrite(f); for i:=1 to N do write(F, aBooks[i]); Close(f);

Assign(f, 'books.dat'); { можна без цього } Rewrite(f); for i:=1 to N do write(F, aBooks[i]); Close(f);

Збереження:

що б не вийти за межі масиву

що б не вийти за межі масиву

Page 28: Pascal (динамічні структури даних)

28

Виділення пам'яті під запис

var pB: ^TBook;

begin

New(pB);

pB^.author := ‘О.С. Пушкін';

pB^.title := 'Полтава';

pB^.year := 1990;

pB^.pages := 129;

Dispose(pB);

end.

var pB: ^TBook;

begin

New(pB);

pB^.author := ‘О.С. Пушкін';

pB^.title := 'Полтава';

pB^.year := 1990;

pB^.pages := 129;

Dispose(pB);

end.

для звернення до поля запису за адресою використовується знак ^

для звернення до поля запису за адресою використовується знак ^

!!

New(pB);виділення пам'яті під запис,

записати адресу в pBвиділення пам'яті під запис,

записати адресу в pB

pB^

Dispose(pB);вивільнити

пам’ятьвивільнити

пам’ять

pB: ^TBook; змінна - вказівник на TBook

змінна - вказівник на TBook

Page 29: Pascal (динамічні структури даних)

29

Сортування масиву записів

Ключ (ключове поле) – це поле запису (або комбінація полів), за яким виконується сортування.

const N = 100;var aBooks: array[1..N] of TBook; i, j, N: integer; temp: TBook; { для обміну }begin { заповнити масив aBooks } { відсортувати = переставити } for i:=1 to N do writeln(aBooks[i].title, aBooks[i].year:5);end.

const N = 100;var aBooks: array[1..N] of TBook; i, j, N: integer; temp: TBook; { для обміну }begin { заповнити масив aBooks } { відсортувати = переставити } for i:=1 to N do writeln(aBooks[i].title, aBooks[i].year:5);end.

Page 30: Pascal (динамічні структури даних)

30

Сортування масиву записів

for i:=1 to N-1 do

for j:=N-1 downto i do

if aBooks[j].year > aBooks[j+1].year

then begin

temp := aBooks[j];

aBooks[j] := aBooks[j+1];

aBooks[j+1] := temp;

end;

for i:=1 to N-1 do

for j:=N-1 downto i do

if aBooks[j].year > aBooks[j+1].year

then begin

temp := aBooks[j];

aBooks[j] := aBooks[j+1];

aBooks[j+1] := temp;

end;

Який ключ сортування? Який ключ сортування??? Який метод сортування? Який метод сортування???

Що погано? Що погано???

Page 31: Pascal (динамічні структури даних)

31

Сортування масиву записівПроблема:

як уникнути копіювання запису при сортуванні?

Рішення: використовувати допоміжний масив вказівників, при сортуванні переставляти вказівники.

5 1 3 2 4

p[1] p[2] p[3] p[4] p[5]

p[5] p[1] p[3] p[2] p[4]

Досортування:

Після сортування:

Виведення результату:

for i:=1 to N do writeln(p[i]^.title, p[i]^.year:5);for i:=1 to N do writeln(p[i]^.title, p[i]^.year:5);p[i]^ p[i]^

5 1 3 2 4

Page 32: Pascal (динамічні структури даних)

32

Реалізація в програмі

type PBook = ^TBook; { новий тип даних }var p: array[1..N] of PBook; begin { заповнення масиву записів} for i:=1 to N do p[i] := @aBooks[i];

for i:=1 to N do writeln(p[i]^.title, p[i]^.year:5);end.

type PBook = ^TBook; { новий тип даних }var p: array[1..N] of PBook; begin { заповнення масиву записів} for i:=1 to N do p[i] := @aBooks[i];

for i:=1 to N do writeln(p[i]^.title, p[i]^.year:5);end.

for i:=1 to N-1 do for j:=N-1 downto i do if p[j]^.year > p[j+1]^.year then begin temp := p[j]; p[j] := p[j+1]; p[j+1] := temp; end;

допоміжні вказівникидопоміжні вказівники

міняємо тільки вказівники, записи залишаються на

місцях

міняємо тільки вказівники, записи залишаються на

місцях

початкова розстановкапочаткова

розстановка

Page 33: Pascal (динамічні структури даних)

Тема 4. Списки

Динамічні структури даних(мова Паскаль)

© К.Ю. Поляков, 2008-2010

Переклад: Р. М. Васильчик

Page 34: Pascal (динамічні структури даних)

34

Динамічні структури даних

Будова: набір вузлів, об'єднаних за допомогою посилань.

Як влаштований вузол:

даніпосилання на

інші вузли

Типи структур:списки дерева графи

nil

nilnil

однозв'язні

двонаправлені (двозв'язні)

циклічні списки (кільця)nil

nil

nil nil

nil nil

Page 35: Pascal (динамічні структури даних)

35

Коли потрібні списки?Завдання (алфавітно-частотний словник). В файлі записаний текст. Потрібно записати в інший файл в стовпчик всі слова, що зустрічаються в тексті, в алфавітному порядку, і кількість повторень для кожного слова.

Проблеми:

1) кількість слів заздалегідь невідомо (статичний масив);

2) кількість слів визначається тільки в кінці роботи (динамічний масив).

Рішення – список.

Алгоритм:

1) створити список;

2) якщо слова в файлі закінчилися, то стоп.

3) прочитати слово и шукати його у списку;

4) якщо слово знайдено – збільшити лічильник повторень, інакше додати слово до списку;

5) перейти до кроку 2.

Page 36: Pascal (динамічні структури даних)

36

Що таке список:1) порожня структура – це список;2) список – це початковий вузол (голова)

і пов'язаний з ним список.

Списки: нові типи даних

type PNode = ^Node; { вказівник на вузол } Node = record { структура вузла } word: string[40];{ слово } count: integer; { лічильник повторень } next: PNode; {посилання на наступний} end;

type PNode = ^Node; { вказівник на вузол } Node = record { структура вузла } word: string[40];{ слово } count: integer; { лічильник повторень } next: PNode; {посилання на наступний} end;

Нові типи даних:

Адреса початку списку:

var Head: PNode;...Head := nil;

var Head: PNode;...Head := nil;

Рекурсивне визначення!

Рекурсивне визначення!!!

nil

Для доступу до списку достатньо знати адресу його голови!

Для доступу до списку достатньо знати адресу його голови!

!!

Page 37: Pascal (динамічні структури даних)

37

Що потрібно вміти робити зі списком?

1. Створити новий вузол.

2. Добавити вузол:

а) в початок списку;

б) в кінець списку;

в) після заданого вузла;

г) до заданого вузла.

3. Шукати потрібний вузол в списку.

4. Видалити вузол.

Page 38: Pascal (динамічні структури даних)

38

Створення вузла

function CreateNode(NewWord: string): PNode;var NewNode: PNode;begin New(NewNode); NewNode^.word := NewWord; NewNode^.count := 1; NewNode^.next := nil; Result := NewNode;end;

function CreateNode(NewWord: string): PNode;var NewNode: PNode;begin New(NewNode); NewNode^.word := NewWord; NewNode^.count := 1; NewNode^.next := nil; Result := NewNode;end;

Функція CreateNode (створити вузол): вхід: нове слово, прочитане з файлу; вихід: адреса нового вузла, створеного в пам'яті.

повертає адресу створеного вузлаповертає адресу створеного вузланове словонове слово

Якщо пам'ять виділити не вдалося?

Якщо пам'ять виділити не вдалося?

??

Page 39: Pascal (динамічні структури даних)

39

Добавлення вузла в початок списку

NewNodeNewNode

HeadHead nil

1) Встановити посилання нового вузла на голову списку:

NewNode^.next := Head;NewNode^.next := Head;NewNodeNewNode

HeadHead nil

nil

2) Встановити новий вузол як голову списку:

Head := NewNode;Head := NewNode;

procedure AddFirst ( var Head: PNode; NewNode: PNode );begin NewNode^.next := Head; Head := NewNode;end;

procedure AddFirst ( var Head: PNode; NewNode: PNode );begin NewNode^.next := Head; Head := NewNode;end;

var

адреса голови міняєтьсяадреса голови міняється

Page 40: Pascal (динамічні структури даних)

40

Добавлення вузла після заданого

1) Встановити посилання нового вузла на вузол, наступний за p:

NewNode^.next = p^.next;NewNode^.next = p^.next;

2) Встановити посилання вузла p на новий вузол:

p^.next = NewNode;p^.next = NewNode;

NewNodeNewNode

ppnil

nil

NewNodeNewNode

ppnil

procedure AddAfter ( p, NewNode: PNode );begin NewNode^.next := p^.next; p^.next := NewNode;end;

procedure AddAfter ( p, NewNode: PNode );begin NewNode^.next := p^.next; p^.next := NewNode;end;

Page 41: Pascal (динамічні структури даних)

41

Завдання: зробити що-небудь хороше з кожним елементом списку.

Алгоритм:

1) встановити допоміжний вказівник q на голову списку;

2) якщо вказівник q дорівнює nil (дійшли до кінця списку), то стоп;

3) виконати дію над вузлом з адресою q ;

4) перейти до наступного вузла, q^.next.

Прохід по списку

var q: PNode;...q := Head; // почали з голови while q <> nil do begin // поки не дійшли до кінця ... // робимо щось хороше з q q := q^.next; // переходимо до наступного end;

var q: PNode;...q := Head; // почали з голови while q <> nil do begin // поки не дійшли до кінця ... // робимо щось хороше з q q := q^.next; // переходимо до наступного end;

HeadHead nil

qq

Page 42: Pascal (динамічні структури даних)

42

Добавлення вузла в кінець спискуЗавдання: додати новий вузол в кінець списку.

Алгоритм:

1) знайти останній вузол q, такий що q^.next дорівнює nil; 2) добавити вузол після вузла з адресою q (процедура AddAfter).

Особливий випадок: добавлення в порожній список.

procedure AddLast ( var Head: PNode; NewNode: PNode );var q: PNode;begin if Head = nil then AddFirst ( Head, NewNode ) else begin q := Head; while q^.next <> nil do q := q^.next; AddAfter ( q, NewNode ); end;end;

procedure AddLast ( var Head: PNode; NewNode: PNode );var q: PNode;begin if Head = nil then AddFirst ( Head, NewNode ) else begin q := Head; while q^.next <> nil do q := q^.next; AddAfter ( q, NewNode ); end;end;

особливий випадок – добавлення в порожній список

особливий випадок – добавлення в порожній список

шукаємо останній вузолшукаємо останній вузол

добавити вузол після вузла q

добавити вузол після вузла q

Page 43: Pascal (динамічні структури даних)

43

Проблема: потрібно знати адресу попереднього вузла, а йти назад не можна!

Рішення: знайти попередній вузол q (прохід з початку списку).

Добавлення вузла перед заданим

NewNodeNewNodepp

nil

nil

procedure AddBefore(var Head: PNode; p, NewNode: PNode);var q: PNode;begin q := Head; if p = Head then AddFirst ( Head, NewNode ) else begin while (q <> nil) and (q^.next <> p) do q := q^.next; if q <> nil then AddAfter ( q, NewNode ); end;end;

procedure AddBefore(var Head: PNode; p, NewNode: PNode);var q: PNode;begin q := Head; if p = Head then AddFirst ( Head, NewNode ) else begin while (q <> nil) and (q^.next <> p) do q := q^.next; if q <> nil then AddAfter ( q, NewNode ); end;end;

в початок спискув початок списку

шукаємо вузол, наступний за яким -

вузол p

шукаємо вузол, наступний за яким -

вузол p

добавити вузол після вузла q

добавити вузол після вузла q

Що погано? Що погано???

Page 44: Pascal (динамічні структури даних)

44

Добавлення вузла перед заданим (II)Завдання: вставити вузол перед заданим без пошуку попереднього.

Алгоритм:

1) поміняти місцями дані нового вузла і вузла p;

2) встановити посилання вузла p на NewNode.

procedure AddBefore2 ( p, NewNode: PNode );var temp: Node;begin temp := p^; p^ := NewNode^; NewNode^ := temp; p^.next := NewNode;end;

procedure AddBefore2 ( p, NewNode: PNode );var temp: Node;begin temp := p^; p^ := NewNode^; NewNode^ := temp; p^.next := NewNode;end;

NewNodeNewNode

ppnil

Так не можна, якщо p = nil або адреси вузлів десь ще запам'ятовуються!

Так не можна, якщо p = nil або адреси вузлів десь ще запам'ятовуються!

!!

NewNodeNewNode

ppnil

nil

Page 45: Pascal (динамічні структури даних)

45

Пошук слова в спискуЗавдання:

знайти в списку задане слово або визначити, що його немає.

Функція Find:вхід: слово (символьний рядок);

вихід: адреса вузла, з цим словом або nil.

Алгоритм: прохід по списку.

function Find(Head: PNode; NewWord: string): PNode;var q: PNode;begin q := Head; while (q <> nil) and (NewWord <> q^.word) do q := q^.next; Result := q;end;

function Find(Head: PNode; NewWord: string): PNode;var q: PNode;begin q := Head; while (q <> nil) and (NewWord <> q^.word) do q := q^.next; Result := q;end;

шукаємо це словошукаємо це слово

результат – адреса вузла або nil (нема

такого)

результат – адреса вузла або nil (нема

такого)

while (q <> nil) and (NewWord <> q^.word) do q := q^.next;

поки не дійшли до кінця списку і слово не дорівнює заданому

поки не дійшли до кінця списку і слово не дорівнює заданому

Page 46: Pascal (динамічні структури даних)

46

Куди вставити нове слово?Завдання:

знайти вузол, перед яким потрібно вставити, задане слово, так щоб у списку зберігався алфавітний порядок слів.

Функція FindPlace:вхід: слово (символьний рядок);вихід: адреса вузла, перед яким потрібно вставити це слово або

nil, якщо слово потрібно вставити в кінець списку.

function FindPlace(Head: PNode; NewWord: string): PNode;var q: PNode;begin q := Head; while (q <> nil) and (NewWord > q^.word) do q := q^.next; Result := q;end;

function FindPlace(Head: PNode; NewWord: string): PNode;var q: PNode;begin q := Head; while (q <> nil) and (NewWord > q^.word) do q := q^.next; Result := q;end;

>

слово NewWord стоїть за алфавітом перед q^.wordслово NewWord стоїть за

алфавітом перед q^.word

Page 47: Pascal (динамічні структури даних)

47

Видалення вузла

procedure DeleteNode ( var Head: PNode; p: PNode );var q: PNode;begin if Head = p then Head := p^.next else begin q := Head; while (q <> nil) and (q^.next <> p) do q := q^.next; if q <> nil then q^.next := p^.next; end; Dispose(p);end;

procedure DeleteNode ( var Head: PNode; p: PNode );var q: PNode;begin if Head = p then Head := p^.next else begin q := Head; while (q <> nil) and (q^.next <> p) do q := q^.next; if q <> nil then q^.next := p^.next; end; Dispose(p);end;

while (q <> nil) and (q^.next <> p) do q := q^.next;

if Head = p then Head := p^.next

qq

HeadHead

pp

nil

Проблема: потрібно знати адресу попереднього вузла q.

особливий випадок: видаляємо перший вузол

особливий випадок: видаляємо перший вузол

шукаємо вузол, такий що

q^.next = p

шукаємо вузол, такий що

q^.next = p

Dispose(p);

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

Page 48: Pascal (динамічні структури даних)

48

Алфавітно-частотний словникАлгоритм:

1) відкрити файл для читання;

2) прочитати чергове слово (як?)3) якщо файл закінчився, то перейти до кроку 7;4) якщо слово знайдено, збільшити лічильник (поле count);5) якщо слова немає в списку, то

• створити новий вузол, заповнити поля (CreateNode);• знайти вузол, перед яким потрібно вставити слово

(FindPlace);• добавити вузол (AddBefore);

6) перейти до кроку 2;7) закрити файл8) вивести список слів, використовуючи прохід по списку.

var F: Text;...Assign(F, 'input.dat');Reset ( F );

var F: Text;...Assign(F, 'input.dat');Reset ( F );

Close ( F );Close ( F );

Page 49: Pascal (динамічні структури даних)

49

Як прочитати одне слово з файлу?

function GetWord ( F: Text ) : string;var c: char;begin Result := ''; { порожній рядок } c := ' '; { пропуск – щоб увійти в цикл } { пропускаємо спецсимволи і пропуски } while not eof(f) and (c <= ' ') do read(F, c); { читаєм слово } while not eof(f) and (c > ' ') do begin Result := Result + c; read(F, c); end;end;

function GetWord ( F: Text ) : string;var c: char;begin Result := ''; { порожній рядок } c := ' '; { пропуск – щоб увійти в цикл } { пропускаємо спецсимволи і пропуски } while not eof(f) and (c <= ' ') do read(F, c); { читаєм слово } while not eof(f) and (c > ' ') do begin Result := Result + c; read(F, c); end;end;

Алгоритм:1) пропускаємо всі спецсимволи і пропуски (з кодами <= 32)2) читаємо всі символи до першого пропуску або спецсимволу

Можна поміняти місцями рядки в циклі?

Можна поміняти місцями рядки в циклі???

Page 50: Pascal (динамічні структури даних)

50

Повна програма

type PNode = ^Node; Node = record ... end; { нові типи даних }var Head: PNode; { адреса голови списку } NewNode, q: PNode; { допоміжні вказівники } w: string; { слово з файлу } F: text; { файлова змінна } count: integer; { лічильник різних слів } { процедури і функції }begin Head := nil; Assign ( F, 'input.txt' ); Reset ( F ); { читаємо слова з файлу, будуємо список } Close ( F ); { виводимо список в інший файл }end.

type PNode = ^Node; Node = record ... end; { нові типи даних }var Head: PNode; { адреса голови списку } NewNode, q: PNode; { допоміжні вказівники } w: string; { слово з файлу } F: text; { файлова змінна } count: integer; { лічильник різних слів } { процедури і функції }begin Head := nil; Assign ( F, 'input.txt' ); Reset ( F ); { читаємо слова з файлу, будуємо список } Close ( F ); { виводимо список в інший файл }end.

Page 51: Pascal (динамічні структури даних)

51

Повна програма (II)

{ читаємо слова з файлу, будуємо список }

while True do begin { нескінченний цикл }

w := GetWord ( F ); { читаємо слово }

if w = '' then break; { слова закінчились, вихід }

q := Find ( Head, w ); { шукаємо слово в списку }

if q <> nil then { знайшли, збільшити лічильник

}

q^.count := q^.count + 1

else begin { не знайшли, добавити в список

}

NewNode := CreateNode ( w );

q := FindPlace ( Head, w );

AddBefore ( Head, q, NewNode );

end;

end;

{ читаємо слова з файлу, будуємо список }

while True do begin { нескінченний цикл }

w := GetWord ( F ); { читаємо слово }

if w = '' then break; { слова закінчились, вихід }

q := Find ( Head, w ); { шукаємо слово в списку }

if q <> nil then { знайшли, збільшити лічильник

}

q^.count := q^.count + 1

else begin { не знайшли, добавити в список

}

NewNode := CreateNode ( w );

q := FindPlace ( Head, w );

AddBefore ( Head, q, NewNode );

end;

end;

Page 52: Pascal (динамічні структури даних)

52

Повна програма (III)

{ виводимо список в інший файл }

q := Head; { прохід з початку списку }

count := 0; { обнулили лічильник слів }

Assign(F, 'output.txt');

Rewrite(F);

while q <> nil do begin { поки не кінець

списку }

count := count + 1; { ще одне слово }

writeln ( F, q^.word, ': ', q^.count );

q := q^.next; { перейти до

наступного }

end;

writeln ( F, ‘Знайдено ',

count, ' різних слів.' );

Close(F);

{ виводимо список в інший файл }

q := Head; { прохід з початку списку }

count := 0; { обнулили лічильник слів }

Assign(F, 'output.txt');

Rewrite(F);

while q <> nil do begin { поки не кінець

списку }

count := count + 1; { ще одне слово }

writeln ( F, q^.word, ': ', q^.count );

q := q^.next; { перейти до

наступного }

end;

writeln ( F, ‘Знайдено ',

count, ' різних слів.' );

Close(F);

Page 53: Pascal (динамічні структури даних)

type PNode = ^Node; { вказівник на вузол } Node = record { структура вузла } word: string[40]; { слово } count: integer; {лічильник повторений} next: PNode; {посилання на наступний} prev: PNode; {посилання на попередній} end;

type PNode = ^Node; { вказівник на вузол } Node = record { структура вузла } word: string[40]; { слово } count: integer; {лічильник повторений} next: PNode; {посилання на наступний} prev: PNode; {посилання на попередній} end;

53

Двозв’язні списки

Структура вузла:

Адреси «голови» і «хвоста»:

var Head, Tail: PNode; ...Head := nil; Tail := nil;

var Head, Tail: PNode; ...Head := nil; Tail := nil;

nextprevprevious

можна рухатися в обидва боки

потрібно працювати з двома вказівниками замість одного

nilnil

HeadHead TailTail

Page 54: Pascal (динамічні структури даних)

54

Завдання

«4»: «Зібрати» з цих функцій програму для побудови алфавітно-частотного словника. В кінці файлу вивести загальну кількість різних слів (кількість елементів списку).

«5»: Те ж саме, але використовувати двозв’язні списки.

«6»: Те ж саме, що і на «5», але вивести список слів в порядку зменшення частоти, тобто, спочатку ті слова, які зустрічаються найчастіше.

Page 55: Pascal (динамічні структури даних)

Тема 5. Стеки, черги, деки

Динамічніструктури даних(мова Паскаль)

© К.Ю. Поляков, 2008-2010

Переклад: Р. М. Васильчик

Page 56: Pascal (динамічні структури даних)

56

Стек

Стек – це лінійна структура даних, в якій додавання і видалення елементів можливо тільки з одного кінця (вершини стека). Stack = кипа, куча, стопка (англ.)

LIFO = Last In – First Out«Хто останнім увійшов, той першим вийшов».

Операції зі стеком:

1) додати елемент на вершину (Push = заштовхнути);

2) зняти елемент з вершини(Pop = вилетіти зі звуком).

Page 57: Pascal (динамічні структури даних)

57

Приклад завданняЗавдання: вводиться символьний рядок, в якому записано вираз з

дужками трьох типів: [], {} і (). Визначити, чи правильно розставлені дужки (не звертаючи уваги на інші символи). Приклади:

[()]{} ][ [({)]} Спрощене завдання: те ж саме, але з одним видом дужок.

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

Чи можна вирішити вихідне завдання так само, але з трьома лічильниками?

Чи можна вирішити вихідне завдання так само, але з трьома лічильниками?

?? [ ( { ) ] }(: 0 1 0[: 0 1 0{: 0 1 0

[ ( { ) ] }[ ( { ) ] }

( ( ) ) ( )1 2 1 0 1 0( ( ) ) ( )( ( ) ) ( ) ( ( ) ) ) (

1 2 1 0 -1 0( ( ) ) ) (( ( ) ) ) ( ( ( ) ) (

1 2 1 0 1( ( ) ) (( ( ) ) (

Page 58: Pascal (динамічні структури даних)

58

Рішення завдання з дужками

Алгоритм:1) на початку стек порожній;2) в циклі переглядаємо всі символи рядка по порядку;3) якщо черговий символ – відкриваюча дужка, заносимо її на

вершину стека;4) якщо символ – закриваюча дужка, перевіряємо вершину стека:

там повинна бути відповідна відкриваюча дужка (якщо це не так, то помилка);

5) якщо наприкінці стек не порожній, вираз неправильний.

[ ( ( ) ) ] { }[ ( ( ) ) ] { }

[ [

(

[

(

(

[

(

(

[

(

[ { {

Page 59: Pascal (динамічні структури даних)

59

Реалізація стека (масив)Структура-стек:

const MAXSIZE = 100;type Stack = record { стек на 100 символів } data: array[1..MAXSIZE] of char; size: integer; { кількість елементів } end;

const MAXSIZE = 100;type Stack = record { стек на 100 символів } data: array[1..MAXSIZE] of char; size: integer; { кількість елементів } end;

Додавання елемента:

procedure Push( var S: Stack; x: char);begin if S.size = MAXSIZE then Exit; S.size := S.size + 1; S.data[S.size] := x;end;

procedure Push( var S: Stack; x: char);begin if S.size = MAXSIZE then Exit; S.size := S.size + 1; S.data[S.size] := x;end;

помилка: переповнення

стека

помилка: переповнення

стека

добавити елементдобавити елемент

Що погано? Що погано???

Page 60: Pascal (динамічні структури даних)

60

Реалізація стека (масив)

function Pop ( var S:Stack ): char;begin if S.size = 0 then begin Result := char(255); Exit; end; Result := S.data[S.size]; S.size := S.size - 1;end;

function Pop ( var S:Stack ): char;begin if S.size = 0 then begin Result := char(255); Exit; end; Result := S.data[S.size]; S.size := S.size - 1;end;

Зняття елемента з вершини:

Порожній чи ні?

function isEmpty ( S: Stack ): Boolean;begin Result := (S.size = 0);end;

function isEmpty ( S: Stack ): Boolean;begin Result := (S.size = 0);end;

помилка: стек порожній

помилка: стек порожній

Page 61: Pascal (динамічні структури даних)

61

Програма

var br1, br2, expr: string; i, k: integer; upper: char; { то, що зняли зі стека } error: Boolean; { ознака помилки } S: Stack;begin br1 := '([{'; br2 := ')]}'; S.size := 0; error := False; writeln('Введіть вираз з дужками'); readln(expr); ... { тут буде основний цикл обробки }

if not error and isEmpty(S) then

writeln('Вираз правильний.')

else writeln('Вираз неправильний.')

end.

var br1, br2, expr: string; i, k: integer; upper: char; { то, що зняли зі стека } error: Boolean; { ознака помилки } S: Stack;begin br1 := '([{'; br2 := ')]}'; S.size := 0; error := False; writeln('Введіть вираз з дужками'); readln(expr); ... { тут буде основний цикл обробки }

if not error and isEmpty(S) then

writeln('Вираз правильний.')

else writeln('Вираз неправильний.')

end.

відкриваючі дужкивідкриваючі дужки

закриваючі дужкизакриваючі дужки

Page 62: Pascal (динамічні структури даних)

62

Обробка рядка (основний цикл)

for i:=1 to length(expr)

do begin

for k:=1 to 3 do begin

if expr[i] = br1[k] then begin { відкр. дужка }

Push(S, expr[i]); { заштовхнути в стек}

break;

end;

if expr[i] = br2[k] then begin { закр. дужка }

upper := Pop(S); { зняти символ із стека }

error := upper <> br1[k];

break;

end;

end;

if error then break;

end;

for i:=1 to length(expr)

do begin

for k:=1 to 3 do begin

if expr[i] = br1[k] then begin { відкр. дужка }

Push(S, expr[i]); { заштовхнути в стек}

break;

end;

if expr[i] = br2[k] then begin { закр. дужка }

upper := Pop(S); { зняти символ із стека }

error := upper <> br1[k];

break;

end;

end;

if error then break;

end;

цикл по всіх символах рядка expr

цикл по всіх символах рядка expr

цикл за всіма видами дужокцикл за всіма видами дужок

помилка: стек порожній або не

та дужка

помилка: стек порожній або не

та дужка

була помилка: далі немає сенсу перевіряти

була помилка: далі немає сенсу перевіряти

Page 63: Pascal (динамічні структури даних)

63

Реалізація стека (список)

Додавання елемента:

Структура вузла:

type PNode = ^Node; { вказівник на вузол } Node = record data: char; { дані } next: PNode; { вказівник на наст. елемент } end;

type PNode = ^Node; { вказівник на вузол } Node = record data: char; { дані } next: PNode; { вказівник на наст. елемент } end;

procedure Push( var Head: PNode; x: char);var NewNode: PNode;begin New(NewNode); { виділити пам’ять } NewNode^.data := x; { записати символ } NewNode^.next := Head; { зробити першим вузлом } Head := NewNode;end;

procedure Push( var Head: PNode; x: char);var NewNode: PNode;begin New(NewNode); { виділити пам’ять } NewNode^.data := x; { записати символ } NewNode^.next := Head; { зробити першим вузлом } Head := NewNode;end;

Page 64: Pascal (динамічні структури даних)

64

Реалізація стека (список)

Зняття елемента з вершини:

function Pop ( var Head: PNode ): char;var q: PNode;begin if Head = nil then begin { стек порожній } Result := char(255);{невикористовуваний символ} Exit; end; Result := Head^.data; { взяти верхній символ } q := Head; { запам’ятати вершину } Head := Head^.next; { видалити вершину з стека } Dispose(q); { видалити з пам’яті }end;

function Pop ( var Head: PNode ): char;var q: PNode;begin if Head = nil then begin { стек порожній } Result := char(255);{невикористовуваний символ} Exit; end; Result := Head^.data; { взяти верхній символ } q := Head; { запам’ятати вершину } Head := Head^.next; { видалити вершину з стека } Dispose(q); { видалити з пам’яті }end;

Чи можна переставляти оператори? Чи можна переставляти оператори???

q := Head; Head := Head^.next;Dispose(q);

q := Head; Head := Head^.next;Dispose(q);

Page 65: Pascal (динамічні структури даних)

65

Реалізація стека (список)

Зміни в основній програмі:

var S: Stack;...S.size := 0;

var S: Stack;...S.size := 0;

var S: PNode;var S: PNode;

S := nil;S := nil;

Більше нічого не змінюється! Більше нічого не змінюється!!!

Порожній чи ні?function isEmpty ( S: Stack ): Boolean;begin Result := (S = nil);end;

function isEmpty ( S: Stack ): Boolean;begin Result := (S = nil);end;

Page 66: Pascal (динамічні структури даних)

66

Обчислення арифметичних виразів

a b + c d + 1 - /a b + c d + 1 - /

Як обчислювати автоматично:

Інфіксний запис(знак операції між операндами)

(a + b) / (c + d – 1)(a + b) / (c + d – 1)

необхідні дужки!

Постфіксний запис (знак операції після операндів)

польська нотація,Jan Łukasiewicz (1920)польська нотація,

Jan Łukasiewicz (1920)

дужки не потрібні, можна однозначно обчислити!

Префіксний запис (знак операції до операндів)

/ + a b - + c d 1/ + a b - + c d 1

зворотна польська нотація,F. L. Bauer and E. W. Dijkstra

зворотна польська нотація,F. L. Bauer and E. W. Dijkstra

a + ba + b

a + ba + b

c + dc + d

c + dc + d

c + d - 1c + d - 1

c + d - 1c + d - 1

Page 67: Pascal (динамічні структури даних)

67

Запишіть в постфіксній формі

(32*6-5)*(2*3+4)/(3+7*2)(32*6-5)*(2*3+4)/(3+7*2)

(2*4+3*5)*(2*3+18/3*2)*(12-3)(2*4+3*5)*(2*3+18/3*2)*(12-3)

(4-2*3)*(3-12/3/4)*(24-3*12)(4-2*3)*(3-12/3/4)*(24-3*12)

Page 68: Pascal (динамічні структури даних)

68

Обчислення виразів

Постфіксна форма:

a b + c d + 1 - / a b + c d + 1 - /

Алгоритм:1) взяти черговий елемент;2) якщо це не знак операції, добавить його в стек;3) якщо це знак операції, то

• взяти з стека два операнди;• виконати операцію і записати результат в стек;

4) перейти до кроку 1.

a

b

a a+b

c

a+b

d

c

a+b

c+d

a+b

1

c+d

a+b

c+d-1

a+b X

X = X =

Page 69: Pascal (динамічні структури даних)

69

Системний стек (Windows – 1 Мб)

Використовується для

1) розміщення локальних змінних;

2) зберігання адрес повернення (за якими переходить програма після виконання функції або процедури);

3) передачі параметрів в функції та процедури;

4) тимчасового зберігання даних (в програмах на мові Асемблер).

Переповнення стека (stack overflow):

1) занадто багато локальних змінних(вихід – використовувати динамічні масиви);

2) дуже багато рекурсивних викликів функцій і процедур (вихід – переробити алгоритм так, щоб зменшити глибину рекурсії або відмовитися від неї взагалі).

Page 70: Pascal (динамічні структури даних)

70

Черга

1

23

45

6

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

FIFO = First In – First Out«Хто першим увійшов, той першим вийшов».

Операції з чергою:

1) додати елемент в кінець черги (PushTail = заштовхнути в кінець);

2) видалити елемент з початку черги (Pop).

Page 71: Pascal (динамічні структури даних)

71

Реалізація черги (масив)

1

1 2

1 2 3

1 2 3

найпростіший спосіб

1) потрібно заздалегідь виділити масив;2) при вибірці з черги потрібно зрушувати всі елементи.

Page 72: Pascal (динамічні структури даних)

72

Реалізація черги (кільцевий масив)

1 2

HeadHead TailTail

1 2 3

2 3 2 3 4

3 4 Скільки елементів можна зберігати в такій черзі?

Скільки елементів можна зберігати в такій черзі?

??

Як розрізнити стани «черга порожня» і «черга повна»?

Як розрізнити стани «черга порожня» і «черга повна»?

??3 45

Page 73: Pascal (динамічні структури даних)

73

Реалізація черги (кільцевий масив)

1

HeadHead TailTail

В черзі 1 елемент:

Черга порожня:

Черга повна:

Head = Tail + 1Head = Tail + 1 Head = (Tail mod N) + 1Head = (Tail mod N) + 1

1 N

розмір масивурозмір масиву

1 23

Head = Tail + 2Head = Tail + 2 Head = (Tail+1) mod N + 1Head = (Tail+1) mod N + 1

1 N

1 2 3

Head = TailHead = Tail

Page 74: Pascal (динамічні структури даних)

74

Реалізація черги (кільцевий масив)

type Queue = record data: array[1..MAXSIZE] of integer; head, tail: integer; end;

type Queue = record data: array[1..MAXSIZE] of integer; head, tail: integer; end;

Структура даних:

Додавання в чергу:

procedure PushTail( var Q: Queue; x: integer);

begin

if Q.head = (Q.tail+1) mod MAXSIZE + 1 then Exit; { черга повна, не додавати }

Q.tail := Q.tail mod MAXSIZE + 1;

Q.data[Q.tail] := x;

end;

procedure PushTail( var Q: Queue; x: integer);

begin

if Q.head = (Q.tail+1) mod MAXSIZE + 1 then Exit; { черга повна, не додавати }

Q.tail := Q.tail mod MAXSIZE + 1;

Q.data[Q.tail] := x;

end;

замкнути в кільце

замкнути в кільцеmod MAXSIZE

Page 75: Pascal (динамічні структури даних)

75

Реалізація черги (кільцевий масив)

Вибірка з черги:

function Pop ( var S: Queue ): integer;

begin

if Q.head = Q.tail mod MAXSIZE + 1 then begin

Result := MaxInt;

Exit;

end;

Result := Q.data[Q.head];

Q.head := Q.head mod MAXSIZE + 1;

end;

function Pop ( var S: Queue ): integer;

begin

if Q.head = Q.tail mod MAXSIZE + 1 then begin

Result := MaxInt;

Exit;

end;

Result := Q.data[Q.head];

Q.head := Q.head mod MAXSIZE + 1;

end;

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

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

взяти перший елемент

взяти перший елемент

видалити його з черги

видалити його з черги

максимальне ціле число

максимальне ціле число

Page 76: Pascal (динамічні структури даних)

76

Реалізація черги (списки)

type PNode = ^Node; Node = record data: integer; next: PNode; end;

type PNode = ^Node; Node = record data: integer; next: PNode; end;

type Queue = record

head, tail: PNode;

end;

type Queue = record

head, tail: PNode;

end;

Структура вузла:

Тип даних «черга»:

Page 77: Pascal (динамічні структури даних)

77

Реалізація черги (списки)

procedure PushTail( var Q: Queue; x: integer );

var NewNode: PNode;

begin

New(NewNode);

NewNode^.data := x;

NewNode^.next := nil;

if Q.tail <> nil then

Q.tail^.next := NewNode;

Q.tail := NewNode; { новий вузол – в кінець}

if Q.head = nil then Q.head := Q.tail;

end;

procedure PushTail( var Q: Queue; x: integer );

var NewNode: PNode;

begin

New(NewNode);

NewNode^.data := x;

NewNode^.next := nil;

if Q.tail <> nil then

Q.tail^.next := NewNode;

Q.tail := NewNode; { новий вузол – в кінець}

if Q.head = nil then Q.head := Q.tail;

end;

Додавання елемента:

створюємо новий вузолстворюємо

новий вузол

якщо в списку вже щось було,

додаємо в кінець

якщо в списку вже щось було,

додаємо в кінець

якщо в списку нічого не було, …

якщо в списку нічого не було, …

Page 78: Pascal (динамічні структури даних)

78

Реалізація черги (списки)

function Pop ( var S: Queue ): integer;

var top: PNode;

begin

if Q.head = nil then begin

Result := MaxInt;

Exit;

end;

top := Q.head;

Result := top^.data;

Q.head := top^.next;

if Q.head = nil then Q.tail := nil;

Dispose(top);

end;

function Pop ( var S: Queue ): integer;

var top: PNode;

begin

if Q.head = nil then begin

Result := MaxInt;

Exit;

end;

top := Q.head;

Result := top^.data;

Q.head := top^.next;

if Q.head = nil then Q.tail := nil;

Dispose(top);

end;

Вибірка елемента:

якщо список порожній, …якщо список порожній, …

запам'ятали перший елемент

запам'ятали перший елемент

якщо в списку нічого не

залишилося, …

якщо в списку нічого не

залишилося, …

звільнити пам'ять

звільнити пам'ять

Page 79: Pascal (динамічні структури даних)

79

Дека

Дека (deque = double ended queue, черга з двома кінцями)– це лінійна структура даних, в якій додавання і видалення елементів можливе з обох кінців.

12 34 56

Операції з декою:1) додавання елемента в початок (Push);2) видалення елемента з початку (Pop);3) додавання елемента в кінець (PushTail);4) видалення елемента з кінця (PopTail).

Реалізація:1) кільцевий масив;2) двохзв'язний список.

Page 80: Pascal (динамічні структури даних)

80

Завдання

«4»: У файлі input.dat знаходиться список чисел (або слів). Переписати його в файл output.dat в зворотному порядку.

«5»: Скласти програму, яка обчислює значення арифметичного виразу, записаного в постфіксній формі, за допомогою стека. Вираз правильний, допускаються тільки однозначні числа і знаки +, -, *, /.

«6»: Те ж саме, що і в «5», але допускаються багатозначні числа.

Page 81: Pascal (динамічні структури даних)

Тема 6. Дерева

Динамічніструктури даних(мова Паскаль)

© К.Ю. Поляков, 2008-2010

Переклад: Р. М. Васильчик

Page 82: Pascal (динамічні структури даних)

82

Дерева

директордиректор

гол. інженергол. інженер гол. бухгалтергол. бухгалтер

інженерінженер

інженерінженер

інженерінженер

бухгалтербухгалтер

бухгалтербухгалтер

бухгалтербухгалтер Що спільного в усіх прикладах?

Що спільного в усіх прикладах???

Page 83: Pascal (динамічні структури даних)

83

ДереваДерево – це структура даних, що

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

Корінь – це початковий вузол дерева.

Листок – це вузол, з якого не виходить жодної дуги.

корінькорінь

22

88

5566

99

11

3344

1010

77

Які структури – не дерева?

44

11

3322

55

22

44

66

11

33

55

33

22

11

3322

11

44

Page 84: Pascal (динамічні структури даних)

84

Дерева

Предок вузла x – це вузол, з якого існує шлях по

стрілках у вузол x.

Потомок вузла x – це вузол, в який існує шлях по

стрілках з вузла x.

Батько вузла x – це вузол, з якого існує дуга

безпосередньо у вузол x.

За допомогою дерев зображають відносини підпорядкованості (ієрархія, «старший – молодший», «батько – дитина»).

За допомогою дерев зображають відносини підпорядкованості (ієрархія, «старший – молодший», «батько – дитина»).

!!

22

44

66

11

33

55

Син вузла x – це вузол, в який існує дуга безпосередньо з вузла x.

Брат вузла x (sibling) – це вузол, у якого той же батько, що й у

вузла x.Висота дерева – це найбільша відстань від кореня до листка

(кількість дуг).

Page 85: Pascal (динамічні структури даних)

85

Дерево – рекурсивна структура даних

Рекурсивне визначення:1. Порожня структура – це дерево.2. Дерево – це корінь і кілька

пов'язаних з ним дерев.22

44

66

11

33

55Двійкове (бінарне) дерево – це дерево, в

якому кожен вузол має не більше двох синів.

1. Порожня структура – це двійкове дерево.2. Двійкове дерево – це корінь і два пов'язаних з

ним двійкових дерева (ліве і праве піддерева).

Page 86: Pascal (динамічні структури даних)

86

Двійкові дерева

Структура вузла:type PNode = ^Node; { покажчик на вузол } Node = record data: integer; { корисні дані } left, right: PNode; { посилання на лівого і правого синів } end;

type PNode = ^Node; { покажчик на вузол } Node = record data: integer; { корисні дані } left, right: PNode; { посилання на лівого і правого синів } end;

Застосування:1) пошук даних у спеціально побудованих деревах

(бази даних);2) сортування даних;3) обчислення арифметичних виразів;4) кодування (метод Хаффмана).

Page 87: Pascal (динамічні структури даних)

87

Двійкові дерева пошуку

1616 4545

3030

7676 125125

9898

5959 Яка закономірність? Яка закономірність???

Зліва від кожного вузла знаходяться вузли з меншими ключами, а справа – з більшими.

Ключ – це характеристика вузла, по якій виконується пошук (найчастіше – одне з полів структури).

Як шукати ключ, рівний x:1) якщо дерево порожнє, ключ не знайдений;2) якщо ключ вузла дорівнює x, то стоп.

3) якщо ключ вузла менше x, то шукати x в лівому піддереві;

4) якщо ключ вузла більше x, то шукати x в правом піддереві.

Зведення задачі до такої ж задачі меншою розмірності – це …?

Зведення задачі до такої ж задачі меншою розмірності – це …???

Page 88: Pascal (динамічні структури даних)

88

Двійкові дерева пошуку

1616 4545

3030

7676 125125

9898

5959

Пошук в масиві (N елементів):

1616454530307676 12512598985959

При кожному порівнянні відкидається 1 елемент.Кількість порівнянь – N.

Пошук по дереву (N елементів):

При кожному порівнянні відкидається половина залишившихся елементів.Кількість порівнянь ~ log2N.

швидкий пошук

1) потрібно заздалегідь побудувати дерево;2) бажано, щоб дерево було мінімальної висоти.

Page 89: Pascal (динамічні структури даних)

89

Реалізація алгоритму пошуку

{ Функція Search – пошук по дереву Вхід: Tree - адреса кореня, x - що шукаємо Вихід: адреса вузла або nil (не знайшли) }function Search(Tree: PNode; x: integer): PNode;begin if Tree = nil then begin Result := nil; Exit; end; if x = Tree^.data then Result := Tree else if x < Tree^.data then Result := Search(Tree^.left, x) else Result := Search(Tree^.right, x);end;

{ Функція Search – пошук по дереву Вхід: Tree - адреса кореня, x - що шукаємо Вихід: адреса вузла або nil (не знайшли) }function Search(Tree: PNode; x: integer): PNode;begin if Tree = nil then begin Result := nil; Exit; end; if x = Tree^.data then Result := Tree else if x < Tree^.data then Result := Search(Tree^.left, x) else Result := Search(Tree^.right, x);end;

дерево порожнє: ключ не знайшли…дерево порожнє:

ключ не знайшли…

знайшли, повертаємо

адресу кореня

знайшли, повертаємо

адресу кореня шукати в лівому

піддереві

шукати в лівому

піддереві

шукати в правому піддеревішукати в правому піддереві

Page 90: Pascal (динамічні структури даних)

90

Як побудувати дерево пошуку?

{ Процедура AddToTree – додати елемент Вхід: Tree - адреса кореня, x - що додаємо }procedure AddToTree( var Tree: PNode; x: integer );begin if Tree = nil then begin New(Tree); Tree^.data := x; Tree^.left := nil; Tree^.right := nil; Exit; end; if x < Tree^.data then AddToTree(Tree^.left, x) else AddToTree(Tree^.right, x);end;

{ Процедура AddToTree – додати елемент Вхід: Tree - адреса кореня, x - що додаємо }procedure AddToTree( var Tree: PNode; x: integer );begin if Tree = nil then begin New(Tree); Tree^.data := x; Tree^.left := nil; Tree^.right := nil; Exit; end; if x < Tree^.data then AddToTree(Tree^.left, x) else AddToTree(Tree^.right, x);end;

дерево порожнє: створюємо новий вузол (корінь)

дерево порожнє: створюємо новий вузол (корінь)

адреса кореня може змінюватися

адреса кореня може змінюватися

додаємо до лівого або правого піддерево

додаємо до лівого або правого піддерево

Мінімальна висота не гарантується! Мінімальна висота не гарантується!!!

Page 91: Pascal (динамічні структури даних)

91

Обхід дерева

1616 4545

3030

7676 125125

9898

5959Обхід дерева – це перерахування всіх вузлів в певному порядку.

Обхід ЛКП («лівий – корінь – правий»):

125125989876764545 595930301616

Обхід ПКЛ («правий – корінь – лівий»):

1616303045457676 59599898125125

Обхід КЛП («корінь – лівий – правий»):

125125767698981616 454530305959

Обхід ЛПК («лівий – правий – корінь»):

595998981251253030 767645451616

Page 92: Pascal (динамічні структури даних)

92

Обхід дерева – реалізація

{ Процедура LKP – обхід дерева в порядку ЛКП (лівий – корінь – правий) Вхід: Tree - адреса кореня }

procedure LKP(Tree: PNode);

begin

if Tree = nil then Exit;

LKP(Tree^.left);

write(' ', Tree^.data);

LKP(Tree^.right);

end;

{ Процедура LKP – обхід дерева в порядку ЛКП (лівий – корінь – правий) Вхід: Tree - адреса кореня }

procedure LKP(Tree: PNode);

begin

if Tree = nil then Exit;

LKP(Tree^.left);

write(' ', Tree^.data);

LKP(Tree^.right);

end;

обхід цієї гілки закінчено

обхід цієї гілки закінчено

обхід лівого піддерева

обхід лівого піддерева

виведення даних кореня

виведення даних кореня

обхід правого піддерева

обхід правого піддерева

Для рекурсивної структури зручно застосовувати рекурсивну обробку!

Для рекурсивної структури зручно застосовувати рекурсивну обробку!!!

Page 93: Pascal (динамічні структури даних)

93

Розбір арифметичних виразів

aa bb

++

++ 11

--

//

cc dd

a b + c d + 1 - /a b + c d + 1 - /

Як обчислювати автоматично:

Інфіксний запис, обхід ЛКП(знак операції між операндами)

(a + b) / (c + d – 1)(a + b) / (c + d – 1)

необхідні дужки!

Постфіксний запис, ЛПК (знак операції після операндів)

a + b / c + d – 1a + b / c + d – 1

польська нотація,Jan Łukasiewicz (1920)польська нотація,

Jan Łukasiewicz (1920)

дужки не потрібні, можна однозначно обчислити!

Префіксний запис, КЛП (знак операції до операндів)

/ + a b - + c d 1/ + a b - + c d 1

зворотна польська нотація,F. L. Bauer and E. W. Dijkstra

зворотна польська нотація,F. L. Bauer and E. W. Dijkstra

Page 94: Pascal (динамічні структури даних)

94

Обчислення виразів

Постфіксна форма:

a b + c d + 1 - / a b + c d + 1 - /

Алгоритм:1) взяти черговий елемент;2) якщо це не знак операції, додати його в стек;3) якщо це знак операції, то

• взяти з стека два операнди;• виконати операцію і записати результат у стек;

4) перейти до кроку 1.

a

b

a a+b

c

a+b

d

c

a+b

c+d

a+b

1

c+d

a+b

c+d-1

a+b X

X = X =

Page 95: Pascal (динамічні структури даних)

95

Обчислення виразів

Завдання: в символьному рядку записаний правильний арифметичний вираз, який може містити лише однозначні числа і знаки операцій +-*\. Обчислити цей вираз.

Алгоритм:1) ввести рядок;2) побудувати дерево;3) обчислити вираз по дереву.

Обмеження:1) помилки не обробляються;2) багатозначні числа не дозволені;3) дробові числа не дозволені;4) дужки не дозволені.

Page 96: Pascal (динамічні структури даних)

96

Побудова дерева

Алгоритм:

1) якщо first=last (залишився один символ – число), то створити новій вузол і записати в нього цей елемент; інакше...

2) серед елементів віт first до last включно знайти останню операцію (елемент з номером k);

3) створити новий вузол (корінь) і записати в нього знак операції;

4) рекурсивно застосувати цей алгоритм два рази:• побудувати ліве піддерево, розібравши вираз з елементів

масиву з номерами від first до k-1;• побудувати праве піддерево, розібравши вираз з

елементів масиву з номерами від k+1 до last.

5 + 7 * 6 - 3 * 2

firstfirst lastlastkk

k+1k+1k-1k-1

Page 97: Pascal (динамічні структури даних)

97

Як знайти останню операцію?

Порядок виконання операцій• множення і ділення;• додавання і віднімання.

5 + 7 * 6 - 3 * 2

Потрібно шукати останню операцію з найменшим пріоритетом!

Потрібно шукати останню операцію з найменшим пріоритетом!!!

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

• множення і ділення (пріоритет 2);• додавання і віднімання (пріоритет 1).

Page 98: Pascal (динамічні структури даних)

98

Пріоритет операції

{ Функція Priority – пріоритет операції Вхід: символ операції Вихід: пріоритет або 100, якщо не операція }function Priority ( c: char ): integer;begin case ( c ) of '+', '-': Result := 1; '*', '/': Result := 2; else Result := 100; end;end;

{ Функція Priority – пріоритет операції Вхід: символ операції Вихід: пріоритет або 100, якщо не операція }function Priority ( c: char ): integer;begin case ( c ) of '+', '-': Result := 1; '*', '/': Result := 2; else Result := 100; end;end;

додавання і віднімання: пріоритет 1

додавання і віднімання: пріоритет 1

множення і ділення:

пріоритет 2

множення і ділення:

пріоритет 2це взагалі не операція

це взагалі не операція

Page 99: Pascal (динамічні структури даних)

99

Номер останньої операції

{ Функція LastOperation – номер останньої операції Вхід: рядок, номери першого і останнього символів розглянутої частини Вихід: номер символу - останньої операції }function LastOperation ( Expr: string; first, last: integer): integer;var MinPrt, i, k, prt: integer;begin MinPrt := 100; for i:=first to last do begin prt := Priority ( Expr[i] ); if prt <= MinPrt then begin MinPrt := prt; k := i; end; end; Result := k;end;

{ Функція LastOperation – номер останньої операції Вхід: рядок, номери першого і останнього символів розглянутої частини Вихід: номер символу - останньої операції }function LastOperation ( Expr: string; first, last: integer): integer;var MinPrt, i, k, prt: integer;begin MinPrt := 100; for i:=first to last do begin prt := Priority ( Expr[i] ); if prt <= MinPrt then begin MinPrt := prt; k := i; end; end; Result := k;end;

перевіряємо всі символиперевіряємо всі символи

повернути номер символу

повернути номер символу

знайшли операцію з мінімальним пріоритетом

знайшли операцію з мінімальним пріоритетом

Page 100: Pascal (динамічні структури даних)

100

Побудова дерева

Структура вузла

type PNode = ^Node; Node = record data: char; left, right: PNode; end;

type PNode = ^Node; Node = record data: char; left, right: PNode; end;

Створення вузла для числа (без нащадків)

function NumberNode(c: char): PNode;begin New(Result); Result^.data := c; Result^.left := nil; Result^.right := nil;end;

function NumberNode(c: char): PNode;begin New(Result); Result^.data := c; Result^.left := nil; Result^.right := nil;end;

повертає адресу створено вузла

повертає адресу створено вузла

один символ, число

один символ, число

Page 101: Pascal (динамічні структури даних)

101

Побудова дерева{ Функція MakeTree – побудова дерева Вхід: рядок, номери першого і останнього символів розглянутої частини Вихід: адреса побудованого дерева }function MakeTree ( Expr: string; first, last: integer): PNode;var k: integer;begin if first = last then begin Result := NumberNode ( Expr[first] ); Exit; end; k := LastOperation ( Expr, first, last ); New(Result); Result^.data := Expr[k]; Result^.left := MakeTree ( Expr, first, k-1 ); Result^.right := MakeTree ( Expr, k+1, last );end;

{ Функція MakeTree – побудова дерева Вхід: рядок, номери першого і останнього символів розглянутої частини Вихід: адреса побудованого дерева }function MakeTree ( Expr: string; first, last: integer): PNode;var k: integer;begin if first = last then begin Result := NumberNode ( Expr[first] ); Exit; end; k := LastOperation ( Expr, first, last ); New(Result); Result^.data := Expr[k]; Result^.left := MakeTree ( Expr, first, k-1 ); Result^.right := MakeTree ( Expr, k+1, last );end;

залишилося тільки числозалишилося тільки число

новий вузол: операціяновий вузол: операція

Page 102: Pascal (динамічні структури даних)

102

Обчислення виразів по дереву{ Функція CalcTree – обчислення по дереву Вхід: адреса дерева Вихід: значення виразу }function CalcTree(Tree: PNode): integer;var num1, num2: integer;begin if Tree^.left = nil then begin Result := Ord(Tree^.data) - Ord('0'); Exit; end; num1 := CalcTree(Tree^.left); num2 := CalcTree(Tree^.right); case Tree^.data of '+': Result := num1+num2; '-': Result := num1-num2; '*': Result := num1*num2; '/': Result := num1 div num2; else Result := MaxInt; end;end;

{ Функція CalcTree – обчислення по дереву Вхід: адреса дерева Вихід: значення виразу }function CalcTree(Tree: PNode): integer;var num1, num2: integer;begin if Tree^.left = nil then begin Result := Ord(Tree^.data) - Ord('0'); Exit; end; num1 := CalcTree(Tree^.left); num2 := CalcTree(Tree^.right); case Tree^.data of '+': Result := num1+num2; '-': Result := num1-num2; '*': Result := num1*num2; '/': Result := num1 div num2; else Result := MaxInt; end;end;

повернути число, якщо це лист

повернути число, якщо це лист

обчислюємо операнди

(піддерева)

обчислюємо операнди

(піддерева)

виконуємо операцію

виконуємо операцію

некоректна операція

некоректна операція

Page 103: Pascal (динамічні структури даних)

103

Основна програма

{ Введення і обчислення виразу за допомою дерева }program qq;var Tree: PNode; Expr: string;{ процедури і функції }begin write('Введіть вираз > '); readln( Expr ); Tree := MakeTree( Expr, 1, Length(Expr) ); writeln(' = ', CalcTree(Tree) );end.

{ Введення і обчислення виразу за допомою дерева }program qq;var Tree: PNode; Expr: string;{ процедури і функції }begin write('Введіть вираз > '); readln( Expr ); Tree := MakeTree( Expr, 1, Length(Expr) ); writeln(' = ', CalcTree(Tree) );end.

Page 104: Pascal (динамічні структури даних)

104

Дерево ігри

Завдання. Перед двома гравцями лежать дві купки каміння, у першій з яких 3, а в другій – 2 камені. У кожного гравця необмежено багато каменів.

Гравці ходять по черзі. Хід полягає в том, що гравець або збільшує в 3 рази кількість каменів в якійсь купі, або додає 1 камінь в якусь купу.

Виграє гравець, після ходу якого загальна кількість каменів у двох купах стає не менше 16.

Хто виграє при безпомилковій грі – гравець, що робить перший хід, чи гравець, який робить другий хід? Як повинен ходити гравець що виграв?

Page 105: Pascal (динамічні структури даних)

105

Дерево ігри

3, 23, 2

гравецьгравець 1 1

3, 63, 6

27, 227, 227, 227, 2

3, 183, 183, 183, 18

3, 33, 3

4, 24, 2

12, 212, 2

4, 64, 6

5, 25, 2

4, 34, 3

9, 39, 3

4, 34, 3

3636, , 223636, , 22

44, , 181844, , 1818

1515, , 221515, , 22

12, 212, 2

4, 64, 6

5, 35, 3

4, 44, 4

3636, 2, 23636, 2, 2

1212, , 661212, , 66

1515, , 331515, , 33

1212, , 441212, , 44

27, 327, 327, 327, 3

При правильній грі виграє гравець 2гравець 2! При правильній грі виграє гравець 2гравець 2!!!

гравецьгравець 1 1гравець 2гравець 2 гравецьгравець 2 2

9, 29, 2

4, 34, 3

4, 34, 3

ключовий хід

ключовий хід

виграв гравець 1гравець 1

виграв гравець 1гравець 1

Page 106: Pascal (динамічні структури даних)

106

Завдання

«4»: «Зібрати» програму для обчислення правильного арифметичного виразу, що включає тільки однозначні числа і знаки операцій +, -, * , /.

«5»: Те ж саме, але допускаються також багатозначні числа і дужки.

«6»: Те ж саме, що і в «5», але з обробкою помилок (повинно виводитися повідомлення).

Page 107: Pascal (динамічні структури даних)

Тема 7. Графи

Динамічніструктури даних(мова Паскаль)

© К.Ю. Поляков, 2008-2010

Переклад: Р. М. Васильчик

Page 108: Pascal (динамічні структури даних)

108

Визначення

Граф – це набір вершин (вузлів) і з'єднуючих їх ребер (дуг).

Спрямований граф (орієнтований, орграф) – це граф, в якому

всі дуги мають напрями.

Ланцюг – це послідовність ребер, що з'єднують дві вершини (в орграфі – шлях).

Цикл – це ланцюг з якоїсь вершини в неї саму.

Зважений граф (мережа) – це граф, в якому кожному ребру приписується вага (довжина).

5533

22

44

11

33

22

44

11

Дерево – це граф? Дерево – це граф???Так, без циклів!Так, без циклів!

Page 109: Pascal (динамічні структури даних)

109

ВизначенняЗв'язний граф – це граф, в якому існує ланцюг між кожною парою

вершин.

k-зв'язний граф – це граф, який можна розбити на k зв'язних частин.

Повний граф – це граф, в якому проведено всі можливі ребра

(n вершин → n(n-1)/2 ребер).

5533

22

44

11

88

66

77

22

11

3322

11

33

44

Page 110: Pascal (динамічні структури даних)

110

Опис графа

Матриця суміжності – це матриця, елемент M[i][j] якої

дорівнює 1, якщо існує ребро з вершини i в вершину j, і

дорівнює 0, якщо такого ребра немає.

5533

22

44

11 0 1 1 1 0

1 0 0 1 1

1 0 0 0 0

1 1 0 0 1

0 1 0 1 0

1 2 3 4 5

1

2

3

4

5

5533

22

44

11 0 1 1 1 0

0 0 0 1 1

0 0 0 0 0

0 0 0 0 1

0 0 0 0 0

1 2 3 4 5

1

2

3

4

5

Симетрія! Симетрія!!!

Список суміжності

2 3 4

1 4 5

1

1 2 5

2 4

1

2

3

4

5

2 3 4

4 5

5

1

2

3

4

5

Page 111: Pascal (динамічні структури даних)

111

Матриця і список суміжності

1 2 3 4 5

1

2

3

4

5

1

2

3

4

5

2211

5533

44

1 2 3 4 5

1

2

3

4

5

1

2

3

4

5

11

5533

44

22

Page 112: Pascal (динамічні структури даних)

112

Побудова графа по матриці суміжності

0 0 1 0 0

0 0 1 0 1

1 1 0 1 0

0 0 1 0 1

0 1 0 1 0

1 2 3 4 5

1

2

3

4

5

0 0 1 1 1

0 1 0 1 0

0 1 0 1 0

1 1 0 0 0

0 1 1 0 0

1 2 3 4 5

1

2

3

4

5

1

2

3

4

5

1

2

3

4

5

11

33

55 22

44

11

33

55 22

44

Page 113: Pascal (динамічні структури даних)

113

Як виявити ланцюги і цикли?

Завдання: визначити, чи існує ланцюг довжини k з вершини i в

вершину j (або цикл довжиною k з вершини i в неї саму).

M2[i][j]=1, если M[i][1]=1 і M[1][j]=1 абоM[i][2]=1 і M[2][j]=1 абоM[i][3]=1 і M[3][j]=1

рядок iрядок i

логічне множення

логічне множення

стовпець jстовпець j

логічне додавання

логічне додавання

11

33

44

220 0 1 0

1 0 0 0

0 1 0 1

1 0 0 0

1 2 3 4

1

2

3

4

M =

абоM[i][4]=1 і M[4][j]=1

Page 114: Pascal (динамічні структури даних)

114

Як виявити ланцюги і цикли?

M2 = M MM2 = M M

Логічне множення матриці на себе:

матриця шляхів довжини 2

матриця шляхів довжини 2

0 0 1 0

1 0 0 0

0 1 0 1

1 0 0 0

M2 = 0 0 1 0

1 0 0 0

0 1 0 1

1 0 0 0

=

0 1 0 1

0 0 1 0

11 0 0 0

0 0 1 0

11

33

44

22

1 2 3 4

1

2

3

4

M2[3][1] = 0·0 + 1·1 + 0·0 + 1·1 = 1

маршрут 3-2-1маршрут 3-2-1 маршрут 3-4-1маршрут 3-4-1

Page 115: Pascal (динамічні структури даних)

115

Як виявити ланцюги і цикли?

M3 = M2 MM3 = M2 M

Матриця шляхів довжини 3:

0 0 1 0

1 0 0 0

0 1 0 1

1 0 0 0

M3 = =

11 0 0 0

0 11 0 1

0 0 11 0

0 1 0 11

11

33

44

22

1 2 3 4

1

2

3

4

0 1 0 1

0 0 1 0

1 0 0 0

0 0 1 0

на головній діагоналі –

цикли!

на головній діагоналі –

цикли!

0 0 1 0

1 0 0 0

0 1 0 1

1 0 0 0

M4 = =

00 00 11 00

11 00 00 00

00 11 00 11

11 00 00 00

1 2 3 4

1

2

3

4

1 0 0 0

0 1 0 1

0 0 1 0

0 1 0 1

Page 116: Pascal (динамічні структури даних)

116

Вагова матриця

5533

22

44

113

5

7

4

6

8

5533

22

44

113

5

7

4

6

8

Вагова матриця – це матриця, елемент W[i,j] якої дорівнює

вазі ребра з вершини i в вершину j (якщо воно є), або дорівнює

∞, якщо такого ребра немає.

0 7 3 5 ∞

7 0 ∞ 4 8

3 ∞ 0 ∞ ∞

5 4 ∞ 0 6

∞ 8 ∞ 6 0

1 2 3 4 5

1

2

3

4

5

0 7 3 5 ∞

∞ 0 ∞ 4 8

3 ∞ 0 ∞ ∞

5 ∞ ∞ 0 6

∞ 8 ∞ ∞ 0

1 2 3 4 5

1

2

3

4

5

Page 117: Pascal (динамічні структури даних)

117

Задача Прима-Краскала

Завдання: з'єднати N міст телефонною мережею так, щоб довжина телефонних ліній була мінімальною.

Те ж завдання: дано зв'язний граф з N вершинами, веги

ребер задані ваговою матрицею W. Потрібно знайти набір ребер, що з'єднує всі вершини графа (остовне дерево) і має найменшу вагу.

5533

22

44

113

5

7

4

6

8

0 7 3 5 ∞

7 0 ∞ 4 8

3 ∞ 0 ∞ ∞

5 4 ∞ 0 6

∞ 8 ∞ 6 0

1 2 3 4 5

1

2

3

4

5

Page 118: Pascal (динамічні структури даних)

118

Жадібний алгоритмЖадібний алгоритм – це багатокроковий алгоритм, в якому на

кожному кроці приймається рішення, краще в даний момент.

В цілому може вийти не оптимальне рішення (послідовність кроків)!

В цілому може вийти не оптимальне рішення (послідовність кроків)!!!

Крок в задачі Прима-Краскала – це вибір ще невибраного ребра і додавання його до рішення.

5533

22

44

113

5

7

4

6

8 В задачі Прима-Краскала жадібний алгоритм дає оптимальне рішення!

В задачі Прима-Краскала жадібний алгоритм дає оптимальне рішення!

!!

Page 119: Pascal (динамічні структури даних)

119

Реалізація алгоритму Прима-КраскалаПроблема: як перевірити, що

1) ребро не вибрано, і 2) ребро не утворює циклу з вибраними ребрами.

Рішення: присвоїти кожній вершині свій колір і перефарбовувати вершини при додаванні ребра.

5533

22

44

11113

5

7

4

6

8

3333

2222

44 5555

Алгоритм: 1) пофарбувати всі вершини в різні кольори;

2) зробити N-1 раз в циклі: вибрати ребро (i,j) мінімальної довжини з усіх ребер,

що з'єднують вершини різного кольору; перефарбувати всі вершини, що мають колір j, в колір i.

3) вивести знайдені ребра.

4444

Page 120: Pascal (динамічні структури даних)

120

Реалізація алгоритму Прима-Краскала

Структура «ребро»:type rebro = record i, j: integer; { номери вершин } end;

type rebro = record i, j: integer; { номери вершин } end;

const N = 5;var W: array[1..N,1..N] of integer; Color: array[1..N] of integer; i, j, k, min, col_i, col_j: integer; Reb: array[1..N-1] of rebro;begin... { тут треба ввести матрицю W }for i:=1 to N do { розфарбувати в різні кольори } Color[i] := i;... { основний алгоритм – заповнення масиву Reb }... { вивести знайдені ребра (масив Reb)}end.

const N = 5;var W: array[1..N,1..N] of integer; Color: array[1..N] of integer; i, j, k, min, col_i, col_j: integer; Reb: array[1..N-1] of rebro;begin... { тут треба ввести матрицю W }for i:=1 to N do { розфарбувати в різні кольори } Color[i] := i;... { основний алгоритм – заповнення масиву Reb }... { вивести знайдені ребра (масив Reb)}end.

Основна програма: вагова матрицявагова

матрицяколір

вершин

колір верши

н

Page 121: Pascal (динамічні структури даних)

121

Реалізація алгоритму Прима-Краскала

for k:=1 to N-1 do begin

min := MaxInt;

for i:=1 to N do for j:=i+1 to N do if (Color[i] <> Color[j]) and (W[i,j] < min) then begin min := W[i,j]; Reb[k].i := i; Reb[k].j := j; col_i := Color[i]; col_j := Color[j]; end;

for i:=1 to N do if Color[i] = col_j then Color[i] := col_i;end;

for k:=1 to N-1 do begin

min := MaxInt;

for i:=1 to N do for j:=i+1 to N do if (Color[i] <> Color[j]) and (W[i,j] < min) then begin min := W[i,j]; Reb[k].i := i; Reb[k].j := j; col_i := Color[i]; col_j := Color[j]; end;

for i:=1 to N do if Color[i] = col_j then Color[i] := col_i;end;

Основний алгоритм: потрібно вибрати всього N-1 ребропотрібно вибрати всього N-1 ребро

цикл по всіх парах вершинцикл по всіх

парах вершин

враховуємо тільки пари з різним

кольором вершин

враховуємо тільки пари з різним

кольором вершин

запам'ятовуємо ребра і кольори вершин

запам'ятовуємо ребра і кольори вершин

перефарбовуєм вершини кольору col_j

перефарбовуєм вершини кольору col_j

Page 122: Pascal (динамічні структури даних)

122

Складність алгоритмуОсновний цикл:

O(N3)O(N3)

for k:=1 to N-1 do begin

...

for i:=1 to N do for j:=i+1 to N do

...

end;

for k:=1 to N-1 do begin

...

for i:=1 to N do for j:=i+1 to N do

...

end;

три вкладених цикли, в кожному

кількість кроків <=N

три вкладених цикли, в кожному

кількість кроків <=N

зростає не швидше, ніж N3

Необхідна пам'ять:

var W: array[1..N,1..N] of integer; Color: array[1..N] of integer; Reb: array[1..N-1] of rebro;

var W: array[1..N,1..N] of integer; Color: array[1..N] of integer; Reb: array[1..N-1] of rebro;

O(N2)O(N2)

Кількість операцій:

Page 123: Pascal (динамічні структури даних)

123

Найкоротші шляхи (алгоритм Дейкстри)Завдання: задана мережа доріг між містами, частина яких можуть

мати односторонній рух. Знайти найкоротші відстані від заданого міста до всіх інших міст.

Та же завдання: дано зв'язний граф з N вершинами, ваги ребер

задані матрицею W. Знайти найкоротші відстані від заданої вершини до всіх інших.

1) присвоїти всім вершинам мітку ∞;2) серед нерозглянутих вершин знайти

вершину j з найменшою міткою;

3) для кожної необробленої вершини i:

якщо шлях до вершини i через вершину

j менше існуючої мітки, замінити мітку на нову відстань;

4) якщо залишилися необроблені вершини, перейти до кроку 2;

5) мітка = мінімальна відстань.

7

55

33

22

44

11

66

10 15

11

6

9

2

914

Алгоритм Дейкстри (E.W. Dijkstra, 1959)

Page 124: Pascal (динамічні структури даних)

124

Алгоритм Дейкстри

7

55

33

22

44

11

66

10 15

11

6

9

2

914

∞∞

0 7

55

33

22

44

1111

66

10 15

11

6

9

2

914

∞14

7

9

0 7

55

33

2222

44

1111

66

10 15

11

6

9

2

914

22

∞14

7

9

0

7

55

3333

2222

44

1111

66

10 15

11

6

9

2

914

20

∞11

7

9

0 7

55

3333

2222

44

1111

6666

10 15

11

6

9

2

914

20

2011

7

9

0 7

5555

3333

2222

44

1111

6666

10 15

11

6

9

2

914

20

2011

7

9

0

Page 125: Pascal (динамічні структури даних)

125

Реалізація алгоритму ДейкстриМасиви:

1) масив a, такий що a[i]=1, якщо вершина вже розглянута, і a[i]=0, якщо ні.

2) масив b, такий що b[i] – довжина поточного найкоротшого шляху з заданої вершини x в вершину i;

3) масив c, такий що c[i] – номер вершини, з якої потрібно йти в вершину i в поточному найкоротшому шляху.

Ініціалізація:1) заповнити масив a нулями (вершини не оброблені);2) записати в b[i] значення W[x][i];3) заповнити масив c значеннями x;4) записати a[x]=1.

7

55

33

22

44

1111

66

10 15

11

6

9

2

914

∞14

7

9

0

1 0 0 0 0 0

0 7 9 ∞ ∞ 14

0 0 0 0 0 0

a

b

c

1 2 3 4 5 6

Page 126: Pascal (динамічні структури даних)

126

Реалізація алгоритму ДейкстриОсновний цикл:

1) якщо всі вершини розглянуті, то стоп.

2) серед всіх нерозглянутих вершин (a[i]=0) знайти вершину

j, для якої b[i] – мінімальне;

3) записати a[j]:=1;

4) для всіх вершин k: якщо шлях в вершину k через вершину j коротший, ніж знайдений раніше найкоротший шлях,

запам'ятати його: записати b[k]:=b[j]+W[j,k] і

c[k]=j.

1 1 0 0 0 0

0 77 9 22 ∞ 14

0 0 0 1 0 0

a

b

c

1 2 3 4 5 6

Крок 1:

7

55

33

2222

44

1111

66

10 15

11

6

9

2

914

22

∞14

7

9

0

Page 127: Pascal (динамічні структури даних)

127

Реалізація алгоритму Дейкстри

1 1 1 0 0 0

0 7 99 20 ∞ 11

0 0 0 2 0 2

a

b

c

1 2 3 4 5 6

Крок 2:

7

55

3333

2222

44

1111

66

10 15

11

6

9

2

914

20

∞11

7

9

0

1 1 1 0 0 1

0 7 9 20 20 1111

0 0 0 2 5 2

a

b

c

1 4 3 4 5 6Крок 3:

7

55

3333

2222

44

1111

6666

10 15

11

6

9

2

914

20

2011

7

9

0 Далі масиви не змінюються!

Далі масиви не змінюються!!!

Page 128: Pascal (динамічні структури даних)

128

Як вивести маршрут?

1 1 1 1 1 1

0 7 9 20 20 11

0 0 0 2 5 2

a

b

c

1 2 3 4 5 6

Результат роботи алгоритму Дейкстри:

довжини шляхів

довжини шляхів

Маршрут з вершини 0 в вершину 4:

44

0 5 2

55 22 00

Складність алгоритму Дейкстри:

O(N2)O(N2)два вкладених цикли по N кроків

Виведення маршруту в вершину i (використання масиву c):

1) встановити z:=i;

2) поки c[i]<>x присвоїти z:=c[z] і вивести z.

Page 129: Pascal (динамічні структури даних)

129

Алгоритм Флойда-УоршеллаЗавдання: задана мережа доріг між містами, частина яких може

мати односторонній рух. Знайти всі найкоротші відстані, від кожного міста до всіх інших міст.

for k: =1 to N for i: = 1 to N for j: = 1 to N if W[i,j] > W[i,k] + W[k,j] then W[i,j] := W[i,k] + W[k,j];

for k: =1 to N for i: = 1 to N for j: = 1 to N if W[i,j] > W[i,k] + W[k,j] then W[i,j] := W[i,k] + W[k,j];

ii jj

kk

W[i,j]

W[i,k] W[k,j] Якщо з вершини i в

вершину j коротше їхати

через вершину k, ми їдемо

через вершину k!

Якщо з вершини i в

вершину j коротше їхати

через вершину k, ми їдемо

через вершину k!

Немає інформації про маршрут, тільки найкоротші відстані!

Немає інформації про маршрут, тільки найкоротші відстані!!!

Page 130: Pascal (динамічні структури даних)

130

Алгоритм Флойда-Уоршелла

Версія з запам'ятовуванням маршруту:

for i:= 1 to N for j := 1 to N c[i,j] := i; ...for k: =1 to N for i: = 1 to N for j: = 1 to N if W[i,j] > W[i,k] + W[k,j] then begin W[i,j] := W[i,k] + W[k,j]; c[i,j] := c[k,j]; end;

for i:= 1 to N for j := 1 to N c[i,j] := i; ...for k: =1 to N for i: = 1 to N for j: = 1 to N if W[i,j] > W[i,k] + W[k,j] then begin W[i,j] := W[i,k] + W[k,j]; c[i,j] := c[k,j]; end;

i–ий рядок будується

так само, як масив c в алгоритмі Дейкстри

i–ий рядок будується

так само, як масив c в алгоритмі Дейкстри

в кінці циклу c[i,j] – передостання вершина в найкоротшому маршруті

з вершини i в вершину j

в кінці циклу c[i,j] – передостання вершина в найкоротшому маршруті

з вершини i в вершину j

c[i,j] := c[k,j];

Яка складність алгоритму?

Яка складність алгоритму???

O(N3)O(N3)

Page 131: Pascal (динамічні структури даних)

131

Завдання комівояжера

Завдання комівояжера. Комівояжер (бродячий торговець) повинен вийти з першого міста і, відвідавши по разу в невідомому порядку

міста 2,3,...N, повернуться назад в перше місто. У якому порядку треба обходити міста, щоб замкнутий шлях (тур) комівояжера був найкоротший?

Це NP-повна задача, яка строго вирішується тільки перебором варіантів (поки що)!

Це NP-повна задача, яка строго вирішується тільки перебором варіантів (поки що)!!!Точні методи:

1) простий перебір;2) метод гілок і меж;3) метод Літтла;4) …

Наближені методи:1) метод випадкових перестановок (Matlab);2) генетичні алгоритми;3) метод мурашиних колоній;4) …

великий час рахунку для

великих N O(N!)O(N!)

не гарантується оптимальне рішення

Page 132: Pascal (динамічні структури даних)

132

Інші класичні завдання

Завдання на мінімум суми. Є N населених пунктів, у кожному з

яких живе pi школярів (i=1,...,N). Треба розмістити школу в

одному з них так, щоб загальна відстань, яку проходять всі учні по дорозі в школу, була мінімальною.

Завдання про найбільший потік. Є система труб, які мають

з'єднання в N вузлах. Один вузол S є джерелом, ще один –

стоком T. Відомі пропускні спроможності кожної труби. Треба знайти найбільший потік від джерела до стоку.

Завдання про найбільше паросполучення. Є M чоловіків і N

жінок. Кожен чоловік вказує декілька (від 0 до N) жінок, на яких він

згоден одружуватися. Кожна жінка вказує кілька чоловіків (від 0

до M), за яких вона згодна вийти заміж. Потрібно укласти найбільшу кількість моногамних шлюбів.

Page 133: Pascal (динамічні структури даних)

133

Кінець фільму