У рачунарској науци, двоструко повезана листа је повезана структура података која се састоји из сета секвенцијално повезаних података званих чворови. Сваки чвор садржи два поља, звана везе, која су референце на претходни и следећи чвор у секвенци чворова. Претходна и следећа веза почетног и крајњег чвора, показују на неку врсту терминатора, типично на стражарски чвор или нулу, да би олакшало управљање листом. Ако постоји само један чвор чувар, онда је листа циркуларно повезана преко чвора стражара. То може бити конципирано као једноструко повезане листе формиране од истих података, али у обрнутим секвенцијалним редовима.
Две везе с чворовима омогућавају руковање листом с било које стране. Док додавање или склањање чвора у двоструко повезаној листи захтева мењање више веза него исте операције на једноструко повезаној листи, операције су једноставније и потенцијално ефикасније (за чворове који нису почетни) јер не постоји потреба за праћењем претходног чвора током руковања или не постоји потреба да се пређе листа да би се пронашао претходни чвор, тако да се његова веза може модификовати.
Концепт је такође основа за mnemonic link system memorization технику.
Номенклатура и имплементација
Први и последњи чворови двоструко повезане листе су одмах доступне (тј., доступне без прелажења, и обично се називају глава и реп) и премда дозвољавају прелазак листе с почетка или краја листе: тј., прелажење листе од почетка до краја, или од краја до почетка, у потрази за чвором са одређеном вредношћу податка. Било који чвор у двоструко повезаној листи, једном када се добије, може да се искористи за почетак новог прелажења листе, у било ком правцу (према почетку или крају), од датог чвора.
Поља за везе чвора двоструко повезаних листи се често називају следећа и претходна или напред и назад. Референце сачуване у пољима за везе су често имплементиране као показивачи, али (као у свакој повезаној структури података) такође могу да буду офсети адреса или индекси у низу где живе чворови.
Основни алгоритми
Размотрите следеће основне алгоритме написане у Ади:
Отворене двоструко повезане листе
recordDoublyLinkedNode {
prev // A reference to the previous node
next // A reference to the next node
data // Data or a reference to data
}
recordDoublyLinkedList {
DoublyLinkedNode firstNode // points to first node of listDoublyLinkedNode lastNode // points to last node of list
}
Преношење листе
Прелажење двоструко повезане листе може да се одвија у било ком правцу. У ствари, правац преласка може да се промени пуно пута, ако то желимо. Прелазак се такође назива и итерација, али тај избор терминологије је несрећан, за итерацију постоји добро дефинисана семантика (тј., у математици) која није аналогна преласку.
Унапред
node := list.firstNode
while node ≠ null
<do something with node.data>
node := node.next
Уназад
node := list.lastNode
while node ≠ null
<do something with node.data>
node := node.prev
Убацивање чвора
Ове симетричне функције убацују чвор или после или пре датог чвора:
Једна мала последица горе поменуте процедура је да брисање последњег чвора листе поставља и први и последњи чвор на нулу, и тако се коректно рукује уклањањем чвора из једноелементне листе. Приметите да нам такође нису потребне методе уклони пре и уклони после, јер у двоструко повезаним листама можемо да користимо само једну за оба смера. Ово такође претпоставља да чвор који је уклоњен гарантовано постоји. Ако чвор не постоји у листи, онда је потребна нека врста руковања грешкама.
Циркуларна двоструко повезана листа
Преношење листе
Претпостављајући да неки чвор у непразној листи постоји, овај код пролази кроз ту листу почевши од тог чвора (било ког):
Унапред
node := someNode
do
do something with node.value
node := node.next
while node ≠ someNode
Уназад
node := someNode
do
do something with node.value
node := node.prev
while node ≠ someNode
//NODEPA Приметите одлагање теста на крају петље. Ово је важно у случају када листа садржи само један чвор односно тај чвор.
Додавање чвора
Ова једноставна функција убацује чвор у у двоструко повезану циркуларно повезану листу након датог елемента:
Да би смо додали на очетак ми једноставно "insertAfter(list.lastNode, node)".
Коначно, уклањање чвора мора да се избори са случајем када се листа испразни:
function remove(List list, Node node)
if node.next == node
list.lastNode := nullelse
node.next.prev := node.prev
node.prev.next := node.next
if node == list.lastNode
list.lastNode := node.prev;
destroy node
Имплементација двоструко повезане листе
Следеће функције илуструју функционалност имплементацију двоструко повезаних листи у C/C++ програмским језицима. Функције за додавање, брисање, тражење, приказивање, уназад и детектовање циклуса у чворовима су приказане испод.
/*Description:Double linked list header fileLicense: GNU GPL v3*/#ifndef DOUBLELINKEDLIST_H#define DOUBLELINKEDLIST_H/* Codes for various errors */#define NOERROR 0x0#define MEMALLOCERROR 0x01#define LISTEMPTY 0x03#define NODENOTFOUND 0x4/* True or false */#define TRUE 0x1#define FALSE 0x0/* Double linked DoubleLinkedList definition */typedefstructDoubleLinkedList{intnumber;structDoubleLinkedList*pPrevious;structDoubleLinkedList*pNext;}DoubleLinkedList;/* Get data for each node */externDoubleLinkedList*GetNodeData(DoubleLinkedList*pNode);/* Add a new node forward */externvoidAddNodeForward(void);/* Add a new node in the reverse direction */externvoidAddNodeReverse(void);/* Display nodes in forward direction */externvoidDisplayNodeForward(void);/*Display nodes in reverse direction */externvoidDisplayNodeReverse(void);/* Delete nodes in the DoubleLinkedList by searching for a node */externvoidDeleteNode(constintnumber);/* Function to detect cycle in a DoubleLinkedList */externunsignedintDetectCycleinList(void);/*Function to reverse nodes */externvoidReverseNodes(void);/* function to display error message that DoubleLinkedList is empty */voidErrorMessage(constintError);#endif
/* Double linked List functions *//*****************************************************Name: DoubledLinked.cversion: 0.1Description: Implementation of a DoubleLinkedList.These functions provide functionality of a double linked List.Change history:0.1 Initial versionLicense: GNU GPL v3******************************************************/#include"DoubleLinkedList.h"#include"stdlib.h"#include"stdio.h"/* Declare pHead */DoubleLinkedList*pHead=NULL;/* Variable for storing error status */unsignedintError=NOERROR;DoubleLinkedList*GetNodeData(DoubleLinkedList*pNode){if(!(pNode)){Error=MEMALLOCERROR;returnNULL;}else{printf("\nEnter a number: ");scanf("%d",&pNode->number);returnpNode;}}/* Add a node forward */voidAddNodeForward(void){DoubleLinkedList*pNode=malloc(sizeof(DoubleLinkedList));pNode=GetNodeData(pNode);if(pNode){DoubleLinkedList*pCurrent=pHead;if(pHead==NULL){pNode->pNext=NULL;pNode->pPrevious=NULL;pHead=pNode;}else{while(pCurrent->pNext!=NULL){pCurrent=pCurrent->pNext;}pCurrent->pNext=pNode;pNode->pNext=NULL;pNode->pPrevious=pCurrent;}}else{Error=MEMALLOCERROR;}}/* Function to add nodes in reverse direction,Arguments; Node to be added.Returns : Nothing*/voidAddNodeReverse(void){DoubleLinkedList*pNode=malloc(sizeof(DoubleLinkedList));pNode=GetNodeData(pNode);if(pNode){DoubleLinkedList*pCurrent=pHead;if(pHead==NULL){pNode->pPrevious=NULL;pNode->pNext=NULL;pHead=pNode;}else{while(pCurrent->pPrevious!=NULL){pCurrent=pCurrent->pPrevious;}pNode->pPrevious=NULL;pNode->pNext=pCurrent;pCurrent->pPrevious=pNode;pHead=pNode;}}else{Error=MEMALLOCERROR;}}/* Display Double linked list data in forward direction */voidDisplayNodeForward(void){DoubleLinkedList*pCurrent=pHead;if(pCurrent){while(pCurrent!=NULL){printf("\nNumber in forward direction is %d ",pCurrent->number);pCurrent=pCurrent->pNext;}}else{Error=LISTEMPTY;ErrorMessage(Error);}}/* Display Double linked list data in Reverse direction */voidDisplayNodeReverse(void){DoubleLinkedList*pCurrent=pHead;if(pCurrent){while(pCurrent->pNext!=NULL){pCurrent=pCurrent->pNext;}while(pCurrent){printf("\nNumber in Reverse direction is %d ",pCurrent->number);pCurrent=pCurrent->pPrevious;}}else{Error=LISTEMPTY;ErrorMessage(Error);}}/* Delete nodes in a double linked List */voidDeleteNode(constintSearchNumber){unsignedintNodefound=FALSE;DoubleLinkedList*pCurrent=pHead;if(pCurrent!=NULL){DoubleLinkedList*pNextNode=pCurrent->pNext;DoubleLinkedList*pTemp=(DoubleLinkedList*)NULL;if(pNextNode!=NULL){while((pNextNode!=NULL)&&(Nodefound==FALSE)){// If search entry is at the beginningif(pHead->number==SearchNumber){pCurrent=pHead->pNext;pHead=pCurrent;pHead->pPrevious=NULL;Nodefound=TRUE;}/* if the search entry is somewhere in the DoubleLinkedList or at the end */elseif(pNextNode->number==SearchNumber){Nodefound=TRUE;pTemp=pNextNode->pNext;pCurrent->pNext=pTemp;/* if the node to be deleted is not NULL,,, then point pNextnode->pNext to the previous node which is pCurrent */if(pTemp){pTemp->pPrevious=pCurrent;}free(pNextNode);}/* iterate through the Double Linked List until next node is NULL */pNextNode=pNextNode->pNext;pCurrent=pCurrent->pNext;}}elseif(pCurrent->number==SearchNumber){/* add code to delete nodes allocated with other functions if the search entry is found. */Nodefound=TRUE;free(pCurrent);pCurrent=NULL;pHead=pCurrent;}}elseif(pCurrent==NULL){Error=LISTEMPTY;ErrorMessage(Error);}if(Nodefound==FALSE&&pCurrent!=NULL){Error=NODENOTFOUND;ErrorMessage(Error);}}/* Function to detect cycle in double linked List */unsignedintDetectCycleinList(void){DoubleLinkedList*pCurrent=pHead;DoubleLinkedList*pFast=pCurrent;unsignedintcycle=FALSE;while((cycle==FALSE)&&pCurrent->pNext!=NULL){if(!(pFast=pFast->pNext)){cycle=FALSE;break;}elseif(pFast==pCurrent){cycle=TRUE;break;}elseif(!(pFast=pFast->pNext)){cycle=FALSE;break;}elseif(pFast==pCurrent){cycle=TRUE;break;}pCurrent=pCurrent->pNext;}if(cycle){printf("\nDouble Linked list is cyclic");}else{Error=LISTEMPTY;ErrorMessage(Error);}returncycle;}/*Function to reverse nodes in a double linked list */voidReverseNodes(void){DoubleLinkedList*pCurrent=NULL,*pNextNode=NULL;pCurrent=pHead;if(pCurrent){pHead=NULL;while(pCurrent!=NULL){pNextNode=pCurrent->pNext;pCurrent->pNext=pHead;pCurrent->pPrevious=pNextNode;pHead=pCurrent;pCurrent=pNextNode;}}else{Error=LISTEMPTY;ErrorMessage(Error);}}/* Function to display diagnostic errors */voidErrorMessage(constintError){switch(Error){caseLISTEMPTY:printf("\nError: Double linked list is empty!");break;caseMEMALLOCERROR:printf("\nMemory allocation error ");break;caseNODENOTFOUND:printf("\nThe searched node is not found ");break;default:printf("\nError code missing\n");break;}}
/***************************************************Name: main.cversion: 0.1Description: Implementation of a double linked listChange history:0.1 Initial versionLicense: GNU GPL v3****************************************************/#include<stdio.h>#include<stdlib.h>#include"main.h"intmain(void){unsignedintchoice=0;intInputNumber=0;printf("\nThis program creates a double linked list");printf("\nYou can add nodes in forward and reverse directions");do{printf("\n1.Create Node Forward");printf("\n2.Create Node Reverse");printf("\n3.Delete Node");printf("\n4.Display Nodes in forward direction");printf("\n5.Display Nodes in reverse direction");printf("\n6.Reverse nodes");printf("\n7.Exit\n");printf("\nEnter your choice: ");scanf("%u",&choice);switch(choice){case1:AddNodeForward();break;case2:AddNodeReverse();break;case3:printf("\nEnter the node you want to delete: ");scanf("%d",&InputNumber);DeleteNode(InputNumber);break;case4:printf("\nDisplaying node data in forward direction \n");DisplayNodeForward();break;case5:printf("\nDisplaying node data in reverse direction\n");DisplayNodeReverse();break;case6:ReverseNodes();break;case7:printf("Exiting program");break;default:printf("\nIncorrect choice\n");break;}}while(choice!=7);return0;}
Напредни концепти
Асиметрична двоструко повезана листа
Асиметрична двоструко повезана листа је негде између једноструко повезане листе и регуларне двоструко повезане листе. Дели неке ставке са једноструко повезаном листом (прелажење у једном правцу) и неке из двоструко повезане листе (лакоћу модификације).
То је листа где веза за претходни чвор сваког чвора не показује на претходни чвор, већ на везу саму за себе. Док ово има мало разлике између чворова (само показује на офсет унутар претходног чвора), мења главу листе: дозвољава првом чвору да модификује везу с првим чвором врло лако.
[1][2]
Докле год је чвор у листи, његова веза са претходним никада није нула.
Додавање чвора
Да бисмо додали чвор пре неког другог, мењамо везу која је показивала на стари чвор, користећи везу за претходни; затим подесимо везу са следећим чвором овог чвора да показује на стари, и променимо везу са претходни чвор тог чвора према томе.
function insertBefore(Node node, Node newNode)
if node.prev == nullerror "The node is not in a list"
newNode.prev := node.prev
atAddress(newNode.prev) := newNode
newNode.next := node
node.prev = addressOf(newNode.next)