约瑟夫环(Josephus)问题

约瑟夫环(Josephus)问题是由古罗马的史学家 约瑟夫(Josephus)提出的。问题描述为: 编号为1, 2,…,n的n个人按顺时针方向围坐在一张圆桌周围, 每人持有一个密码(正整数)。一开始任选一个正整数 作为报数上限值m,从第一个人开始按顺时针方向自1 开始报数,报到m时停止报数,报m的那个人出列,将 他的密码作为新的m值,从他顺时针方向的下一个人开 始重新从1报数,数到m的那个人又出列;如此下去, 直至圆桌周围的人全部出列为止。

详细描述:
  编号为1,2,3...n的人一词围成一圈,从第k个人开始报数(从1开始),数到m的人退出。接着下一个人又从1开始报数,数到m的人退出,以此类推。问:剩下的人的编号是多少?
 
如:n=6,m=3,k=1
 原始序列: 1  2  3  4  5  6 ; 从编号1开始报数
 第一轮数完后的序列为:  1  2  4  5  ; 3、6出列——从编号1开始报数
 第二轮数完后的序列为:  1  2  5   ; 4出列——从编号5开始报数
 第三轮数完后的序列为:   1  5   ;   2 出列——从5开始报数
 第四轮数完后的序列为:   1  ;5 出列

 所以,最后出列的编号为1.

求解方法一:模拟报数过程,时间复杂度O(M*N)

代码思路:将所有的编号构造成一个环形链表,然后移动到编号为k的节点开始报数,数到M的时候移除链表节点,接着从下一个节点开始报数,移除数到M的节点。以此类推,直到只剩下一个节点。

public static int josephusOMN(int n, int m) {

    return josephusOMN(n, m, 1);

}


public static int josephusOMN(int n, int m, int k) {

    if (n <= 0 || m <= 0 || k <= 0)

        return -1;

    // consruct josephus circle

    JosephusNode header = new JosephusNode(1);

    JosephusNode nodes = header;

    for (int i = 2; i <= n; i++) {

        nodes.mNext = new JosephusNode(i);

        nodes = nodes.mNext;

    }

    nodes.mNext = header;

    // end

    for (int i = 1; i < k; i++) {

        nodes = nodes.mNext; // move to start off person

    }

    while (nodes != nodes.mNext) {

        for (int i = 1; i < m; i++) {

            nodes = nodes.mNext;

        }

        // System.out.print(nodes.mNext.mNum + " , ");

        nodes.mNext = nodes.mNext.mNext;

    }

    return nodes.mNum;

}


private static class JosephusNode {

    public int mNum;

    public JosephusNode mNext;


    public JosephusNode(int num) {

        this(num, null);

    }


    public JosephusNode(int num, JosephusNode node) {

        mNum = num;

        mNext = node;

    }

}
求解方法二:方便求解最后的胜利者同时也适合打印 出列 序列的,时间复杂度O(N).

public static int josepusONWithList(int n, int m) {

    return josepusONWithList(n, m, 1);

}


public static int josepusONWithList(int n, int m, int k) {

    if (n <= 0 || m <= 0 || k <= 0)

        return -1;

    LinkedList<Integer> list = new LinkedList<Integer>();

    for (int i = 1; i <= n; i++)

        list.add(i);

    int outPos;

    while (list.size() > 1) {

        outPos = (int) (k + m - 2) % list.size();

        // System.out.print(list.get(outPos)+" , ");

        list.remove(outPos);

        k = outPos + 1;

    }

    // System.out.println(list.get(0));

    return list.get(0);

}

 

求解方法三:对模拟方法的改进——数学优化方法,时间复杂度O(N)。具体思想参见约瑟夫环的数学优化方法 

 


public static int josephusON(int n, int m) {

    return josephusON(n, m, 1);

}


/**

* Josephus 环的一个O(N)算法

*

* @param n 总人数

* @param m 数到m的人出列

* @param k 开始报数人的编号

* @return 最后一个出列的编号

*/

public static int josephusON(int n, int m, int k) {

    int p = 0;

    for (int i = 2; i <= n; i++) {

        // System.out.print((p + k == n ? n : (p + k) % n)+" ");

        p = (p + m) % i;

    }

    return p + k == n ? n : (p + k) % n; // 返回最后一人的位置

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值