约瑟夫问题的学习(基于循环链表)以及基于循环数组

这是17世纪法国数学家加斯帕在《数目中的游戏问题》讲的一个问题:15个教徒和15个非教徒在海上遇险,必须将一般的人投入海中,其他的人才能幸免于难。与实现各一个办法:30个人围成一个圈,从第个人开始报数,每报到第9个人就将他扔入海中,如此循环进行,直至仅余15个人为止。问怎么样排法,才能使每次投入大海的都是非教徒。
可参照以下代码:
#include<stdio.h>
typedef struct node_s
{
int next;//指向下一个人的指针(下一个人的数组下标)
int no_out;//是否被扔下海的标志,1:没有被扔下海2:被扔下海。
}node_t;
node_t link[31];//30个人,0号元素没有使用。
int main()
{
int i,j,k;
printf(“The original cir is (+:pagandom,@:christian):\n”);
for(i=1;i<=30;i++)//初始化数组
{
link[i].next=i+1;//指针指向下一个人(下一个数组下标)
link[i].no_out=1;//标志置为1,表示都在船上。
}
link[30].next=1;//第30个人的指针指向第一个人以构成环。
j=30;//j指向已经处理完毕的数组元素,从link[j]指向的人开始计数
for(i=0;i<15;i++)//已扔下海的人数计数器
{
for(k=0;?//决定哪个人被扔下海的计数器
if(k<15)//计数不到15則继续计数
{
j=link[j].next;//修改指针,指向下一个人
k+=link[j].no_out;//进行计数,因已扔下海的人标志为0
}//这样标志不会影响计数。
else
break;//计数到15就停止计数
link[j].no_out=0;//标志置0,表示该人扔下海
}
for(i=1;i<=30;i++)//输出结果。
{
printf("%d:%c\n",i,link[i].no_out?’+’:’@’);
}//’+’:表示被扔下海的非教徒,‘@’:表示留在船上的教徒
return 0;
}


#if 0
下面是转自别人的博客
约瑟夫环问题,这是一个很经典算法,处理的关键是:伪链表

问题描述:N个人围成一圈,从第一个人开始报数,报到m的人出圈,剩下的人继续从1开始报数,报到m的人出圈;如此往复,直到所有人出圈。(模拟此过程,输出出圈的人的序号)

在数据结构与算法书上,这个是用链表解决的。我感觉链表使用起来很麻烦,并且这个用链表处理起来也不是最佳的。

我画了一个图用来理解:

有如下问题需要首先考虑:

1、“圈”怎样形成?

以上图为例:下标从0开始,当下标为11的时候,在加1,就应该回到0。

index = (index+1) % count;

2、怎样处理?数组or链表or其他方法?

链表使用起来很笨重,我们有循环数组的方法。

解法一程序分析:

循环的开始和结束:循环的结束取决于圈内是否还有“人”,可以用一个变量alive表示初始人数,每一次出圈,alive-1.判断alive是否非0即可。

while(alive > 0)

每一次循环,就是“过”一个人,但是,这个人有两种不同的状态:在圈内和不在圈内;在圈内就报数,number+1,不在圈内就不参与报数,number不变。

假设有N个int元素的数组,每一个int元素表示一个“人”;并且,取值为0和1, 1表示在圈内,0表示不在圈内,所以,如果这个人在圈内,number+1;如果这个人不在圈内,number+0。那么,在报数的时候,不需要考虑这个人在不在圈内就行(每一个人都需要加1或加0,所以,可以在这块优化一下程序)。

void joseph(int count, int doom) {
int alive = count; //幸存人数
int number = 0; //计数,当number==doom时,淘汰这个人
int index = 0; //下标,为总人数-1
int *circle = NULL; //根据需求设为循环数组,存储每个人

//用calloc()函数申请得到的空间,自动初始化每个元素为0
//所以,0表示在这个人在约瑟夫环内,1表示这个人出圈,即“淘汰” 
circle = (int *) calloc(sizeof(int), count);

//只要幸存人数大于0,则一直进行循环 
while(alive > 0) {
	number += 1- circle[index];	//每轮到一个人报数,不管是"0"还是"1"都进行计数 
	if(number == doom) {		//当number==doom时,就要淘汰当前这个人
		/*
			淘汰一个人需要做四步操作:
				1、输出这个人的位置 
				2、把这个人的状态从在圈内"0"改为不在圈内"1" 
				3、幸存人数alive-- 
				4、 计数器number归零 
		*/ 
		alive == 1 ? printf("%d", index+1) : printf("%d,", index+1);
		circle[index] = 1;
		alive--;
		number = 0;
	}
	//与总人数count取余,则可以使index在0~count-1之间 一直循环,达到循环数组的目的 
	index = (index +1) % count;	
}
printf("\n");

free(circle);		//结束后一定要释放circle所申请的空间 

}

解法二程序分析:

解法二在解法一的基础上进行了优化,对出圈的人的节点进行删除,可以减少时间复杂度。

假设,要删除的节点下标为curIndex,其前驱节点下标为preIndex;

circle[preIndex] = circle[curIndex];

假设要删除的节点下标curIndex=3,那么circle[2] = circle[3] ,circle[2]  = 4,circle[2]之前等于3,现在等于4。即下标为3的第四个人已经被删除了。

怎样遍历?

preIndex = curIndex;

curIndex = circle[curIndex];

但是,在出圈的时候,curIndex 和preIndex 的变化有别于上面的操作:

出圈时,curIndex 需要后移,preIndex 应该不动!

每次循环,直接报数,因为被删除的人(例如上面的第四个人)不可能进行报数了。

void joseph(int count, int doom) {
int alive = count; // 幸存人数
int number = 0; // 报数的数
int curIndex = 0; // 当前人下标
int preIndex = count - 1; // 前一个人下标
int *circle = NULL;
int index;

circle = (int *) malloc (sizeof(int) * count);
//对circle数组进行初始化 
for(index = 0; index < count; index++) {
	circle[index] = (index + 1) % count;
}

while(alive > 0) {
	number++;
	if(number == doom) {
		alive == 1 ? printf("%d", curIndex+1) : printf("%d,", curIndex+1); 
		alive--;
		number = 0;
		circle[preIndex] = circle[curIndex];	//出圈操作 
	} else {
		preIndex = curIndex;	//处理下一个人 
	}
	curIndex = circle[curIndex];
}

free(circle);

}

解法三程序分析:

解法三里没有进行number报数,而是直接计算出需要移动的人数,然后定位到要出圈的人。

num = doom % alive - 1;

for(index = 0; index < (num == -1 ? alive - 1 : num); index++) {
preIndex = curIndex;
curIndex = circle[curIndex];
}

void joseph(int count, int doom) {
int alive = count; // 幸存人数
int curIndex = 0; // 当前人下标
int preIndex = count - 1; // 前一个人下标
int *circle = NULL;
int index;

circle = (int *) malloc(sizeof(int) * count);
for(index = 0; index < count; index++) {
	circle[index] = (index + 1) % count;	// 初始化链表
}

while(alive > 0) {	// 只要还有幸存者,就继续“杀”
	int num = doom % alive - 1; // 直接计算出需要移动的人数,
	// 直接定位到要出圈的人
	for(index = 0; index < (num == -1 ? alive - 1 : num); index++) {
		preIndex = curIndex;
		curIndex = circle[curIndex];
	}
	// 该人出圈!
	alive == 1 ? printf("%d", curIndex+1) : printf("%d,", curIndex+1);  
	alive--;
	circle[preIndex] = circle[curIndex]; // 真正的出圈操作!
	curIndex = circle[curIndex]; // 继续处理下一个人
}
// 这个算法比normalJoseph.c效率提高30%!

free(circle);
}

#endif

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值