详解循环链表

定义和特点

定义

链表是一种常用的数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表可以克服数组需要预先知道数据大小的缺点,而且插入和删除元素很方便,但是失去数组随机读取的优点。

循环链表是链表的一种,它与普通链表的区别在于,循环链表的最后一个节点不是指向一个空值NULL,而是指向链表的第一个节点,形成一个环状结构。循环链表可以实现正向和反向的遍历,广泛用于需要循环引用数据的场景。

特点

  • 是链表的扩展,通过将最后一个节点的指针指向第一个节点,形成了一个环状结构。
  • 提供了正向和反向两种遍历方式。
  • 缺点是在插入和删除数据时,需要考虑调整指针方向,操作比单链表复杂。

基本运算

循环链表的基本运算包括以下几种:

  1. 初始化:创建一个循环链表,并为链表的头节点分配内存空间。
  2. 插入:在循环链表的尾部或指定位置插入一个新的节点,更新后继指针和头节点指针。
  3. 删除:删除循环链表中的指定节点,并更新后继指针和头节点指针。
  4. 求表的长度:计算循环链表中的节点数。
  5. 判空:判断循环链表是否为空。
  6. 释放:释放循环链表的内存空间,包括头节点和各个节点的内存空间。

循环链表的实现

初始化

实现步骤:

  1. 定义结构体
    首先,定义了两个结构体,一个是Node,另一个是CircularLinkedListNode结构体包含两个成员,一个是int类型的data,用于存储节点的数据;另一个是Node*类型的next,这是一个指针,指向下一个Node结构体。

CircularLinkedList结构体包含两个Node*类型的成员,分别是headtailhead指向链表的第一个节点,而tail指向链表的最后一个节点。

  1. 初始化函数

函数initialize用于初始化一个CircularLinkedList结构体。它将传入的链表的headtail都设置为NULL。

  1. 创建新节点函数
    函数createNode接收一个整数参数data,并返回一个新创建的Node的指针。这个函数首先使用malloc动态分配内存来创建一个新的Node,然后将传入的data赋值给新节点的data字段,最后将新节点的next字段设置为NULL。
c 复制代码
// 定义一个名为"Node"的结构体,这个结构体里面有两个成员:一个整型的"data"和一个指向同结构体自身的"next"指针  
typedef struct Node {    
    int data;  // 存储的数据  
    struct Node *next;  // 指向下一个Node结构体的指针  
} Node;    
  
// 定义一个名为"CircularLinkedList"的结构体,这个结构体里面有两个成员:一个指向Node结构体的"head"和一个指向Node结构体的"tail"  
typedef struct CircularLinkedList {    
    Node *head;  // 指向链表头部的Node结构体的指针  
    Node *tail;  // 指向链表尾部的Node结构体的指针  
} CircularLinkedList;    
  
// 定义一个函数"initialize",这个函数接收一个指向CircularLinkedList结构体的指针作为参数  
// 在这个函数里面,我们将这个结构体的"head"和"tail"都设置为NULL,以初始化这个循环链表  
void initialize(CircularLinkedList *list) {    
    list->head = NULL;  // 设置头节点为NULL  
    list->tail = NULL;  // 设置尾节点为NULL  
}    
  
// 定义一个函数"createNode",这个函数接收一个整型数据作为参数,并返回一个指向新创建的Node结构体的指针  
// 在这个函数里面,我们首先使用"malloc"函数动态分配了一个新的Node结构体,并使用"data"参数来初始化它的"data"字段  
// 然后,我们将新结构体的"next"字段设置为NULL,最后返回新结构体的指针  
Node* createNode(int data) {    
    Node *newNode = (Node*)malloc(sizeof(Node));  // 动态分配内存空间以存储新的Node结构体  
    newNode->data = data;  // 设置新节点的数据字段  
    newNode->next = NULL;  // 将新节点的next指针设置为NULL  
    return newNode;  // 返回新节点的指针  
}

插入数据

