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

23 篇文章 0 订阅
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;
}

总结

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

约瑟夫环游戏是一个经典的算法问题,它通常涉及到一个圈形的数组,玩家按照特定的规则跳过元素并删除它们。在C++中,我们可以使用循环链表(Circular Doubly Linked List)来实现,因为它支持高效的插入和删除操作。 首先,我们需要创建一个节点结构,包含数据和指向前后节点的指针: ```cpp struct Node { int data; Node* prev; Node* next; }; ``` 然后,我们建立一个循环链表,并设置一个计数器`current`表示当前轮到谁: ```cpp Node* head = nullptr; // 空链表 int current = 0; // 初始化函数 void init(int size) { Node* newNode = new Node{0, nullptr, nullptr}; for (int i = 1; i < size; ++i) { newNode->next = new Node{i, newNode, nullptr}; newNode->prev = newNode->next->prev; if (head == nullptr) head = newNode; else head->next->prev = newNode; } newNode->next = head; // 创建循环 current = size - 1; // 设置初始位置 } ``` 游戏的主要逻辑是这样的: ```cpp void josephusGame(int k) { while (head != nullptr) { current = (current + k - 1) % nodesCount(); // k步后的位置 delete head->data; // 删除该位置的节点 if (head == current) { // 如果头节点就是下一个淘汰者 head = nullptr; // 游戏结束,链表为空 } else { head = head->next; // 更新头节点 } } } ``` 其中`nodesCount()`是获取链表中节点数量的辅助函数: ```cpp int nodesCount() { Node* temp = head; int count = 0; do { count++; temp = temp->next; } while (temp != head); return count; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值