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

C _Учебник_МОНУ

.pdf
Скачиваний:
206
Добавлен:
12.05.2015
Размер:
11.12 Mб
Скачать

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

479

Права гілка вже є, порівнюємо 20 і 25. 20<25, тому викликаємо функцію від лівого піддерева відносно елемента зі значенням 25:

search_insert(root->right->left, 20)

Створюємо новий елемент.

4)Викликаємо для 6: search_insert(root, 6)

Корінь є, тому порівнюємо 6 і 10. Оскільки 6<10, викликаємо цю ж саму функцію для лівого піддерева:

search_insert(root->left, 6)

Створюємо новий елемент.

5)Викликаємо для 21: search_insert(root, 21)

Корінь є, тому порівнюємо 21 і 10. Оскільки 21>10, викликаємо цю ж саму функцію для правого піддерева:

search_insert(root->right, 21)

Права гілка вже є, тому порівнюємо 21 і 25. 21<25, тому викликаємо функцію від лівого піддерева відносно елемента зі значенням 25:

search_insert(root->right->left, 21)

Там вже є елемент зі значенням 20, тому порівнюємо 21 і 20. Оскільки 21>20, тому викликаємо функцію від правого піддерева відносно елемента зі значенням 20:

search_insert(root->right->left->right, 20)

Створюємо новий елемент.

Далі продовжуємо рекурсивно викликати функції від лівого й правого піддерев, допоки не дістанемось кінця гілки (вузол=0 ). Тоді створюємо новий вузол.

Функція пошуку елемента в дереві: int Find(Node *root, int x)

{if (!root) return 0;

else if (root->d==x) return 1;

else if (x < root->d) return Find(root->left, x); else return Find(root->right, x);

}

Вилучення вузла з дерева являє собою не таке просте завдання, оскільки вузол, що вилучається, може бути кореневим, містити два (рис. 13.2, в), одне (рис. 13.2, б) чи не містити посилання (рис. 13.2, а) на піддерева. Для вузлів, які містять менше двох посилань, вилучення є тривіальне. Щоб зберегти впорядкованість дерева при вилученні вузла із двома посиланнями, його замінюють на вузол з найближчим до нього ключем. Це може бути крайній лівий вузол його правого піддерева чи то крайній правий вузол лівого піддерева (наприклад, щоб вилучити з дерева на рис. 13.1 вузол із ключем 25, його треба замінити на 21 або 30, вузол 10 замінити на 20 або 8).

480

Розділ 13

Рис. 13.2. Різні випадки вилучення вузлів дерева:

a) вилучення вузла без нащадків (листа зі значенням 13);

б) вилучення вузла зі значенням 16, в якого існує лише лівий або правий нащадок (одне посилання);

в) вилучення листа зі значенням 6, в якого існують обидва нащадки (два посилання).

Розглянемо докладно алгоритм функції Del_el(), яка вилучає вузол із заданим значенням з дерева. Відшукуємо вузол із заданим значенням. Якщо його буде віднайдено, функція вилучить його, або повідомить, що елемента немає. Якщо шукане значення є менше за значення вузла, викликаємо функцію від лівого піддерева, якщо більше – від правого піддерева. Якщо значення збігаються, функція запам‟ятовує віднайдений вузол у вказівнику Р і перевіряє наявну кількість посилань від цього вузла. Якщо посилання є лише одне (root->left чи root->right становить нуль), вузол, який підлягає вилученню, замінюється на існуючого лівого чи правого нащадка. Якщо посилання є два, переходимо до лівого піддерева і відшукуємо крайній правий його елемент. На цей елемент замінюємо вузол, який вилучається.

Node * Del_el(Node *root, int x)

{Node* P, *v;

if(!root) { ShowMessage("Елемента нема"); return 0; } if(x < root->d) root->left = Del_el(root->left, x); else

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

481

if(x > root->d) root->right = Del_el(root->right, x); else

{ P = root;

if (!root->right)

//Випадок 2: праве піддерево не існує,

root = root->left;

//лівий елемент стає на місце root.

else

 

if (!root->left)

//Випадок 2: ліве піддерево не існує,

root = root->right;

//правий елемент стає на місце root.

else

//Випадок 3: існують два піддерева.

{ v = root->left;

//Перехід на лівий вузол,

while(v->right)

//проходження праворуч до кінця.

{P = v; v = v->right;} //P – попередній вузол.

root->d = v->d;

//Змінювання значення вузла root.

if(P!=root)

//Якщо Р дорівнює root, долучення праворуч

P->right = v->left;

//елементів з лівого піддерева v

else P->left=v->left;

//або долучення їх ліворуч.

}

 

//Звільнення пам‟яті від крайнього правого елемента (на який замінено root) delete v;

}

return root;

}

