(五)循环链表实例 约瑟夫问题 --- 他们怎么老想不开(心机BIAO)?

约瑟夫问题

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

个人认为,约瑟夫真的是个心机婊。呵呵。。。

抛开政治,讨论算法。
这个是个典型的循环链表,所以先定义结构。

typedef struct node
{
	int data;
	struct node* next;
}node;

node* ghead = NULL;

然后生成41个有序的循环链表。

void create_41(int num)
{
	node* ltail = NULL, * lnew = NULL;
	int i ;
	/*ghead = (node*)malloc(sizeof(node));
	ltail = ghead;*/
	for (i = 0; i < num; i++)
	{
		lnew = (node*)malloc(sizeof(node));
		lnew->data = i + 1;
		if (i == 0)
		{
			ghead = lnew;
			lnew->next = lnew;
		}
		else
		{
			lnew->next = ltail->next;
			ltail->next = lnew;
		}
		ltail = lnew;
	}
}

看看创建的情况,所以再写个打印循环链表。

void printlist(node * ll)
{
	node* llh=NULL;
	llh = ll;
	int i = 0;
	if (!ll)
	{
		printf("链表为空");
		return;
	}
	while (ll->next != llh)
	{
		printf("%d:\t%d\n", i++, ll->data);
		ll = ll->next;
	}
	printf("%d:\t%d\n", i++, ll->data);
}

下边开始进入重点了,如何让约瑟夫逃过此劫,就要发挥想象了。
我想了两种解决方法,一种是第一天写的(没思路,常规套路),一种是第二天写的(顿悟中。。。。)。
第一种方法:
比较传统,找个变量依次记录步数(就三步),然后输出节点信息,修改头指针,重置标志等等。没有新意,效率比较低。

int GetListLength(node* ll)
{
	node* llh = NULL;
	llh = ll;
	int i = 1;
	while (ll->next != llh)
	{
		i++;
		ll = ll->next;
	}
	return i;
}

//约瑟夫问题
void Joseph(node* ll)
{
	/*
	41个人围成圆圈,从第一个人开始报数,数到3的人自杀。
	再从下一个人开始,数到3,自杀。
	最后剩2个人。
	*/
	node* llpos=NULL;
	node* llprev=NULL;
	llpos = ll;
	int i = 1;
	while (GetListLength(ll) > 2)
	{
		//到达第三人
		if (i == 3)
		{
			ll = llpos->next;
			printf("%d \t号自杀了。\n", llpos->data);
			llprev->next = llpos->next;
			free(llpos);
			i = 1;
			llpos = ll;			
		}
		else if (i == 2)  //得到前驱节点
		{
			llprev = llpos;
			i++;
			llpos = llpos->next;
		}
		else
		{
			i++;
			llpos = llpos->next;
		}
	}
	//更新头指针所指向位置
	ghead = ll;
}

第二种方法:
利用循环这个要点,步子迈大点,但是不要扯着淡,ll->next->next,如果剩余两个的话,刚刚回到原点,这种方式只要循环39次就行,但是第一种方法循环的方式为39*3次,对于计算机而言,这都不是事,但是追求代码短、效率高的极致,就要不断提升。

void Joseph2(node* ll)
{
	node* llpos = NULL;
	node* llprev = NULL;

	while (ll->next->next != ll)
	{
		//得到目标节点
		llpos = ll->next->next ;
		//获得前驱节点
		llprev = ll->next;
		//修改头节点指针
		ll = llpos->next;
		//输出
		printf("%d \t号自杀了。\n", llpos->data);
		//删除节点
		llprev->next = llpos->next;
		//释放内存
		free(llpos);
	}
	//更新头指针所指向位置
	ghead = ll;
}

运行结果
在这里插入图片描述
怎么样?是不是很ok。

测试代码

int main()
{

	create_41(41);
	//printlist(ghead);
	//printf("链表长度:%d\n",GetListLength(ghead));
	//printf("-------------------\n");
	//Joseph(ghead);
	Joseph2(ghead);
	printf("-------------------\n");
	printf("逃脱的人员:\n");
	printlist(ghead);
	system("pause");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值