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

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

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

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

469

Функція вставлення нового елемента після елемента с1: void insert_el(Element* c1, int x)

{Element* c=new Element; c->d=x; c->next=c1->next; c1->next=c;

if (c1==last) last=c;

}

Функція вилучення зі списку елемента, який слідує після елемента с1: void del_el(Element* c1)

{Element* c=c1->next; c1->next=c->next;

if(c==first)

// Якщо вилучається перший елемент,

first=c->next;

// першим стає другий

if(c==last)

// Якщо вилучається останній елемент,

last=c1;

// останнім стає передостанній

delete c;

 

}

Функція звільнення пам‟яті від списку: void ochistka()

{Element* c; last->next=0; while(first!=0)

{c=first; first=first->next; delete c;

}

}

Приклад 13.8 Створити список з іменами хлопчиків та дівчаток. Створити файл з лічилками. Обрати у довільний спосіб одну з лічилок і полічити дітей. Вивести до Memo імена дітей з відповідним словом лічилки. Вилучити зі списку ім‟я того, хто вибув.

Розв‟язок. Оскільки імена дітей під час лічби змінюватимуться циклічно, то список слід обрати однозв‟язний циклічний.

Лічилки запишемо до файла lichilki.txt заздалегідь. У програмі буде згенеровано випадкове число і рядок з цим номером буде зчитано з файла. Саме цей рядок і буде обраною лічилкою. Рядок слід поділити на слова (це можна зробити, приміром, за допомогою функції strtok).

Уміст файла lichilki.txt, створеного у Блокноті:

Ходила квочка коло кілочка. Водила діточок біля квіточок! Квок!

Сітка, вітка, дуб, дубки – поставали козаки. Шабельками брязь – вийди, князь. Сидить жаба під корчем, зачинає рити. На кого слово це впаде, той буде жмурити. Еники-беники їли вареники. Еники-беники, квас: вийшов маленький Тарас.

470

Розділ 13

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

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

FILE* f;

char fn[]="lichilki.txt"; // Рядок з назвою файла

//Оголошення елемента списку struct Element

{ char name[15]; Element* next;

} *first=0, *last=0;

// Функція створювання першого елемента. Параметр – ім‟я (рядок) void fir(char * x)

{first=new Element; strcpy(first->name, x); first->next=first; last=first;

}

// Функція долучення елемента до списку. Параметр – ім‟я (рядок) void add_el(char* x)

{Element* c=new Element; strcpy(c->name,x); c->next=first; last->next=c;

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

471

last=c;

}

// Функція виведення списку до компонента memo void print_el(TMemo* memo)

{memo->Clear(); Element* c=first;

if (first==0) {ShowMessage("Empty"); return;} do{

memo->Lines->Add(AnsiString(c->name)); c=c->next;

}while(c!=first);

}

// Функція вилучення елемента, який слідує після елемента с1 void del_el(Element* c1)

{Element* c=c1->next; c1->next=c->next; if(c==first) first=c->next; if(c==last) last=c1;

delete c;

}

// Функція звільнення пам‟яті й очищення форми void ochistka()

{Element* c;last->next=0; while(first!=0)

{c=first; first=first->next;

delete c;

}

}

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

void __fastcall TForm1::Button1Click(TObject *Sender)

{int i, N=Memo1->Lines->Count; for(i=0; i<N; i++)

if(first==0) fir(Memo1->Lines->Strings[i].c_str()); else add_el(Memo1->Lines->Strings[i].c_str());

print_el(Memo2);

}

// Кнопка “Очистити”

void __fastcall TForm1::Button2Click(TObject *Sender)

{ochistka(); print_el(Memo4);

}

// Кнопка “Полічити”

void __fastcall TForm1::Button3Click(TObject *Sender) { randomize();

int n=random(4); // Генерування випадкового числа з проміжку [0,3] int i=0; char s[200];

if((f=fopen(fn, "rt"))==0) {ShowMessage("Can't open file"); return;}

472

Розділ 13

while(i<=n && fgets(s,200,f))

i++; // Зчитування рядків з файла, допоки не зчитано рядок з номером n fclose(f);

if (s[strlen(s)-1]=='\n') s[strlen(s)-1]='\0';

Memo4->Lines->Add(AnsiString(s));

Memo4->Lines->Add("");

 

char* D=new char[10];

 

strcpy(D," .,-!?:");

// Символи-розділювачі

char* p=new char [50];

 

p=strtok(s,D);

 

Element* c=first; while(p!=0)

{ Memo4->Lines->Add(AnsiString(c->name)+" - "+AnsiString(p));

p=strtok(NULL, D);

// Виокремлення наступного слова з рядка

c=c->next;

// і перехід до наступного імені дитини

}

delete []p; delete []D;

// Пошук елемента списку, після якого треба вилучити елемент.

Element* c1=first;

while (c1->next->next!=c) c1=c1->next; Memo4->Lines->Add("");

Memo4->Lines->Add("Виходить "+AnsiString(c1->next->name)); del_el(c1); // Вилучення обраного лічилкою імені print_el(Memo3);

}

