单向环形链表---实现约瑟夫(Josephu)问题

使用单向环形链表实现约瑟夫(Josephu)问题

1.约瑟夫(Josephu)问题描述

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

2.小提示

用一个不带头结点的循环链表来处理 Josephu 问题:先构成一个有 n 个结点的单循环链表,然后由 k 结点起从 1 开 始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从 1 开始计数,直到最后一个结点从链表中删除算法结束。

3.约瑟夫(Josephu)问题—创建单向环形单链表的思路和图解

思路:
(1)先创建一个结点,让first指针指向该节点,并使first.next=first使得第一个结点自己 形成一个环,并将辅助指针指向该节点。核心代码是:

if(i==1)
            {
                first=boy;
                //如果只有一个结点,就先自己构成一个环
                first.next=first;
                //让辅助指针curBoy指向第一个小孩
                curBoy=first;
            }

(2)后面没创建一个结点,就将该节点加入到该环形链表中,核心代码是

                curBoy.next=boy;
                boy.next=first;
                curBoy=boy;

图解:

单向环形链表---实现约瑟夫(Josephu)问题
4.约瑟夫(Josephu)问题—遍历环形单链表思路

(1)创建一个辅助指针curBoy,指向first结点
(2)然后通过while循环遍历单向环形单链表,直到curBoy.next==first结束

5.约瑟夫(Josephu)问题核心—小孩子出圈顺序思路分析

(1)举例:根据用户的输入,生成一个小孩的顺序
n=5:表示单链表中有五个结点,即初始时圈中有五个小孩
k=1:表示从第一个孩子开始报数
m=2:每次数两个人,出圈一个孩子

(2)思路分析

—创建一个helper指针,起始时指向唤醒链表的最后一个结点。
—小孩报数前,先让first指针和helper指针同时移动k-1次(因为题目不一定是从第一个孩子开始报数)
—之后小孩报数时,将first指针和helper指针同时移动m-1次
----这时可以将first指向的小孩结点同时出圈:first=first.next helper.next=first

(3)案例出圈顺序为:2–>4–>1–>5–>3

6.约瑟夫(Josephu)问题代码实现

package cn.zzw.algorithm.LinkedList;

//使用单向环形链表实现约瑟夫问题
public class Josepfu {
    public static void main(String[] args) {
         //测试环形链表和遍历
        CircleSingleLinkedList circleSingleLinkedList=new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.show();

        //测试小孩结点出圈顺序是否正确
        circleSingleLinkedList.countBoy(1,2,5);
    }
}

//创建一个Boy类,表示一个环形链表中的一个结点
//当然可以把属性no和next设为私有,之后使用get和set方法进行访问
class Boy
{
    public int no;
    public Boy next;//表示结点的下一个结点,默认值为null

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

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

//创建一个环形的单链表
class CircleSingleLinkedList
{
    //创建一个first结点,当前默认为null,没有后继结点
    private Boy first=null;

    //添加小孩结点,构建一个环形的链表
    public void addBoy(int nums)
    {
        //为了防止nums不合法,对其进行数据校验
        if(nums<1)
        {
            System.out.println("nums数据值不合法");
            return;
        }

        //使用辅助指针,帮助构建环形链表
        Boy curBoy=null;
        for(int i=1;i<=nums;i++)
        {
            //根据编号,创建小孩结点
            Boy boy=new Boy(i);
            //如果小孩是第一个结点
            if(i==1)
            {
                first=boy;
                //如果只有一个结点,就先自己构成一个环
                first.next=first;
                //让辅助指针curBoy指向第一个小孩
                curBoy=first;
            }
            else
            {
                curBoy.next=boy;
                boy.next=first;
                curBoy=boy;
            }
        }
    }

    //遍历当前链表
    public void show()
    {
        //判断当前链表是否为空
        if (first==null)
        {
            System.out.println("当前链表为空,没有任何结点");
            return;
        }
        //first指针不能动,应该使用一个辅助指针完成链表的遍历
        Boy curBoy=first;
        while (true)
        {
            System.out.printf("当前小孩的编号为:%d\n",curBoy.no);
            if(curBoy.next==first)
            {
                break;
            }
            //后移辅助指针
            curBoy=curBoy.next;
        }
    }

    /**
     *
     * @param startNo
     *           startNo表示从第几个小孩开始报数
     * @param countNum
     *           countNo表示一次数几下
     * @param nums
     *            nums表示一个圈中初始有几个孩子(结点)
     */
    //根据用户输入,指定小孩出圈的顺序,也是整个约瑟夫问题的核心
    public void countBoy(int startNo,int countNum,int nums)
    {
        //首先进行数据校验
        if(first==null||startNo<1||startNo>nums)
        {
            System.out.println("数据输入有误,请重新输入");
            return;
        }
        //创建辅助指针,帮助完成小孩出圈
        Boy helper=first;
        //开始时将辅助指针指向环形链表的最后一个结点
        while (true)
        {
            if(helper.next==first)
            {
                break;
            }
            helper=helper.next;
        }
        //因为有时题目开始报数的并不是1,所以应该先让first和helper同时移动startNo-1次
        for (int j=0;j<startNo-1;j++)
        {
            first=first.next;
            helper=helper.next;
        }

        //当小孩报数时,让first和helper指针同时移动countNum-1次,然后出圈
        //这里是一个循环操作,直到圈里面的人数只有一个
        while(true)
        {
            if (helper==first)
            {
                //此时表示链表中只有一个结点
                break;
            }
            //让first指针和helper指针同时移动countNum-1次
            for(int j=0;j<countNum-1;j++)
            {
                first=first.next;
                helper=helper.next;
            }
            //这时first指向的就是将要出圈的孩子
            System.out.printf("小孩%d出圈\n",first.no);
            //这时将first指向的小孩结点出圈
            first=first.next;
            helper.next=first;
        }
        System.out.printf("最后留在圈中的小孩编号%d\n",first.no);
    }
}



7.代码测试结果

"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" "-javaagent:D:\IntelliJ IDEA\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar=12960:D:\IntelliJ IDEA\IntelliJ IDEA 2019.3.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\Users\1\IdeaProjects\algorithm\out\production\algorithm" cn.zzw.algorithm.LinkedList.Josepfu
当前小孩的编号为:1
当前小孩的编号为:2
当前小孩的编号为:3
当前小孩的编号为:4
当前小孩的编号为:5
小孩2出圈
小孩4出圈
小孩1出圈
小孩5出圈
最后留在圈中的小孩编号3

Process finished with exit code 0

上一篇:练习2 F题 - 平方和与立方和


下一篇:一、Josephu约瑟夫问题