其他分享
首页 > 其他分享> > 约瑟夫问题的解决(后面附有完整实现代码)

约瑟夫问题的解决(后面附有完整实现代码)

作者:互联网

首先我们先明确什么是约瑟夫问题:

约瑟夫问题:设编号为1、2、....n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列

思路:

  1. 先创建一个环形链表,环形链表通过for循环来创建,关键要形成一个循环。
  2. 创建一个方法getCount(),计算出链表的有效数据的个数,通过循环遍历的方式获取链表的有效数据

        作用:创建一个数组来存储出队编号的序列,长度为有效数据的个数

     3.约瑟夫问题,首先传入两个参数k,m,k为开始报数的那个结点,m为出列的结点,所以我们需要先创建一个方法getCircleNode(),在初始报数时,获取到初始报数的那个结点.

     4. 该问题解决为:先找到对应开始报数的那个结点,然后开始循环(即报数)m次(实际为m-1次,可以在代码中浏览),找到对应的结点后,在存储出队编号序列的数组中添加该结点的no值,即编号(有循环遍历,我们一般需要借助一个辅助结点cur,cur指向当前遍历的结点)。添加完该节点后,把该节点删除(故我们也需要创建一个结点删除的方法),然后cur后移,继续循环遍历,重复以上步骤,最终可获得的结果。

前期准备:

1.创建环形链表

 /**
     * 创建一个环形链表
     *
     * @param num 传入要创建的结点的个数
     */
    public void addNode(int num) {
        CircleNode cur = null;
        for (int i = 1; i <= num; i++) {
            if (first == null) {//还没有节点时
                first = new CircleNode(i);
                cur = first;//指向当前节点
            } else {
                CircleNode circleNode = new CircleNode(i);//创建结点对象
                cur.next = circleNode;//让当前结点指向新创建的结点
                circleNode.next = first;//形成循环
                cur = circleNode;//向后递进
            }
        }
    }
class CircleNode {
    int no;//编号
    CircleNode next;

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

    @Override
    public String toString() {
        return "CircleNode{" +
                "no=" + no +
                '}';
    }
}

 2.创建一个方法getCount(),计算出链表的有效数据的个数,通过循环遍历的方式获取链表的有效数据        

  /**
     * 获取链表中有效数据的个数
     *
     * @return
     */
    public int getCount() {
        if (first == null) {
            throw new RuntimeException("该链表中没有数据");
        }
        CircleNode cur = first.next;//接用辅助结点
        int count;//记录有效数据的个数
        if (cur != first) {
            count = 2;//此时已经有两个数据
        } else {//此时只有一个数据
            count = 1;
            return count;
        }
        //如果程序运行到此,说明此时至少有两个数据
        while (cur.next != first) {
            cur = cur.next;//向后递进
            count++;//个数递进
        }
        return count;
    }

 3.约瑟夫问题,首先传入两个参数k,m,k为开始报数的那个结点,m为出列的结点,所以我们需要先创建一个方法getCircleNode(),在初始报数时,获取到初始报数的那个结点.

  /**
     * 查询对应序号的结点并返回
     *
     * @param k 查询的序号
     * @return 返回对应的结点
     */
    //这里在调用之前已经判断k是符合范围的,所以肯定能找到对应的结点,无需考虑找不到
    public CircleNode getCircleNode(int k) {
        CircleNode cur = first;//接用辅助结点
        while (true) {
            if (cur.no == k) {
                return cur;
            }
            cur = cur.next;
        }
    }

