
C _Учебник_МОНУ
.pdfФайли |
439 |
27) Наведіть фрагмент коду, який записує елементи дійсного масиву (вектора) у текстовий файловий потік у стилі С++:
а) всі елементи в один рядок через кому; б) кожний елемент окремим рядком.
28)Наведіть команди для редагування 4-ої структури бінарного файлового потоку у стилі С++.
29)Наведіть і порівняйте команди для обнуління файла при опрацюванні
устилі С, С++ та через дескриптор.
30)Наведіть фрагмент коду, який записує елементи дійсної матриці 3 5
утекстовий файловий потік у стилі С++.

Розділ 13
Динамічні структури даних
Динамічна структура даних – це набір однотипних елементів, розміщуваних у пам‟яті динамічно, тобто у процесі виконування програми за допомогою операції new() чи то функції malloc(). Прикладом динамічної структури даних є динамічний масив (див. розд. 6). Окрім динамічних масивів, широко застосовуються інші динамічні структури даних – списки, стеки, черги і бінарні дерева.
13.1 Поняття списку
Список – динамічна структура, в якій одне (чи більше) з полів є адресами (чи вказівниками) на такі самі структури. Списки використовуються замість масивів для зберігання й опрацювання однотипних даних, кількість яких заздалегідь є невідома й може змінюватися у перебігу роботи. Окрім того, у списках доволі нескладно вилучити елемент чи то долучити новий на довільне місце.
У пам‟яті одновимірний масив з 5-ти елементів (приміром з цілих чисел 4, –5, 0, 1, 8) розміщується в такий спосіб:
4 –5 0 1 8
Тобто всі елементи розташовані в пам‟яті один за одним і це дає можливість їх нумерувати, але унеможливлює у перебігу роботи долучення нових елементів (принаймні це зробити непросто).
Елементи списку розташовуються у пам‟яті хаотично. Це надає можливість безперешкодно долучати чи то вилучати яку завгодно кількість елементів. Наприклад, список з тими самими значеннями може бути розташований у пам‟яті в такий спосіб:
first |
|
|
|
0 |
|
||
4 |
|
|
|
|
|
last |
|
|
|
|
8 0
1
–5
Стрілки на рисунку зазначають порядок проходження елементів. Для того щоб мати можливість працювати з елементами як зі зв‟язною структурою, треба знати, де саме в пам‟яті розташовано кожний з елементів, тобто знати їхні адреси. Як відомо, адреси змінних зберігаються у вказівниках. Виявляється, що немає потреби зберігати одразу всі адреси. Цілком достатньо зберігати лише адресу першого елемента (вказівник на перший елемент first). Кожному елементові треба “знати”, де розташовано елемент, який слідує за ним, і необов‟язково “знати”, де розташовано решту. Тоді до кожного елемента можна буде діставатися послідовно: від першого до другого, від другого до третього і т. д.

Динамічні структури даних |
441 |
У наведеному прикладі списку елемент зі значенням 1 “знає”, де розташовано елемент зі значенням 8 і не “знає”, де розташовано елемент зі значенням 0 і решта. Щоб “дістатися” елемента зі значенням 1, треба звернутися до першого елемента (4), довідатися в нього адресу другого (–5), у другого
– довідатися адресу третього (0), а вже у третього – довідатися адресу четвертого (1). Безпосередньо одразу до четвертого звернутися неможливо.
Список для зручності й наочності можна зобразити в такий спосіб:
first |
|
|
|
|
|
|
|
last |
|
4 |
|
–5 |
|
0 |
|
1 |
|
8 |
0 |
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
Отже, окрім даних, кожен елемент повинен зберігати ще одне значення: адресу наступного елемента. Природно є подати елемент списку у вигляді структури, яка містить одне чи то кілька інформаційних полів (полів даних) і вказівник на наступний елемент (який на рисунку зображується стрілкою). Після останнього елемента більше елементів немає, це означає, що вказівник на наступний елемент після останнього є порожнім (NULL), інакше кажучи, дорівнює 0. Це можна зобразити в такий спосіб:
first |
|
|
|
|
|
|
|
last |
|
4 |
|
–5 |
|
0 |
|
1 |
|
8 |
NULL |
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Порожній |
|
адреса |
|
адреса |
|
адреса |
|
адреса |
|
|
|
|
|
|
|
вказівник |
|
||||
-5 |
|
0 |
|
1 |
|
8 |
|
= 0 |
|
|
|
|
|
|
|
|
|
|
|
У верхній частині кожного елемента зображено його поле даних (число), а в нижній – поле next (адреса наступного елемента в пам‟яті).
Оголошення типу “елемент списку” має такий загальний вигляд:
struct <елемент списку> { <оголошення полів даних>
<елемент списку>* <вказівник на наступний елемент>;
};
Тип елемента наведеного вище списку може бути оголошено у формі struct Element
{ int d; // ціле число
Element *next; // вказівник на наступний елемент
};
Вказівник на перший елемент цього списку оголошується як
Element *first=0;
Його початкове значення завжди дорівнює 0 (тобто списку покищо немає). Після створення списку first набуває певного ненульового значення (адреса пам‟яті, у якій розташовано перший елемент).
Знаючи вказівник на перший елемент, до другого елемента можна звернутися в такий спосіб: first->next, а до третього: first->next->next і т. д. Числове значення першого елемента: first->d.


