本人已经有了一段时间的计算机学习和算法的学习经历,今天碰巧看到了当时学习链表时的一个经典题目,约瑟夫环。下面先大概讲讲题目的一个背景
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式:41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是一开始要站在什么地方才能避免自杀?
看到这里,很多人的就已经跃跃欲试了,这个简单,链表模拟 一下就写好了。 确实,很多人学链表的时候遇到的第一个问题可能就是约瑟夫环了,这里提供一下链表的代码。
public class csdn_24 {
//设计一个结点类, 因为会设计到环形链表所以自己设计是最好的
private static class Node{
int value;
Node next;
public Node(int value){
this.value = value;
}
}
// m 个人 m 个人会噶
public int circle(int n, int m) {
Node head = new Node(0);
Node temp = head;
for(int i = 1 ; i < n ; i++){
temp.next = new Node(i);
temp = temp.next;
}
temp.next = head;
int index = 0;
while(head.next != head){
if(index == m-2){
head.next = head.next.next;
index = 0;
}
else{
index++;
}
head = head.next;
}
return head.value;
}
}
代码比较简单也比较基础,就不多说明了。
分析一下不能发现算法的时间复杂度 高达O(nm) , 当 n, m 非常大的时候,性能其实是很差的。
这里我们重点说一下用数学如何对这个问题进行一个优化。
这里先给出公式
f(N,M)=(f(N−1,M)+M)%N
这里的n表示有n个人 报道m的人就噶。
package org.czl;
public class csdn_24P {
int circle(int n,int m)
{
int p=0;
for(int i=2;i<=n;i++)
{
p=(p+m)%i;
}
return p+1;
}
}
这里我们来详细解释一下上述代码和公式
公式简单点来说 就是描述幸存者下标的位置。
这里我们不妨假设一个问题, 就是此时场上有 m 个人 第 n 个人被删除 我们如果知道了在这种 m和n的情况下, 最后活下来的人是第 y 号, 那我们想一想 第一次删除的时候 把第n个人删了 ,我们下一个开头就是n + 1个人 总人数就变为了 m - 1. 如果我们此时在假设m - 1的下标为0 那最后活下的人就是y + n 号了。 就是把每一个新的开始看做开头,最后活下来的人也会相对应的往后延迟多少个位置, 但是相对位置始终是不变的。
这个公式就能很好的解释了 f(N,M)=(f(N−1,M)+M)%N。