Двобічно зв'язаний список — вид зв'язаного списку, у якому посилання в кожному вузлі вказують на попередній і на подальший вузол у списку.
Вузол двобічно зв'язаного списку складається з трьох полів: вказівника на попередній елемент prev, поля даних data та вказівника next на наступний елемент.
Якщо в списку після останнього елемента йде перший, то такий список називається кільцевим двобічно зв'язаним списком. Тобто, поле prev голови списку вказує на хвіст списку, а поле next хвоста списку вказує на голову списку.
По двобічно зв'язаному списку можна пересуватися в будь-якому напрямку — як від початку до кінця, так і навпаки. Для нього простіше проводити видалення і перестановку елементів, оскільки завжди відомі адреси тих елементів списку, вказівник яких спрямований на змінюваний елемент.
Переваги двобічно зв'язаного списку над однобічно зв'язаним списком
- Додавання нового вузла в певну позицію.
- Видалення i-го елемента з послідовності.
- Перегляд списку в обох напрямках.
Існують і недоліки: з'являється додаткова змінна — вказівник на попередній елемент (prev). У даній статті описаний принцип роботи двобічно зв'язного циклічного списку. Єдина відмінність двобічно зв'язного циклічного списку від двобічно зв'язного списку в тому, що останній елемент указує на перший, а перший — на останній. Дана відмінність дає змогу виключати перевірки на «перший» і «останній» елемент. Двобічно зв'язаний циклічний список (далі просто Двобічно зв'язаний список) можна представити в наступному вигляді (рис.1):
Стандартні функції для двобічно зв'язного списку
Функція push (додати новий елемент в i-ту позицію списку). Вона виконує наступні дії:
- Створює новий елемент.
- Копіює значення інформаційного поля.
- Якщо даний елемент є єдиним:
- Обидва покажчики (next і prev) посилаються на цей елемент (рис. 2).
- Покажчик first вказує на єдиний елемент у списку.
- Інакше зсувається покажчик на i-й елемент і додається новий елемент перед i-м.
Додати нове значення у двобічно зв'язний список (push 4), новий елемент додаватиметься після першого. Після операції push список міститиме один елемент (рис. 2).
Додавши ще кілька значень (push 6 і push 7), кожен новий елемент буде додаватися після першого. Список міститиме три елементи (рис. 3) у такій послідовності:
Функція pop виштовхує i-й елемент зі списку. Вона виконує наступні дії:
- Якщо список порожній, вихід із функції.
- Якщо в список містить єдиний елемент:
- Копіюється значення інформаційного поля.
- Видаляється елемент зі списку.
- Присвоюється заголовку пусто.
- Інакше — зсувається покажчик на i-й елемент;
- якщо заголовок вказує на i-й елемент (first == t), тоді переміщається заголовок на наступний елемент (First = t-> next)
- копіюється значення інформаційного поля;
- видаляється i-й елемент зі списку.
- Повертається значення інформаційного поля.
Виконавши функцію pop над лінійним списком (виштовхується 3-й елемент). Отримується наступний стан зв'язного списку (рис. 4).
Функція print_all пробігає по всіх елементах і виводить в консоль значення інформаційного поля. Перегляд елементів здійснюється зліва направо, але легко можна переписати функцію і змінити перегляд елементів справа наліво (замінити a = a-> next на a = a-> prev). Функція clean видаляє всі елементи в списку і привласнює заголовку пусто (first = null). Після цієї операції список повертається в початковий стан.
Приклад реалізації двобічно зв'язаного списку мовою C#
namespace WikiSamples.LinkedList; public class LinkedList<T> where T : IEquatable<T> { public class Node { public Node(T value) { Value = value; } public Node? Previous { get; set; } public T Value { get; init; } public Node? Next { get; set; } } private Node? _head = null; private Node? _tail = null; public void InsertBefore(Node existingNode, Node newNode) { var previousNode = existingNode.Previous; if (previousNode is not null) { previousNode.Next = newNode; newNode.Previous = previousNode; } existingNode.Previous = newNode; newNode.Next = existingNode; if (existingNode == _head) { _head = newNode; } } public void InsertAfter(Node existingNode, Node newNode) { var nextNode = existingNode.Next; if (nextNode is not null) { nextNode.Previous = newNode; newNode.Next = nextNode; } existingNode.Next = newNode; newNode.Previous = existingNode; if (existingNode == _tail) { _tail = newNode; } } public void InsertFirst(Node newNode) { if (_head is null) { _head = _tail = newNode; } else { InsertBefore(_head, newNode); } } public void InsertLast(Node newNode) { if (_tail is null) { _head = _tail = newNode; } else { InsertAfter(_tail, newNode); } } /// <summary> /// Finds the node in the list with time complexity of O(N). /// </summary> public Node? Find(T searchedValue) { var currentNode = _head; while (currentNode is not null) { if (searchedValue.Equals(currentNode.Value)) { return currentNode; } currentNode = currentNode.Next; } return null; } public IEnumerable<Node> Iterate() { var currentNode = _head; while (currentNode is not null) { yield return currentNode; currentNode = currentNode.Next; } } /// <summary> /// Removes the node from the list with time complexity of O(1). /// </summary> public void Remove(Node node) { var previousNode = node.Previous; var nextNode = node.Next; if (previousNode is not null) { previousNode.Next = nextNode; } if (nextNode is not null) { nextNode.Previous = previousNode; } if (_head == node) { _head = nextNode; } if (_tail == node) { _tail = previousNode; } } }
Приклад реалізації двобічно зв'язаного списку на С++
struct List{ char name[30]; List *next; List *prev; }; // List *head; // голова списку // void CreateList(){ // створення списку head = NULL; } // void add_from_the_head(char newname[30]){ // додати з голови List * n = new List; strcpy_s(n->name,newname); if(head == NULL){ head = n; head->next = NULL; } else{ n->next = head; head->prev = n; head = n; } } // void add_from_the_tail(char newname[30]){ // добавити з хвоста List *n = new List; strcpy_s(n->name,newname); if(head == NULL){ head = n; head->next = NULL; } else{ n->next = head; while(n->next != NULL) n = n->next; List * nova = new List; strcpy_s(nova->name,newname); n->next = nova; nova->prev = n; nova->next = NULL; } } // bool add_after(char after[30]){ // добавити після якогось елемента char newname[30]; List *n = head; while(n != NULL){ if(strcmp(n->name,after) == 0){ cout << after << " Знайдено!" << endl; cout <<"Введіть ім'я, що потрібно додати: "<<endl; cin >> newname; List * list = new List; list->next = n->next; n->next = list; list->prev = n; strcpy_s(list->name,newname); return true; } n = n->next; } cout<<"Не знайдено нікого!"<<endl; return false; } // void Udalit(char smbdy[30]){ // видалення елемента if(head == NULL){ perror("Список порожній!"); } else{ List * n = head; while(strcmp(head->name,smbdy)==0){ head = head->next; } List * pr; n = head; if(n!=NULL){ while(n->next!=NULL){ if(strcmp(n->next->name,smbdy)==0){ pr = n->next->next; delete n->next; n->next = pr; pr->prev = n; } if((n->next!=NULL) && (strcmp(n->next->name,smbdy)!=0)) n = n->next; } } } } // void print_all(){ // друкування елемента int counter = 1; List * n = head; if(n==NULL) perror("Список порожній!"); while(n!=NULL){ cout << counter <<". "<<n->name << endl; n = n->next; counter++; } } // void search(char * name){ // пошук по категорії «ім'я» елемента int counter = 1; List * n = head; while(n!=NULL){ if(strcmp(n->name,name)==0){ cout << counter <<". "<<n->name << endl; counter++; } n = n->next; } if(counter == 1) cout << "Не знайдено нікого!" <<endl; } //
Джерела
- Дональд Кнут. Fundamental Algorithms // The Art of Computer Programming. — 3rd. — Massachusetts : Addison–Wesley, 1997. — Т. 1. — 650 с. — .(англ.)
Цю статтю треба для відповідності Вікіпедії. (лютий 2017) |
Це незавершена стаття про структури даних. Ви можете проєкту, виправивши або дописавши її. |
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Dvobichno zv yazanij spisok vid zv yazanogo spisku u yakomu posilannya v kozhnomu vuzli vkazuyut na poperednij i na podalshij vuzol u spisku Vuzol dvobichno zv yazanogo spisku skladayetsya z troh poliv vkazivnika na poperednij elementprev polya danihdatata vkazivnikanextna nastupnij element Ris 1 Kilcevij dvobichno zv yazanij spisok Yaksho v spisku pislya ostannogo elementa jde pershij to takij spisok nazivayetsya kilcevim dvobichno zv yazanim spiskom Tobto pole prev golovi spisku vkazuye na hvist spisku a pole next hvosta spisku vkazuye na golovu spisku Po dvobichno zv yazanomu spisku mozhna peresuvatisya v bud yakomu napryamku yak vid pochatku do kincya tak i navpaki Dlya nogo prostishe provoditi vidalennya i perestanovku elementiv oskilki zavzhdi vidomi adresi tih elementiv spisku vkazivnik yakih spryamovanij na zminyuvanij element Perevagi dvobichno zv yazanogo spisku nad odnobichno zv yazanim spiskomDodavannya novogo vuzla v pevnu poziciyu Vidalennya i go elementa z poslidovnosti Pereglyad spisku v oboh napryamkah Isnuyut i nedoliki z yavlyayetsya dodatkova zminna vkazivnik na poperednij element prev U danij statti opisanij princip roboti dvobichno zv yaznogo ciklichnogo spisku Yedina vidminnist dvobichno zv yaznogo ciklichnogo spisku vid dvobichno zv yaznogo spisku v tomu sho ostannij element ukazuye na pershij a pershij na ostannij Dana vidminnist daye zmogu viklyuchati perevirki na pershij i ostannij element Dvobichno zv yazanij ciklichnij spisok dali prosto Dvobichno zv yazanij spisok mozhna predstaviti v nastupnomu viglyadi ris 1 Standartni funkciyi dlya dvobichno zv yaznogo spisku Funkciya push dodati novij element v i tu poziciyu spisku Vona vikonuye nastupni diyi Stvoryuye novij element Kopiyuye znachennya informacijnogo polya Yaksho danij element ye yedinim Obidva pokazhchiki next i prev posilayutsya na cej element ris 2 Pokazhchik first vkazuye na yedinij element u spisku Inakshe zsuvayetsya pokazhchik na i j element i dodayetsya novij element pered i m Dodati nove znachennya u dvobichno zv yaznij spisok push 4 novij element dodavatimetsya pislya pershogo Pislya operaciyi push spisok mistitime odin element ris 2 ris 2 Dodavannya novogo znachennya Dodavshi she kilka znachen push 6 i push 7 kozhen novij element bude dodavatisya pislya pershogo Spisok mistitime tri elementi ris 3 u takij poslidovnosti Ris 3 Dodavannya novih znachen Funkciya pop vishtovhuye i j element zi spisku Vona vikonuye nastupni diyi Yaksho spisok porozhnij vihid iz funkciyi Yaksho v spisok mistit yedinij element Kopiyuyetsya znachennya informacijnogo polya Vidalyayetsya element zi spisku Prisvoyuyetsya zagolovku pusto Inakshe zsuvayetsya pokazhchik na i j element yaksho zagolovok vkazuye na i j element first t todi peremishayetsya zagolovok na nastupnij element First t gt next kopiyuyetsya znachennya informacijnogo polya vidalyayetsya i j element zi spisku Povertayetsya znachennya informacijnogo polya Vikonavshi funkciyu pop nad linijnim spiskom vishtovhuyetsya 3 j element Otrimuyetsya nastupnij stan zv yaznogo spisku ris 4 ris 4 Vishtovhuvannya elementu Funkciya print all probigaye po vsih elementah i vivodit v konsol znachennya informacijnogo polya Pereglyad elementiv zdijsnyuyetsya zliva napravo ale legko mozhna perepisati funkciyu i zminiti pereglyad elementiv sprava nalivo zaminiti a a gt next na a a gt prev Funkciya clean vidalyaye vsi elementi v spisku i privlasnyuye zagolovku pusto first null Pislya ciyeyi operaciyi spisok povertayetsya v pochatkovij stan Priklad realizaciyi dvobichno zv yazanogo spisku movoyu C namespace WikiSamples LinkedList public class LinkedList lt T gt where T IEquatable lt T gt public class Node public Node T value Value value public Node Previous get set public T Value get init public Node Next get set private Node head null private Node tail null public void InsertBefore Node existingNode Node newNode var previousNode existingNode Previous if previousNode is not null previousNode Next newNode newNode Previous previousNode existingNode Previous newNode newNode Next existingNode if existingNode head head newNode public void InsertAfter Node existingNode Node newNode var nextNode existingNode Next if nextNode is not null nextNode Previous newNode newNode Next nextNode existingNode Next newNode newNode Previous existingNode if existingNode tail tail newNode public void InsertFirst Node newNode if head is null head tail newNode else InsertBefore head newNode public void InsertLast Node newNode if tail is null head tail newNode else InsertAfter tail newNode lt summary gt Finds the node in the list with time complexity of O N lt summary gt public Node Find T searchedValue var currentNode head while currentNode is not null if searchedValue Equals currentNode Value return currentNode currentNode currentNode Next return null public IEnumerable lt Node gt Iterate var currentNode head while currentNode is not null yield return currentNode currentNode currentNode Next lt summary gt Removes the node from the list with time complexity of O 1 lt summary gt public void Remove Node node var previousNode node Previous var nextNode node Next if previousNode is not null previousNode Next nextNode if nextNode is not null nextNode Previous previousNode if head node head nextNode if tail node tail previousNode Priklad realizaciyi dvobichno zv yazanogo spisku na S Realizaciyi dvobichno zv yazanogo spisku na S struct List char name 30 List next List prev List head golova spisku void CreateList stvorennya spisku head NULL void add from the head char newname 30 dodati z golovi List n new List strcpy s n gt name newname if head NULL head n head gt next NULL else n gt next head head gt prev n head n void add from the tail char newname 30 dobaviti z hvosta List n new List strcpy s n gt name newname if head NULL head n head gt next NULL else n gt next head while n gt next NULL n n gt next List nova new List strcpy s nova gt name newname n gt next nova nova gt prev n nova gt next NULL bool add after char after 30 dobaviti pislya yakogos elementa char newname 30 List n head while n NULL if strcmp n gt name after 0 cout lt lt after lt lt Znajdeno lt lt endl cout lt lt Vvedit im ya sho potribno dodati lt lt endl cin gt gt newname List list new List list gt next n gt next n gt next list list gt prev n strcpy s list gt name newname return true n n gt next cout lt lt Ne znajdeno nikogo lt lt endl return false void Udalit char smbdy 30 vidalennya elementa if head NULL perror Spisok porozhnij else List n head while strcmp head gt name smbdy 0 head head gt next List pr n head if n NULL while n gt next NULL if strcmp n gt next gt name smbdy 0 pr n gt next gt next delete n gt next n gt next pr pr gt prev n if n gt next NULL amp amp strcmp n gt next gt name smbdy 0 n n gt next void print all drukuvannya elementa int counter 1 List n head if n NULL perror Spisok porozhnij while n NULL cout lt lt counter lt lt lt lt n gt name lt lt endl n n gt next counter void search char name poshuk po kategoriyi im ya elementa int counter 1 List n head while n NULL if strcmp n gt name name 0 cout lt lt counter lt lt lt lt n gt name lt lt endl counter n n gt next if counter 1 cout lt lt Ne znajdeno nikogo lt lt endl DzherelaDonald Knut Fundamental Algorithms The Art of Computer Programming 3rd Massachusetts Addison Wesley 1997 T 1 650 s ISBN 0 201 89683 4 angl Cyu stattyu treba vikifikuvati dlya vidpovidnosti standartam yakosti Vikipediyi Bud laska dopomozhit dodavannyam dorechnih vnutrishnih posilan abo vdoskonalennyam rozmitki statti lyutij 2017 Ce nezavershena stattya pro strukturi danih Vi mozhete dopomogti proyektu vipravivshi abo dopisavshi yiyi