Круговий пов’язаний список: переваги та недоліки
⚡ Розумний підсумок
Циклічні зв'язані списки розташовують вузли таким чином, щоб останній вузол повертався до першого, забезпечуючи безперервну структуру без NULL, яка підходить для циклічного планування, токенів та будь-якого робочого процесу, що потребує безперебійного проходження.
Що таке циклічний пов’язаний список?
Циркулярний зв'язаний список - це послідовність вузлів, розташованих таким чином, що кожен вузол можна перебудувати.tracсам по собі. Кожен «вузол» є самопосилальним елементом з вказівниками на один або два вузли в безпосередній близькості від нього.
Нижче наведено зображення кругового пов’язаного списку з 3 вузлами.
Тут ви можете бачити, що кожен вузол повторноtracсам по собі. Наведений вище приклад — це циклічний однозв'язний список.
Примітка: Найпростіший циклічний зв'язаний список — це один вузол, наступний покажчик якого tracповертається до себе, як показано нижче.
Базовий Operaції в циклічних зв'язаних списках
Три основні операції над циклічним зв'язаним списком:
- вставка
- Видалення і
- Обхід
- Вставка — це процес розміщення вузла у вказаній позиції в циклічному зв’язаному списку.
- Видалення — це процес видалення існуючого вузла зі зв’язаного списку. Вузол можна ідентифікувати за його значенням або за його положенням.
- Обхід циклічного зв'язаного списку — це процес відображення всього вмісту зв'язаного списку та його повторного перегляду.tracповернення до вузла-джерела.
У наступному розділі пояснюється, як працює вставка, і два можливі типи вставки в циклічному однозв'язному списку.
вставка Operaції
Спочатку створюється один вузол, наступний вказівник якого вказує на себе, як показано нижче. Без цього початкового вузла перша вставка стає першим вузлом у списку.
Далі є дві можливості:
- Вставка в поточну позицію циклічного зв'язаного списку. Це відповідає вставці на початку або в кінці звичайного однозв'язного списку — у циклічному зв'язаному списку початок і кінець знаходяться в одній точці.
- Вставка після індексованого вузла. Вузол має бути ідентифікований номером індексу, що відповідає значенню його елемента.
Щоб вставити на початок або кінець циклічного зв'язаного списку, тобто в позицію, де було додано перший вузол, виконайте наведені нижче дії.
- Вам доведеться розірвати існуюче власне посилання на існуючий вузол
- Наступний покажчик нового вузла посилатиметься на існуючий вузол.
- Наступний покажчик останнього вузла вказуватиме на вставлений вузол.
ПРИМІТКА. Вказівник, який позначає початок або кінець кола, можна перепризначити будь-якому вузлу. Обхід все одно повернеться до того самого вузла, як обговорюється далі в цій статті.
Етапи (a) i-iii показані нижче:
(Існуючий вузол)
Крок 1) Розірвати існуюче посилання
Крок 2) Створення прямого посилання (від нового вузла до існуючого вузла)
Крок 3) Створіть зв'язок циклу з першим вузлом
Далі ви спробуєте вставити після вузла.
Наприклад, вставте «VALUE2» після вузла, що містить «VALUE0», вважаючи, що початковою точкою є вузол із «VALUE0».
- Розірвіть зв'язок між першим і другим вузлами та помістіть вузол з "VALUE2" між ними.
- Наступний вказівник першого вузла посилається на новий вузол, а наступний вказівник нового вузла посилається на те, що раніше було другим вузлом.
- Решта розташування залишається незмінною. Усі вузли переробленіtracдоступні самим собі.
ПРИМІТКА. Оскільки розташування є циклічним, процедура вставки вузла ідентична незалежно від обраної позиції. Вказівник, який замикає цикл, поводиться як будь-який інший вказівник у списку.
Це показано нижче:
(Припустимо, є лише два вузли. Це тривіальний випадок)
Крок 1) Видаліть внутрішню зв'язок між підключеними вузлами
Крок 2) З’єднайте вузол лівої сторони з новим вузлом
Крок 3) Підключіть новий вузол до правого вузла.
видалення Operaції
Припустимо, що це циклічний зв'язаний список із 3 вузлів. Два випадки видалення:
- Видалення поточного елемента
- Видалення після елемента.
Видалення на початку/кінці:
- Перейдіть до першого вузла від останнього вузла.
- Видалення з кінця вимагає лише одного кроку обходу, від останнього вузла до першого вузла.
- Видалити зв'язок між останнім вузлом та першим вузлом.
- Зв’яжіть останній вузол з наступним елементом першого вузла.
- Звільніть перший вузол.
(Існуюче налаштування)
Крок 1) Видалити кругове посилання
Крок 2) Видаліть зв’язок між першим і наступним, пов’яжіть останній вузол із вузлом, наступним за першим
Крок 3) Звільнити / звільнити перший вузол
Видалення після вузла:
- Переміщайтеся, доки наступний вузол не буде вузлом, який потрібно видалити.
- Перейдіть до наступного вузла, навівши покажчик на попередній вузол.
- З’єднайте попередній вузол із вузлом після поточного вузла, використовуючи його наступний покажчик.
- Звільніть поточний (від’єднаний) вузол.
Крок 1) Скажімо, нам потрібно видалити вузол із «VALUE1».
Крок 2) Видалити зв'язок між попереднім вузлом та поточним вузлом, а потім безпосередньо зв'язати попередній вузол з вузлом, на який вказує наступний вказівник поточного вузла (вузол після VALUE1).
Крок 3) Звільнити або відмінити поточний вузол.
Обхід циклічного пов’язаного списку
Щоб пройтися по циклічному зв'язаному списку від останнього вказівника, спочатку перевірте, чи останній вказівник має значення NULL. Якщо воно не дорівнює NULL, перевірте, чи список містить лише один елемент. В іншому випадку пройдіться по списку з тимчасовим вказівником, доки знову не досягнете останнього вказівника, як показано на анімації нижче.
Переваги циклічного пов’язаного списку
Деякі з переваг циклічних пов’язаних списків:
- Немає вимог щодо призначення NULL у коді. Циркулярний список ніколи не вказує на покажчик NULL, якщо він не повністю звільнений.
- Циклічні зв'язані списки є вигідними для операцій з кінцем списку, оскільки початок і кінець збігаються. Algorithms такі як циклічне планування, можуть чітко проходити через процеси в черзі, не зустрічаючи завислих або NULL-вказівників.
- Циркулярний зв'язаний список все ще підтримує всі звичайні операції однозв'язного списку. Циркулярний двозв'язаний список може навіть усунути необхідність повного обходу для пошуку елемента — у гіршому випадку ціль знаходиться навпроти початкового вказівника, тому потрібно пройти щонайбільше половину списку.
Недоліки циклічного пов'язаного списку
Нижче наведено недоліки використання циклічного пов’язаного списку:
- Кругові списки складніші, ніж однозв'язані списки.
- RevСкасування циклічного списку є складнішим, ніж зворотне виконання одно- або двозв'язного списку.
- Якщо завершення циклу не обробляється обережно, код обходу може увійти в нескінченний цикл.
- Важче знайти кінець списку та записати правильні умови керування циклом.
- Вставка на початку вимагає проходження всього списку, щоб досягти останнього вузла (з точки зору реалізації).
Однозв’язаний список як циклічний зв’язаний список
Рекомендується прочитати та реалізувати наведений нижче код на C. Він ілюструє арифметику вказівників, пов'язану з циклічним однозв'язним списком.
#include<stdio.h> #include<stdlib.h> struct node { int item; struct node *next; }; struct node* addToEmpty(struct node*,int); struct node *insertCurrent(struct node *, int); struct node *insertAfter(struct node *, int, int); struct node *removeAfter(struct node *, int); struct node *removeCurrent(struct node *); void peek(struct node *); int main() { ...
Пояснення коду:
- Перші два рядки коду є необхідними включеними файлами заголовків.
- У наступному розділі визначено структуру кожного самопосилального вузла. Вона містить значення та вказівник того ж типу, що й структура.
- Кожен екземпляр структури пов'язаний з іншими об'єктами структури того ж типу.
- Існують різні прототипи функцій для:
- Додавання елемента до порожнього пов’язаного списку
- Вставляючи в наразі вказано позиція циклічного пов’язаного списку.
- Вставка після конкретного індексований значення у пов’язаному списку.
- Видалення/видалення після певного індексований значення у пов’язаному списку.
- Видалення в поточній позиції кругового пов’язаного списку
- Остання функція друкує кожен елемент через циклічний обхід у будь-якому стані зв’язаного списку.
int main() { struct node *last = NULL; last = insertCurrent(last,4); last = removeAfter(last, 4); peek(last); return 0; } struct node* addToEmpty(struct node*last, int data) { struct node *temp = (struct node *)malloc(sizeof( struct node)); temp->item = data; last = temp; last->next = last; return last; } struct node *insertCurrent(struct node *last, int data)
Пояснення коду:
- Для коду addToEmpty виділіть порожній вузол за допомогою функції malloc().
- Помістіть вхідні дані у тимчасовий вузол.
- Призначте тимчасовий вузол останньому та встановіть його наступний вказівник на себе, щоб єдиний вузол вказував назад на себе.
- Повернути останній вказівник назад до контексту main() / програми.
struct node *insertCurrent(struct node *last, int data) { if(last == NULL) { return addToEmpty(last, data); } struct node *temp = (struct node *)malloc(sizeof( struct node)); temp -> item = data; temp->next = last->next; last->next = temp; return last; } struct node *insertAfter(struct node *last, int data, int item) { struct node *temp = last->next, *prev = temp, *newnode =NULL; …
Пояснення коду
- Якщо список порожній, передайте керування addToEmpty().
- Створіть тимчасовий вузол, який буде розміщено після поточного вузла.
- З'єднайте покажчики, як показано на діаграмі вище.
- Повертає останній вказівник, що відповідає шаблону, використаному в попередній функції.
... struct node *insertAfter(struct node *last, int data, int item) { struct node *temp = last->next, *prev = temp, *newnode =NULL; if (last == NULL) { return addToEmpty(last, item); } do { prev = temp; temp = temp->next; } while (temp->next != last && temp->item != data ); if(temp->item != data) { printf("Element not found. Please try again"); ...
Пояснення коду:
- Якщо список порожній, ігнорувати ключ пошуку, додати поточний елемент як єдиний вузол у списку та повернути керування.
- У кожній ітерації циклу do-while попередній вказівник містить результат останнього проходження.
- Тільки після цього відбувається наступний крок обходу.
- Виконання операції do-while завершується, коли цільові дані знайдено або коли temp знову досягає останнього вказівника. Наступний блок коду вирішує, що робити з знайденим елементом.
...
if(temp->item != data)
{
printf("Element not found. Please try again");
return last;
}
else
{
newnode = (struct node *)malloc(sizeof(struct node));
newnode->item = item;
prev->next = newnode;
newnode->next = temp;
}
return last;
}
struct node *removeCurrent(struct node *last)
...
Пояснення коду:
- Якщо весь список переглянуто, але елемент не знайдено, вивести повідомлення «Елемент не знайдено» та повернути керування викликаючій службі.
- Якщо цільовий вузол знайдено, виділіть новий вузол для значення, яке потрібно вставити.
- посилання попередній вузол до нового вузла та зв'язати наступний вказівник нового вузла з temp (змінною обходу).
- Це розміщує новий елемент одразу після цільового вузла в циклічному зв'язаному списку. Потім керування повертається до викликаючої сторони.
struct node *removeCurrent(struct node *last) { if(last == NULL) { printf("Element Not Found"); return NULL; } struct node *temp = last->next; last->next = temp->next; free(temp); return last; } struct node *removeAfter(struct node *last, int data)
Пояснення коду
- Щоб видалити останній (поточний) вузол, спочатку перевірте, чи список порожній. Якщо так, то жоден елемент видалити неможливо.
- Змінна temp переміщує на одне посилання вперед.
- Зв'яжіть останній вказівник з вузлом після першого вузла.
- Звільніть тимчасовий вказівник, щоб звільнити незв'язаний вузол.
struct node *removeAfter(struct node *last,int data) { struct node *temp = NULL,*prev = NULL; if (last == NULL) { printf("Linked list empty. Cannot remove any element\n"); return NULL; } temp = last->next; prev = temp; do { prev = temp; temp = temp->next; } while (temp->next != last && temp->item != data ); if(temp->item != data) { printf("Element not found"); ...
Пояснення коду
- Як і у випадку з попередньою функцією видалення, спочатку перевірте, чи список порожній. Якщо так, то жоден елемент не може бути видалений.
- Два pointers призначаються певні позиції, щоб знайти елемент, який потрібно видалити.
- Вказівники просуваються один за одним (темп. попередніх трас).
- Обхід продовжується доти, доки не буде знайдено цільовий елемент або наступний вказівник знову не досягне останнього вузла.
if(temp->item != data) { printf("Element not found"); return last; } else { prev->next = temp->next; free(temp); } return last; } void peek(struct node * last) { struct node *temp = last; if (last == NULL) { return;
Пояснення програми
- Якщо весь зв'язаний список переглянуто без знаходження цілі, відображається повідомлення «Елемент не знайдено».
- В іншому випадку елемент буде від’єднано та звільнено на кроках 3 та 4.
- Попередній вказівник пов'язаний з вузлом, на який вказує наступний вказівник temp (вузол після того, що видаляється).
- Потім тимчасовий покажчик звільняється.
... void peek(struct node * last) { struct node *temp = last; if (last == NULL) { return; } if(last -> next == last) { printf("%d-", temp->item); } while (temp != last) { printf("%d-", temp->item); temp = temp->next; } }
Пояснення коду
- Перегляд за допомогою попереднього перегляду неможливий, якщо вузлів немає — користувач повинен спочатку виділити або вставити вузол.
- Якщо є лише один вузол, обхід не потрібен — вміст вузла виводиться безпосередньо, і цикл while не виконується.
- Якщо вузлів більше одного, temp виводить кожен елемент до останнього.
- У момент досягнення останнього елемента цикл завершується, і функція повертає керування до main().
Застосування Циркулярного пов’язаного списку
- Реалізація циклічного планування в системних процесах і циклічного планування у високошвидкісній графіці.
- Планування Token-Ring у комп'ютерних мережах.
- Використовується у дисплеях, таких як цифрові дошки магазинів, що потребують безперервного проходження даних.





























