详解循环链表

1,736 阅读11分钟

定义和特点

定义

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

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

特点

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

基本运算

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

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

循环链表的实现

初始化

循环链表的初始化需要创建一个节点结构体,包含数据域和指针域。然后创建一个循环链表结构体,包含头节点、尾节点和链表长度等属性,以及添加节点、删除节点等方法。

c
typedef struct Node {
    int data;
    struct Node* next;
} Node;

typedef struct CircularLinkedList {
    Node* head;
    Node* tail;
    int length;
} CircularLinkedList;

//进行初始化
void init_circular_linked_list(CircularLinkedList* list) {
    list->head = NULL;
    list->tail = NULL;
    list->length = 0;
}

// 判断循环链表是否为空
int is_empty(CircularLinkedList* list) {
    return list->length == 0;
}

定义了两个数据结构:Node 和 CircularLinkedList。这些是链表的一种,特别的是,这是一个循环链表。在循环链表中,最后一个元素的 next 指针指向头元素,形成一个环。

插入数据

尾插法

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

void insert_at_end(CircularLinkedList* list, int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->data = data;
    new_node->next = NULL;

    if (is_empty(list)) {
        list->head = new_node;
        list->tail = new_node;
        list->length++;
    } else {
        list->tail->next = new_node;
        list->tail = new_node;
        list->length++;
    }
}

头插法

当插入新节点时,利用头节点的指针域始终指向头节点的后继节点的特点,来对新插入的节点完成前驱后继节点的设置。这样链表内数据顺序是与输入顺序逆序的。

void insert_at_start(CircularLinkedList* list, int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->data = data;
    new_node->next = list->head;
    list->head = new_node;
    list->length++;
}

在位置i处插入元素x

我们需要首先创建一个新的节点,然后调整节点的指针以适应新的链表结构。在循环链表中,我们需要特别注意尾节点的处理

void insert_at_position(CircularLinkedList* list, int x, int i) {  
    Node* new_node = (Node*)malloc(sizeof(Node));  
    new_node->data = x;  
    new_node->next = NULL;  
  
    if (i == 0) { // Insertion at the start  
        new_node->next = list->head;  
        list->head = new_node;  
        if (list->tail == NULL) { // If list is empty, update tail pointer as well  
            list->tail = new_node;  
        }  
    } else { // Insertion in the middle or at the end  
        Node* temp = list->head;  
        int count = 0;  
        while (temp != NULL && count < i-1) {  
            temp = temp->next;  
            count++;  
        }  
        if (temp == NULL) { // Insertion at the end  
            new_node->next = list->tail;  
            list->tail->next = new_node;  
            list->tail = new_node;  
        } else { // Insertion in the middle  
            new_node->next = temp->next;  
            temp->next = new_node;  
        }  
    }  
    list->length++; // Increment list length  
}

删除指定节点

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

  • 将遍历当前节点的指针 curr 移动到头结点的下一个节点开始,并初始化前一个节点 prev 为头结点。
  • 进入循环,检查当前节点的数据是否与给定数据匹配。如果不匹配,则将前一个节点移动到当前节点,将当前节点移动到下一个节点。如果匹配,则将前一个节点的 next 指针指向当前节点的下一个节点,从而删除当前节点。同时,释放当前节点的内存,并结束循环。
  • 最后返回即可。
//删除指定值得节点
void delete_node(CircularLinkedList* list, int data) {
    Node* current = list->head;
    Node* previous = NULL;
    while (current != NULL && current->data != data) {
        previous = current;
        current = current->next;
    }
    if (current == NULL) { // 没有找到要删除的节点
        printf("Element not found.\n");
        return;
    }
    if (previous == NULL) { // 删除头节点
        list->head = current->next;
        if (list->length == 1) {
            list->tail = NULL;
        }
    } else { // 删除其他节点
        previous->next = current->next;
        if (list->length == 1) {
            list->tail = previous;
        }
    }
    free(current);
    list->length--;
}

查找指定值的节点

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

int find_position(CircularLinkedList* list, int x) {
    Node* current = list->head;
    int position = 0;
    while (current != NULL) {
        if (current->data == x) {
            return position;
        }
        current = current->next;
        position++;
    }
    return -1; // 没有找到要查找的元素
}

循环顺序表的长度

// 返回循环链表长度
int get_length(CircularLinkedList* list) {
    return list->length;
}

打印

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

// 打印循环链表
void print_circular_linked_list(CircularLinkedList* list) {  
    Node* node = list->head;  
    int i;  
    for (i = 0; i < list->length; i++) {  
        printf("%d ", node->data);  
        node = node->next;  
        if (node == list->head) break; // 循环链表结束,退出循环  
    }  
    printf("\n");  
}

运行截图

image.png

QQ图片20231014112653.png

image.png

image.png

完整demo

#include <stdio.h>
#include <stdlib.h>
#include<cstring>  
typedef struct Node {
    int data;
    struct Node* next;
} Node;

typedef struct CircularLinkedList {
    Node* head;
    Node* tail;
    int length;
} CircularLinkedList;

