目录
问题描述
一堆猴子都有编号,编号是1,2,3 ...m,这群猴子(m个)按照1-m的顺序围坐一圈,从第K开始数,每数到第N个,该猴子就要离开此圈,这样依次下来,直到圈中只剩下最后一只猴子,则该猴子为大王。
单向循环链表处理
- 用单向循环链表模拟猴子围成圈坐,如下图所示
first指向第一个报数的猴子,每报一个数first指向它的next,直到停在需要出圈的猴子位置
- 当要编号为2的猴子要出圈时,操作如下:
① first指向当前要删除结点,helper指向要删除结点的后一个结点,同时将first赋值给temp
② first指向要删除结点的下一个结点
③ helper的next指向first
④ 释放要删除结点
单向循环链表处理具体代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
/* 约瑟夫问题——循环单向链表解决(不带头结点) */
//定义链表结点
typedef struct Node{
int no; //编号
struct Node *next; //指向下一个链表结点的指针
}CircleSLNode;
//创建一个单向循环链表
CircleSLNode *CreateList(int nums) {
//first: 单向循环链表 第一个结点,curnode:辅助遍历,node:新的结点
CircleSLNode *first = NULL, *curnode = NULL, *node = NULL;
int i;
if(nums < 1){
printf("猴子个数输入不正确!\n");
return NULL;
}
first = (CircleSLNode *)malloc(sizeof(CircleSLNode)); //指向第一个结点
for(i = 1; i <= nums; i++){
node = (CircleSLNode *)malloc(sizeof(CircleSLNode)); //根据编号创建猴子结点
node->no = i;
//如果是第一只猴子
if(i == 1){
first = node; //first 指向 第一个结点
first->next = first; //构成环
curnode = node;
} else{
curnode->next = node; // curnode是当前环形链表最后一次添加的结点,让 curnode->next 指向 node
//node->next = first; //node->next 指向 first ,构成环
curnode = node; //curnode后移
}
}
curnode->next = first;
return first;
}
//猴子出圈,返回留下来的第n只猴子
/*
* first:要穿过来的单链表
* startnum:开始报数的猴子编号
* nums:全部猴子个数
* countnums:数到第 countnums 出圈
*/
int findout(CircleSLNode *first, int startnum, int nums, int countnums){
//设置帮助指针helper,temp, 帮忙 出圈
CircleSLNode *helper = first, *temp = NULL;
int flag = 1, i;
//判断传过来的参数是否符合要求
if(first == NULL || startnum < 1 || nums < 1 || startnum > nums){
printf("参数输入错误!\n");
return 0;
}
//将helper移到first的前一个结点
while(flag){
if(helper->next == first){
flag = 0;
}else{
helper = helper->next;
}
}
//猴子报数前,先把指针移到开始报数的猴子结点,移动startnum - 1次
for(i = 0; i < startnum - 1; i++){
first = first->next; //最后first指向 开始报数的猴子结点
helper = helper->next; //保证跟在first后面
}
//当猴子开始报数时,让 first , helper 移动 countnums - 1 次,直到圈中只有一个结点
flag = 1;
while(flag){
if(helper == first){
flag = 0;
}else{
for(i = 0; i < countnums - 1; i++){
first = first->next; //最后first指向 要出圈的猴子结点
helper = helper->next; //保证跟在first后面
}
printf("猴子 %d 出圈\n", first->no);
//将first指向的猴子结点出圈
temp = first;
first = first->next;
helper->next = first;
free(temp); //释放出圈结点内存
temp = NULL;
}
}
printf("最后称王的是第 %d 只猴子\n", first->no);
return first->no;
}
//撤销单链表
void Destroy(CircleSLNode **first, int nums){
//定义辅助指针 ,head暂时不能没有,故temp1指向head,temp2配合释放掉
CircleSLNode *temp1 = NULL, *temp2 = NULL;
int i;
//定义temp1辅助遍历
temp1 = *first;
for(i = 1; i <= nums; i++){ //最终:temp1 = NULL,temp2被释放
temp2 = temp1; //temp2指向temp1,便于释放
temp1 = temp1->next; //temp1指向下一个,避免链表丢失
free(temp2); //释放temp2
temp2 = NULL; //避免free都成为“野指针”
}
*first = NULL; //最后让head = NULL
}
//测试
int main(int argc, char *argv[]) {
CircleSLNode *first = NULL;
int nums= 0, startnums = 0, countnums= 0;
printf("请输入想要称王的猴子个数: ");
scanf("%d", &nums);
printf("从第几个人开始报数:");
scanf("%d", &startnums);
printf("要出圈的数: ");
scanf("%d", &countnums);
//创建单链表
first = CreateList(nums);
//test:
//共六个人(nums=6),从第一个人开始报数(startnum=1),数到5的出圈(countnums=5),出圈的顺序是:5,4,6,2,3
findout(first, startnums, nums, countnums);
//撤销单链表
Destroy(&first, nums);
return 0;
}
数学取模方式处理
默认从第一只猴子开始报数。
数学取模方式处理具体代码
#include <stdio.h>
#include <stdlib.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char *argv[]) {
int nums, countnums, i, s = 0;
printf("请输入想要称王的猴子总数: ");
scanf("%d", &nums);
printf("请输入要出圈的数字: ");
scanf("%d", &countnums);
for(i = 2; i <= nums; i++)
s = (s + countnums) % i;
printf("称王的猴子是:%d\n", s + 1);
return 0;
}
本篇参考:
数组模拟参考:尚硅谷Java数据结构与java算法(Java数据结构与算法)
数学取模方式处理解析为原创,请读者转载时附上本篇链接!谢谢!