Циклічний двозв‟язний (двонапрямлений) список – це циклічний список, кожний елемент якого зберігає вказівники на наступний і попередній елементи. Схематичне зображення такого списку:

 

 

 

–5

 

0

 

 

 

 

 

 

 

first

 

 

 

 

4

 

 

 

 

 

 

 

 

 

 

 

1

 

 

 

 

last

 

 

 

 

 

 

 

 

 

 

 

 

 

 

8

Оголошення циклічного двозв‟язного списку буде таким самим, як ого-

лошення лінійного двозв‟язного списку.

 

Функція створювання першого елемента:

first

void fir(int x)

{ first=new Element; 4 first->d=x;

first->next=first; first->prev=first; last=first;

}

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

473

Функція долучення нового елемента до списку (поміж останнім і першим):

void add_el(int x)

 

 

 

 

first

 

 

 

 

 

{ Element* c=new Element;

c

 

 

 

 

 

 

 

 

 

4

 

 

c->d=x;

 

 

 

 

 

 

 

 

 

 

 

c->next=first;

1

 

 

 

 

 

 

c->prev=last;

 

 

 

 

 

 

 

 

 

 

 

 

 

last->next=c;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

first->prev=c;

 

 

 

 

 

 

-5

last=c;

 

0

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Функція виведення списку до Memo від

 

 

last

першого елемента до останнього: void print_beg(TMemo* memo)

{memo->Clear(); Element* c=first;

if(first==0) {ShowMessage ("Порожній"); return;}

do{ memo->Lines->Add(IntToStr(c->d)); c=c->next;

}while(c!=first);

}

Функція виведення списку до Memo від останнього елемента до першого: void print_end(TMemo* memo)

{memo->Clear(); Element* c=last;

if(last==0) { ShowMessage ("Порожній"); return;} do{ memo->Lines->Add(IntToStr(c->d));

c=c->prev;

} while(c!=last);

}

Функція вставлення нового елемента після елемента с1: void insert_el(Element* c1, int x)

{Element* c=new Element; c->d=x; c->next=c1->next; c1->next->prev=c; c1->next=c; c->prev=c1; if(c1==last) last=c;

}

Функція вилучення зі списку елемента, який слідує після елемента с1:

void del_el(Element* c1) { Element* c=c1->next;

c1->next=c->next; c->next->prev=c1; if(c==first) first=c->next; if(c==last) last=c1;

474

Розділ 13

delete c;

}

Функція звільнення пам‟яті від списку: void ochistka()

{Element* c; last->next=0; first->prev=0; while(first!=0)

{c=first; first=first->next; delete c;

}

last=0;

}

13.6 Бінарні дерева

Бінарне дерево – це динамічна структура даних, яка складається з вузлів, кожен з яких містить, окрім даних, не більше двох посилань на різні бінарні дерева. На кожний вузол є лише одне посилання. Початковий вузол називається коренем дерева. Схематичне зображення бінарного дерева наведено на рис. 13.1.

 

 

10

 

 

6

 

25

1

8

20

30

 

 

 

21

Рис. 13.1. Приклад зображення бінарного дерева

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

Вузол, який не має піддерев, називається листом. Вузол зі значенням 6 є предком для вузлів 1 та 8, а вузли 1 та 8 є нащадками вузла 6. Висота дерева визначається кількістю рівнів, на яких розташовуються його вузли. Значення вузла дерева називається ключем.

Оголошення вузла дерева з цілих чисел: struct Node

{ int d;

// ключ – числове поле

Node *left;

// вказівник на ліве піддерево

Node *right;

// вказівник на праве піддерево

};

 

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

475

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

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

void way_around (<дерево>)

{way_around (<ліве піддерево>) <відвідування кореня>

way_around (<праве піддерево>)

}

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

1, 6, 8, 10, 20, 21, 25, 30

Якщо у функції обходу перше звертання іде до правого піддерева, результат обходу буде іншим:

30, 25, 21, 20, 10, 8, 6, 1

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

Для бінарних дерев визначені такі операції:

вилучення вузла до дерева;

пошук по дереву;

обхід дерева;

вилучення вузла.

Для кожного рекурсивного алгоритму можна створити його нерекурсивний еквівалент.

Приклад 13.9 Увести цілі числа до Memo і сформувати з них дерево пошуку.

Розв‟язок. У програмі реалізуємо нерекурсивну функцію пошуку по дереву із включенням і рекурсивну функцію обходу дерева.

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

476

Розділ 13

У функції search_insert() поточний вказівник для пошуку по дереву позначений pv, вказівник на предка pv позначений prev, змінна pnew використовується для виділення пам‟яті під вузол, який включається в дерево. Рекурсії вдалося уникнути, зберігши всього одну змінну prev і повторивши при включенні оператори, які визначають, до якого піддерева долучається новий вузол.

У функції print_tree() другим параметром передається ціла змінна, яка визначає, на якому рівні розміщено вузол. Корінь перебуває на рівні 0. Дерево виводиться на екран по горизонталі в такий спосіб, щоб корінь містився ліворуч (дерево повернути набік). Перед значенням вузла для імітації структури дерева виводиться кількість пробілів, пропорційна до рівня вузла. Якщо закоментувати цикл виведення пробілів, відсортований за зростанням масив буде виведено у стовпчик.

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

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

{int d; Node *left;

Node *right; };

Node* root=0;

//Створення першого вузла (кореня) дерева

Node * first(int x)

{ Node *pv = new Node; pv->d= x;

pv->left = 0; pv->right = 0; return pv;

}

//Пошук із долученням – вставлення нового елемента у дерево з перевіркою на збіг

Node * search_insert(Node *root, int x) { Node *pv = root, *prev;

bool found=false; //Ознака: чи знайшли вузол зі значенням х (спочатку false)

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

477

// Допоки в дереві є елементи і елемент зі значенням х не знайдено while(pv && !found)

{prev = pv;

if(x==pv->d) found = true;

else

 

// Якщо значення вузла не збігається з х

if(x<pv->d)

 

// і, якщо х є менше за значення вузла,

pv = pv->left;

// перехід до лівого піддерева,

else pv = pv->right;// або перехід до правого піддерева.

}

 

 

if(found)

// Якщо вузол зі значенням х було знайдено,

return pv;

//повертання вказівника на нього

Node *pnew = new Node;

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

pnew->d = x;

 

 

pnew->left = 0;

 

 

pnew->right = 0;

 

 

if(x<prev->d)

 

 

prev->left = pnew;

// Долучення до лівого піддерева предка

else

 

 

prev->right = pnew;

//Долучення до правого піддерева предка

return pnew;

 

//Повертання вказівника на новий вузол

}

// Виведення дерева – рекурсивна функція void print_tree(Node *p, int level) { AnsiString S="";

if(p)

{ print_tree(p->left, level+1);

//Виведення лівого піддерева

for(int i=0; i<level; i++) S=S+"

";

S=S+IntToStr(p->d);

 

 

Form1->Memo2->Lines->Add(S);

//Виведення кореня піддерева

print_tree(p->right, level +1); //Виведення правого піддерева

}

}