На поданому нижче рисунку схематично зображено вилучення вузла зі значенням 6

 

 

 

10

 

 

 

 

root

 

 

 

 

 

6

 

 

25

 

root->d=v->d

 

 

 

1

 

 

8

20

30

 

 

P

 

 

 

0

3

7

9

 

21

v

2 5

P->right=v->left

4

Вузол root має два піддерева. Переходимо на ліве (1). Йдемо лівим піддеревом до крайнього правого елемента (5). Замінюємо значення root (6) на 5. Перш ніж звільнити пам‟ять від елемента зі значенням 5, долучаємо його ліву гілку до попереднього елемента у якості правої гілки.

482

Розділ 13

Приклад 13.10 Створити дерево – українсько-англійський словник. Надати можливість швидкого пошуку українських слів для перекладання.

Розв‟язок. Кожен вузол дерева містить два інформаційні поля: український та англійський варіанти слова. Слова з перекладом заздалегідь запишемо у файлі vocabulary.txt і дерево пошуку створюватимемо за даними з файла. Домовимося, що у файлі кожне слово з перекладом займатиме окремий рядок. Український варіант буде розташовано на початку рядка, а англійський – після нього. Поміж словами будуть пробіл, тире і пробіл.

Наприклад, уміст файла vocabulary.txt може бути таким:

море - sea

ніч - night спати - sleep вікно - window

розмовляти - speak вільний - free

чай - tea

сніданок - breakfast школа - school футбол - football час - time

апельсин - orange

Форма додатка з результатами роботи матиме вигляд

Текст програми:

FILE* f;

char fn[]="vocabulary.txt";

char* v=0;

// Порожній рядок

struct Node

 

{ char ukr[20];

// Слово українською мовою

char eng[20];

// Слово англійською мовою

Node *left; Node *right; };

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

483

Node* root=0;

Node * first(char ukr[20], char eng[20])

{Node *pv = new Node; strcpy(pv->ukr, ukr); strcpy(pv->eng, eng); pv->left = 0; pv->right = 0;

return pv;

}

// Пошук з долученням

Node * search_insert(Node *root, char ukr[20], char eng[20])

{if(!root)

{root = new Node; strcpy(root->ukr, ukr);

strcpy(root->eng, eng); root->left=0; root->right=0;

}

else

if(strcmp(ukr, root->ukr)<0) root->left=search_insert(root->left, ukr, eng);

else root->right=search_insert(root->right, ukr, eng); return root;

}

//Пошук слова українською мовою

char* Find(Node *root, char ukr[20])

{if(!root) return v; else

if(strcmp(root->ukr,ukr)==0) //Якщо слово віднайдено,

return root->eng;

//повертання його англійського перекладу

else

 

if(strcmp(ukr,root->ukr)<0) return Find(root->left, ukr);

else

return Find(root->right, ukr);

}

// Виведення слів з дерева до Memo2 (слова відсортовано за алфавітом) void print_tree(Node *p)

{AnsiString S; if(p)

{print_tree(p->left);

S=AnsiString(p->ukr)+" - "+AnsiString(p->eng); Form1->Memo2->Lines->Add(S); print_tree(p->right);

}

}

// Звільнення пам‟яті від дерева void ochistka(Node* p)

{if(p)

{ ochistka(p->left);

484

Розділ 13

ochistka(p->right); delete p;

}

}

// Кнопка “Створити дерево”

void __fastcall TForm1::Button1Click(TObject *Sender)

{char s[50], ukr[21], eng[21]; int i; if((f=fopen(fn,"rt"))==0)

{ShowMessage("Неможливо відкрити файл"); return;} while(fgets(s, 50, f))

{if(s[strlen(s)-1]=='\n') s[strlen(s)-1]='\0'; Memo1->Lines->Add(AnsiString(s));

for (i=0; i<strlen(s) && s[i]!=' '; i++);

strncpy(ukr, s, i); ukr[i]='\0';

// Копіювання слова українською

strcpy(eng, &s[i+3]);

// Копіювання слова англійською

if(root==0)root = first(ukr, eng);// Долучення слова до дерева else search_insert(root, ukr, eng);

}

fclose(f); print_tree(root);

}

