15. 约瑟夫问题

1 问题描述

约瑟夫斯是公元1世纪的犹太历史学家,他领导了反抗罗马人的武装起义,但是失败了。他和四十名犹太士兵被罗马人围困在一个山洞中。这四十个士兵宁死不屈,决定杀身成仁。但约瑟夫斯不想,

但又不便公开反对,于是提出一个方法,就是四十一个人站成一个圈,从某人开始数起,凡数到三的人就让大家成全他升天,这样下去直到剩下最后一个人,这个人就自杀。大家都没有意见,于是约瑟夫斯就挑选了第31号的位置。结果所有人都死了,剩下他一个活下来投降了罗马人。这也是约瑟夫斯问题的最初提法。

但现在我们不想知道最后还在队列的是谁,我们想知道每次出队列的是谁,以及他们的顺序。

现在,有n个人围成一圈,从第一个人开始报数,数到 m的人出列,再由下一个人重新从 1开始报数,数到 m的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

输入描述

输入两个整数 n,m。n表示总人数,m表示数到m的人就出列

输出描述

输出一行 n 个整数,按顺序输出每个出圈人的编号。

n的区间为[1,1000]

 测试输入 期待的输出 时间限制 内存限制 额外进程
测试用例 1以文本方式显示
  1. 10 3↵
以文本方式显示
  1. 3↵
  2. 6↵
  3. 9↵
  4. 2↵
  5. 7↵
  6. 1↵
  7. 8↵
  8. 5↵
  9. 10↵
  10. 4↵
1秒64M0

2 解决

(1)数组

  • 这个之前写过了一遍,直接建立一个数组,0号位不存,直接赋值为0,从1开始,和人的顺序对应,tag[i]存储的就是i,也就是这个人的序号,出列以后赋值为0,表示出去了
  • 或者新建一个数组初始化为都是1,出列了就记为0,因为下标从1开始,每个数的下标就是该位置人的序号,不必遍历一遍数组为其赋值,减少操作,但是一开始就要为数组分配好空间
  • 代码
#include<iostream>
#include<cstdlib>
#include<cstdio>

using namespace std;

int main(int argc,char *argv[]){
	//freopen("file in.txt","r",stdin);
	int n,m;
	cin>>n>>m;
	int i;
	int index=0;  //下标
	int flag;  //为了不改变n的值
	int *tag;		
	tag = (int*)malloc(sizeof(int)*(n+1));
	

	tag[0]=0;
	// tag作为这个位置的人是否已经出列的标志,同时他的值是这个位置的人的序号
	for(i=1;i<=n;i++){
		tag[i]=i;    
	}
	flag=n;
	while(flag){
		for(i=1;i<=m;i++){
			index++;
			if(index>n)  //到达末尾,手动调到开头
				index=1;
			while(!tag[index]){
				index++;
				if(index>n)
					index=1;
			}			
		}
		cout<<tag[index]<<endl;
		tag[index]=0;  //说明已经出列
		flag--;
	}

	return 0;
}
  • 很明显,这个题目没有这么简单,所以我最后几个用例TLE在这里插入图片描述

(2) 链表

  • 既然每次出列的人的位置都要遍历并且判断,显然是多余的操作,那么我直接使用链表来操作看看
  • 建立一个循环链表,每次出去一个人去掉这个节点
  • 代码
#include<iostream>
#include<cstdlib>
#include<cstdio>
using namespace std;

struct listnode{
	int order;
	struct listnode *next;
};
typedef struct listnode Node;

void yoseph(int n, int m){
	int i;
	Node *head;
	Node *p;
	Node *temp;	

	head = (Node*)malloc(sizeof(Node));
	if(!head){
		exit(0);
	}
	head->order=1;
	head->next=NULL;
	p=head;
	for(i=2;i<=n;i++){
		temp = (Node*)malloc(sizeof(Node));
		if(!temp)
			exit(0);
		// 尾插法
		temp->order=i;
		temp->next=NULL;
		p->next=temp;
		p=temp;
	}
	p->next=head;  //形成封闭链表
	p=head;
	temp=p;  //防止使用未初始化的指针
	while(p->next!=p){   //只剩下一个节点的时候退出循环,然后最后单独输出
		for(i=1;i<m;i++){
			temp = p;			
			p = p->next;
		}
		cout<<p->order<<endl;

		temp->next = p->next;  //删除这个节点
		p = p->next;
	}
	cout<<p->order<<endl;

}

int main(int argc,char *argv[]){
	//freopen("file in.txt","r",stdin);
	int n,m;
	cin>>n>>m;
	yoseph(n,m);

	return 0;
}
  • 很悲伤在这里插入图片描述,我还是没有AC,还是提示TLE
  • 那要这么办呢,递归肯定是不行的吧,不管怎么样,还是试一试吧

(3)递归(最终版)

  • 这个方法的思想是总结第i次出列的人与第i-1次出列的人具有什么关系
  • 复习
  • 假设n=8,m=3,从第一个数开始报数
    在这里插入图片描述
  • %是为了防止越界,由于是递归操作,每次传进去的n可是不一样的在这里插入图片描述
  • 代码
// 第i个出列的人和第i-1出列的人有什么关系
// 下标从零开始
#include<iostream>
#include<cstdlib>
#include<cstdio>
using namespace std;

int  yoseph(int n, int m,int i){
	if(i==1)
		return (m-1)%n;
	
	return (yoseph(n-1,m,i-1)+m)%n;

}

int main(int argc,char *argv[]){
	//freopen("file in.txt","r",stdin);
	int n,m;
	int i;
	cin>>n>>m;
	for(i=1;i<=n;i++){  //第i个出列的人
		cout<<yoseph(n,m,i)+1<<endl;
	}

	return 0;
}
  • 没想到竟然过了,还以为递归肯定过不了

4 总结

  • 考虑一种情况,假如m非常大的话,前两种解法就会一直在那里无效绕圈,而递归会直接通过计算给出答案
  • 既然这样的话,链表改进一下应该是可以解决的,循环m个节点之前先对m进行取余操作
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值