Динамічні структури даних |
443 |
Якщо треба долучити новий елемент перед першим, вище наведені пункти 3 – 5 слід змінити на два інших:
3) новий елемент долучити до списку, тобто в його поле next записати адресу першого елемента:
c->next=first;
|
|
first |
|
|
|
|
|
|
|
last |
0 |
5 |
|
4 |
|
-5 |
|
0 |
|
1 |
|
8 |
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
4)вказівникові first присвоїти вказівник на новий елемент: first=c;
first |
first |
|
|
|
|
|
|
|
last |
0 |
|
5 |
|
4 |
|
-5 |
|
0 |
|
1 |
|
8 |
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
Проходження за списком виконується переважно у циклі while. Допоки не дісталися потрібного елемента чи то допоки список не завершено, переходимо до наступного елемента за його адресою. Для цього використовується допоміжний вказівник с, який оголошується як
Element *c;
Спочатку треба переміститися до першого елемента: c=first;
У циклі, допоки список не завершено, тобто допоки елемент с не дорівнює 0, з черговим елементом списку виконується потрібна дія, після чого здійснюється перехід до наступного елемента списку:
while(c!=0) // Допоки список не завершено, { . . . // виконання дії з c->d
c=c->next; // й перехід до наступного елемента списку.
}
При вилученні першого елемента списку його адреса запам‟ятовується в допоміжному вказівнику с, а вказівник first переміщується на другий елемент. Після цього пам‟ять від елемента с очищується:
с |
first |
|
|
|
|
|
|
|
last |
0 |
|
5 |
|
4 |
|
–5 |
|
0 |
|
1 |
|
8 |
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
Вставлення й вилучення елементів з середини списку виконується простим присвоюванням адрес і буде розглянуто пізніше разом з прикладами програм. Найбільш простими різновидами списків є СТЕК і ЧЕРГА.


Динамічні структури даних |
445 |
елемента буде зберігати адресу попереднього елемента, тобто елемента зі значенням 4. Окрім того, після долучення вершиною стане новий елемент.
Так само, як і зі списком, для роботи зі стеком використовується проміжний вказівник с.
З елементами стека можна виконувати такі дії: долучення нового елемента до стека (аналогічно до долучення нового елемента до списку перед першим елементом), вилучення верхнього елемента стека (аналогічно до вилучення першого елемента списку), переглядання елементів стека (аналогічно до переглядання елементів списку).
Приклад 13.1 Створити стек, який міститиме цілі числа, і передбачити такі можливості:
долучення елемента до стека; вилучення верхнього елемента стека; виведення вмісту стека в Memo;
обчислення середнього арифметичного парних значень елементів стека; очищення пам‟яті від стека.
Форма додатка з результатами роботи матиме вигляд
Текст програми:
struct Elem |
// Оголошення елемента стека |
{ int x; Elem *next; |
|
}; |
|
Elem *root=0; |
// Оголошення вершини стека і початкове обнулення |
void add (int d)// Долучення елемента зі значенням d до стека
{// Оголошення й розміщення у пам‟яті допоміжного елемента
Elem *c=new Elem; c->x=d;
c->next=0; |
// Попередній елемент покищо не визначено. |
c->next=root; |
// Новий елемент пов‟язується зі старою вершиною стека |
root=c; |
// і після цього він стає новою вершиною. |
}
Динамічні структури даних |
447 |
// Кнопка “Вилучити”
void __fastcall TForm1::Button3Click(TObject *Sender)
{del(); print();
}
// Кнопка “Переглянути”
void __fastcall TForm1::Button4Click(TObject *Sender) { print();
}
// Кнопка “Розв‟язок”
void __fastcall TForm1::Button5Click(TObject *Sender)
{if(root==0){ ShowMessage("Стек є порожній"); return; }
Edit2->Text=FloatToStr(srednee()); while(root != 0 && root->x <= 0) del();
}
// Кнопка “Очистити”
void __fastcall TForm1::Button6Click(TObject *Sender) { while(root!=0) del(); // Очищення пам‟яті
Memo1->Clear(); Memo2->Clear(); |
// Очищення форм |
Edit1->Clear(); Edit2->Clear(); |
|
} |
|
13.3 Черга
Черга – це різновид списку, який має таку особливість: нові елементи можна долучати лише в кінець черги, а вилучати можна лише перший елемент черги. Уявімо собі чергу до каси магазину. Нові покупці стають у кінець черги (після останнього покупця), а розплачується і йде спочатку той, хто стоїть першим. Для черги існує спеціальний термін FIFO (“first in – first out”, тобто “першим прийшов – першим пішов”).
З елементами черги можна виконувати такі дії: долучення нового елемента до черги (аналогічно до долучення нового елемента у кінець списку після останнього), вилучення першого елемента черги (аналогічно до вилучення першого елемента списку), переглядання елементів черги (аналогічно до переглядання елементів списку).
Приклад 13.2 Створити чергу, елементи якої містять інформацію про назву міста і його населення. Передбачити такі можливості:
долучення елемента до черги;
вилучення першого елемента черги;
виведення вмісту черги до Memo;
очищення пам‟яті від черги;
вилучення з черги інформації про всі міста до міста з максимальним населенням.
