约瑟夫环实例

一、问题叙述

据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而.Josephus和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第K个人。接着,再越过k-1个人,并杀掉第K个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

二、分析

设编号分别为: 1,2,.,n的n个人围坐一圈。约定序号为k (1≤k≤n)的人从1开始计数,数到m的那个人出列,他的下一位又从1开始计数,数到m的那个人又出列,依次类推,直到所有人出列为止或者最后剩下一个人。

例如:有7个人,数到3出列,直到只剩一个人 那么出列的顺序为 3 6 2 7 5 1 最后剩下4.

三、代码实现

功能函数如下

//创建循环链表
linklist *createLinklist();
//求循环链表的结点个数
int linklist_length(linklist *head);
//按位置添加结点
int linklistInsertByPos(linklist *head, int pos, data_t data);
//约瑟夫环的实现
void linklist_huan(linklist *p, int m);

函数实现

//创建链表
linklist *createLinklist()
{
	linklist *head = (linklist *)malloc(sizeof(linklist));
	if(NULL == head)
	{
		perror("malloc");
		return NULL;
	}
	head->next = head;//循环,自己指向自己
	return head;
}

//求链表的结点个数
int linklist_length(linklist *head)
{
	linklist *p = head->next; //第一个有效结点
	int num = 0;
	//遍历链表
	while(p != head)
	{
		num++;
		p = p->next;
	}
	return num;
}

//按位置添加结点
int linklistInsertByPos(linklist *head, int pos, data_t data)
{
	int len = linklist_length(head);
	if(pos < 0)
		return -1;	
	pos=pos%(len+1);//循环,一圈,插入位置等于当前位置取余(总长度+1);例如,一个循环长度为6,当前位置为2,则在2后面插入的位置=6%3=3;
	linklist *new = (linklist *)malloc(sizeof(linklist));
	new->data = data;
	new->next = NULL;
	//找到pos的前一个位置
	linklist *p = head;
	while(pos--)
	{
		p=p->next;
	}
	//先连后断
	new->next = p->next;
	p->next = new;
	return 0;
}

//环
void linklist_huan(linklist *p, int m)
{
	int j = 1;//记录人数
	linklist *q = NULL;
	while(p->next != p)
	{
		int i;
		for(i=1; i<m-1; i++)//p移动到周期数的前一个位置;假如周期m= 3,则循环执行一次,p从第一个有效结点,往后移动一次,指向第二个结点。
		{
			p=p->next;
		}
		printf("第%d个人是:%d\n", j,p->next->data);//打印当前指向的下一个结点的值。
		j++;
		q = p->next;//把p指向的下一个结点地址给q,为了释放结点
		p->next = q->next;//把q指向的地址,给 p->next;
		free(q);//释放q
		p = p->next;//此时p->next 保存的是q指向的地址,先赋值,再释放,再把p->next保存的地址给p;
	}
	printf("最后剩下的是:%d\n", p->data);//退出循环就说明只剩一个人。
}

四、完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef int data_t;
typedef struct node{
	data_t data;
	struct node *next;
}linklist;
//创建链表
linklist *createLinklist()
{
	linklist *head = (linklist *)malloc(sizeof(linklist));
	if(NULL == head)
	{
		perror("malloc");
		return NULL;
	}
	head->next = head;//循环,自己指向自己
	return head;
}

//求链表的结点个数
int linklist_length(linklist *head)
{
	linklist *p = head->next; //第一个有效结点
	int num = 0;
	//遍历链表
	while(p != head)
	{
		num++;
		p = p->next;
	}
	return num;
}

//按位置添加结点
int linklistInsertByPos(linklist *head, int pos, data_t data)
{
	int len = linklist_length(head);
	if(pos < 0)
		return -1;	
	pos=pos%(len+1);//循环,一圈,插入位置等于当前位置取余(总长度+1);例如,一个循环长度为6,当前位置为2,则在2后面插入的位置=6%3=3;
	linklist *new = (linklist *)malloc(sizeof(linklist));
	new->data = data;
	new->next = NULL;
	//找到pos的前一个位置
	linklist *p = head;
	while(pos--)
	{
		p=p->next;
	}
	//先连后断
	new->next = p->next;
	p->next = new;
	return 0;
}

//环
void linklist_huan(linklist *p, int m)
{
	int j = 1;//记录人数
	linklist *q = NULL;
	while(p->next != p)
	{
		int i;
		for(i=1; i<m-1; i++)//p移动到周期数的前一个位置;假如周期m= 3,则循环执行一次,p从第一个有效结点,往后移动一次,指向第二个结点。
		{
			p=p->next;
		}
		printf("第%d个人是:%d\n", j,p->next->data);//打印当前指向的下一个结点的值。
		j++;
		q = p->next;//把p指向的下一个结点地址给q,为了释放结点
		p->next = q->next;//把q指向的地址,给 p->next;
		free(q);//释放q
		p = p->next;//此时p->next 保存的是q指向的地址,先赋值,再释放,再把p->next保存的地址给p;
	}
	printf("最后剩下的是:%d\n", p->data);//退出循环就说明只剩一个人。
}

int main(int argc, const char *argv[])
{
	linklist *head = createLinklist();
	if(NULL == head)
	{
		printf("createLinklist failed\n");
		return -1;
	}
	int i = 0;
	while(i<41)//插入41个元素,相当于41个人,可以随意改变人数
	{
		linklistInsertByPos(head, i, i+1);
		i++;
	}
	//去掉头结点
	linklist *p = head->next;
	linklist *q = NULL;
	while(p->next != head)
	{
		p=p->next;
	}
	p->next = head->next;
	free(head);
	head = NULL;
	q = p->next; //q就是第一个有效结点
	int m; //周期
	puts("请输入一个周期: ");
	scanf("%d", &m);
	linklist_huan(q, m);
}

五,样例

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值