[数据结构与算法] 单向环形链表 和 Josephu环

Josephu

约瑟夫环–单向环形链表的引出

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

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

image-20220225013310278

创建环形链表的思路图解

image-20220225013636927

  1. 构建一个单向的环形链表

    先创建第一个结点,让first指向该结点,并形成环形

    后面当我们每创建一个新的结点,改变原来的尾结点和新结点的next指向形成新的环即可

  2. 遍历环形链表

    先让辅助指针指向first结点

    然后通过循环,后移指针,直到指针.next==first结束遍历

小孩出圈思路图解

image-20220225014239666

问题:n个小孩围一圈,从第k个小孩开始,数到第m个小孩出圈

思路:

  1. 先将first后移k-1次,表示从第k个小孩开始
  2. 再将first后移m-2次,表示待删除的第m个结点的前一个
  3. 输出第m个结点
  4. first.next跳过第m个结点指向其下一个
  5. 后移first形成新的first
  6. 剩下唯一一个结点输出并退出循环

代码实现:

/*
Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,数到
m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此
产生一个出队编号的序列。
 */
public class Josephu {
    public static void main(String[] args) {
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.add(25);
//        circleSingleLinkedList.out(5,1,2);//24153
        circleSingleLinkedList.out(25,1,2);//

    }
}

//创建单向环形链表
class CircleSingleLinkedList {
    //第一个有效结点的引用
    private Boy first = null;

    /**
     * n个小孩围一圈,从第k个小孩开始,数到第m个小孩出圈
     * @param n 表示最初有多少小孩在圈中
     * @param k 表示从第几个小孩开始数数
     * @param m 表示数几下
     */
    public void out(int n,int k,int m) {

        if (k > n || k < 1 ||first==null) {//数据有误
            System.out.println("游戏没法玩...");
            return;
        }
        //报数前对照k,先向后移动变量first k-1次,
        for (int i = 0; i < k - 1; i++) {
            first = first.getNext();
        }
        while (true) {
            for (int i = 0; i <m-2 ; i++) {
                first = first.getNext();//first后移m-2次,表示待删除的第m个结点的前一个
            }
            System.out.println("出圈的小孩为"+first.getNext().getNo());
            first.setNext(first.getNext().getNext());//first.next跳过第m个结点指向其下一个
            first = first.getNext();//后移first
            if (first.getNext() == first) {//第一个结点自身成环-->场上仅剩一个小孩
                System.out.println("最后出圈的小孩为"+first.getNo());
                break;
            }

        }
    }

    //创造n个小孩结点的链表
    public void add(int num) {
        //校验
        if (num < 1) {
            System.out.println("游戏没法玩...");
        }
        Boy cur=null;
        for (int i = 1; i <= num; i++) {
            Boy boy = new Boy(i);
            if (i == 1) {//第一个小孩为first,且自身成环
                first = boy;
                first.setNext(first);
                cur = first;
            }
            cur.setNext(boy);//cur的next指向下一个结点
            cur = boy;//迭代:cur后移
            cur.setNext(first);//新的next指向first形成环
        }
    }

    //遍历
    public void list() {
        if (first == null) {
            System.out.println("链表为空~");
            return;
        }
        Boy cur=first;
        while (true) {

            System.out.println("场上小孩编号为:"+cur.getNo());
            cur = cur.getNext();//cur后移
            if (cur ==first) {//发现到first了,遍历结束
                break;
            }
        }
    }
}
class Boy {
    private int no;
    private Boy next;

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

    public int getNo() {
        return no;
    }

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

    public Boy getNext() {
        return next;
    }

    public void setNext(Boy next) {
        this.next = next;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值