约瑟夫环问题
一、问题描述
1.1 问题
题目描述:
-
- (1)编号为[1,2,…,n]的n个人按顺时针方向围坐一圈(一般给定一个数,从1~n)
- (2)任选一个正整数作为报数上限m,从第一个人开始按顺时针方向从自1开始顺序报数,
- (3)报到m时停止报数,报m的人出列
- (4)从上一个出列得人顺时针方向上的下一个人开始重新从1报数
- (5)如此下去
1.2 提问方式
- (1)输出出队顺序
- (2)输出最后一个出队的编号
二、输出出队顺序
实现思想:利用双端队列模拟整个过程。代码如下:
1 vector<int> getOrder(int n, int k)
2 {
3 deque<int> q;
4 //开始将所有数字正确入队,1在队头,n在队尾
5 for (int i = 1; i <= n; i++)
6 q.push_back(i);
7
8 //保存出队顺序
9 vector<int> ans;
10
11 //报数记录
12 int cnt = 0;
13
14 //队列不为空就继续报数
15 while (!q.empty())
16 {
17 cnt++;
18 //如果当前报数等于k,就将其出队,并保存在结果中
19 if (cnt == k)
20 {
21 ans.push_back(q.front());
22 q.pop_front();
23 //重置报数
24 cnt = 0;
25 }
26 else
27 {
28 //如果当前报数不等于k,将其插队队尾,等待下一轮的报数
29 q.push_back(q.front());
30 q.pop_front();
31 }
32 }
33
34 //返回结果
35 return ans;
36 }
三、最后出队的数字
思路:反向模拟!
进行公式推算:由于我们需要求最后一个出队人的编号,而最后一个出队人的编号一定为1,所以我们需要反推,也就是说通过新编号推算得到旧编号!
-
- 旧编号 --> 新编号:(旧编号 - 报数值)% 旧人数
- 新编号 --> 旧编号:(新编号 + k)% 旧人数,也就是old_index = (new_index + k) % old_number
最后,我们经过n-1次的公式计算即可反推得到最后一个出队人的本来的序号!
需要注意的是,上述推导的公式必须从0开始才有效,也就是说我们将上述图中所有的值都减去1,最后输出时再加上1即可。
代码如下:
1 int getLastNum(int n, int k)
2 {
3 //新编号,且最后一个人的编号,无论如何都是0。
4 int index = 0;
5
6 //i代表上一轮剩余的人数
7 for (int i = 2; i <= n; i++)
8 {
9 index = (index + k) % i;
10 }
11
12 //返回编号
13 return index + 1;
14 }
四、参考文章
http://182.92.190.128/archives/suan-fa-12--yue-se-fu-huan-wen-ti
本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/15243182.html