4.创建一个删除结点的方法

 /**
     * 删除环形队列中的结点
     *
     * @param circleNode 要删除的结点
     */
    public void deleteNode(CircleNode circleNode) {
        if (first == null) {
            System.out.println("该链表中没有数据");
            return;
        }
        CircleNode cur = first;//借助辅助指针
        if (first.no == circleNode.no) {//如果第一个数据就是要删除的结点
            first = first.next;//需要将first向后移动
            for (int i = 1; i < getCount(); i++) {
                cur = cur.next;//找出最后链表中的最后一个数据
            }
            cur.next = first;//让链表中的最后一个数据指向新的first,继续形成循环
            return;
        }
        boolean flag = false;//记录是否找到对应的结点
        for (int i = 0; i < getCount(); i++) {//对链表的所有有效数据进行遍历
            if (cur.next.no == circleNode.no) {//此时cur指向的是要删除结点的前一个结点(因为这是单向链表,删除要找到待删除结点的前一个结点)
                flag = true;//找到后标记
                break;
            }
            cur = cur.next;//向后递进
        }
        if (flag) {
            cur.next = cur.next.next;//进行指针的向后递进,没被指向的结点会被java的垃圾处理机制gc()处理,即被删除的结点
        }else{
            System.out.println("没有该结点,无法删除");
        }

    }

 最后实现约瑟夫问题的解决

 /**
     * 解决约瑟夫问题
     *
     * @param k 编号为k的人开始报数
     * @param m 报数到m的人出列
     * @return 返回一个出列编号的序列
     */
    public int[] Josephu(int k, int m) {
        if (k > getCount() || k <= 0) {//编号大于总的有效人数
            throw new RuntimeException("输入的数:" + k + "有误");
        }
        int[] result = new int[getCount()];//记录出列序号,长度为有效数据的个数
        int index = 0;//记录数组的指针
        CircleNode curNode = null;//记录当前的结点
        while (first.next != first) {//当链表中只剩下一个数据时,循环终止
            if (curNode == null) {//初次判断curNode结点是否赋值
                curNode = getCircleNode(k);//如果没赋值,就通过getCircleNode()方法获取到对应编号的数。
            }
            for (int i = 1; i < m; i++) {
                curNode = curNode.next;//循环m-1次,因为在下面的代码中已经移动到下一个开始计数的点了
            }

            deleteNode(curNode);//要删除已找到的这个结点
            //经过循环后获取到对应的出列的人
            result[index++] = curNode.no;
            curNode=curNode.next;//把curNode指针向后移动一位,故只需循环m-1次


        }
        result[index] = first.no;
        return result;

    }

即问题解决。

完整代码附上:

package com.liu.linkedlist;


import java.util.Arrays;

/**
 * @author liuweixin
 * @create 2021-09-07 19:00
 */
//约瑟夫问题:设编号为1、2、....n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,
// 数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,
// 由此产生一个出队编号的序列

//使用没有头结点的链表来实现
public class JosephuLinkedList {
    private CircleNode first;//创建一个first节点

    public static void main(String[] args) {
        JosephuLinkedList list = new JosephuLinkedList();
        list.addNode(5);
        list.show();
        System.out.println("约瑟夫问题的出列序号");
        System.out.println(Arrays.toString(list.Josephu(1,2)));
        list.Josephu(1, 2);
    }


    /**
     * 解决约瑟夫问题
     *
     * @param k 编号为k的人开始报数
     * @param m 报数到m的人出列
     * @return 返回一个出列编号的序列
     */
    public int[] Josephu(int k, int m) {
        if (k > getCount() || k <= 0) {//编号大于总的有效人数
            throw new RuntimeException("输入的数:" + k + "有误");
        }
        int[] result = new int[getCount()];//记录出列序号,长度为有效数据的个数
        int index = 0;//记录数组的指针
        CircleNode curNode = null;//记录当前的结点
        while (first.next != first) {//当链表中只剩下一个数据时,循环终止
            if (curNode == null) {//初次判断curNode结点是否赋值
                curNode = getCircleNode(k);//如果没赋值,就通过getCircleNode()方法获取到对应编号的数。
            }
            for (int i = 1; i < m; i++) {
                curNode = curNode.next;//循环m-1次,因为在下面的代码中已经移动到下一个开始计数的点了
            }

            deleteNode(curNode);//要删除已找到的这个结点
            //经过循环后获取到对应的出列的人
            result[index++] = curNode.no;
            curNode=curNode.next;//把curNode指针向后移动一位,故只需循环m-1次


        }
        result[index] = first.no;
        return result;

    }

