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

Зубенко, Омельчук - Програмування. Поглиблений курс

.pdf
Скачиваний:
50
Добавлен:
07.03.2016
Размер:
4.72 Mб
Скачать

Розділ IV. АЛГОРИТМИ

OPERS=push(OPERS, a[k]);

}

}

/*Переходимо до наступного символу вхідного рядка*/ k++;

}

/*після розгляду всього виразу*/ while(OPERS!=NULL)

/*Переписуємо всі операції зі стеку у вихідний рядок і друкуємо його*/

outstring[point++]=pop(&OPERS);

outstring[point]='\0'; printf("\n%s\n", outstring); fflush(stdin); puts("\nПовтоpити(y/n)?");

} while(getchar()!='n'); return 0;

}

/*Функція push записує у стек (на вершину якого вказує HEAD) символ a. Повертає покажчик на нову вершину стеку*/

Stptr push(Stptr HEAD, char a)

{

Stptr PTR=(Stptr)malloc(sizeof(St)); if(PTR==NULL) {

puts("Не вистачає пам'яті"); exit(-1);}

PTR->c=a; PTR->next=HEAD; return PTR;

}

/*Функція pop видаляє символ із вершини стеку. Повертає символ, що видаляється*/

char pop(Stptr *HEAD)

{

Stptr PTR; char a;

if(*HEAD==NULL) return '\0'; PTR=*HEAD;

a=PTR->c; *HEAD=PTR->next; free(PTR); return a;

}

511

ПРОГРАМУВАННЯ

/*Функція prior повертає пpіоpитет арифметичної операції*/ int PRIOR(char a)

{

switch(a)

{

case '*': case '/': return 3;

case '-': case '+': return 2;

case '(': return 1;

}

}

4.3.3. ЧЕРГИ

Черги означені в підрозд. 1.4.4 як кортежі однотипних елементів із відповідними операціями. Нагадаємо, що операціями з чергами є: read читання вершини, pop усунення голови черги, push(c) до- давання в кінець черги нової компоненти, empty перевірка черги на порожність. У реалізаціях черг, як і стеків, перші дві операції об'єд- нуються в одну.

Функцію додавання елемента до черги позначимо enqueue, а функ- цію читання й вилучення елемента з черги dequeue.

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

Черги, як і стеки, подають за допомогою масивів і зв'язаних списків. Розглянемо перший варіант (рис. 4.2). Цілий масив int queue[MAX] зо- бражує елементи черги, змінна-індекс int rpos її голову, змінна- індекс int spos наступний за останнім елемент хвоста черги в маси- ві. При такій реалізації spos==0 означає, що черга порожня.

512

Розділ IV. АЛГОРИТМИ

push

pop

el

el

spos

el

el

el rpos el

queue[0]

Рис. 4.2

Приклад 4.23. Черга на основі масиву цілих чисел:

Лістинг.

int queue[MAX];

int spos=0; /*індекс наступного вільного місця в черзі*/ int rpos=0; /*індекс елемента, що підлягає читанню*/

/*функція enqueue додає елемент у кінець черги*/ void enqueue (int i)

{

if(spos==MAX) {

printf("Список переповнено \n"); return;

}

queue[spos]=i;

spos++;

}

/*функція dequeue знімає перший елемент черги*/ int dequeue ()

{

if(rpos==spos) { printf("Список порожній\n"); return 0;

}

rpos++;

return queue[rpos-1];

}

513

ПРОГРАМУВАННЯ

Варіанти обробки черг:

Початковий

Функція та її

Результат

Вміст черги

вміст черги

аргумент

після операції

NULL (порожня)

enqueue(A)

Void

A

A

enqueue(B)

Void

A B

A B

enqueue(C)

Void

A B C

A B C

dequeue()

A

B C

B C

dequeue()

B

C

C

enqueue(D)

Void

C D

C D

dequeue()

C

D

D

dequeue()

D

NULL

4.3.4. БІНАРНІ ДЕРЕВА

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

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

(англ. – tree traversal).

Бінарні дерева подаються за допомогою структур із покажчиками на себе:

struct item {/*вузол числового дерева*/ int data; /*число*/

struct item *left; /*ліве піддерево*/ struct item *right; /*праве піддерево*/ } Item;