void init_circular_linked_list(CircularLinkedList* list) {
    list->head = NULL;
    list->tail = NULL;
    list->length = 0;
}

// 判断循环链表是否为空
int is_empty(CircularLinkedList* list) {
    return list->length == 0;
}

// 在尾部插入节点
void insert_at_end(CircularLinkedList* list, int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->data = data;
    new_node->next = NULL;

    if (is_empty(list)) {
        list->head = new_node;
        list->tail = new_node;
        list->length++;
    } else {
        list->tail->next = new_node;
        list->tail = new_node;
        list->length++;
    }
}

// 在位置i处插入元素x
void insert_at_position(CircularLinkedList* list, int x, int i) {  
    Node* new_node = (Node*)malloc(sizeof(Node));  
    new_node->data = x;  
    new_node->next = NULL;  
  
    if (i == 0) { // Insertion at the start  
        new_node->next = list->head;  
        list->head = new_node;  
        if (list->tail == NULL) { // If list is empty, update tail pointer as well  
            list->tail = new_node;  
        }  
    } else { // Insertion in the middle or at the end  
        Node* temp = list->head;  
        int count = 0;  
        while (temp != NULL && count < i-1) {  
            temp = temp->next;  
            count++;  
        }  
        if (temp == NULL) { // Insertion at the end  
            new_node->next = list->tail;  
            list->tail->next = new_node;  
            list->tail = new_node;  
        } else { // Insertion in the middle  
            new_node->next = temp->next;  
            temp->next = new_node;  
        }  
    }  
    list->length++; // Increment list length  
}

// 在头部插入节点
void insert_at_start(CircularLinkedList* list, int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->data = data;
    new_node->next = list->head;
    list->head = new_node;
    list->length++;
}

// 返回循环链表长度
int get_length(CircularLinkedList* list) {
    return list->length;
}
//删除指定值得节点
void delete_node(CircularLinkedList* list, int data) {
    Node* current = list->head;
    Node* previous = NULL;
    while (current != NULL && current->data != data) {
        previous = current;
        current = current->next;
    }
    if (current == NULL) { // 没有找到要删除的节点
        printf("Element not found.\n");
        return;
    }
    if (previous == NULL) { // 删除头节点
        list->head = current->next;
        if (list->length == 1) {
            list->tail = NULL;
        }
    } else { // 删除其他节点
        previous->next = current->next;
        if (list->length == 1) {
            list->tail = previous;
        }
    }
    free(current);
    list->length--;
}

// 查找指定值x所在的位置i
int find_position(CircularLinkedList* list, int x) {
    Node* current = list->head;
    int position = 0;
    while (current != NULL) {
        if (current->data == x) {
            return position;
        }
        current = current->next;
        position++;
    }
    return -1; // 没有找到要查找的元素
}

// 打印循环链表
void print_circular_linked_list(CircularLinkedList* list) {  
    Node* node = list->head;  
    int i;  
    for (i = 0; i < list->length; i++) {  
        printf("%d ", node->data);  
        node = node->next;  
        if (node == 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");
	
    int data; // 用于存储用户输入的节点数据  
    int position;//插入的位置 
    int option; // 用于存储用户选择的操作选项  
	CircularLinkedList list;
    init_circular_linked_list(&list); //初始化 
    do {  
        printf("\-----------顺序表演示程序----------\n");
        printf("1. 初始化顺序表\n");
        printf("2. 尾插法\n");
        printf("3. 头插法\n");
        printf("4. 中间插入法\n");
        printf("5. 删除\n");
        printf("6. 查询\n");
        printf("7. 循环链表的长度\n");
        printf("8. 打印循环链表\n");
        printf("10. 帮助\n"); 
        printf("0. 退出\n");
        printf("请输入您要进行的操作(1~8,0退出):");
        scanf("%d", &option);  
  
        switch (option) { 
			case 1:
				printf("初始化成功!\n");
				break; 
            case 2: // 添加节点到循环链表  
                printf("请输入要添加的数值\n");  
                scanf("%d", &data);  
                insert_at_end(&list, data);
                printf("添加成功!\n");
                break;
			case 3:
				printf("请输入要添加的数值\n");  
                scanf("%d", &data);  
                insert_at_start(&list, data);
                printf("添加成功!\n");
                break;
            case 4:
            	printf("请输入要添加的数值和需要插入的位置\n");  
                scanf("%d %d", &data,&position);  
                insert_at_position(&list,data,position); 
                printf("添加成功!\n");
                break;
            case 5: // 删除节点  
                printf("请输入要删除的数值\n");  
                scanf("%d", &data);  
                delete_node(&list, data);
                printf("删除成功!\n");
                break;  
            case 6: // 查找节点  
                {
                	printf("请输入要查询的数值\n");  
                	scanf("%d", &data);  
                	int position = find_position(&list, data); 
				    printf("Element %d found at position %d.\n",data, find_position(&list, data)); 
				}
				break;  
            case 7: // 获取循环链表的长度  
                printf("循环链表的长度为: %d\n", get_length(&list));  
                break;  
            case 8:
				print_circular_linked_list(&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)