根据需要,创建一个新的节点,并将其加入到循环链表的尾部。如果链表为空,则新节点既是头节点也是尾节点;否则,将其添加到尾节点的后面,并更新尾节点为新节点。同时,将新节点的 next 指针指向头节点,形成循环。

c 复制代码
// 添加新函数,用于向循环链表中添加新的节点  
void add(CircularLinkedList *list, int data) {  
  
    // 创建一个新节点  
    Node *newNode = createNode(data);  
  
    // 检查链表是否为空。如果为空,则新节点既是头节点也是尾节点  
    if (list->head == NULL) {  
        list->head = newNode;  // 设置头节点  
        list->tail = newNode;  // 设置尾节点  
        newNode->next = list->head;  // 设置新节点的next指针指向头节点,形成循环  
    } else {  
        // 如果链表不为空,则将新节点添加到尾节点的后面  
        list->tail->next = newNode;  // 修改尾节点的next指针  
        list->tail = newNode;  // 更新尾节点为新节点  
        newNode->next = list->head;  // 设置新节点的next指针指向头节点,形成循环  
    }  
}

删除指定节点

遍历循环链表,查找是否有节点的数据与给定的数据匹配。当找到匹配的节点后,将其从链表中删除。具体实现步骤如下:

  • 将遍历当前节点的指针 curr 移动到头结点的下一个节点开始,并初始化前一个节点 prev 为头结点。
  • 进入循环,检查当前节点的数据是否与给定数据匹配。如果不匹配,则将前一个节点移动到当前节点,将当前节点移动到下一个节点。如果匹配,则将前一个节点的 next 指针指向当前节点的下一个节点,从而删除当前节点。同时,释放当前节点的内存,并结束循环。
  • 最后返回即可。
ini 复制代码
// 用于从循环链表中删除指定数据的节点  
void deleteNode(CircularLinkedList *list, int data) {  
  
    // 头节点的前一个节点,初始化为头节点  
    Node *prev = list->head;  
  
    // 头节点的下一个节点,初始化为头节点的下一个节点  
    Node *curr = list->head->next;  
  
    // 遍历链表,查找是否有节点的数据与给定的数据匹配  
    while (curr != list->head) {  // 当当前节点不是头节点时继续循环  
        if (curr->data == data) {  // 如果当前节点的数据与给定数据匹配  
            // 将前一个节点的next指针指向当前节点的下一个节点,从而删除当前节点  
            prev->next = curr->next;  
            // 释放当前节点的内存  
            free(curr);  
            // 结束循环  
            break;  
        }  
        // 如果当前节点的数据不匹配,则前一个节点移动到当前节点,当前节点移动到下一个节点  
        prev = curr;  
        curr = curr->next;  
    }  
}

查找指定值的节点

这个代码主要是实现一个在循环链表中找到特定值的功能,遍历链表中的每个节点,如果节点的数据等于目标值,就返回该节点在链表中的位置(从0开始计数),如果遍历结束还未找到目标值,就返回-1表示未找到。需要注意的是,这里的位置与链表的顺序有关,并不是按照值的大小来排序的。

c 复制代码
int findValue(CircularLinkedList *list, int value) {    
    // 创建一个Node类型的指针变量current,表示当前访问的节点  
    Node *current = list->head;    
    // 创建一个整型变量count,表示当前找到的匹配节点的数量,初始值为0  
    int count = 0;    
    // 使用while循环进行遍历,条件是当前节点不是头节点  
    while (current != list->head) {    
        // 如果当前节点的数据等于目标值value  
        if (current->data == value) {    
            // 返回当前找到的匹配节点的数量count  
            return count;    
        }    
        // 否则,将current指向下一个节点  
        current = current->next;    
        // 并将count加1  
        count++;    
    }    
    // 如果遍历结束还未找到目标值,则返回-1表示未找到有效值  
    return -1;    
}

循环顺序表的长度

这段代码的主要功能是计算循环链表中的节点数量。通过一个while循环遍历链表,每当遍历到一个节点,就将计数器加1。当遍历到头节点时,由于头节点会再次指向链表的开始,因此跳出循环。最后返回计数器的值,即链表中的节点数量。

