经典算法–约瑟夫环问题的三种解法

经典算法–约瑟夫环问题的三种解法约瑟夫环问题,这是一个很经典算法,处理的关键是:伪链表问题描述:N个人围成一圈,从第一个人开始报数,报到m的人出圈,剩下的人继续从1开始报数,报到m的人出圈;如此往复,直到所有人出圈。(模拟此过程,输出出圈的人的序号)在数据结构与算法书上,这个是用链表解决的。我感觉链表使用起来很麻烦,并且这个用链表处理起来也不是最佳的。我画了一个图用来理解:有如下问题需要首先考虑:1、“圈…

大家好,又见面了,我是你们的朋友全栈君。

约瑟夫环问题,这是一个很经典算法,处理的关键是:伪链表

问题描述: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);
}

 

经典算法--约瑟夫环问题的三种解法

 

GitHub源码地址:

https://github.com/yangchaoy259189888/JosephRing/

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/135968.html原文链接:https://javaforall.net

(0)
上一篇 2022年6月4日 下午12:36
下一篇 2022年6月4日 下午12:36


相关推荐

  • 2022年全球与中国机器人操作系统(ROS)市场现状及未来发展趋势

    2022年全球与中国机器人操作系统(ROS)市场现状及未来发展趋势2022 2028 全球与中国机器人操作系统 ROS 市场现状及未来发展趋势本文研究全球及中国市场机器人操作系统 ROS 现状及未来发展趋势 侧重分析全球及中国市场的主要企业 同时对比北美 欧洲 中国 日本 东南亚和印度等地区的现状及未来发展趋势 根据 QYR 恒州博智 的统计及预测 2021 年全球机器人操作系统 ROS 市场销售额达到了亿美元 预计 2028 年将达到亿美元 年复合增长率 CAGR 为 2022 2028 地区层面来看 中国市场在过去几年变化较快 2021 年市场规模为百万美元

    2026年3月19日
    3
  • usb转rs485 linux驱动下载,USB转485万能驱动下载

    usb转rs485 linux驱动下载,USB转485万能驱动下载USB转RS485串口驱动是一款非常专业的USB转RS485驱动安装程序。这款软件适合WIN7/WINXP/LINUX等系统,能够帮助用户一键解决USB无法转换成RS485的问题,需要的小伙伴可下载体验。【安装方法】1、在安装前可以先看看使用说明再安装。将USB转换线插入电脑的USB接口中,系统会提示检测到新设备并出现新硬件添加向导,选择从列表或指定的位置安装,手动安装,找到刚刚驱动的解压目录,…

    2022年6月12日
    61
  • 机器学习中的数学——距离定义(二十六):Wasserstein距离(Wasserstei Distance)/EM距离(Earth-Mover Distance)

    机器学习中的数学——距离定义(二十六):Wasserstein距离(Wasserstei Distance)/EM距离(Earth-Mover Distance)Wasserstein 距离也被称为推土机距离 EarthMover sDistance EMD 用来表示两个分布的相似程度 Wasserstein 距离衡量了把数据从分布 ppp 移动成 分布 q 时所需要移动的平均距离的最小值 Wasserstein 距离是 2000 年 IJCV 期刊文章 TheEarthMove sDistanceasa 提出的一种直方图相似度量 如果两个分布 ppp 和 q 离得很远 完全没有重叠的时候 那么 KL 散度值是没有意义的

    2026年3月17日
    1
  • 数组遍历的几种方法及用法

    数组遍历的几种方法及用法js 提供了多种遍历数组的方法 具体使用场景略有区别 在此简单介绍一下 一 forEach 方法 forEach 是最简单 最常用的数组遍历方法 它提供一个回调函数 可用于处理数组的每一个元素 默认没有返回值 以上是个简单的例子 计算出数组中大于等于 3 的元素的个数 回调函数的参数 第一个是处于当前循环的元素 第二个是该元素下标 第三个是数组本身 三个参数均可选 二 map 方法 m

    2026年3月26日
    3
  • mysql libaio_手动编译安装mysql,报错没有libaio模块,

    mysql libaio_手动编译安装mysql,报错没有libaio模块,在初始化 mysql 时 报错 scripts mysql install dbuser mysqldatadir data mysqlbasedir usr local mysqlCouldno root my cnfFatalerro Programabort

    2026年3月16日
    2
  • Postman安装教程_postman需要联网吗

    Postman安装教程_postman需要联网吗1.官网安装(别看)打开官网,https://www.getpostman.com安装很麻烦还很容易安装失败(先请擦掉眼泪,不要忧伤,我们依然可以好好的)2.非官网安装这是一种直接通过打包已经安装的扩展程序的方式,来进行我认为的「非法安装」,但没办法,只能这样。我会给你一个安装包,见附件。你应该下载下来,解压缩到你喜欢的位置。(解压的位置自己要记得)安装包(Postman4.1.2下载地址:http://files.cnblogs.com/files/mafly/postman-4

    2025年12月16日
    7

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号