约瑟夫问题[韩顺平算法]

文章介绍了约瑟夫问题的解决方案,通过构建单向环形链表来模拟问题情境。在Java代码示例中,创建并遍历链表的过程中遇到了StackOverflowError,这是由于对象之间的循环引用导致的。文章提供了解决此问题的方法,并给出了实现约瑟夫问题的具体代码,包括出圈顺序的计算。此外,还提到了IDEA中设置方法自动注释的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

约瑟夫问题

在计算机编程的算法中,类似问题又称为约瑟夫环
约瑟夫环:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。

构建单向的环形链表思路

  1. 首先创建第一个节点,并且该结点有first指针指向第一个结点,而这个结点的next域指向自己,从而形成环状
  2. 创建辅助结点curboy,用于指向第一个结点后的所有结点咯,辅助创建结点
  3. 每创建一个新的结点,就把该结点添加到已有的环形链表

遍历环形链表思路

  1. 创建临时指针curboy,先指向first结点
  2. 然后通过一个while循环遍历该环形链表即可curBoy == first结束

代码实例

package com.Yjm.josepfu;

/**
 * @author 游锦民
 * @version 1.0
 */
public class Josepfu {

    public static void main(String[] args) {

        //测试
        //创建链表
        RingSingleLinkedList linkedList = new RingSingleLinkedList();
        linkedList.addBoy(5);
        linkedList.showBoy();
    }
    
}

/*
 * 思路
 * 1.创建环形链表
 * 2.首先创建结点咯
 * 3.然后再创建链表在里面创建结点
 * */

//2.创建链表
class RingSingleLinkedList {
    //单链表有什么内容?
    //first结点
    private Boy first = null;

    //添加结点的方法
    //添加的思路就是传进结点的个数然后通过遍历创建链表
    public void addBoy(int num) {
        if (num < 1) {
            System.out.println("请输入正确的数量");
            return;
        }

        //需要创建临时结点,用于辅助添加结点
        Boy curBoy = null;
        //使用循环创建环形链表
        for (int i = 1; i <= num; i++) {
            //根据序号创建结点
            Boy boy = new Boy(i);
            if (i == 1) {
                //头指针指向第一个结点
                first = boy;
                //即使是单个,指针也要指向自己,构成环形
                first.setNext(first);
                //临时结点就是指向第一个结点的
                curBoy = first;
            } else {

                //使用临时指针指向新结点
                curBoy.setNext(boy);
                //新结点指向first结点
                boy.setNext(first);
                //临时指针移到最新结点位置
                curBoy = boy;
            }
        }
    }


    //变遍历当前链表


    public void showBoy() {
        //链表校验
        if (first == null) {
            System.out.println("什么都没有还遍历我? 你想要我命?");
            return;
        }
        //遍历每一个结点
        //创建临时结点
        //临时结点指向第一个结点啊
        Boy curBoy = first;
        //通过循环遍历链表
        while (true) {
            //直接获取
            //细节:不能获取next域
            //报错的信息是:Exception in thread "main" java.lang.StackOverflowError
            // 解析:栈溢出的意思。就是指对象之间相互引用,最终会导致栈溢出。
            System.out.println("结点的编号 "+curBoy.getNo());
            //判断全部遍历完毕
            //当临时指针的next已经指向first就证明到最后了
            if (curBoy.getNext() == first) {
                break;
            }

            //获取前一个之后就遍历到后一个
            curBoy = curBoy.getNext();

        }

    }
}


//1.创建结点类
class Boy {
    //结点有序号
    private int no;
    //next指针用于指向下一个结点
    private Boy next;

    public Boy() {
    }

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

    public Boy(int no, Boy next) {
        this.no = no;
        this.next = next;
    }

    /**
     * 获取
     *
     * @return no
     */
    public int getNo() {
        return no;
    }

    /**
     * 设置
     *
     * @param no
     */
    public void setNo(int no) {
        this.no = no;
    }

