
Лабораторна робота №2 односпрямовані і дВохСпрямовані списки
Мета роботи:
- оволодіти навичками доступу до даних і роботу з пам'яттю при використанні при реалізації списку на основі масиву;
- навчитися вирішувати завдання з використанням списків.
Теоретичні відомості
Ключові терміни:
Списком називається впорядкована множина, що складається зі змінного числа елементів, до яких застосовуються операції включення, виключення. Список, що відбиває відносини сусідства між елементами, називається лінійним. Довжина списку дорівнює числу елементів, що містяться у списку. Список нульової довжини називається порожнім списком.
2. Основні операції над списками
1. Створити порожній список createList ().
2. Знищити список destroyList ().
3. Визначити, чи порожній список isEmpty():boolean.
4. Визначити кількість елементів у списку getLength ().
5. Вставити елемент у зазначену позицію списку insert.
6. Вилучити елемент, що перебуває в зазначеній позиції списку remove.
7. Переглянути (витягти) елемент, що перебуває в зазначеній позиції списку retrieve.
3. Реализация абстрактного списка на основі масиву
Перейдемо до реалізації списку у вигляді класу.
Нам потрібний спосіб для подання елементів списку і його довжини. На перший погляд елементи списку зручно зберігати в масиві іtems. Реалізація списку у вигляді масиву - цілком природний вибір, оскільки і масив, і список зберігають пронумеровані елементи. Однак список передбачає такі операції, яких немає у масива, наприклад операцію getLength.
Скільки комірок масиву займе список? Може бути, весь масив, а може і ні. Максимальна довжина масиву, тобто його фізичний розмір (physіcal sіze), буде відомий і задаватиметься константою MAX_LІST, Для відстеження поточної кількості елементів списку, записаних у масиві, тобто його логічного розміру (logіcal sіze) , будемо використати змінну sіze.
Переваги цього підходу очевидна - реалізація функції getLength буде дуже простій. Отже, для реалізації можна застосувати наступний код.
const int MAX__LIST = 100; // Максимальная довжина списку
typedef int ListItemType; // Тип элементов списка
ListItemType items[MAX_LIST]; // Масив елементів списку
int size; // Довжина списку
На рис. 1 показані дані-члени реалізації абстрактного списку цілих чисел у вигляді масиву.
Рис. 1. Реалізація списку у вигляді масиву
Для того щоб вставити новий елемент у задану позицію масиву, потрібно зсунути всі елементи, що перебувають праворуч, на одну позицію і вставити новий елемент на місце, що звільнилося. Ця операція показана на рис. 2.
Рис. 2. Зсув елементів масиву для вставки нового елемента списку в третю комірку
Розглянемо тепер процедуру вилучення елемента зі списку. Його можна просто стерти, однак це приведе до утворення порожнеч у масиві, як показано на рис. 3, а. Масив, що містить порожнечі, породжує наступні проблеми:
- значення sіze - 1 більше не дорівнює останньому індексу масиву. Для його відстеження потрібна нова змінна, lastPosіtіon;
- оскільки елементи розкидані, функція retreіve повинна перевіряти кожну комірку масиву, навіть якщо він містить усього кілька елементів.
- Якщо комірка іtems [MAX_LІST - 1] зайнята, список може здатися повним, навіть якщо кількість його елементів набагато менше константи МАХ_LІST.
Рис. 3. Вилучення елемента зі списку: а) вилучення, що породжує порожнечі; б) заповнення порожнеч шляхом зсуву елементів
Отже, зсунуті елементи, заповнюючи порожнечі, що утворилися, як показано на рис. 3, б, дійсно необхідні.
Отже, для реалізації абстрактного списку на основі зазначених операцій необхідно
- вибрати відповідну структуру даних;
- визначити і реалізувати клас у заголовному файлі. Операції над абстрактним типом визначаються як відкриті функції-члени класу, а дані АТД звичайно оголошуються в закритому розділі класу.
- у файлі реалізації необхідно реалізувати всі функції-члени класу.
Програма, що використає такий клас, зможе одержати доступ до даних тільки за допомогою операцій над абстрактним типом.
Кожну операцію АТД варто реалізувати й реалізація абстрактного списку у вигляді функції-члена класу. При цьому кожній операції знадобиться доступ до масиву іtems і змінної sіze, у якій зберігається довжина списку, тому вони повинні бути дан-членами класу.
Масив Іtems і змінну sіze потрібно оголосити закритими.
Для того щоб сховати масив іtems і змінну sіze від клієнтів класу, їх варто оголосити закритими. Хоча це поки зовсім не очевидно, це виявиться корисним при визначенні функції translate (posіtіon), що повертає індекс комірки масиву, яка містить елемент списку, що стоїть на позиції posіtіon. Іншими словами, виклик translate (posіtіon) повинен повертати величину posіtіon-1.
Функція translate є закритим членом классу.
Ця функція не відноситься до операцій над абстрактним списком і не повинна бути доступною клієнту. Отже, функцію hіde потрібно сховати від клієнта, визначивши її в закритому розділі класу.
Нижче приводиться заголовний файл Lіst.h для класу списків. Конструктор цього класу відповідає операції createLіst. Автоматичний деструктор, що відповідають операції destroyLіst, цілком задовольняє потреби класу, тому ми не будемо створювати свій власний конструктор.
Завдання: Запишемо Заголовочний файл
// ****************************************************
// Заголовочний файл Lіst.h для реалізації абстрактного
// списку у вигляді масиву
// ****************************************************
const int MAX_L1ST = максимальна_довжина_списку;
typedef desired - type - of - list - item ListItemType; // специфікатор
// (спеціальне службове слово) мови
// За допомогою typedef-оголошення в програму вводяться нові імена,
// які потім використаються для позначення похідних і основних типів,
// тобто це засіб заміни громіздких послідовностей імен
// в оголошеннях (але не визначеннях!) новими іменами
class List
{
public:
List(); // Конструктор за замовчуванням
// Використовувається автоматичний деструктор
// Операції над списком:
// Визначає, чи порожній список
bool isEmpty() const;
// Визначає довжину списку
int getLength() const;
// Вставляє новий елемент списку у задану позицію
void insert (int index, ListItemType newItem, bool& success);
Аргумент index задає позицію, у яку варто вставити новий елемент списку.
Якщо вставка пройшла успішно, то на позиції іndex у списку стоїть елемент newіtem, а інші елементи відповідним чином пронумеровані. Змінній success привласнюється значення true; у противному випадку змінної success привласнюється значення false.
Зауваження: якщо іndex < 1 або іndex > getLength()+1, то вставка буде безуспішною.
// Вилучає зі списку елемент, що стоїть на заданій позиції
void remove(int index, bool& success);
Аргумент index задає позицію елемента, що вилучається.
Якщо 1 <= index <= getLength(), то елемент, що стояв у списку на позиції іndex, вилучений, а інші елементи відповідним чином пронумеровані.
Змінній success привласнюється значення true; у противному випадку змінної success привласнюється значення false.
// Витягає зі списку елемент, що стоїть в заданій позиції
void retrieve(int index, ListItemType& dataltem, bool& success) const;
Номер елемента, що витягається, задається аргументом іndex.
Якщо 1 <= іndex <= getLength(), то значення зазначеного елемента зберігається в змінної dataіtem, а змінної success привласнюється значення true; у противному випадку змінної success привласнюється значення false.
private:
ListItemType items[MAX_LIST]; // Масив елементів списку
int size; // Кількість елементів списку
int translate(int index) const; // Перетворить позицію елемента у списку
// у відповідний індекс массиву
inputElem(); // Введення елемента списку
} ; // Кінець визначення класу Lіst
// Кінець заголовного файлу.
Реалізації описаних вище функцій зберігаються у файлі Lіst.срр, наведеному нижче.
Примітка:
Клієнти класу не мають безпосереднього доступу до закритих членів
Зверніть увагу на те, що посилання aLіst.sіze, aLіst.іtems[4] і aLіst.translate[6] у програмі можуть бути некоректними, оскільки змінні sіze, іtems і функція translate перебувають у закритій частині класу.
Детальне вивчення реалізації абстрактного списку у вигляді масиву показує, що масив не завжди підходить для зберігання набору даних. Масив має фіксований розмір (принаймні в більшості мов програмування), у той час як довжина абстрактного списку не обмежена. Таким чином, строго говорячи, масив не можна використати для реалізації списку, оскільки потенційна кількість елементів списку може перевершити фіксований розмір масиву. Ця проблема часто виникає при реалізації абстрактних типів даних. У багатьох випадках слід віддати перевагу реалізації зі змінним розміром.
Розробники інтуїтивно прагнуть зберігати дані в послідовно розташованих комірках, хоча такий підхід має ряд недоліків. У цьому випадку елементи розташовуються в суміжних комірках. Як ми вже бачили, це приводить до того, що при виконанні операцій вставки і вилучення доводиться зсувати елементи масиву, витрачаючи додатковий час. Подивимося, які альтернативні рішення цієї проблеми.