【数据结构与算法】使用单向环形链表解决约瑟夫(Josephu)问题

1. 约瑟夫问题

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

2. 约瑟夫问题的解决办法

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

3. 单向环形链表代码实现

当n = 5,k = 1,m = 2时,出队列的顺序为2、4、1、5、3

单向环形链表各方法说明

  • add:向单向环形链表添加指定个数节点
  • show:遍历显示链表所有节点的数据
  • outListShow:解决约瑟夫问题. 打印出小孩出圈的数据

使用单向环形链表解决约瑟夫问题的实现

public class Josephu {
    public static void main(String[] args) {

        // 创建单向环形链表
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        // 添加小孩到单向环形链表
        circleSingleLinkedList.add(5);

        // 查看单向环形链表所有节点数据
        circleSingleLinkedList.show();

        // 解决约瑟夫问题. 打印出小孩出圈的数据
        circleSingleLinkedList.outListShow(1, 2);
    }
}


class CircleSingleLinkedList {
    // 定义第一个first节点
    private Boy firstBoy;
    // 记录小孩数量
    private int nums;

    // 添加指定数量的小孩到单向环形链表
    public void add(int nums) {
        // 进行nums参数校验
        if (nums < 1) {
            System.out.println("nums的值不正确");
            return;
        }
        this.nums = nums;

        Boy tmpBoy = null;
        // 循环添加小孩到单向环形链表
        for (int i = 1; i <= nums; i++) {
            Boy boy = new Boy(i, "boy" + i);

            // 如果是第一个,则进行赋值,然后firstBoy.next指向firstBoy自身
            // 再将firstBoy赋值给临时节点
            if (i == 1) {
                this.firstBoy = boy;
                this.firstBoy.next = this.firstBoy;
                tmpBoy = this.firstBoy;
                // 如果不是第一个,则添加到单向链表的最后,然后将新节点的next指向firstBoy
                // 同时将临时节点指向新节点,以便下一次循环处理
            } else {
                tmpBoy.next = boy;
                boy.next = this.firstBoy;
                tmpBoy = boy;
            }
        }
    }

    // 遍历显示链表的数据
    public void show() {
        if (this.firstBoy == null) {
            System.out.println("没有任何小孩");
            return;
        }
        Boy tmpBoy = this.firstBoy;
        while (true) {
            System.out.println(tmpBoy.toString());
            // 如果当前节点的next,等于firstBoy,则表示遍历完成
            if (tmpBoy.next == this.firstBoy) {
                break;
                // 否则循环处理后面的节点
            } else {
                tmpBoy = tmpBoy.next;
            }
        }
    }

    // 解决约瑟夫问题. 通过传入从第几个小孩开始数,和数几下,打印出小孩出圈的数据
    public void outListShow(int startId, int countNum) {
        // 进行参数的校验
        if (this.firstBoy == null || startId < 1 || startId > this.nums) {
            System.out.println("firstBoy为空,或startId输入有误, 请重新输入");
            return;
        }

        // 创建一个帮助节点,用于指向删除节点的上一个节点,用来删除节点
        Boy helpBoy = this.firstBoy;
        // 循环遍历,让helpBoy指向firstBoy的前一个节点
        while (true) {
            if (helpBoy.next == this.firstBoy) {
                break;
            } else {
                helpBoy = helpBoy.next;
            }
        }

        Boy currentBoy = this.firstBoy;
        // 先向后移动startId -1个节点。其中helpBoy也跟着移动
        for (int i = 0; i < startId - 1; i++) {
            currentBoy = currentBoy.next;
            helpBoy = helpBoy.next;
        }
        // 然后循环开始计数并依次出圈
        while (true) {
            // 先移动到要出圈的节点。其中helpBoy也跟着移动
            for (int i = 0; i < countNum - 1; i++) {
                currentBoy = currentBoy.next;
                helpBoy = helpBoy.next;
            }
            // 进行出圈操作
            System.out.println("出圈的小孩数据: " + currentBoy.toString());
            // 出圈的节点没有被引用, 会被垃圾回收
            currentBoy = currentBoy.next;
            helpBoy.next = currentBoy;

            // 如果helpBoy等于currentBoy, 则表示只剩最后一个节点了, 进行出圈
            if (helpBoy == currentBoy) {
                System.out.println("最后出圈的小孩数据: " + currentBoy.toString());
                break;
            }
        }
    }
}


// 每个Boy对象就是一个节点
class Boy {
    public int id;
    public String name;
    // 指向下一个节点
    public Boy next;

    public Boy(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Boy [id = " + id + ", name = " + name + "]";
    }

}

运行程序,结果如下:

Boy [id = 1, name = boy1]
Boy [id = 2, name = boy2]
Boy [id = 3, name = boy3]
Boy [id = 4, name = boy4]
Boy [id = 5, name = boy5]
出圈的小孩数据: Boy [id = 2, name = boy2]
出圈的小孩数据: Boy [id = 4, name = boy4]
出圈的小孩数据: Boy [id = 1, name = boy1]
出圈的小孩数据: Boy [id = 5, name = boy5]
最后出圈的小孩数据: Boy [id = 3, name = boy3]

猜你喜欢

转载自blog.csdn.net/yy8623977/article/details/126537052
今日推荐