约瑟夫环问题

先描述下问题:
已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。

由于求余的特殊性质(从0开始),建议把编号从0~n-1,最后结果+1即为原问题的解。

再次简化,将编号k的人定义为编号0,其他依次编号%k。

从而最后answer=(answer(简化后)+k)%n(这个好理解,就是编号要加回去嘛!!!!)


先说说程序猿(我)的思路(很遗憾写不漂亮):

当时第一反应就是循环链表,循环报数,去除结点得到结果,大概代码如下(仓促写的,也没好的逻辑性可言,大致略过就好,见谅)

node类:

public class Node {
    public Node next;
    public int value;

    public Node(){};
    public Node(Node next,int value){
        this.next=next;
        this.value=value;
    }
}
实现类:

public class Function {

    //测试用m,n
    private static int testm=3;
    private static int testn=8;

    //生成循环链表并进行操作
    public void function(int m,int n) {
        Node head = new Node(null, 1);
        Node node = head;
        for (int i = 2; i <= n; i++) {
            Node newnode = new Node(null, i);
            node.next = newnode;
            node = node.next;
        }
        node.next = head;
        
/*m-1的原因是为了使用报到m那个数的前一个数的结点,然后指向下下个结点及跳过报到m的那个数,其实感觉太仓促写的,比较简陋没考虑太多*/
        Node nownode = head;
        if (m == 1) {
            System.out.println("answer:"+n);
        } else {
            for (int j = 1; j <= n - 1; j++) {
                for (int i = 1; i <= m - 1; i++) {
                    if (i == m - 1) {
                        System.out.println("out:" + nownode.next.value);
                        nownode.next = nownode.next.next;
                        nownode = nownode.next;
                    } else {
                        nownode = nownode.next;
                    }
                }
            }
            System.out.println("answer:" + nownode.value);
        }
    }

    public static void main(String[] args){

        Function f=new Function();
        f.function(testm,testn);
    }
}
一眼就能看出时间复杂度O(m*n),那么接下去就是优化咯\(^o^)/~


还是程序猿思维(比我厉害点的,我就不说名字了):

如果当前游戏中还有n1个人, 给这个n1个人编号 从1到n1 ,那么下一次出局的人会是编号 (m+n1-1)%n1+1 或者简化为(m-1)%n1+1(我觉得简化的好理解) 。
通过这样的优化可以快速找到每一轮需要出局的人,这种情况下如list来存储当前还在游戏中的人的功能就会存在一定的优势。顺带一提最后时间复杂度为O(n)。


开始渐渐接近数学家思维了:

如果当前游戏还有n个人,从1到n进行编号,并且这一轮编号为a 的人出局了 
原始: 1 2 3 … a-1 a a+1 …n 
a出局之后: 1 2 3 … a-1 a+1 …n 
编号a-1的人的下一位人的从n变为了n+1
而报数顺序变成了如下结构 :
a+1 a+2 …n 1 2 3 …a-1

(啊哈,一个新的N-1约瑟夫环,这样就是开始做递推了不是么。)

给 a1+1,a1+2....1,2,...a1-1. 每个元素做映射:
a1+1 -> 1,
a1+2 -> 2,
.....
a1-1 -> n-1.

即F(n,m) ->  F(n-1,m)

可以看出从n转换到n-1的时候,每个元素都进行了减少a1的循环映射,而且a1 =m%n(重要点!!!上个状态可以决定下个状态,动态规划!!!)

从而得到递推公式F(n,m) =  ( F(n-1,m) +  (m%n) -1 )%n + 1.(这边1的含义其实是为了实现程序求余结果直观化,重新编号了,都从0开始编号,但是数学思维为了说明方便都是以1开始!!!)
且F(1,m)=1;(其中F(n,m)为最终留下的数)

得到公式的你还有什么不会的呢?递归咯。。。

 public int function2(int m,int n){
        if (n==1){
            return 1;
        }else {
            return (function2(m, n - 1) + (m % n) - 1) % n + 1;
        }
    }
(我和公式中的n,m位置不同,为了体现我和数学家不同的思维,哼。)

时间复杂度也降为O(n)了。


(说是转载,其实很大一部分都是我自己理解的,代码也是自己的,但是毕竟不是我研究出来的,就不算是原创了,祝君好运。)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值