约瑟夫环问题是一道很经典的算法题,相信大家也都很熟悉,这里我们就一起来看看一看这道经典算法题的解法。
>约瑟夫环问题的描述如下
编号为 1-N 的 N 个士兵围坐在一起形成一个圆圈,从编号为 1 的士兵开始依次报数(1,2,3…这样依次报),数到 m 的 士兵会被杀死出列,之后的士兵再从 1 开始报数,直到最后剩下一士兵,求这个士兵的编号。 |
方法一:数组
采用数组来解决这道题。
创建一个和题目人数相等的数组,然后给数组每个元素赋值1,2 ,3…n,数组每个元素就代表一个士兵。(这里假设n=8,m=3)。
下面就来演示这个报数的过程: (-1表示该士兵死亡)
最开始:1——2——3——4——5——6——7——8
第一次报数完毕:1——2——-1 ——4——5——6——7——8
第二次报数完毕:1——2——-1 ——4——5 ——-1——7——8
第三次报数完毕:-1——2——-1 ——4——5——-1 ——7——8
第四次报数完毕:-1——2——-1 ——4——-1——-1——7——8
第五次报数完毕:-1——-1——-1 ——4——-1 ——-1——7——8
第六次报数完毕:-1——-1——-1 ——4——-1——-1——7——-1
第六次报数完毕:-1——-1——-1 ——-1 ——-1——-1——7——-1
最后只剩下7,也就是活下来的士兵是7号。 |
下面就给出核心的算法代码
void Solution1(short* Array , int n,int m)
{
int count = 1; //报数标记
int nums = 0; //记录死掉士兵的个数
for (int i = 0; i < n; i++)
{
if (Array[i] != -1 && count == m) //报到m的士兵被杀死
{
count = 1;
Array[i] = -1;
nums++;
}
if (Array[i] != -1)
count++;
if (i == n - 1) //到数组末尾重新设置下标为0
i = -1;
if (nums == n - 1) //只剩下最后一个士兵则退出循环
break;
}
}
下面是主函数
#include
using namespace std;
const int NUM = 1000; //最大数量的士兵
int main()
{
short Array[NUM];
//法一:用数组方式解决
for (int i = 0; i < NUM; i++)
Array[i] = i+1;
int n, m; //n是士兵数量,m是被杀的序号
cin >> n >> m;
Solution1(Array, n, m);
for (int i = 0; i < n; i++)
{
if (Array[i] != -1)
cout << "剩下的士兵编号是:"<<Array[i]<<endl;
}
}
这种做法的时间复杂度是 O(n * m), 空间复杂度是 O(n); |
方法二:环形链表
一般我们都是在学习链表的时遇到约瑟夫环问题的,所以这道题多数人会采用链表的方式去解决的。
链表和数组其实很类似,只不过在表示杀死士兵的时候,直接删除这个节点就可以了,直到最后链表只剩下一个节点,那么这个节点的val值就是最后存活士兵的编号。
这里就不详细演示具体过程了,直接贴代码,过程和上面的数组法类似。
节点类 |
---|
class List_Node
{
public:
using pList_Node = List_Node* ;
int val; //士兵编号
pList_Node next;
public:
List_Node()
{
this->val = 0;
this->next = nullptr;
}
List_Node(int val)
{
this->val = val;
this->next = nullptr;
}
};
解决方案类 |
---|
class Solution
{
public:
int Solution1(List_Node::pList_Node head, int m , int n)
{
int count = 1;
List_Node::pList_Node Cur = head;
List_Node::pList_Node pre_node = nullptr; //前驱节点
if (m == 1 || n < 2)
return n;
while (Cur->next != Cur)
{
// 删除节点
if (count == m)
{
count = 1;
pre_node->next = Cur->next;
Cur = pre_node->next;
}
else
{
count++;
pre_node = Cur;
Cur = Cur->next;
}
}
return Cur->val;
}
//创建链表
List_Node::pList_Node Create_List(int n)
{
List_Node::pList_Node head = new List_Node(1);
List_Node::pList_Node last = head;
for (int i = 1; i < n; i++)
{
List_Node::pList_Node nNode = new List_Node(i+1);
last->next = nNode;
nNode->next = head;
last = nNode;
}
return head;
}
};
主函数 |
---|
#include
using namespace std;
int main()
{
//创建链表
int n, m;
cin >> n >> m;
Solution solution1;
List_Node::pList_Node head = solution1.Create_List(n);
cout << "剩下的士兵编号是:" << solution1.Solution1(head, m , n);
return 0;
}
这种做法的时间复杂度是 O(n * m), 空间复杂度是 O(n); |
那么有没有一种更为简洁的方案呢?答案是有的,下面就来揭晓(一行代码就可以搞定 )
方法三:递归
相信不少人都不会想到可以用递归的思想去解决这道题。笔者之前也不知道可以用递归的思想来处理这道题,没想到用递归一行代码就可以搞定,简直不要太过分。(哈哈)
学过算法的道友都应该直到,递归的关键就是要找到前后的的递推关系。那么就这道题而言的话,就是要找到杀死某个士兵前后每个士兵编号映射关系。
我们定义递归函数 fun(n,m) 的返回结果是存活士兵的编号。
当 n = 1 时,f(n, m) = 1。
现在就是要找到 f(n,m) 和 f(n-1,m) 之间的关系。我们假设士兵数为 n, 报数到 m 的人就被处死。则刚开始的编号为
1 2 3 4 5 …m-2 m-1 m m+1 m+2 m+3 …n
删除一个结点之后结点编号将会发生变化,现在只剩下 n - 1 个士兵。且士兵死亡前编号为 m + 1, m + 2, m + 3 的士兵成了编号为 1, 2, 3 的节点。
假设 pre 为删除之前的节点编号, now 为删除了一个节点之后的编号,则 pre 与 now之间的关系为 pre = (now + m - 1) % n + 1。
int fun(int n, int m)
{
return n == 1 ? n : (fun(n - 1, m) + m - 1) % n + 1;
}
确实这一行代码就可以搞定,而且他的性能更加的好
这种做法的时间复杂度是 O(n ), 空间复杂度是 O(n); |
如果那你觉得这篇文章对你有所帮助的话,请给笔者点个赞 ,让更多的读者看到这篇文章。先在这里感谢各位道友啦~~~