赞
踩
写在前面:链表是数据结构线性表中的一种,用于动态解决因数据量不确定而造成的频繁插入、删除操作的情况。同时因其特殊的物理结构,也被广泛用于队列,栈,图等其他数据结构,以及大数据处理,数据缓存当中。本文通俗易懂,深入解析算法,手撕代码,适用于有一定编程语言基础并且刚接触数据结构算法的uu们。
- #include <stdio.h>
-
- // 定义一个结构体用于表示矩形的长和宽
- typedef struct {
- int length;
- int width;
- } Rectangle;
-
- int main() {
- // 使用typedef重新定义int为Length, 表示长度
- typedef int Length;
-
- // 使用Rectangle结构体类型定义rect1和rect2变量
- Rectangle rect1 = {10, 5};
- Rectangle rect2 = {8, 6};
-
- // 使用Length类型定义length1和length2变量
- Length length1 = 10;
- Length length2 = 5;
-
- // 输出矩形的长和宽
- printf("Rectangle 1: Length = %d, Width = %d\n", rect1.length, rect1.width);
- printf("Rectangle 2: Length = %d, Width = %d\n", rect2.length, rect2.width);
-
- // 输出长度
- printf("Length 1: %d\n", length1);
- printf("Length 2: %d\n", length2);
-
- return 0;
- }

- #include <stdio.h>
- #include <stdlib.h>
-
- // 定义一个结构体表示学生信息
- struct Student {
- char name[20];
- int age;
- };
-
- // 函数:打印学生信息
- void printStudentInfo(struct Student* ptr) {
- printf("Student Name: %s\n", ptr->name);
- printf("Student Age: %d\n", ptr->age);
- }
-
- int main() {
- // 声明并初始化一个结构体变量
- struct Student student1 = {"Alice", 20};
-
- // 声明一个结构体指针,并指向结构体变量student1
- struct Student* ptr_student = &student1;
-
- // 通过结构体指针访问和修改结构体成员
- printf("Before modification:\n");
- printStudentInfo(ptr_student);
-
- // 修改结构体成员
- strcpy(ptr_student->name, "Bob");
- ptr_student->age = 25;
-
- // 打印修改后的学生信息
- printf("\nAfter modification:\n");
- printStudentInfo(ptr_student);
-
- return 0;
- }

原型:void* malloc (size_t size);
用途:向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。size为字节数。
- #include <stdio.h>
- #include <stdlib.h>
-
- // 定义一个结构体表示学生信息
- struct Student {
- char name[20];
- int age;
- };
-
- int main() {
- // 使用malloc动态分配内存给结构体指针
- struct Student* ptr_student = (struct Student*)malloc(sizeof(struct Student));
-
- // 检查内存是否成功分配
- if (ptr_student == NULL) {
- printf("Memory allocation failed.\n");
- return 1;
- }
-
- // 设置结构体成员值
- strcpy(ptr_student->name, "Alice");
- ptr_student->age = 20;
-
- // 打印结构体成员值
- printf("Student Name: %s\n", ptr_student->name);
- printf("Student Age: %d\n", ptr_student->age);
-
- // 释放动态分配的内存
- free(ptr_student);
-
- return 0;
- }