typedef Item *Itemptr; tepedef Itemptr *Itemref;

При зв'язуванні різних структур типу Item утворюється бінарна де- ревоподібна структура. Ідучи за покажчиками left, right, можна обійти всі вузли дерева (здійснити його обхід).

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

514

Розділ IV. АЛГОРИТМИ

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

Приклад 4.24. Стратегії обходу бінарного дерева:

 

 

 

 

 

 

L

d

R

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

L

a

R

 

 

 

 

 

 

 

 

 

 

 

L

f

R

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

L

c

R

 

 

 

 

L

e

R

 

L

g

R

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

L b R

Пряма стратегія: d , a , c , b , f , e , g . Внутрішня стратегія: a , b , c , d , e , f , g . Обернена стратегія: b , c , a , e , g , f , d

Бінарне дерево називається деревом пошуку (див. підрозд. 1.4.4), якщо для кожної його вершини вершини лівого піддерева менші за неї, а правого більші.

Продемонструємо роботу з бінарним словарним деревом пошуку (лінійний порядок лексикографічний).

Приклад 4.25. Бінарне дерево пошуку: прямий обхід.

Лістинг.

#include <stdio.h> #include <string.h>

typedef struct item {/*вузол дерева*/ char *data; /*покажчик на текст*/ struct item *left; /*ліве піддерево*/

struct item *right; /*праве піддерево*/ } Item;

515

ПРОГРАМУВАННЯ

typedef Item *Itemptr;

Itemptr root; /*покажчик на глобальний корінь дерева*/

void search(Itemptr *tree, const char *s); void preOrder(Itemptr node);

int main()

{

int done=0; char s[12];

puts("Tree demonstration"); while(!done){

printf("Data:");

gets(s);

done=(strlen(s)==0);

if(!done) search(&root, s);

}

puts("\nPREORDER:\n");

preOrder(root);

return 0;

}

/* search: пошук елемента, що вже існує, чи створення нового*/ void search(Itemptr *tree, const char *s)

{

Itemptr p; int i;

if(*tree==NULL) { p=(Itemptr)malloc(sizeof(Item)); p->left=NULL;

p->right=NULL; p->data=strdup(s); *tree=p;

} else { p=*tree;

i=strcmp(s, p->data); if(i<0)

search(&p->left, s); else if(i>0)

search(&p->right, s); else {

puts("Duplicate data!"); printf("%s\t", p->data); puts("");

516

Розділ IV. АЛГОРИТМИ

}

}

}

/*пряма стратегія*/

void preOrder(Itemptr node)

{

if(node!=NULL){

printf("%s\t", node->data); /*замість printf може бути довільний процес, що потрібно виконати з елементом*/

preOrder(node->left); preOrder(node->right);

}

}

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

typedef struct item2 {/*вузол числового дерева*/ int data; /*число у вузлі*/

struct item *left; /*ліве піддерево*/ struct item *right; /*праве піддерево*/

} Item2;

typedef Item2 *Itemptr2;

/*CreateTS: побудова збалансованого числового дерева пошуку з вершинами, що належать інтервалу [n,m]*/

Itemptr2 CreateTS(int n,int m)

{Itemptr2 root=(Itemptr2)malloc(sizeof(*Itemptr2)); /*корінь дерева*/

int mid=(n+m)/2; /*cередина інтервалу*/

if (n<m) return NULL;

else {if (!root) {printf("No mamory."); return NULL;} root->data=mid;

root->left=CreateTS(n,mid-1); root->right=CreateTS(mid+1,m);

}

return root;

}

517

ПРОГРАМУВАННЯ

4.3.5. КОНТЕЙНЕРНІ ТИПИ

Ми розглянули реалізацію динамічних структур даних засобами мови C. Для роботи з ними в мові C++ існують контейнерні типи, що входять до складу її стандартної бібліотеки.

Послідовний контейнер містить упорядкований набір елементів од- ного типу. Основними типами контейнерів є вектор (vector), список (list) і двостороння черга (deque). Тип deque забезпечує ту саму функ- ціональність, що й vector, але особливо ефективно реалізує операції вставлення й видалення першого елемента. Усе зазначене нижче для контейнерного типу vector буде стосуватися й типу deque.

