正在载入交互式动画窗口请稍等
循环单链表 可视化交互式动画版
什么是循环链表?
循环 链表 是所有节点连接成一个圆的链表。 在循环链表中,第一个节点和最后一个节点相互连接,形成一个循环。 末尾没有NULL。
循环链表一般有两种类型:
- 循环单链表: 在循环单链表中,链表的最后一个节点包含指向链表第一个节点的指针。 我们遍历循环单链表,直到到达起始节点。 循环单链表没有起点也没有终点。 任何节点的下一部分中都不存在空值。
- 循环双向链表: 循环双向链表兼有双向链表和循环链表的性质,其中两个连续的元素通过前一个和后一个指针链接或连接,最后一个节点通过下一个指针指向第一个节点,并且第一个节点通过前一个指针指向最后一个节点。
注意: 我们将使用单循环链表来表示循环链表的工作方式。
循环链表的表示:
循环链表与单链表类似,只是将最后一个节点连接到第一个节点。
循环链表的节点表示:
- C
- C++
- Python3
- Java
- C#
- JavaScript
- PHP
C
struct Node { int
data; struct Node *next; }; |
C++
Python3
Java
C#
Javascript
PHP
循环单链表示例:
上面的循环单链表可以表示为:
- C
- C++
- Python3
- Java
- C#
- JavaScript
- PHP
C
Node* one = createNode(3);
Node* two = createNode(5);
Node* three = createNode(9);
// Connect nodes one->next = two; two->next = three; three->next = one; |
C++
Python3
Java
C#
Javascript
PHP
解释: 在上面的程序中,一、二、三分别是值为 3、5、9 的节点,它们以循环方式连接:
- 对于节点一: Next 指针存储节点二的地址。
- 对于节点二: 下一个存储节点三的地址
- 对于节点三: Next 指向节点一。
循环链表的操作:
我们可以对循环链表进行一些类似于单链表的操作,分别是:
- 插入
- 删除
1、 循环链表中的插入:
可以通过三种方式添加节点:
- 在列表开头插入
- 在列表末尾插入
- 在节点之间插入
1) 在链表开头插入: 要在链表开头插入节点,请按以下步骤操作:
- 创建一个节点,比如 T。
- 使 T -> 下一个 = 最后一个 -> 下一个。
- 最后一个 -> 下一个 = T。
进而,
2) 在链表末尾插入: 要在链表末尾插入节点,请执行以下步骤:
- 创建一个节点,比如 T。
- 使 T -> 下一个 = 最后一个 -> 下一个;
- 最后一个 -> 下一个 = T。
- 最后=T。
插入前,
插入后,
3) 在节点之间插入: 要在两个节点之间插入节点,请按照下列步骤操作:
- 创建一个节点,比如 T。
- 查找需要插入T的节点,假设该节点为P。
- 使 T -> 下一个 = P -> 下一个;
- P -> 下一个 = T。
假设节点值为10后需要插入12,
经过查找和插入后,
2、循环链表中的删除:
1) 仅当该节点是循环链表中唯一的节点时才删除该节点:
- 释放节点的内存
-
最后一个值应该为 NULL 一个节点总是指向另一个节点,因此不需要 NULL 赋值。
可以将任意节点设置为起点。
从第一个节点到最后一个节点快速遍历。
2)删除最后一个节点:
- 找到最后一个节点之前的节点(让它是临时的)
- 保留 temp 中最后一个节点旁边的节点的地址
- 删除最后的记忆
- 把温度放在最后
3)从循环链表中删除任意节点: 我们将得到一个节点,我们的任务是从循环链表中删除该节点。
算法:
情况 1
:列表为空。
- 如果列表为空,我们将简单地返回。
情况 2 :列表不为空
- 如果列表不为空,那么我们定义两个指针 curr 和 prev 并用 头 节点初始化指针 curr 。
- 使用curr 遍历链表 找到要删除的节点,在移动到 curr 到下一个节点之前,每次设置 prev = curr。
- 如果找到该节点,请检查它是否是列表中的唯一节点。 如果是,则设置 head = NULL 和 free(curr)。
- 如果列表有多个节点,请检查它是否是列表的第一个节点。 检查这个的条件(curr == head)。 如果是,则移动 prev 直到到达最后一个节点。 当 prev 到达最后一个节点后,设置 head = head -> next 和 prev -> next = head。 删除当前。
- 如果 curr 不是第一个节点,我们检查它是否是列表中的最后一个节点。 检查的条件是(curr -> next == head)。
- 如果 curr 是最后一个节点。 设置 prev -> next = head 并通过 free(curr) 删除节点 curr。
- 如果要删除的节点既不是第一个节点,也不是最后一个节点,则设置prev -> next = curr -> next,删除curr。
- 如果该节点不存在于列表中,则返回头并且不执行任何操作。
下面是上述方法的实现:
- C
- C++
- Java
- Python3
- JavaScript
- C#
C
#include <stdio.h> #include <stdlib.h> // Structure for a node
struct Node { int
data; struct Node* next; }; // Function to insert a node at the // beginning of a Circular linked list void push( struct Node** head_ref, int data) { // Create a new node and make head // as next of it. struct Node* ptr1 = ( struct Node*) malloc ( sizeof ( struct Node)); ptr1->data = data; ptr1->next = *head_ref; // If linked list is not NULL then // set the next of last node if
(*head_ref != NULL) { // Find the node before head and // update next of it. struct Node* temp = *head_ref; while (temp->next != *head_ref)
temp = temp->next; temp->next = ptr1; } else // For the first node ptr1->next = ptr1; *head_ref = ptr1; } // Function to print nodes in a given // circular linked list
void printList( struct Node* head) { struct Node* temp = head; if
(head != NULL) { do { printf ( "%d " , temp->data); temp = temp->next; } while (temp != head); } printf ( "\n" ); } // Function to delete a given node // from the list void deleteNode( struct Node** head, int key) { // If linked list is empty if
(*head == NULL) return ; // If the list contains only a // single node if
((*head)->data == key && (*head)->next == *head) {
free (*head); *head = NULL; return ; } struct Node *last = *head, *d; // If head is to be deleted if
((*head)->data == key) { // Find the last node of the list while (last->next != *head) last = last->next; // Point last node to the next of // head i.e. the second node // of the list last->next = (*head)->next; free (*head); *head = last->next; return ; } // Either the node to be deleted is // not found or the end of list // is not reached while (last->next != *head && last->next->data != key) {
last = last->next; } // If node to be deleted was found if
(last->next->data == key) { d = last->next; last->next = d->next; free (d); } else printf ( "Given node is not found in the list!!!\n" ); }
// Driver code int main() { // Initialize lists as empty struct Node* head = NULL;
// Created linked list will be // 2->5->7->8->10 push(&head, 2); push(&head, 5); push(&head, 7); push(&head, 8); push(&head, 10);
printf ( "List Before Deletion: " ); printList(head);
deleteNode(&head, 7);
printf ( "List After Deletion: " ); printList(head);
return 0; } |
C++
Java
Python3
Javascript
C#
删除前列表:10 8 7 5 2 删除后列表:10 8 5 2
时间复杂度:
O(N),最坏的情况发生在要删除的元素是最后一个元素并且我们需要遍历整个列表时。
辅助空间:
O(1),使用恒定的额外空间。
循环链表的优点:
- 任何节点都可以作为起点。 我们可以从任意点开始遍历整个列表。 我们只需要在再次访问第一个访问的节点时停止即可。
- 对于队列的实现很有用。 与此 实现不同的是 ,如果我们使用循环链表,则不需要维护 front 和 back 两个指针。 我们可以维护一个指向最后插入的节点的指针,并且前面总是可以作为最后一个的下一个获得。
- 循环列表在重复遍历列表的应用程序中非常有用。 例如,当PC上运行多个应用程序时,操作系统通常会将正在运行的应用程序放在一个列表中,然后循环遍历它们,给每个应用程序一段时间来执行,然后让它们等待一段时间。 CPU 被分配给另一个应用程序。 操作系统使用循环列表是很方便的,这样当它到达列表末尾时,它可以循环到列表的前面。
- 循环双向链表用于实现 斐波那契堆 等高级数据结构。
- 与其他更复杂的数据结构(如树或图)相比,实现循环链表相对容易。
循环链表的缺点:
- 与单链表相比,循环表更为复杂。
- 反转循环列表比单次或双重反转循环列表更复杂。
- 如果处理不仔细,代码有可能进入无限循环。
- 找到列表末尾并控制循环更加困难。
- 尽管循环链表在某些应用程序中可能很高效,但在某些情况下(例如需要对列表进行排序或搜索时),它们的性能可能比其他数据结构慢。
- 循环链表不提供对单个节点的直接访问
循环链表的应用:
- 多人游戏利用这一点为每个玩家提供玩游戏的机会。
- 循环链表可用于组织操作系统上多个正在运行的应用程序。 这些应用程序由操作系统迭代。
- 循环链表可以用于资源分配问题。
- 循环链表通常用于实现循环缓冲区,
- 循环链表可用于模拟和游戏。
为什么要循环链表?
- 一个节点总是指向另一个节点,因此不需要 NULL 赋值。
- 可以将任意节点设置为起点。
- 从第一个节点到最后一个节点快速遍历。
下一篇文章: 循环链表 | 集合 2(遍历) 循环单链表 | 插入 如果您在上述代码/算法中发现任何错误,请写评论,或寻找其他方法来解决相同的问题