    /**
     * 获取
     *
     * @return next
     */
    public Boy getNext() {
        return next;
    }

    /**
     * 设置
     *
     * @param next
     */
    public void setNext(Boy next) {
        this.next = next;
    }

    public String toString() {
        return "Boy{no = " + no + ", next = " + next + "}";
    }
}

遇到问题

  while (true) {
            //直接获取
            //细节:不能获取next域
            //报错的信息是:Exception in thread "main" java.lang.StackOverflowError
            // 解析:栈溢出的意思。就是指对象之间相互引用,最终会导致栈溢出。
            System.out.println("结点的编号 "+curBoy);

解决: 异常----Exception in thread “main” java.lang.StackOverflowError_欧吉吉的博客-CSDN博客

约瑟夫问题实现

问题: 根据用户的输入,生成一个小孩出圈的顺序
n=5,即有5个人
k=2,从第2个人开始报数
m=2,数2下
实现思路思路:
1.创键一个辅助指针helper指向链表的最后一个结点
2.在小孩出圈前先把heleper和first指针移动到k-1个位置,(k就是决定位置)
3.1操作出圈,当小孩报数时候就让first指针和helper指针同时移动m-1次
3.2让first指针指向后一个,first=first.getNext(); 断开当前first的连接就会被垃圾回收机制回收
4.把helper指向的next域指向最新的first, helper.setNext(first);

代码实例

class RingSingleLinkedList {

    //根据用户输入,计算出小孩出圈的顺序
    //穿创建出圈方法


    /*
*
* 问题: 根据用户的输入,生成一个小孩出圈的顺序
        n=5,即有5个人
        k=2,从第2个人开始报数
        m=2,数2下

* 思路:
*   1.创键一个辅助指针helper指向链表的最后一个结点
*   2.在小孩出圈前先把heleper和first指针移动到k-1个位置,(k就是决定位置)
*   3.1操作出圈,当小孩报数时候就让first指针和helper指针同时移动m-1次
*   3.2让first指针指向后一个,first=first.getNext(); 断开当前first的连接就会被垃圾回收机制回收
*   4.把helper指向的next域指向最新的first, helper.setNext(first);
*
* */

    /**
     * @param startNo  表示开始的序号
     * @param countNum 表示每次出圈的报数
     * @param nums     总数
     */

    public void countBoy(int startNo, int countNum, int nums) {

        //思路:
        //1.首先进行参数校验
        if (first == null || startNo < 1 || countNum > nums) {
            System.out.println("参数输入有误,请检查");
            return;
        }

        //2.创建指向first的辅助指针,是指向而不是位于
        //并且是位于最后一位
        Boy helper = first;

        while (true) {
            //当hleper的后一个是first就证明到最后一个结点了
            if (helper.getNext() == first) {
                break;
            }
            //first指针是永远不动的
            // first=first.getNext();
            //helper已经获取到first结点的所有东西了,直接使用它后移就可以
            helper = helper.getNext();
        }

        //出圈前要把first和helper指针移到starNo-1的位置
        for (int i = 0; i < startNo - 1; i++) {
            first = first.getNext();
            helper = helper.getNext();
        }

        // 3.1操作出圈,当小孩报数时候就让first指针和helper指针同时移动countNum-1次
        while (true) {
            if (helper == first) {//说明圈中只有一个结点
                break;
            }
            for (int i = 0; i < countNum - 1; i++) {


                //移动到该位置就把该位置的first出圈
                first = first.getNext();
                helper = helper.getNext();
                //把first出圈
                System.out.println("出去号码:" + first.getNo());
                first = first.getNext();
                //把first前一个结点的指针指向最新的first即可
                helper.setNext(first);

            }
        }
        System.out.println("最后活下来的:" + first.getNo());
    }
}

补充知识:设置方法自动注释

在IDEA中设置方法自动注释(带参数和返回值) - 陈德洲 - 博客园 (cnblogs.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值