При використанні послідовних контейнерів варто дотримуватися та- ких рекомендацій: якщо потрібен довільний доступ до елементів, то краще застосовувати вектор; при завчасно відомій кількості елементів тип vector; за необхідності вставляти чи видаляти елементи всередині контейнера list; якщо не потрібно вставляти чи видаляти елементи на початку контейнера, то краще застосовувати vector, а не deque.

Асоціативний контейнер ефективно реалізує операції перевірки іс- нування й вилучення елементів.

Два основних асоціативних контейнера це відношення (map) і множина (set). Контейнер map складається з пар ключів-значень, де ключ використовується для пошуку елемента, а значення для збе- реження інформації.

Елемент контейнера set містить тільки ключ. Цей контейнер ефек- тивно реалізує операцію перевірки існування.

В асоціативних контейнерах (map, set) не може бути дублікатів. Для підтримки дублікатів існують контейнери multimap та multiset.

Для визначення об'єкта контейнерного типу потрібно ввести відпо- відний заголовок:

#include <vector> #include <list> #include <deque> #include <map> #include <set>

Після цього можна визначити об'єкт контейнерного типу:

vector<string> svec; //вектор елементів типу string list<int> ilist; //список елементів типу int map<string, int> simap;

set<string> sset;

518

Розділ IV. АЛГОРИТМИ

Крім того, можна задати розмір масиву як константою, так і виразом:

extern int get_size();

const int vect_size=25; vector<string> svec1(vect_size); list<int> ilist1(get_size());

Для визначення розміру контейнера існує метод size().

Можна змінити попередньо заданий розмір контейнера в процесі роботи:

svec1.resize(3*svec1.size());

Кожен елемент контейнера ініціалізується значеннями за умовчан- ням (int значення 0). Також можна вказати початкове значення всіх елементів:

vector<string> svec2(vect_size, "aaa"); list<int> ilist2(get_size(), 100);

При такому визначенні спочатку контейнери порожні. Для переві- рки на порожність контейнера існує метод empty:

Ilist.empty() // true – порожній

Для вставлення елемента в кінець контейнера використовують ме-

тод push_back():

string txt; while (cin>>txt)

svec.push_back(txt);

Метод push_front() використовують для додавання елемента в по- чаток списку:

int i=1; ilist.push_front(i);

Для видалення останнього елемента контейнера існує метод pop_back():

svec.pop_back(); ilist.pop_back();

Для перебору елементів контейнера (як послідовного, так і асоціа- тивного) існують ітератори:

519

ПРОГРАМУВАННЯ

vector<string>::iterator iter=svec1.begin(); /*повертає ітера-

тор, що вказує на перший елемент*/ vector<string>::iterator iter1;

list<int>>::iterator iter2=ilist1.end(); /*повертає ітератор,

що вказує на останній елемент*/

Переміщення ітератора:

++iter; /*переміщення ітератора на наступний елемент контейнера*/

--iter; /*переміщення ітератора на попередній елемент контейнера*/

Взяття значення елемента контейнера за ітератором:

*iter;

for(iter1=svec2.begin(); iter1 !=svec2.end(); ++iter1) cout<<*iter1<<endl;

Для вставлення елемента в довільну позицію послідовного контей- нера існує метод insert():

string str("abc"); /*створення об'єкта string зі значенням "abc"*/

svec2.insert((svec2.begin()+svec2.end())/2, str); /*вставлення елемента str на позицію, що передує (svec2.begin()+svec2.end())/2,

тобто всередину контейнера*/

Для усунення елемента в заданій позиції (чи елементів із діапазону) існує дві форми методу erase():

svec2.erase(iter1); /*усунення елемента, на який указує ітера-

тор iter1*/

svec2.erase(svec2.begin(), svec2.end()); /*усунення елементів від svec2.begin() до svec2.end() (у даному випадку усунення всіх елементів)*/

svec=svec2; /*після присвоювання контейнер svec містить елементи контейнера svec2 і тільки їх, навіть якщо попередньо в цих контейнерів були різні розміри*/

svec2.swap(svec); /*обмін значеннями контейнерів*/ list<int>::iterator first;

first=find(ilist.begin(), ilist.end(), 23); /*пошук значення 23 у діапазоні від ilist.begin() до ilist.end()*/

Контейнер list підтримує також операції sort() та merge().

520