C语言写单向链表解决约瑟夫问题
约瑟夫问题简介:
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
思路:
-
创建单向循环链表,(插入操作)并将尾指针指向首元结点,形成闭环
(1)创建一个结点,用i来作为存放的数据和结点数的记录,s->data = i++;是将i先给到数据域,再进行自增操作
s->data = i++; // 存放数据
(2)p为尾指针,指向最后一个结点,将插入后结点s给到p,实现p的跟进
p->next = s; // 插入新结点 p = s; // 尾指针跟进
(3)通过while循环,在循环中插入结点,判断是否已经创建完成指定长度的链表
(4)创建完成,退出while,将尾结点的指针域指向首元结点,构成闭环
s->next = head->next; // 指向首元结点, 构成环
(5)此时,头结点已经没有作用,只需要函数返回首元结点地址就可以
free(head); return s->next; // 返回首元结点的地址
-
杀死指定位置的结点(删除操作)
(1)获取到要杀死的结点的前驱结点,方便删除
// 获取要杀死的结点 if( i < (death - 1)){ p = p->next; i++; }
(2)输出要杀死的结点的数据域数据
printf("%2d->", p->next->data);
(3)进行删除操作
// 找到结点后杀死, 相当于删除结点 temp = p->next; // 保存前驱结点 p->next = temp->next; // 链接到后继结点 free(temp); // 释放结点
(4) 通过循环去进行删除操作,循环判断语句为判断表是否为只剩最后一个结点,及为指针域指向自己,代码中通过p来进行链表操作,则在删除一个结点结束后,需要重新计数,在删除结束后通过将p = p->next;再进行循环,知道p == p->next;
2.代码实现
/*
* @Author: xgh
* @Date: 2020-06-14 08:33:35
* @LastEditTime: 2020-06-14 09:51:37
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \VS-CODE-C\.vscode\JosephProblem\JosephProblem.c
*/
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType; // 数据域类型
#define ERROER 0 // 错误
typedef struct ClinkNode
{
ElemType data; //数据域
// 这里不能写成Node *next
// 因为类型名的作用域是从大括号结尾开始,而在结构体内部不能使用,因为还没有定义
// 需要声明
struct ClinkNode *next; // 指针域
}clinkNode;
clinkNode *create(int num);
void dealJoseph_01(int num, int death);
int main(void){
int num = 41; // 循环结点数
int death = 3; // 到达第几个结点先死
dealJoseph_01(num , death);
return 0;
}
/**
* @description: 创建循环链表
* @param {int num: 循环链表的结点数}
* @return: 首元结点的地址
*/
clinkNode *create(int num){
if(num == 0){ // 数据错误
exit(ERROER);
}
clinkNode *p = NULL, *head;
int i = 1; // 用于结点数, 也作为每个结点数据域存放的数据
// 创建头结点
head = (clinkNode *) malloc (sizeof(clinkNode));
p = head;
clinkNode *s; // 中间结点,用于存放生成插入的结点
// 是否已经创建结束
while(i <= num){
// 创建一个新结点
s = (clinkNode *) malloc (sizeof(clinkNode));
s->data = i++; // 存放数据
p->next = s; // 插入新结点
p = s; // 尾指针跟进
}
s->next = head->next; // 指向首元结点, 构成环
// 释放头结点, 有元素的结点已经构成一个环了,
// 只需要返回结点地址就可以进行访问,
// 无需头指针,释放空间,减少资源占用
free(head);
return s->next; // 返回首元结点的地址
}
/**
* @description: 解决约瑟夫问题
* @param {int num: 循环结点数}
* @param {int death: 到达第几个结点先死}
* @return: NULL
*/
void dealJoseph_01(int num, int death){
// 创建循环链表并接收
clinkNode *p = create(num);
clinkNode *temp;
//解决约瑟夫问题
printf("死亡顺序是:\n");
int i =1;
// 判断表是否只剩最后一个结点
while(p != p->next){
// 获取要杀死的结点
if( i < (death - 1)){
p = p->next;
i++;
}
printf("%2d->", p->next->data);
// 找到结点后杀死, 相当于删除结点
temp = p->next; // 保存前驱结点
p->next = temp->next; // 链接到后继结点
free(temp); // 释放结点
p = p->next; // 重新开始计数
}
// 最后剩下一个结点
printf("\n最后剩下的是:%d\n", p->data);
}