链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表中每个最小组成单元称为节点,每个节点包含数据域和指针域。对于单链表,最后一个节点的指针域为NULL。同时,按照一般习惯,我们会定义一个指针plist指向第一个节点。
循环链表是另一种形式的链式存储结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
1. 是否带头:带头 不带头
2. 方向判断:单向 双向
3. 是否循环:循环 不循环
PS:带头链表⾥的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,仅用于避免链表在遍历时出现死循环。
下图为双向链表 - 带头双向循环链表
据说著名犹太历史学家Josephus(弗拉维奥·约瑟夫斯)有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
具体问题:编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。下一个人继续从 1 开始报数。n - 1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?
以5个人,每次报2的人淘汰为例,构造下列循环链表:
设置prev指针的目的:因后续链表中存在删除操作,当前节点需要被销毁释放,为了能够继续找到pcur的下一个节点,因此需要prev指针来记录pcur的位置。
开始遍历,当2号节点数到2时被删除。在删除2号节点之前,将prev的指针域指向3号节点(即pcur的下个节点),然后释放2号节点。由于此时prev的下个节点正好是3号节点,所以立即将pcur置为3号节点,接着继续重复此步骤,直至只剩下最后一个节点。最后返回其中的值。
遍历过程需要:
遍历停止的条件:当prev和pcur重合时,即指向自身。
- typedef struct ListNode {
- int val;
- struct ListNode* next;
- }ListNode;
该结构体(节点)包含一个整型数据和下个节点的地址。
考虑到struct ListNode太难写,我们使用typedef重命名节点名称为ListNode。
根据题目中的元素个数n创建节点。因节点不止一个,可以将其封装为一个函数,方便后续在创建更多节点时使用。
- ListNode* BuyNode(int x) {
- ListNode* node = (ListNode*)malloc(sizeof(ListNode));
- if (node == NULL) {
- exit(1);
- }
- node->val = x;
- node->next = NULL;
- return node;
- }
其中传入的参数x即节点的数据域。
函数原型:
ListNode* CreateCircle(int n) ;
链表初始为空,头节点和尾节点均指向同一节点。
在遍历过程中,ptail会不断改变,而phead会一直记录第一个节点的位置。
- ListNode* phead = BuyNode(1);
- ListNode* ptail = phead;
不断创建新节点。与此同时,实时更新新的尾节点,将其依次插入到ptail的下一个位置。
- for (int i = 2; i <= n; i++) {
- ptail->next = BuyNode(i);
- ptail = ptail->next;
- }
将其首尾相连,即可成环,最终返回ptail。
- ptail->next = phead;
- return ptail;
注意,在链表的遍历过程当中,指向当前节点的指针要和指向前一个节点的指针同步移动。若返回phead则无法找到ptail。
函数原型:
int ysf(int n, int m);
接收带环链表的返回值,下一个节点即为头节点。同时定义一个计数器。
- ListNode* prev = CreateCircle(n);
- ListNode* pcur = prev->next;
- int count = 1;
根据步骤二,在循环中判断报的数是不是m,若是则删除,否则保留并继续循环。
- while (pcur->next!=pcur) {
- if (count == m) {
- prev->next = pcur->next;
- free(pcur);
- pcur = prev->next;
- count = 1;
- }
- else {
- prev = pcur;
- pcur = pcur->next;
- count++;
- }
- }
注意:
返回最后一个节点里的值。
return pcur->val;
- typedef struct ListNode {
- int val;
- struct ListNode* next;
- }ListNode;
-
- //创建节点
- ListNode* BuyNode(int x) {
- ListNode* node = (ListNode*)malloc(sizeof(ListNode));
- if (node == NULL) {
- exit(1);
- }
- node->val = x;
- node->next = NULL;
- return node;
- }
-
- //创建循环链表
- ListNode* CreateCircle(int n) {
- ListNode* phead = BuyNode(1);
- ListNode* ptail = phead;
- for (int i = 2; i <= n; i++) {
- ptail->next = BuyNode(i);
- ptail = ptail->next;
- }
- //成环
- ptail->next = phead;
- return ptail;
-
- }
-
- int ysf(int n, int m) {
- //根据n创建带环链表
- ListNode* prev = CreateCircle(n);
- ListNode* pcur = prev->next;
- int count = 1;
- //直到链表中只有一个节点时
- while (pcur->next!=pcur) {
- if (count == m) {
- //销毁pcur节点
- prev->next = pcur->next;
- free(pcur);
- pcur = prev->next;
- count = 1; //返回默认值
- }
- else {
- //不销毁
- prev = pcur;
- pcur = pcur->next;
- count++;
- }
- }
- //此时剩下的一个节点是要返回节点里的值
-
- }
-
- int main() {
- int n = 5;
- int m = 2;
- printf("留下的人的编号是:%d\n", ysf(n, m));
- return 0;
- }

定义结构体和基本操作:
创建循环链表:
实现 CreateCircle 函数,根据给定的节点数量 n 创建一个包含 n 个节点的循环链表。
解决约瑟夫问题:
实现主函数:
在 main 函数中调用 ysf 函数并打印结果。
完
若文中有错误之处,望各位学术同道不吝赐教。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。