本文是通过例子学习C++的第六篇,通过这个例子可以快速入门c++相关的语法。

1.问题描述

n 个人围坐在一个圆桌周围,现在从第 s 个人开始报数,数到第 **m **个人,让他出局;然后从出局的下一个人重新开始报数,数到第 m 个人,再让他出局......,如此反复直到所有人全部出局为止。

2.问题分析及用数组求解

约瑟夫环是经典的算法问题,如同“一千个读者就有一千个哈姆雷特”,该问题每个人都有不同的解答。常见的有:数组;单向循环链表;静态链表;双向链表;队列;递推公式 ......

首先简化问题,从s=1开始数,通过数组实现需要:

​ 数组 bool a[1000],可能会浪费了大量的存储空间;

​ 变量 t 从s=1开始数,指示当前数组的位置;

​ 变量 f 记录出局人数;

​ 变量 s 从1到m;

整个过程一个do-while循环即可实现,但理解起来却是非常“拗口”。

代码如下:

#include<iostream>
using namespace std;
int n,m,s,f,t;
bool a[1000];
int main()
{
cin>>n>>m; //共n人,从1开始数,数到m出局
for (int i=1;i<=n;++i){
a[i]=false;
} t=0;//从数组a的a[1]开始...记录数组a的第t个位置
f=0;//记录出局人数
s=0;//从1数到m,然后再从1数到m...
do
{
++t;
if (t==n+1) t=1; //数到最后一个后,将t指向第一个
if (a[t]==false) ++s; //第t个位置上有人则报数
if (s==m) //当前报的数是m
{
s=0; //计数器清零
cout<<t<<" "; //出局人的编号
a[t]=true; //设置该位置已出局
f++; //出局的人数加一
}
} while(f!=n); //所有的人都出局为止
return 0;
}

程序运行效果如下图:

3.数组方式求解改进

下面,我们将s调整为键盘输入,即从第s个人开始报数,实现代码如下:

#include<iostream>
using namespace std;
int n,m,s,f,t;
bool a[1000];
int main()
{
cin>>n>>t>>m; //共n人,从t开始数,数到m出局
cout<<endl;
for (int i=1;i<=n;++i){
a[i]=false;
} t = t -1;
f=0;//记录出局人数
s=0;//从1数到m,然后再从1数到m...
do
{
++t;
if (t==n+1) t=1; //数到最后一个后,将t指向第一个
if (a[t]==false) ++s; //第t个位置上有人则报数
if (s==m) //当前报的数是m
{
s=0; //计数器清零
cout<<t<<" "; //出局人的编号
a[t]=true; //设置该位置已出局
f++; //出局的人数加一
}
} while(f!=n); //所有的人都出局为止
return 0;
}

程序运行效果如下图:

4.静态链表实现约瑟夫环

静态链表,顾名思义就是用数组模拟链表。为了程序的可读性,特用一个函数表示约瑟夫环求解问题,调用的时候,只需要传入n,s,m即可。由于数组的长度n是动态生成的,故通过指针来生成数组。

实现代码如下:

#include<iostream>
using namespace std; //约瑟夫环问题
void Josephus(int n,int s, int m)
{
cout<<n<<" "<<s<<" "<<m<<endl;
int i,j,k;
int *next= new int[n];
//初始化静态链表
for(i=0;i<n-1;++i){
next[i] = i+1;
}
next[n-1] = 0; //k初始化为s的前一个位置,数组下标从0开始
if(s==1){
k = n-1;
}else{
k = s-2;
} for(i=1;i<=n;++i){
//找到出局人的前驱
for(j=1;j<m;++j){
k=next[k];
}
cout<<next[k]+1<<" ";//数组下标从0开始,故需要+1
//数到m的人出列,删除该元素
next[k] = next[next[k]];
}
}
int main(){
Josephus(9,2,5);
return 0;
}

程序运行后效果如下:

5.总结

本文中通过数组、静态链表实现了约瑟夫环。数组方式实现,各个元素之间的“线性关系”未在数据结构中体现,需要通过变量t、f、s来分别指示当前数组元素的位置、出局人数、计数1-m直到所有元素都“出局”为止。类似的,通过队列实现,跟数组实现逻辑上差不多。区别在于数据结构不同,但算法一样。

静态链表方式实现,各个元素之间的“线性关系”通过next指示了,只需要k遍历即可,理解起来更加直观。类似的,通过单向循环链表、双向链表实现跟静态链表实现逻辑上差不多。区别在于数据结构不同,但算法一样。

至于通过递推公式实现,从“计算机”角度看,一般难以“想到”该方法。

通过该例子,可以学习:

  • 指针;
  • 函数定义、调用;
  • 通过优化求解约瑟夫环,加深问题的理解。

本文从构思到完成,可谓是耗费了大量的心血。

如果您阅读本文后哪怕有一丢丢收获,请不要吝啬你手中关注和点赞的权力,谢谢!

另外,如果需要相关代码,请留言,可以提供完整源代码!