void ochistka(Node* p) { if(p)

{ ochistka(p->left); ochistka(p->right); delete p;

}

else return;

}

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

void __fastcall TForm1::Button1Click(TObject *Sender) {int i, N=Memo1->Lines->Count;

for(i=0; i<N; i++)

if(root==0) root=first(StrToInt(Memo1->Lines->Strings[i])); else search_insert(root, StrToInt(Memo1->Lines->Strings[i])); print_tree(root, 0);

}

//-------------------------------------------------------------

}
else
if(x < root->d)

478

Розділ 13

// Кнопка “Очистити”

void __fastcall TForm1::Button2Click(TObject *Sender) { ochistka(root);

Memo1->Clear(); Memo2->Clear();

}

Розглянемо рекурсивний аналог функції search_insert(). У функції число порівнюється зі значенням у корені. Якщо воно є менше, функція викликається для долучення цього значення до лівого піддерева, якщо – навпаки, функція викликається для долучення до правого піддерева. В результаті виконується спуск до порожнього піддерева, замість якого формується новий елемент і долучається до дерева.

Node * search_insert(Node *root, int x)

{ if(!root) //Створювання вузла, якщо дерева ще немає.

{root = new Node; root->d=x;

root->left=0; root->right=0;

// Якщо дерево вже є, // якщо нове значення є менше за значення вузла

// викликається функція від лівого піддерева root->left=search_insert(root->left, x);

else

// Викликається функція від правого піддерева root->right=search_insert(root->right, x);

return root;

}

Наприклад, треба створити дерево з числами 10, 25, 20, 6, 21, 8, 1, 30 (див.

рис. 13.1).

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

Кореня ще немає (root=0), тому створюємо корінь зі значенням 10.

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

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

search_insert(root->right, 25)

Правої гілки ще немає, тому створюємо новий елемент.

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

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

search_insert(root->right, 20)

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