c 复制代码
// 定义一个函数,名为getLength,它接收一个CircularLinkedList类型的指针作为参数,并返回一个整型数。  
int getLength(CircularLinkedList *list) {    
    int count = 0;    
    Node *temp = list->head;      
    while (temp != NULL) {    
        // 在计数器count上加1,表示已经遍历到一个节点。  
        count++;    
        // 将temp指向下一个节点。  
        temp = temp->next;    
        // 如果temp指向了链表的头节点,即temp == list->head,那么就跳出循环。这是因为在遍历到头节点后,继续遍历会重新回到头节点,形成无限循环。  
        if (temp == list->head) break;
    }      
    return count;    
}

打印

这段代码的主要功能是接受一个循环链表的指针,然后从头节点开始遍历整个链表,并打印出每个节点的数据值。当遍历到链表的尾部,也就是头节点时,退出循环。最后,打印一个换行符以清晰地展示链表数据。

c 复制代码
// 打印方法:从头节点开始遍历,打印每个节点的数据  
void printList(CircularLinkedList *list) {
	printf("循环链表为:");  
    Node *current = list->head;  
    while (current != NULL) {  
        printf("%d ", current->data);  
        current = current->next;  
        if (current == list->head) {  
            break;  // 如果遍历到头节点,则退出循环  
        }  
    }  
    printf("\n");  
}

执行结果

完成Demo

c 复制代码
#include <stdio.h>  
#include <stdlib.h>  
#include<cstring>  
// 定义一个名为"Node"的结构体,这个结构体里面有两个成员:一个整型的"data"和一个指向同结构体自身的"next"指针  
typedef struct Node {    
    int data;  // 存储的数据  
    struct Node *next;  // 指向下一个Node结构体的指针  
} Node;    
  
// 定义一个名为"CircularLinkedList"的结构体,这个结构体里面有两个成员:一个指向Node结构体的"head"和一个指向Node结构体的"tail"  
typedef struct CircularLinkedList {    
    Node *head;  // 指向链表头部的Node结构体的指针  
    Node *tail;  // 指向链表尾部的Node结构体的指针  
} CircularLinkedList;    
  
// 定义一个函数"initialize",这个函数接收一个指向CircularLinkedList结构体的指针作为参数  
// 在这个函数里面,我们将这个结构体的"head"和"tail"都设置为NULL,以初始化这个循环链表  
void initialize(CircularLinkedList *list) {    
    list->head = NULL;  // 设置头节点为NULL  
    list->tail = NULL;  // 设置尾节点为NULL  
}    
  
// 定义一个函数"createNode",这个函数接收一个整型数据作为参数,并返回一个指向新创建的Node结构体的指针  
// 在这个函数里面,我们首先使用"malloc"函数动态分配了一个新的Node结构体,并使用"data"参数来初始化它的"data"字段  
// 然后,我们将新结构体的"next"字段设置为NULL,最后返回新结构体的指针  
Node* createNode(int data) {    
    Node *newNode = (Node*)malloc(sizeof(Node));  // 动态分配内存空间以存储新的Node结构体  
    newNode->data = data;  // 设置新节点的数据字段  
    newNode->next = NULL;  // 将新节点的next指针设置为NULL  
    return newNode;  // 返回新节点的指针  
}  
  
void add(CircularLinkedList *list, int data) {  
  
    // 创建一个新节点  
    Node *newNode = createNode(data);  
  
    // 检查链表是否为空。如果为空,则新节点既是头节点也是尾节点  
    if (list->head == NULL) {  
        list->head = newNode;  // 设置头节点  
        list->tail = newNode;  // 设置尾节点  
        newNode->next = list->head;  // 设置新节点的next指针指向头节点,形成循环  
    } else {  
        // 如果链表不为空,则将新节点添加到尾节点的后面  
        list->tail->next = newNode;  // 修改尾节点的next指针  
        list->tail = newNode;  // 更新尾节点为新节点  
        newNode->next = list->head;  // 设置新节点的next指针指向头节点,形成循环  
    }  
} 
  
