循环链表简介:
循环链表是一种特殊的链表数据结构,与单向链表或双向链表相比,循环链表的最后一个节点的下一个节点指向第一个节点,从而形成一个环形结构。因此,循环链表可以在最后一个节点后继续添加节点,并且可以像单向链表或双向链表一样遍历、查找和删除节点。循环链表通常有一个头指针和一个尾指针,它们指向第一个节点和最后一个节点,以便在添加或删除节点时快速定位。
循环链表可以用于解决某些问题,例如模拟一个环形队列,或者用于实现循环播放的音乐列表等。循环链表的优点在于它具有链表的灵活性,同时可以避免在添加或删除节点时需要遍历整个链表的缺点。
以下是一个循环链表:
/*
This shows the coding basics of circular-linked-list in C programming.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
struct node
{
int data;
int key;
struct node *next;
};
struct node *head = NULL;
struct node *tail = NULL;
struct node *current = NULL;
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;
}
//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;
// attention here, difference
if (isEmpty())
{
head = link;
head->next = head;
tail = head;
}
else
{
//point it to old first node
link->next = head;
//point first to new first node
head = link;
//update tail node
tail->next = head;
}
//printf("%p,%d,%d,%p\n",link,link->data,link->key,link->next);
//printf("current head: %p\n",head);
//printf("current tail->next: %p\n",tail->next);
}
//delete first item
struct node *deleteFirst()
{
//save reference to first link
struct node *tempLink = head;
if(head->next == head)
{
head = NULL;
return tempLink;
}
//mark next to first link as first
head = head->next;
//update tail node
tail->next = head;
//return the deleted link
return tempLink;
}
//display the list
void printList()
{
struct node *ptr = head;
printf("\n[ ");
//start from the beginning
if(head != NULL)
{
// difference here
while(ptr->next != head)
{
//printf("(%d,%d) ",ptr->key,ptr->data);
printf("(%p,%d,%d,%p) ",ptr,ptr->key,ptr->data,ptr->next);
ptr = ptr->next;
}
//print the last node
//printf("(%d,%d) ",ptr->key,ptr->data);
printf("(%p,%d,%d,%p) ",ptr,ptr->key,ptr->data,ptr->next);
}
printf(" ]");
}
struct node* find(int key)
{
//start from the first link
struct node* current = head;
//if list is empty
if(head == NULL)
{
return NULL;
}
//navigate through list
while(current->key != key)
{
//if it is last node
if(current->next == head) {
return NULL;
} else {
//go to next link
current = current->next;
}
}
//if data found, return the current Link
return current;
}
//在中间插入,在key1后加入新节点
void insertBetween(int key1, int key, int data)
{
//create a link
struct node *link = (struct node*) malloc(sizeof(struct node));
struct node *next = NULL;
current = find(key1);
link->key = key;
link->data = data;
next = current->next;
current->next = link;
link->next = next;
}
//在中间删除,并且返回被删除的节点的地址
struct node*deletebetween(int key)
{
struct node* current = find(key);
struct node* next = NULL;
if(current == NULL || current == tail)
{
return NULL;
}
next = current->next;
current->data = next->data;
current->key = next->key;
current->next = next->next;
free(next);
return current;
}
int main()
{
insertFirst(1,10);
insertFirst(2,20);
insertFirst(3,30);
insertFirst(4,40);
insertFirst(5,50);
insertFirst(6,60);
insertBetween(2,1231,32423);
insertBetween(5,324,1231);
deletebetween(6);
deletebetween(2);
printf("Original List: ");
//print list
printList();
while(!isEmpty()) {
struct node *temp = deleteFirst();
printf("\nDeleted value:");
printf("(%d,%d) ",temp->key,temp->data);
}
printf("\nList after deleting all items: ");
printList();
return 0;
}
这是一个 C 语言编写的循环链表的基本代码示例。该代码定义了一个结构体 node
表示链表中的每个节点,节点包含两个整型数据 key
和 data
,以及一个指向下一个节点的指针 next
。整个链表通过三个指针 head
、tail
和 current
来维护。其中,head
指向链表的头部节点,tail
指向链表的尾部节点,current
用于遍历链表。
除此之外,该代码还实现了以下几个函数:
isEmpty()
:判断链表是否为空,如果链表为空则返回 true,否则返回 false。length()
:计算链表的长度,返回链表中节点的数量。insertFirst(int key, int data)
:在链表的头部插入一个新节点,该节点的键值为key
,数据为data
。deleteFirst()
:删除链表的头部节点,并返回被删除节点的地址。printList()
:打印链表中所有节点的信息。find(int key)
:查找链表中键值为key
的节点,并返回该节点的地址。insertBetween(int key1, int key, int data)
:在键值为key1
的节点后面插入一个新节点,该节点的键值为key
,数据为data
。deletebetween(int key)
:删除键值为key
的节点,并返回被删除节点的地址。
在 main()
函数中,该代码创建了一个循环链表,并在其中插入、删除节点,最后打印链表中所有节点的信息,然后将链表中所有节点删除,最终再次打印链表中的所有节点,可以看到链表已经为空。
各个函数的详细解释:
isEmpty()函数:
这段代码定义了一个函数 isEmpty()
,用来判断链表是否为空。它通过比较头结点 head
是否为 NULL
来确定链表是否为空。如果 head
为 NULL
,则说明链表为空,函数返回 true
,否则返回 false
。由于返回值类型是 bool
,因此函数的返回值只有两种情况:true
和 false
。
length()函数:
这段代码实现了一个循环双链表(doubly linked list)的长度(number of elements)的计算。
首先初始化计数器 length
为 0。如果双链表为空(即头节点 head
为 NULL
),则直接返回长度为 0。否则,从双链表的第一个元素开始遍历,通过 current = head->next
将 current
指针指向第一个元素(即头节点的下一个节点),然后开始循环。在循环中,每当遍历到一个元素时,将计数器 length
加一,并将 current
指针指向下一个元素,直到遍历到头节点 head
为止。最后返回计数器 length
,即双链表的长度。
需要注意的是,双链表的长度的计算与单链表不同,因为双链表中每个节点都有一个前驱节点,因此在计算长度时需要遍历到头节点才能停止。
insertFirst()函数:
这段代码实现了在循环链表的头部插入一个新节点。首先,使用malloc函数动态分配一个新节点,并将key和data值设置为给定的参数。接下来,检查循环链表是否为空。如果为空,则将新节点设置为头节点,同时将头节点的下一个指针指向头节点本身,并将尾节点设置为头节点。如果循环链表不为空,则将新节点的下一个指针指向当前头节点,将新节点设置为新的头节点,最后更新尾节点的下一个指针为新的头节点,以确保循环链表的闭环性质不变。最后,这段代码输出了一些调试信息,包括新节点的地址、值以及链表的头节点和尾节点指针。
deleteFirst()函数:
这段代码定义了一个名为deleteFirst()
的函数,它的返回类型是一个指向struct node
类型的指针。该函数的作用是从循环链表的开头删除一个节点,并返回被删除节点的指针。函数首先将头指针保存在一个临时变量tempLink
中。如果循环链表中只有一个节点,函数将把头指针设置为NULL,表示链表为空,然后返回tempLink
。否则,函数将把头指针更新为原来的下一个节点,并更新尾节点的指针以使其指向新的头节点。最后,函数返回指向被删除节点的指针tempLink
。
printList()函数:
这段代码是定义了一个打印循环链表的函数。首先,它将头指针赋值给一个指针变量ptr。然后,它打印一个左中括号,表示链表的开始。接下来,如果头指针不为空,它进入一个while循环,该循环一直迭代到ptr指向的节点的下一个节点是头节点为止。在循环内,它使用printf()函数打印指向节点的指针、关键字、数据和下一个节点的指针。然后,它将指针ptr移动到链表中的下一个节点,以准备下一次迭代。最后,当ptr指向的下一个节点是头节点时,它打印出最后一个节点,然后打印右中括号,表示链表的结束。
find()函数:
这段代码实现了一个循环链表中寻找某个节点的功能。它首先从链表的头节点开始,判断链表是否为空,如果为空,则直接返回 NULL。如果链表不为空,则循环遍历链表,判断当前节点的关键字是否与所需寻找的关键字相等。如果相等,则返回当前节点。如果当前节点不是所需节点,则将指针移到下一个节点继续判断,如果到达链表末尾仍未找到,则返回 NULL。
insertBetween()函数:
这段代码定义了一个名为insertBetween
的函数,它的参数包括要在哪个节点后面插入(即key1
),要插入的新节点的键值和数据。
首先,它使用malloc
函数动态地创建一个新的节点,并将其存储在指针变量link
中。然后,使用find
函数查找与给定键值key1
匹配的节点,并将其存储在指针变量current
中。
接下来,将要插入的新节点的键值和数据设置为link->key
和link->data
。然后,将指针变量next
设置为当前节点current
的下一个节点。
然后,将指针变量link
插入到当前节点current
和其下一个节点next
之间。具体来说,将当前节点的下一个节点设置为指针变量link
,将指针变量link
的下一个节点设置为节点next
。这样就完成了在给定节点后插入新节点的操作。
需要注意的是,这段代码假定链表中已经存在具有键值key1
的节点。如果该节点不存在,则会出现问题。
deletebetween()函数:
这段代码实现了删除链表中某个节点的功能,通过find()函数找到具有指定关键字key的节点current;检查current是否为NULL或者是否为tail节点,若是则直接返回NULL;否则,取得current节点的下一个节点next;将next节点的数据和关键字赋值给current节点,同时将current节点的next指针指向next节点的next;最后,释放next节点的内存空间,返回current节点。这里的删除操作可以理解为将链表中某个节点的数据和关键字“移动”到当前节点,并将下一个节点删除的过程,从而达到删除的效果。
单链表和循环链表的区别:
我们在学习循环链表时,一定要认识到循环链表和单链表的区别。循环链表和单链表的主要区别在于最后一个节点的指针。在单链表中,最后一个节点的指针为 NULL,表示链表的结束。而在循环链表中,最后一个节点的指针指向第一个节点,形成一个循环。这意味着在循环链表中可以从任何一个节点开始遍历整个链表,而在单链表中只能从第一个节点开始遍历。由于循环链表中没有真正的“末尾”,因此对于一些特定的应用场景(如循环队列),循环链表可以更加方便和高效地实现。同时,循环链表也需要更多的注意,避免出现死循环。