目录
循环双向链表简介:
循环双向链表是一种数据结构,与双向链表类似,但是它的尾节点指向头节点,形成了一个循环。循环双向链表的每个节点都包含两个指针,一个指向前驱节点,一个指向后继节点,这样可以实现节点的前后遍历。在循环双向链表中,插入和删除操作都比较容易实现,并且它还支持前向遍历和后向遍历,这使得循环双向链表在某些情况下比单向链表更为便利。循环双向链表常用于实现循环队列和双向循环队列等数据结构。
下面是一个双向循环链表:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
struct node
{
int data;
int key;
struct node *next;
struct node *prev;
};
//this link always point to first Link
struct node *head = NULL;
//this link always point to last Link
struct node *last = NULL;
struct node *current = NULL;
//is list empty
bool isEmpty()
{
return head == NULL;
}
int length()
{
int length = 0;
//if list is empty
if(head == NULL)
{
return 0;
}
current = head->next;
// here different to single linked list
while(current != head)
{
length++;
current = current->next;
}
return length;
}
//display the list in from first to last
void displayForward()
{
//start from the beginning
struct node *ptr = head;
//navigate till the end of the list
printf("\n[ ");
while(ptr != last)
{
//print data
printf("(%d,%d) ",ptr->key,ptr->data);
//printf("(%p,%p,%d,%d,%p) ",ptr->prev,ptr,ptr->key,ptr->data,ptr->next);
//traverse forward to next node
ptr = ptr->next;
}
//print the last node
printf("(%d,%d) ",ptr->key,ptr->data);
//printf("(%p,%p,%d,%d,%p) ",ptr->prev,ptr,ptr->key,ptr->data,ptr->next);
printf(" ]\n");
}
//display the list from last to first
void displayBackward()
{
//start from the last
struct node *ptr = last;
//navigate till the start of the list
printf("\n[ ");
while(ptr != head)
{
//print data
printf("(%d,%d) ",ptr->key,ptr->data);
//printf("(%p,%p,%d,%d,%p) ",ptr->prev,ptr,ptr->key,ptr->data,ptr->next);
//traverse backward to next node
ptr = ptr ->prev;
}
//print the first node
printf("(%d,%d) ",ptr->key,ptr->data);
//printf("(%p,%p,%d,%d,%p) ",ptr->prev,ptr,ptr->key,ptr->data,ptr->next);
printf(" ]\n");
}
//insert link at the first location
void insertFirst(int key, int data)
{
//create a link
struct node *link = (struct node*) malloc(sizeof(struct node));
link->key = key;
link->data = data;
if(isEmpty())
{
head = link;
head->next = head;
head->prev = head;
last = head;
}
else
{
last->next = link;
link->prev = last;
link->next = head;
head->prev = link;
head = link;
last->next = head;
head->prev = last;
}
}
//insert link at the last location
void insertLast(int key, int data)
{
//create a link
struct node *link = (struct node*) malloc(sizeof(struct node));
link->key = key;
link->data = data;
if(isEmpty())
{
head = link;
head->next = head;
head->prev = head;
last = head;
}
else
{
head->prev = link;
link->next = head;
last->next = link;
link->prev = last;
last = link;
last->next = head;
head->prev = link;
}
}
//delete first item
struct node* deleteFirst()
{
//save reference to first link
struct node *tempLink = head;
//if only one link
if(head->next == head)
{
last = NULL;
}
else
{
head->next->prev = last;
last->next = head->next;
head = head->next;
}
return tempLink;
}
//delete link at the last location
struct node* deleteLast()
{
//save reference to last link
struct node *tempLink = last;
//if only one link
if(head->next == last)
{
head = NULL;
}
else
{
last->prev->next = head;
head->prev = last->prev;
last = last->prev;
}
return tempLink;
}
//delete a link with given key
struct node* delete(int key)
{
//start from the first link
struct node* current = head;
struct node* previous = NULL;
struct node* temp = NULL;
//if list is empty
if(head == NULL) {
return NULL;
}
//navigate through list
while(current->key != key)
{
//if it is last node
if(current->next == NULL)
{
return NULL;
}
else
{
//store reference to current link
previous = current;
//move to next link
current = current->next;
}
}
temp = current;
//found a match, update the link
if(current == head)
{
head->next->prev = last;
last->next = head->next;
head = head->next;
}
else if(current == last)
{
last->prev->next = head;
head->prev = last->prev;
last = last->prev;
}
else
{
current->prev->next = previous;
previous->prev = current->prev;
current = current->prev;
}
return temp;
}
bool insertAfter(int key, int newKey, int data)
{
//start from the first link
struct node *current = head;
//if list is empty
if(head == NULL)
{
return false;
}
//navigate through list
while(current->key != key)
{
//if it is last node
if(current->next == head)
{
return false;
}
else
{
//move to next node
current = current->next;
}
}
//create a link
struct node *newLink = (struct node*) malloc(sizeof(struct node));
newLink->key = newKey;
newLink->data = data;
current->next->prev = newLink;
newLink->next = current->next;
newLink->prev = current;
current->next = newLink;
return true;
}
void main()
{
//create list from the beginning
insertFirst(1,10);
insertFirst(2,20);
insertFirst(3,30);
insertFirst(4,40);
insertFirst(5,50);
insertFirst(6,60);
//traverse forward
printf("\nTraversing the Double Linked List (First to Last): ");
displayForward();
//traverse backward
printf("\n");
printf("\nTraversing the Double Linked List (Last to first): ");
displayBackward();
//deletion
printf("\nList---after deleting first node: ");
struct node *temp = deleteFirst();
printf("\nDeleted node:");
printf("(%d,%d) ",temp->key,temp->data);
displayForward();
printf("\nList---after deleting last node: ");
deleteLast();
displayForward();
//insertion
printf("\nList---insert after key(4): ");
insertAfter(4,9,90);
displayForward();
printf("\nList---after delete key(5): ");
delete(5);
displayForward();
printf("\nDouble linked lisk operation completed......");
}
这是一个使用双向循环链表实现的链表数据结构,支持在链表头部和尾部插入、删除节点,并且可以从前向后和从后向前遍历链表。代码中包含以下函数:
- isEmpty():判断链表是否为空。
- length():返回链表的长度。
- displayForward():从前向后遍历链表,并打印出每个节点的 key 和 data 值。
- displayBackward():从后向前遍历链表,并打印出每个节点的 key 和 data 值。
- insertFirst():在链表头部插入一个节点,需要给出 key 和 data。
- insertLast():在链表尾部插入一个节点,需要给出 key 和 data。
- deleteFirst():删除链表头部的节点,并返回被删除的节点。
- deleteLast():删除链表尾部的节点,并返回被删除的节点。
- delete(key):删除链表中第一个 key 值为给定值 key 的节点,并返回被删除的节点。
其中,节点的数据结构为 struct node,包含 key、data、next 和 prev 四个成员变量,其中 next 和 prev 分别指向下一个和上一个节点。在程序的开头,head、last 和 current 分别是指向链表头部、尾部和当前节点的指针,都被初始化为空
各个函数的详细解释:
isEmpty()函数:
这段代码定义了一个名为 isEmpty()
的函数,其作用是判断一个链表是否为空。具体来说,这个函数会返回一个 bool
类型的值,即 true
或 false
。如果链表的头结点 head
指针为空指针(即 NULL
),那么函数会返回 true
,表示链表为空;否则函数会返回 false
,表示链表不为空。因此,这个函数的实现非常简单,只需要判断头结点是否为空即可。这样,我们就可以通过调用 isEmpty()
函数来判断链表是否为空,从而进行相应的操作。
length()函数:
这段代码是用来计算双向循环链表的长度的。它首先定义了一个变量length,并将其初始化为0。然后它检查链表是否为空,如果为空,则直接返回0。接着,它将当前节点指针设置为链表的头结点的下一个节点,即第一个实际节点。然后它使用一个循环来遍历整个链表,每当它找到一个节点时,就将长度加1。在每次迭代中,它将当前节点指针设置为下一个节点,直到它回到头结点。最后,函数返回计算出的链表长度。
displayForward()函数:
这段代码实现了一个双向循环链表的正向遍历(displayForward)操作。其中,该链表中的每个节点包含一个key和一个data,用于存储数据。代码中定义了一个指向头节点的指针ptr,从头节点开始依次遍历链表,直到遍历到尾节点last为止。在遍历链表的过程中,每次输出当前节点的key和data,然后将指针ptr移动到下一个节点,直到遍历结束。最后输出整个链表中的所有节点的key和data信息,以及方括号,以示链表的结束。
displayBackward()函数:
这段代码定义了一个名为displayBackward
的函数,它可以反向遍历双向链表并输出节点的数据值。函数中首先定义一个指针ptr
指向链表的最后一个节点,然后从后向前遍历链表,每次输出节点的数据值,直到遍历到链表的第一个节点为止。函数的输出格式为一个包含所有节点数据值的数组,每个数据值由一对括号括起来,并以逗号分隔。整个数组由一对方括号括起来,两侧各有一个空格。函数最后输出一个换行符。
insertFirst(int key, int data)函数:
这段代码是一个双向循环链表的插入操作,可以在链表的起始位置插入一个新的节点。函数的参数是需要插入的节点的键值和数据。
首先,该函数创建一个新节点link并为其分配内存,然后设置节点的键值和数据。接着,通过调用函数isEmpty()来检查链表是否为空。如果链表为空,将头指针head指向新节点link,然后将头指针head的next和prev都指向自己。由于这是一个双向循环链表,因此尾指针last也指向头节点head。
如果链表不为空,则将新节点link插入到链表的起始位置。首先将尾节点last的next指针指向新节点link,将新节点link的prev指针指向尾节点last,将新节点link的next指针指向头节点head,将头节点head的prev指针指向新节点link。然后更新头指针head,将尾节点last的next指针指向头节点head,将头节点head的prev指针指向尾节点last。这样就完成了在链表的起始位置插入一个新节点的操作。
insertLast(int key, int data)函数:
这段代码实现了往双向循环链表的尾部插入一个新节点的操作。该函数的参数是新节点的键值 key
和数据值 data
。这个函数会首先创建一个新的节点并分配内存,然后判断链表是否为空。如果链表为空,则将新节点设置为头结点,并且让新节点的 next
和 prev
指向自身,同时将 last
指向头结点,因为此时链表只有一个节点。
如果链表不为空,则将新节点插入到链表的尾部,即原来的最后一个节点后面。具体来说,首先将头节点的 prev
指针指向新节点,新节点的 next
指针指向头节点,原来的最后一个节点的 next
指针指向新节点,新节点的 prev
指针指向原来的最后一个节点。然后将 last
指向新的最后一个节点,即新节点,最后更新头节点的 prev
指针和新的最后一个节点的 next
指针,让它们分别指向最后一个节点,这样链表就形成了一个循环。
deleteFirst()函数:
这段代码定义了一个名为deleteFirst()
的函数,它的返回类型是struct node*
,即返回一个指向被删除的节点的指针。函数的作用是删除链表的第一个节点,并将链表的头指针指向原来第一个节点的下一个节点。首先,该函数保存了指向链表第一个节点的指针head
的副本,命名为tempLink
。接着,函数判断链表中是否只有一个节点,如果是,则将last
指针置为NULL
,否则,将链表中第二个节点的prev
指针指向最后一个节点,同时将最后一个节点的next
指针指向链表中第二个节点。最后,将head
指针指向链表中第二个节点,从而实现删除第一个节点的目的。最后,函数返回指向被删除节点的指针,以便调用者对节点进行处理。
deleteLast()函数:
这段代码是双向循环链表实现删除最后一个节点的方法。首先保存对最后一个节点的引用,然后根据链表中的元素个数执行相应的操作。如果链表中只有一个元素,则将head指针设置为NULL;否则将指向最后一个节点的指针(即last->prev)的下一个指针(即last->prev->next)设置为head,将指向第一个节点的指针(即head)的前一个指针(即head->prev)设置为指向最后一个节点的前一个指针(即last->prev),最后将指向最后一个节点的指针(即last)更新为指向它前面的节点。最后返回被删除的节点的指针。
delete(int key)函数:
这段代码实现了从双向循环链表中删除一个指定key的节点。首先,定义了三个指针变量:current指向当前节点,previous指向前一个节点,temp保存当前节点的引用。然后,从头结点开始遍历链表,寻找指定key的节点。如果找到了匹配的节点,则更新链表链接以将其从链表中删除,如果该节点是头节点或尾节点,则更新头节点和尾节点。最后,返回被删除的节点的引用。如果链表为空或未找到匹配的节点,则返回NULL。需要注意的是,这个代码假定链表中的key是唯一的。如果有多个节点有相同的key值,那么这个代码只会删除第一个匹配的节点,而不是所有匹配的节点。
insertAfter(int key, int newKey, int data)函数:
这是一个在双向循环链表中插入一个新节点的函数。它接受三个参数:key
表示要在其之后插入新节点的节点键值,newKey
表示新节点的键值,data
表示新节点的数据。该函数返回一个布尔值,表示是否成功插入新节点。
该函数首先检查链表是否为空,如果为空,则返回 false
,表示插入失败。接下来,函数遍历链表,寻找键值为 key
的节点。如果找到链表末尾还没有找到,则返回 false
。如果找到了节点,则创建一个新节点 newLink
,并将它插入到 key
节点之后。
具体地说,它将 newLink
插入到 current
节点和 current->next
节点之间。要做到这一点,我们需要执行以下步骤:
- 将
newLink
的前一个节点设置为current
。 - 将
newLink
的后一个节点设置为current->next
。 - 将
current
的后一个节点的前一个节点设置为newLink
。 - 将
current
的后一个节点设置为newLink
。
最后,函数返回 true
表示插入成功。
双向循环链表和循环链表的区别:
在学习双向循环链表时,要注意双向循环链表和循环链表的区别。在循环链表中,每个节点只有一个指针,它指向下一个节点。而最后一个节点的指针指向第一个节点,形成一个循环。在双向循环链表中,每个节点有两个指针,一个指向前一个节点,一个指向后一个节点。最后一个节点的指针指向第一个节点,第一个节点的指针指向最后一个节点,形成一个双向的循环。因此,双向循环链表可以双向遍历,而循环链表只能单向遍历。双向循环链表也更适合需要在链表中间或结尾处进行删除或插入操作的情况,因为双向链表可以直接访问前一个节点。但是,双向循环链表需要更多的指针来维护链表的结构,所以占用的内存可能会更多。