前言
本篇文章概括了关于单向循环链表的基本操作和约瑟夫环问题的循环链表解法。
单向链表
相关结构体
这里还是另外定义了一个结构体专门存放链表信息
typedef struct people {
int num;
struct people* next;
}People;
typedef struct linklist {
People* head;
People* tail;
}Linklist;
初始化
循环链表在初始化时,只有一个结点,那么这个结点的next指向他本身
void Initlist(Linklist* linklist) {
//创建第一个结点(其实可以和add放在一块
People* p = (People*)malloc(sizeof(People));
printf("请输入第一个数据:");
scanf("%d", &p->number);
linklist->head = p;
linklist->tail = p;
p->next = p;
printf("链表创建完成\n");
}
增
采用“尾插法”,在尾指针和头指针分别指向的结点中间插入新结点
void add(Linklist* linklist) {
int flag = 0;
while (1) {
People* p = (People*)malloc(sizeof(People));
printf("请输入要添加的数据:");
scanf("%d", &p->number);
p->next = linklist->head;
linklist->tail->next = p;
linklist->tail = p;
//记得插入之后,尾指针要指向新的结点!
printf("继续输入请按1,结束输入请按0:");
scanf("%d", &flag);
if (!flag) {
break;
}
}
}
其实在初始化和增加结点的时候,最好在使用malloc之后判断一下分配内存是否成功。
删
void delete_sth(Linklist* linklist) {
int num = 0;//储存要删除的数据
People* p = linklist->head;
People* q = NULL;
int flag = 0;//flag是0,未找到;flag不是0,找到了
printf("请输入要删除的数据:");
scanf("%d", &num);
if (p->number == num) {
//先判断,头指针指向的结点是不是要删除的结点
linklist->tail->next = p->next;
linklist->head = p->next;
free(p);
}
else {
while (p->next != linklist->head) {
//如果没找到出循环的时候,p会指向最后一个结点
if (p->next->number == num) {
q = p->next;//此时q指向要删的结点
if (q->next != linklist->head) {
//如果要删的不是尾指针指向的结点,就直接删
p->next = q->next;
flag = 1;
free(q);
break;
}
else {
p->next = q->next;
linklist->tail = p;
flag = 1;
free(q);
break;
}
}
else {
p = p->next;
}
}
if (!flag) {
printf("未找到相应数据\n");
}
}
}
改
void amend(Linklist* linklist) {
People* p = linklist->head;
int flag = 0;
int newnum = 0, oldnum = 0;
printf("请输入要修改的数据:");
scanf("%d", &oldnum);
printf("请输入更正后的数据:");
scanf("%d", &newnum);
while (p->next != linklist->head) {
if (p->number == oldnum) {
p->number = newnum;
flag = 1;
printf("修改成功!");
break;
}
p = p->next;
}
if (!flag) {
printf("没有找到对应数据\n");
}
}
遍历输出
void traversal(Linklist* linklist) {
People* p = linklist->head;
while (p->next != linklist->head) {
printf("%d ", p->number);
p = p->next;
}
printf("%d", p->number);
printf("\n");
}
在学习过程中,发现其实循环链表就相当于不带头结点单链表的最后一个结点与第一个结点相连成环,在遍历时以该结点的next是否与头结点的值一致(而不是next是否为空)来判断是否遍历完成。
约瑟夫环问题
题目描述
一个圈共有N个人(N为不确定的数字),第一个人的编号为1,那么第二个人的编号就为2号,第三个人的编号就为3号,第N个人的编号就为n号,现在提供一个数字m,第一个人开始从1报数,第二个人报的数就是2,依次类推,报到m这个数字的人出局,紧接着从出局的这个人的下一个人重新开始从1报数,和上面过程类似,报到m的人出局,直到n个人全部出局,请问,这个出局的顺序是什么?
代码及解析
#include<stdio.h>
#include<stdlib.h>
typedef struct people {
int num;
struct people* next;
}People;
typedef struct linklist {
People* head;
People* tail;
}Linklist;
void add(Linklist* linklist, int n);
void delete_m(Linklist* linklist, int m);
int main(void) {
int m = 0, n = 0;
Linklist linklist;
scanf("%d%d", &n, &m);
add(&linklist, n);
delete_m(&linklist, m);
}
//创建链表
void add(Linklist* linklist,int n) {
People* p = (People*)malloc(sizeof(People));
int i = 1;
p->next = p;
p->num = 1;
linklist->head = p;
linklist->tail = p;
for (i = 2; i <= n; i++) {
p = (People*)malloc(sizeof(People));
p->num = i;
linklist->tail->next = p;
p->next = linklist->head;
linklist->tail = p;
}
}
//输出并删除报第m个的编号
void delete_m(Linklist* linklist, int m){
int count = 0, i = 0;
People* p = linklist->head;
People* q = linklist->tail;
//p将会指向要删结点,q指向p的前一个
while (p != q) {
//当p与q重合的时候,只剩下一个人,可以结束循环
count++;
if (count == m) {
printf("%d ", p->num);
q->next = p->next;
free(p);
p = q->next;
count = 1;
//这一步相当于p往后挪了一个位置,所以要报一位数,令count = 1
}
p = p->next;
q = q->next;
}
printf("%d", p->num);
//如果题目设置最后一个人不会死,则不用输出最后一个人
}