通过c/c++使用循环链表实现约瑟夫问题

46 篇文章 2 订阅
3 篇文章 0 订阅

问题导入

据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。 [1]
17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。

预期测试效果

按照尾插法插入的 41 个数字编号,经过 39次淘汰后,应该最后剩下来的元素是 16 号和 31 号元素
程序在 VC++2010 上运行的结果如下所示满足预期
在这里插入图片描述

循环链表原理图

在这里插入图片描述
在这里插入图片描述

尾插法原理图

在这里插入图片描述

循环链表结构体定义

//数据域

typedef struct ListNode{
//这里的 dateType 为了方便演示就用 int 类型定义
int date;
ListNode *next;

}LinkList,LinkNode;

我们这里解决这个问题的数据结构总共使用到了三个方法

循环链表初始化

void init_List(LinkList *&L){

//初始化循环链表,创建头节点 并分配内存
L = new LinkList;
if(!L){
	printf("初始化内存失败\n");
	return ;
}

L->date = -1;
L->next = L;

}

插入元素 尾插法

bool ListInsert_back(LinkList*&L,int e){

if(!L) return false;//防御性编程

//找到尾部指指针的前面一个
LinkList* p = L;
while(p->next!=L) p = p->next;		//找到尾部元素 

//找到之后
LinkList *tmp = new LinkList;

tmp->date = e;
tmp->next = p->next;
p->next = tmp;
return true;

}

遍历双链表元素

void prinLink(LinkList* L){

LinkList *head = L;

L = L->next;
if(!L) return;

while(L!=head&&L){
	printf("%d\t",L->date);
	L = L->next;
}

}

#实现源代码

#include<stdio.h>
#include<iostream>


typedef struct ListNode{

	int date;
	ListNode *next;

}LinkList,LinkNode;
//初始化链表
void init_List(LinkList*&L);
//插入元素 尾插法
bool ListInsert_back(LinkList*&L,int e);
//遍历元素
void prinLink(LinkList* L);

#include"List.h"

void init_List(LinkList *&L){

	//初始化循环链表,创建头节点 并分配内存
	L = new LinkList;
	if(!L){
		printf("初始化内存失败\n");
		return ;
	}

	L->date = -1;
	L->next = L;

}

//插入元素 尾插法
bool ListInsert_back(LinkList*&L,int e){

	if(!L) return false;//防御性编程


	//找到尾部指指针的前面一个

	LinkList* p = L;

	while(p->next!=L) p = p->next;		//找到尾部元素 



	//找到之后


	LinkList *tmp = new LinkList;

	tmp->date = e;

	tmp->next = p->next;
	p->next = tmp;
	return true;
}

void prinLink(LinkList* L){

	LinkList *head = L;

	L = L->next;
	if(!L) return;

	while(L!=head&&L){
		printf("%d\t",L->date);
		L = L->next;
	}



}
#include"List.h"

#define PERSON_COUNT 41   //定义游戏总人数

//报数出列

void kill(LinkList *&Heap,int interval = 3){
	//从第一个开始报数 每三个出列

	int i = 1;	 //做为淘汰的判断
	int j = 1;	//记录淘汰的人数

	LinkList * L = Heap->next;
		printf("\n");
	while(PERSON_COUNT-j>=interval-1){	//出局39个元素

		//找到要被扔向海中的上一个元素
		
		while(1){
			//头节点直接下一个
			if(L==Heap) L = L->next;  //头节点跳过

			if(i>0&&(i+1)%3==0){	//满足条件直接出局
				LinkList *tmp = L->next;
				
				if(tmp==Heap){
					L = tmp;
					tmp = tmp->next;
					
				}

				printf("第:%d个出局的编号:%d\n",j,tmp->date);

				j++;
				i= 1;

				L->next = tmp ->next;
			
				L = L->next;
				delete tmp;

				break;

			}else{
				L = L->next;
				i++;
			}

		}

	}



}
int main(){

	LinkList *L = NULL;

	init_List(L);

	for(int i=1;i<=PERSON_COUNT;i++){
		ListInsert_back(L,i);

	}
	printf("初始化编号\n");
	prinLink(L);
	kill(L);

	printf("最后活下来的人是:\n");
	prinLink(L);

	printf("\n");
	system("pause");
	return 0;
}

总结

使用循环链表能通过逐次遍历解决约瑟夫问题,但这种解法并不是最直观的解法,其中头节点是不存储数据元素的,这提高了算法实现的难度.约瑟夫问题通常可以使用递归,数组等方法来解决.

微信扫码订阅
UP更新不错过~
关注
  • 4
    点赞
  • 3
    收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论

打赏作者

ice_elephant

你的肯定是我们前进的动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值