    /**
     * 查询对应序号的结点并返回
     *
     * @param k 查询的序号
     * @return 返回对应的结点
     */
    //这里在调用之前已经判断k是符合范围的,所以肯定能找到对应的结点,无需考虑找不到
    public CircleNode getCircleNode(int k) {
        CircleNode cur = first;//接用辅助结点
        while (true) {
            if (cur.no == k) {
                return cur;
            }
            cur = cur.next;
        }
    }

    /**
     * 获取链表中有效数据的个数
     *
     * @return
     */
    public int getCount() {
        if (first == null) {
            throw new RuntimeException("该链表中没有数据");
        }
        CircleNode cur = first.next;//接用辅助结点
        int count;//记录有效数据的个数
        if (cur != first) {
            count = 2;//此时已经有两个数据
        } else {//此时只有一个数据
            count = 1;
            return count;
        }
        //如果程序运行到此,说明此时至少有两个数据
        while (cur.next != first) {
            cur = cur.next;//向后递进
            count++;//个数递进
        }
        return count;
    }

    /**
     * 遍历链表
     */
    public void show() {
        if (first == null) {
            System.out.println("该链表中没有数据");
            return;
        }
        System.out.println(first);//先打印首个结点
        CircleNode cur = first.next;//接用辅助结点
        while (cur != first) {//借用首个结点进行循环终止条件
            System.out.println(cur);
            cur = cur.next;
        }
    }

    /**
     * 删除环形队列中的结点
     *
     * @param circleNode 要删除的结点
     */
    public void deleteNode(CircleNode circleNode) {
        if (first == null) {
            System.out.println("该链表中没有数据");
            return;
        }
        CircleNode cur = first;//借助辅助指针
        if (first.no == circleNode.no) {//如果第一个数据就是要删除的结点
            first = first.next;//需要将first向后移动
            for (int i = 1; i < getCount(); i++) {
                cur = cur.next;//找出最后链表中的最后一个数据
            }
            cur.next = first;//让链表中的最后一个数据指向新的first,继续形成循环
            return;
        }
        boolean flag = false;//记录是否找到对应的结点
        for (int i = 0; i < getCount(); i++) {//对链表的所有有效数据进行遍历
            if (cur.next.no == circleNode.no) {//此时cur指向的是要删除结点的前一个结点(因为这是单向链表,删除要找到待删除结点的前一个结点)
                flag = true;//找到后标记
                break;
            }
            cur = cur.next;//向后递进
        }
        if (flag) {
            cur.next = cur.next.next;//进行指针的向后递进,没被指向的结点会被java的垃圾处理机制gc()处理,即被删除的结点
        }else{
            System.out.println("没有该结点,无法删除");
        }

    }

    /**
     * 创建一个环形链表
     *
     * @param num 传入要创建的结点的个数
     */
    public void addNode(int num) {
        CircleNode cur = null;
        for (int i = 1; i <= num; i++) {
            if (first == null) {//还没有节点时
                first = new CircleNode(i);
                cur = first;//指向当前节点
            } else {
                CircleNode circleNode = new CircleNode(i);//创建结点对象
                cur.next = circleNode;//让当前结点指向新创建的结点
                circleNode.next = first;//形成循环
                cur = circleNode;//向后递进
            }
        }
    }

}

class CircleNode {
    int no;//编号
    CircleNode next;

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

    @Override
    public String toString() {
        return "CircleNode{" +
                "no=" + no +
                '}';
    }
}

标签:结点,cur,附有,int,代码,约瑟夫,链表,next,first
来源: https://blog.csdn.net/weixin_54950870/article/details/120168129