约瑟夫环问题(丢手绢)

问题描述:

Josephu 问题:

设编号为 1,2,…… n 的n个人未作一圈,约定编号为 k (1<=k<=n)的人从1 1开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,疏导 m 的那个人出列。依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

思路分析:

可以使用单向环形链表来解决该类题目:
在这里插入图片描述
例如:当 n = 5, k = 1,m = 2,时
它的出列顺序依次是 2 -> 4 -> 1 -> 5 -> 3;

  • 第一步:先创建一个节点类,将单向环形链表的各个节点用类来封装,因为每个节点都包括 序号、next,因此就可以直接从某个特定的类表示,每次添加链表时,创建这个对象即可;
  • 第二步:创建一个单向环形链表类,里边包括 创建链表的方法、出链表的方法;
  • 第三步:主函数运行即可。

代码实现:

节点类

class BoyNode{
    private int no;
    private BoyNode next;

    public BoyNode(int no){
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public BoyNode getNext() {
        return next;
    }

    public void setNext(BoyNode next) {
        this.next = next;
    }
}

链表类

class CircleSingleLinkedList{
    // 环形链表的第一个节点
    private BoyNode first = null;

    // 给定一个值 n 创建 大小为 n 的环形链表
    public void makeLinkedList(int n){
        // 先对 n 做判断
        if(n < 1){
            System.out.println("n 的值不正确~~~");
            return;
        }
        BoyNode cur = null;
        for(int i = 1; i <= n; i++){
            BoyNode boy = new BoyNode(i);
            if(i == 1){
                first = boy;
                first.setNext(first); //构成一个环
                cur = boy;
            }else{
                cur.setNext(boy);
                boy.setNext(first);
                cur = boy;
            }
        }
    }

    // 打印验证
    public void show(){
        if(first == null){
            System.out.println("链表为空");
            return;
        }
        BoyNode cur = first;
        while(true){
            if(cur.getNext() == first){
                System.out.printf("这是第%d个小孩\n",cur.getNo());
                return;
            }
            System.out.printf("这是第%d个小孩\n",cur.getNo());
            cur = cur.getNext();
        }
    }

    // 出圈的问题:有大小为 n 的链表,从第 k 个人开始,每次喊 m ,喊到 m 的人出列,从出列的下一个人重新开始

    /**
     *
     * @param n 代表这个单向环形链表的大小
     * @param k 代表从 第 几个 人开始
     * @param m 代表每次喊 几个
     */
    public void outLinkedList(int n, int k, int m){
        if(first == null | k < 1 || k > n){
            System.out.println("参数输入有误~");
            return;
        }

        // (1)先将 first 挪到第 k 个人跟前
        for(int i = 1; i < k; i++){
            first = first.getNext();
        }

        // (2)删除
        // (2.1)先找着第 k - 1 个节点
        BoyNode cur = first;
        while(true){
            if(cur.getNext() == first){ // 当 cur 的下一个节点是 first 时,就是当前环形链表的最后一个节点
                break;
            }
            cur = cur.getNext();
        }

        // (2.2)从 第 k 个人开始喊,这是一个死循环,直到链表中只剩下一个的时候
        // 要删某个元素,思考单链表删除指定节点的方式
        // 删除指定的节点,必须要定义一个 临时节点,使这个临时节点指向要删除节点的上一个节点,此时只需:cur.next = cur.next.next
        while(true){
            if(cur == first){
                break;
            }

            for(int i = 1; i < m; i++){
                first = first.getNext();
                cur = cur.getNext();
            }
            // 这是 first 指向的小孩就是要出圈的小孩,cur是first 的上一个节点
            System.out.printf("小孩 %d 要出圈\n",first.getNo());
            first = first.getNext();
            cur.setNext(first);
        }
        System.out.printf("最后要出圈的小孩是 %d\n",cur.getNo());
    }
}

测试主类

public class JosephCircle {
    public static void main(String[] args) {
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.makeLinkedList(5);
        circleSingleLinkedList.show();
        System.out.println("=============================");
        circleSingleLinkedList.outLinkedList(5,1,2);
    }
}

代码运行结果:

在这里插入图片描述

当然这类问题还有其他方法可以解决,比如数组,不过我还没写呢,等写好了再看

发布了98 篇原创文章 · 获赞 5 · 访问量 6450

猜你喜欢

转载自blog.csdn.net/weixin_43580746/article/details/105275325