通过例子进阶学习C++(六)你真的能写出约瑟夫环么的更多相关文章

  1. 通过例子进阶学习C++(七)CMake项目通过模板库实现约瑟夫环

    本文是通过例子学习C++的第七篇,通过这个例子可以快速入门c++相关的语法. 1.问题描述 回顾一下约瑟夫环问题:n 个人围坐在一个圆桌周围,现在从第 s 个人开始报数,数到第 m 个人,让他出局:然 ...

  2. 通过例子进阶学习C++(五)计算2的1次方至2的64次方之和

    本文是通过例子学习C++的第五篇,通过这个例子可以快速入门c++相关的语法. 1.上篇回顾 在上一篇中,我们通过字符数组计算264次方: 通过例子进阶学习C++(四)计算2的64次方 带着这个问题:为 ...

  3. selenium webdriver学习(六)------------如何得到弹出窗口

    selenium webdriver学习(六)------------如何得到弹出窗口 在selenium 1.X里面得到弹出窗口是一件比较麻烦的事,特别是新开窗口没有id.name的时候.当时还整理 ...

  4. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  5. 通过例子进阶学习C++(四)计算2的64次方,不服写写看

    ​ 本文是通过例子学习C++的第四篇,通过这个例子可以快速入门c++相关的语法. 1.乍一看题目非常简单,简单思考一下,可以通过for循环实现: #include <iostream> u ...

  6. ruby -- 进阶学习(六) devise修改邮件发送者邮箱

    在config/environment.rb/development.rb或者config/environment/production.rb中, 简单示范例子: Text03::Applicatio ...

  7. Android的进阶学习(六)--理解View事件分发

    http://www.jianshu.com/p/34cb396104a7 有些无奈,期末考试抱佛脚,还好没有挂,现在继续进阶. 好久以前就看到了View的事件分发,但是当时功底不够,源码也不敢深究, ...

  8. MYSQL进阶学习笔记六:MySQL视图的创建,理解及管理!(视频序号:进阶_14,15)

    知识点七:MySQL视图的创建(14) 视图的定义: 什么是视图: 视图数由查询结果形成的一张虚拟的表. 什么时候要用到视图? 如果某个查询结果出现的非常频繁,也就是,要经常拿这个查询结果来做子查询. ...

  9. Struts2学习第六课 实现登录登出功能

    关于Struts2请求的扩展名问题: 1).org.apache.struts2包下的default.properties中配置了struts2应用的一些常量 2).struts.action.ext ...

随机推荐

  1. mybatis 基础理解resultType和resultMap

    resultType和resultMap: 1.resultType: 一.返回一般数据类型比如要根据 id 属性获得数据库中的某个字段值. mapper (dao)接口: // 根据 id 获得数据 ...

  2. 微软的可疑更新DhMachineSvc.exe

    最近微软大范围的推出了一个只针对中国的更新,包含了DhMachineSvc.exe,也就是所谓的'微软设备健康助手服务'. 这个更新很神秘,首先这个更新只针对中国区,其次这个更新支持WinXP,第三这 ...

  3. [转]关于/r与/n 以及 /r/n 的区别总结

    应该说还是区别的,\r就是回到行首,\n就是到下一行的,但是一般我们输出程序时,看不到明显的差别的 '\r'是回车,'\n'是换行,前者使光标到行首,后者使光标下移一格.通常用的Enter是两个加起来 ...

  4. java位运算和无符号运算

    计算机在底层使用的是二进制补码进行运算. 计算规则: 正数的原码.反码.补码是其二进制本身. 负数的原码首先计算其二进制数,然后最高位使用1表示负数,反码是最高位不变其它位取反,补码是在反码的基础上进 ...

  5. linux 内核定时器的实现

    为了使用它们, 尽管你不会需要知道内核定时器如何实现, 这个实现是有趣的, 并且值得 看一下它们的内部. 定时器的实现被设计来符合下列要求和假设: 定时器管理必须尽可能简化. 设计应当随着激活的定时器 ...

  6. flex布局属性说明

    flex布局又称为盒子布局或弹性布局,用来为盒状模型提供最大的灵活性,任何一个容器都可以指定为 Flex 布局. 给父容器添加display: flex/inline-flex;属性,即可使容器内容采 ...

  7. jquery 选择多级父子元素

    <div class="box"> <div class="item"> <div class="out"&g ...

  8. AutoHotKey 用打码的快捷键

    本文告诉大家如何使用 AutoHotKey 将 - 键默认输入的时候是下划线,因为使用下划线在写代码的时候是用在私有字段,而 - 很少使用 我打码经常需要使用下划线_而下划线需要按shift+- 两个 ...

  9. dotnet 删除只读文件

    如果直接通过 File.Delete 删除只读文件会出现没有权限提示,可以先设置文件不是只读然后删除文件 try { File.Delete(file); } catch (UnauthorizedA ...

  10. Java虚拟机理解-内存管理

    运行时数据区域 jdk 1.8之前与之后的内存模型有差异,方法区有变化(https://cloud.tencent.com/developer/article/1470519). java的内存数据区 ...