// Кнопка “Перекласти”

void __fastcall TForm1::Button2Click(TObject *Sender)

{char ukr[21], eng[21]; strcpy(ukr, Edit1->Text.c_str()); strcpy(eng,Find(root, ukr));

if(strlen(eng)==0)ShowMessage("Слово у словнику є відсутнє");

else Edit2->Text=AnsiString(eng);

}

13.7 Автомати

13.7.1 Поняття автомата

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

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

Означення скінченого автомата (в англійській літературі використовується також поняття finite state mashine – машина скінчених станів) було наведено

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

485

1955 р. Джорджем Х. Мілі й 1956 р. Едвардом Ф. Муром. Автомати Мілі та Мура дещо відрізнялися один від одного, але ці відмінності є неістотні. Наведемо означення, більш подібне до автомата Мілі.

Автоматом називається п‟ятірка M = (S, X, Y, F, s0),

де S – непорожня скінчена множина, елементи якої (s1, s2, s3, …) називаються станами автомата;

X – непорожня скінчена множина, яка називається вхідним алфавітом автомата;

Y – непорожня скінчена множина, яка називається вихідним алфавітом автомата;

F – відображення F: S × X → S × Y; s0 – початковий стан автомата.

Кожен автомат має скінчену множину станів. Автомат переходить з одного стану до іншого під впливом послідовності літер вхідного алфавіту. На момент запуску він перебуває у початковому стані s0. На вхід автомата поступає перший елемент вхідної послідовності x1, й відображення перетворює пару (s0, x1) до пари (s1, y1). Елемент s1 стає новим станом автомата, а елемент y1 – першим елементом вихідної послідовності. Отже, автомат перетворює певну вхідну послідовність у вихідну.

Алфавіт автомата – це скінчена множина певних символів. Наприклад, множина {1, 2} – це алфавіт, який складається з одиниці та двійки, множина {A, B, …, Z}– це алфавіт великих латинських літер.

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

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

О – відчинити двері (від англ. open – відчинити);

С – зачинити двері (від англ. close – зачинити);

D – спуститися на перший поверх (від англ. down – донизу);

U – піднятися до другого поверху (від англ. up – догори).

Окрім того, ліфт може виконувати комбінацію вище наведених команд, наприклад: “СUО”, що означає: “зачинити двері”, “піднятися до другого поверху”, “відчинити двері”. Але існують команди, які для такого ліфта будуть некоректними. Приміром, не можна рухатися з відчиненими дверима чи підніматися на другий поверх, коли ліфт уже перебуває на другому поверсі.

Тепер наведемо чотири можливі стани ліфта:

CD – перший поверх, двері зачинено;

OD – перший поверх, двері відчинено;

CU – другий поверх, двері зачинено;

OU – другий поверх, двері відчинено.

486

Розділ 13

Вважатимемо, що спочатку ліфт (автомат) перебуває на першому поверсі, його двері є відчинено, тобто початковий стан ліфту є “СD”.

Завдання полягає у тому, щоб змоделювати такий ліфт на комп‟ютері, тобто написати програму, спроможну виконувати команди ліфта (чи то повідомляти, що команда є некоректна).

Кожна з команд переводитиме ліфт із одного стану до іншого, приміром, якщо ліфт перебував у стані “СU” і надійшла команда “D”, то ліфт перейде до стану “СD”.

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

O

OD CD C

U D

O

OU CU

C

На цій схемі чотирма кружечками позначено стани автомата (початковим станом є стан “ОD” – перший поверх, двері відчинено), дугами зі стрілками – переходи з одного стану до іншого, текстовим підписом біля дуг – команди, виконувані ліфтом. Наприклад, зі стану “ОU” за команди “С” автомат перейде до стану

СU”.

Ідентичним до графічного поданням автомата є так звана “таблиця правил” (чи “таблиця переходів”):

Стан

 

Команди

 

O

C

U

D

 

CD

OD

CU

OD

CD

CU

OU

CD

OU

CU

Якщо ліфт перебуває у стані “СD” і надійшла команда “О”, то ліфт відчинить двері і, згідно до таблиці, перейде до стану “ОD” (перший поверх, двері відчинено).

