约瑟夫问题 --- swust oj 0956

本文围绕约瑟夫问题展开,该问题是n个人围成圈报数,报到k的人出圈,求最后胜利者标号。介绍了三种解决方法,包括数组算法,通过改变报数人状态;数学算法,改变报数人逻辑连接关系;链表算法,直接删除报数人节点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

Question:                             约瑟夫问题的实现

        n个人围成一个圈,每个人分别标注为1、2、...、n,要求从1号从1开始报数,报到k的人出圈,接着下一个人又从1开始报数,如此循环,直到只剩最后一个人时,该人即为胜利者。例如当n=10,k=4时,依次出列的人分别为4、8、2、7、3、10,9、1、6、5,则5号位置的人为胜利者。给定n个人,请你编程计算出最后胜利者标号数。(要求用单循环链表完成。)

        题目就是这样了,要求输入两个整数,输出胜利者(最后一个出圈)。题目要求用链表完成,除了链表方法外,还有数组和数学公式来解决,一共三种。

        首先是最常见的数组算法,首先定义一个一维整形数组a[size],并初始化为0,作为模拟所有人以及是否出圈;用一个整形数作为报数count的编号,当等于k时,初始化;用一个整形数m表示剩下的人数。算法原理如下:当count等于k时,将a[i](i是当前报数人的编号)赋值为1,并初始化count,剩余人数m自减1,报数继续。当遇到a[i]等于1时,直接跳过,让下一个人报当前的数。当m等于1时,表示只剩下最后一个人,报数结束。

      操作如下

关键代码如下(C):

#include<stdio.h>
#define size 1001
int main(){
	int n = 10;
	int k = 4;
	int a[size] = {0};
	printf("%d", a[0]);
	scanf("%d", &n);
	scanf("%d", &k);
	int flag = 0;		//记录最后一个人 
	int m = n;
	int s = 0;
	while(m >= 1){
		for(int i = 1; i <= n; i++){
			if(a[i] != 1){
				s++;
			}else{
				continue;
			}
			if(s % k == 0){
//				printf("%d\n", i);		//查看每次出圈的人 
				if(m == 1){
					flag = i;
					break;
				}
				s = 0;
				a[i] = 1;
				m--;
			}
		}
	}
	printf("%d", flag);
	return 0;
}

      接下来就是对数学算法的探究,数组是直接改变“报数人”的状态,而这个算法是改变“报数人”逻辑上的连接关系。当“报数人”出圈后,将下一个人的编号改为0,然后依次编号,当只剩最后一个人时,他的编号就是0,通过倒推,可以得知这个人的编号,因为是从0开始编号的,所以这个人的实际编号应加一。但是我们只是只是求最后一个人的编号,而不是如何所有的“报数人”出圈只剩一个人,这是没必要的。

      “报数人”出圈大致如下:

       如图所示,n表示总人数,k表示报数的最大值,i表示剩余人的总数,a表示最后一个人的编号。可以看到i=5的那一行,当编号为2时,则出圈。那么,为了重新构建新的序列,3是如何变为0的呢?不难想到,因为k=3,所以序号为3的要变为0;而0呢,是如何变为2的?因为0小于k,所以0 - k = -3,序数为负数,显然不对。因此,但h(当前报数人的编号) - k 小于0时,应该与当前人数i相加。因为要判断差是否小于零,可以事先让所有的小于k的编号都加上当前总人数i,在进行求余,就可以得到下一轮的新编号(h = h > k?  h: h + i;)。那么,这样就可以实现报数人出圈了。但问题是如何求得最后一个报数人的实际编号呢?

      同理,根据报数人出圈的规律,可以的知最后一个人的编号一定为0,反推就可以得到比实际编号小一的编号。就可以得到公式a = (a + k) % i。

      算法具体实现如下:

#include<stdio.h>
int main(){
	int n = 10;
	int k = 4;
	scanf("%d", &n);
	scanf("%d", &k);
	int a = 0;
	for(int i = 1; i <= n; i++){
		a = (a + k) % i;
	}
	printf("%d", a + 1);
	return 0;
}

参考博文:https://blog.youkuaiyun.com/yanweibujian/article/details/50876631

      最后就是链表实现,链表直接将报数人删除,十分的形象。

      用链表实现,就得先建立,用结构体存储编号的连接下一个节点。便于操作,建立一个循环链表,采用头插法存储编号。

     当报数到k时,获取上一个节点,把上一个节点的指针指向当前节点的下一个节点,然后free当前节点。当只剩下最后一个节点时,取出节点的数据,这就是最后一个报数人。

     算法具体实现如下:

#include<stdio.h>
#include<stdlib.h>
struct List{
	int data;
	struct List *next;
};
void createList(struct List *&head, int n){
	struct List *p1, *p2;
	p1 = head = (struct List *)malloc(sizeof(struct List));
	for(int i = 1; i <= n; i++){
		p2 = (struct List *)malloc(sizeof(struct List));
		p2->data = i;
		if(i == 1){
			head = p2;
			head->next = NULL;
			p1 = head;
		}else{
			p1->next = p2;
			p1 = p2;
		}
	}
	p1->next = head;//首尾相连 
}
//void testOut(struct List *head){//输出所有节点内容 
//	struct List *p;
//	p = head;
//	do{
//		printf("%d\n", p->data);
//		p = p->next;
//	}while(p != head);
//}
void deal(struct List *head, int m, int n){
	int count  = 0;
	struct List *p;
	p = head;
	while(head->next != p){
		head = head->next;
	}
	p = head;
	head = head->next;
	while(n != 1){
		count++;
		if(count % m == 0){
			struct List *k;
			k = head;
//			printf("%d\n", head->data);//输出每一次出圈人 
			head = head->next;
			p->next = head;
			n--;
			free(k);
		}else{
			p = p->next;
			head = head->next;
		}
	}
	printf("%d", head->data);
	free(head);
}
int main(){
	struct List *head, *p;
	int n = 10;
	int m = 4;
	scanf("%d", &n);
	scanf("%d", &m);
	createList(head, n);
	deal(head, m, n);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值