// 用于从循环链表中删除指定数据的节点  
void deleteNode(CircularLinkedList *list, int data) {  
  
    // 头节点的前一个节点,初始化为头节点  
    Node *prev = list->head;  
  
    // 头节点的下一个节点,初始化为头节点的下一个节点  
    Node *curr = list->head->next;  
  
    // 遍历链表,查找是否有节点的数据与给定的数据匹配  
    while (curr != list->head) {  // 当当前节点不是头节点时继续循环  
        if (curr->data == data) {  // 如果当前节点的数据与给定数据匹配  
            // 将前一个节点的next指针指向当前节点的下一个节点,从而删除当前节点  
            prev->next = curr->next;  
            // 释放当前节点的内存  
            free(curr);  
            // 结束循环  
            break;  
        }  
        // 如果当前节点的数据不匹配,则前一个节点移动到当前节点,当前节点移动到下一个节点  
        prev = curr;  
        curr = curr->next;  
    }  
}
   
int findValue(CircularLinkedList *list, int value) {    
    // 创建一个Node类型的指针变量current,表示当前访问的节点  
    Node *current = list->head;    
    // 创建一个整型变量count,表示当前找到的匹配节点的数量,初始值为0  
    int count = 0;    
    // 使用while循环进行遍历,条件是当前节点不是头节点  
    while (current != list->head) {    
        // 如果当前节点的数据等于目标值value  
        if (current->data == value) {    
            // 返回当前找到的匹配节点的数量count  
            return count;    
        }    
        // 否则,将current指向下一个节点  
        current = current->next;    
        // 并将count加1  
        count++;    
    }    
    // 如果遍历结束还未找到目标值,则返回-1表示未找到有效值  
    return -1;    
}    
  
// 定义一个函数,名为getLength,它接收一个CircularLinkedList类型的指针作为参数,并返回一个整型数。  
int getLength(CircularLinkedList *list) {    
    int count = 0;    
    Node *temp = list->head;    
    while (temp != NULL) {    
        // 在计数器count上加1,表示已经遍历到一个节点。  
        count++;    
        // 将temp指向下一个节点。  
        temp = temp->next;    
        // 如果temp指向了链表的头节点,即temp == list->head,那么就跳出循环。这是因为在遍历到头节点后,继续遍历会重新回到头节点,形成无限循环。  
        if (temp == list->head) break;
    }      
    return count;    
}  
  
// 打印方法:从头节点开始遍历,打印每个节点的数据  
void printList(CircularLinkedList *list) {
	printf("循环链表为:");  
    Node *current = list->head;  
    while (current != NULL) {  
        printf("%d ", current->data);  
        current = current->next;  
        if (current == list->head) {  
            break;  // 如果遍历到头节点,则退出循环  
        }  
    }  
    printf("\n");  
}   
  