Отже розглянутий автомат здатен опрацьовувати команди: “О”, “С”, “U”, “D”. Ця множина формує алфавіт наведеного автомата, тобто набір можливих опрацьовуваних автоматом даних: {O, C, D, U}.

З алфавітних символів можна формувати рядки, приміром, “СUО” означає три послідовні команди: “зачинити двері”, “піднятися до другого поверху” і “відчинити двері”. Для виконання цих трьох команд слід застосувати автомат тричі. За першого застосування автомат стартує з початкового стану “ОD”. Після

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

487

першого перетворення автомат змінить стан: зі стану “ОD” дугою “С” здійсниться перехід до стану “СD”. Після другого переходу зі стану “СD” за команди “U” автомат змінить свій стан на “СU”. Після третього переходу за команди “О” автомат перейде до стану “ОU”.

13.7.2 Синхронні автомати

Автомат називають синхронним, якщо він перетворює один елемент вхідного на один елемент вихідного алфавіту.

Програми, що реалізують синхронні автомати, можуть використовувати оператори циклу for для опрацювання послідовності елементів та вкладені в нього оператори switch та if для аналізу й перетворювання елементів.

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

Приклад 13.11 Автомат задано алфавітом X = Y ={0, 1} і трьома станами –

A, B, C:

А

0/1

В

 

 

 

0/0

1/0

1/0

1/1

0/1

С

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

Розв‟язок. По дугах автомата можна визначити, наприклад, що в стані А елемент 0 буде перетворено на 1 і при цьому автомат перейде до стану В, а елемент 1 перетвориться на 0 і автомат залишиться у тому ж самому стані А. Аналогічно інтерпретуються інші дуги. Цей автомат, заданий графічно, можна подати також у вигляді таблиці переходів:

Стан

Вхідний елемент

 

 

0

1

 

 

 

 

А

B,1

A,0

 

 

 

В

А,0

C,0

С

B,1

A,1

 

 

 

У наведеній програмі опрацювання вхідної послідовності реалізовано у функції. Зауважимо, що цю функцію можна реалізувати багатьма іншими способами. Стан автомата (А, В чи С) обирається послідовно для кожного з опрацьовуваних символів рядка за допомогою оператора switch.

На вхід автомата подається рядок символів з вхідного алфавіту. Кожен з цих символів буде перетворено згідно до команд переходу, при цьому автомат змінюватиме свій стан. Розглянемо послідовно дії автомата у кожному стані.

Якщо автомат перебуває у стані А можливі дві ситуації: 1) при опрацю-

//Початковий стан автомата

488

Розділ 13

ванні символу '0' автомат перейде до стану В і видасть символ '1'; 2) при опрацюванні символу '1' автомат залишиться у стані А і видасть '0'.

У стані В при опрацюванні символу '0' автомат перейде до стану А без зміни символу, а символ '1' перетворить на '0' і перейде до стану С.

Стан С для обох можливих символів ('0' чи '1') як результат видаватиме '1' і змінюватиме свій стан: для символу '0'автомат перейде до стану В, а '1' – до стану А.

Текст функції та програми дл я командної кнопки:

AnsiString Automat1(AnsiString S)

{ // Для визначення станів автомата використовується перераховний тип даних enum conditions{A, B, C};

conditions sit=A;

for(int i=1;i<=S.Length();i++) switch (sit)

{case A:{if (S[i]=='0') {S[i]='1'; sit=B;}else S[i]='0';break;} case B:{if (S[i]=='0') sit=A; else sit=C; S[i]='0';break;} case C:{if (S[i]=='0') sit=B; else sit=A; S[i]='1';break;}

}

return S;

}

void __fastcall TForm1::Button1Click(TObject *Sender)

{AnsiString S=Edit1->Text; Edit2->Text=Automat1(S); }

Приклад 13.12 Задано автомат алфавітом X = Y = {0, 1, 2} та двома станами

A та B. Ввести вхідну послідовність символів з алфавіту Х й здобути вихідну послідовність після дії заданого автомата.

 

 

 

1/0

1/1

А

0/1

В

2/0

 

 

 

 

0/1

2/2

Розв‟язок. Цей автомат, заданий графічно, можна подати також у вигляді таблиці переходів:

Стан

Вхідний елемент

0

1

2

 

А

B,1

A,1

A,0

 

 

 

 

В

A,1

B,0

A,2

 

 

 

 

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]