int main() {  
	char welcome[] = "\
           /\ \
          ( *)======/\==== \
           )(      /  \ \
__________/  )    /    \ \
\___         /   / \"\"   \ \
  \____    _/   / (**)   \ \
     / \__/    (----------) \
    /____|__//_ ( 送给您- ) \
         |      ( 亲爱的 ) \
         |       (      )\
         |        (____)\
        _|__\
         \\    ☆新年 . 快乐☆";
	int i = 0; 
	int m = 0; 
	int n = 0;
    for(i=0;i<strlen(welcome);i++)
	{
		printf("%c",welcome[i]);
		for(m=0;m<10000;m++)
			for(n=0;n<1000;n++)
			{
				;
			}
	}
	printf("\n\n\n");
	

    CircularLinkedList list; // 定义循环链表变量  
    int data; // 用于存储用户输入的节点数据  
    int option; // 用于存储用户选择的操作选项  
    Node *temp; // 用于遍历循环链表的临时变量  
  
    initialize(&list); // 初始化循环链表  
  
    // 使用循环语句do-while循环获取用户选择的操作选项,直到用户输入0为止  
    do {  
        printf("\-----------顺序表演示程序----------\n");
        printf("1. 初始化顺序表\n");
        printf("2. 插入\n");
        printf("3. 删除\n");
        printf("4. 查询\n");
        printf("5. 循环链表的长度\n");
        printf("6. 打印循环链表\n");
        printf("10. 帮助\n"); 
        printf("0. 退出\n");
        printf("请输入您要进行的操作(1~6,0退出):");
        scanf("%d", &option);  
  
        switch (option) { 
			case 1:
				printf("初始化成功!\n");
				break; 
            case 2: // 添加节点到循环链表  
                printf("请输入要添加的数值\n");  
                scanf("%d", &data);  
                add(&list, data); 
                printf("添加成功!\n");
                break;  
            case 3: // 删除节点  
                printf("请输入要删除的数值\n");  
                scanf("%d", &data);  
                deleteNode(&list, 2);
                printf("删除成功!\n");
                break;  
            case 4: // 查找节点  
                {
                	printf("请输入要查询的数值\n");  
                	scanf("%d", &data);  
                	int position = findValue(&list, data); 
				    if (position != -1) {  
				        printf("位置 %d  有值为 %d的数值\n", position + 1, data);  
				    } else {  
				        printf("没有查询到\n");  
				    }
				}
				break;  
            case 5: // 获取循环链表的长度  
                printf("循环链表的长度为: %d\n", getLength(&list));  
                break;  
            case 6:
				printList(&list);  // 打印循环链表 
				break;
			case 10:
				printf(" 本程序为链表的演示程序,由许娜设计开发,程序完成了插入、删除、更新、返回顺序链表和打印顺序链表等功能!\n本人作业如果问题,尽请指教学习!");
				break;
            case 0: // 退出程序  
                printf("Exiting program...\n");  
                break;  
            default: // 处理无效选项  
                printf("Invalid option entered. Please enter a valid option.\n");  
                break;  
        }  
    } while (option != 0); // 当用户选择退出程序时,结束循环  
  
    return 0;  
}

小结

循环链表是一种特殊的链表,它的最后一个节点指向第一个节点,形成一个循环。下面是一个循环链表小结:

  1. 循环链表的结构

循环链表由一个个节点组成,每个节点包含数据域和指针域。指针域用于指向下一个节点。最后一个节点的指针指向第一个节点,形成一个循环。 2. 循环链表的实现

循环链表节点的实现包括节点的数据域和指针域。数据域用于存储节点的数据,指针域用于指向下一个节点。在定义循环链表时,需要定义一个头结点,该结点不存储任何数据,仅作为循环链表的起点。

  1. 循环链表的遍历

循环链表的遍历与普通链表类似,可以使用 while 循环或 for 循环进行遍历。在遍历时,需要注意判断当前节点是否为空,以及当前节点是否已经遍历过。可以使用一个标志变量来记录当前节点是否已经遍历过。

  1. 循环链表与普通链表的比较

循环链表与普通链表的区别在于最后一个节点的指针指向第一个节点,形成一个循环。因此,在遍历循环链表时,需要特别注意判断当前节点是否已经遍历过,否则会出现死循环的情况。

总之,循环链表是一种特殊的链表,具有环形结构。它的实现与普通链表类似,但需要注意最后一个节点的指针指向第一个节点。在遍历循环链表时,需要使用循环结构进行遍历,并判断当前节点是否已经遍历过。

参考文献

[1] 李刚 刘万辉. "线性表的结构分析和应用." 9787040461473: 16. 2017.1.1

[2] C语言循环链表创建,遍历,插入,删除,查找_循环链表的遍历-CSDN博客

[3] 文心一言 (baidu.com)

相关推荐
南宫生1 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
懒惰才能让科技进步2 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
Ni-Guvara2 小时前
函数对象笔记
c++·算法
泉崎2 小时前
11.7比赛总结
数据结构·算法
你好helloworld2 小时前
滑动窗口最大值
数据结构·算法·leetcode
AI街潜水的八角3 小时前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
白榆maple3 小时前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少3 小时前
数据结构——线性表与链表
数据结构·c++·算法
此生只爱蛋4 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
咕咕吖5 小时前
对称二叉树(力扣101)
算法·leetcode·职场和发展