算法 -- 约瑟夫问题

一、什么是约瑟夫问题

有n个人,从第m个人开始,每隔k个人,就出局一个人,求出局人的出局顺序列表。

二、三种解决问题的思路

  • 数组
  • 循环链表
/**
 * 单向环形链表解决约瑟夫问题
 * @date 2022/1/4 0:01
 */
public class Josepfu {
    public static void main(String[] args) {
        // TODO: 1、构建环形链表
        // (1)创建第一个节点,first指针指向第一个节点,让第一个节点形成环
        // (2)将新节点加入链表,设置一个辅助指针curBoy
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        // TODO: 2、遍历环形链表
        // (1)先让辅助指针指向first节点
        // (2)通过一个while循环遍历该环形链表即可
        circleSingleLinkedList.showBoy();
        // TODO: 3、出圈队列 (2->4->1>5->3)
        circleSingleLinkedList.countBoy(1, 2, 5);
    }
}

// 创建一个环形的单向链表
class CircleSingleLinkedList {
    // 创建一个first节点,当前没有编号
    private Boy first = new Boy(-1);
    // 添加节点,构建长度为nums的环形链表
    public void addBoy(int nums){
        if(nums < 1){
            System.out.println("nums值不合法!");
            return;
        }
        // 辅助指针:帮助构建链表
        Boy curBoy = null;
        // 使用for来创建环形链表
        for (int i = 1; i <= nums; i++) {
            // 根据编号创建小孩节点
            Boy boy = new Boy(i);
            // 如果是第一个节点
            if (i == 1){
                first = boy;
                first.setNext(first); // 构成一个环
                curBoy = first; // 让辅助指针指向第一个节点,因为first不能动,所以使用curBoy
            } else { // 下面就和first没关系了
                curBoy.setNext(boy);
                boy.setNext(first);
                curBoy = boy; // 让curBoy移动到最后位置上
            }
        }
    }

    // 遍历链表
    public void showBoy(){
        // 判断是否为空
        if (first == null){
            System.out.println("链表为空!");
            return;
        }
        // 因为first不能动,因此需要一个辅助指针
        Boy curBoy = first;
        while (true){
            System.out.printf("小孩的编号 %d \n", curBoy.getNo());
            if (curBoy.getNext() == first){ // 遍历完成
                break;
            }
            curBoy = curBoy.getNext(); // 后移
        }
    }

    // 根据用户的输入,计算出小孩出圈的顺序

    /**
     *
     * @param startNo   从第几个开始数
     * @param countNum  表示数几下
     * @param nums      表示最初一共有几个小孩在圈中
     */
    public void countBoy(int startNo, int countNum, int nums){
        // 校验
        if (first == null || startNo < 1 || startNo > nums){
            System.out.println("参数输入有误!请重新输入!");
            return;
        }
        // 创建辅助指针,帮助完成小孩出圈
        Boy helper = first;
        // 让helper指向最后一个节点
        while (true){
            if (helper.getNext() == first){ // helper指向了最后一个节点
                break;
            }
            helper = helper.getNext();
        }
        // 小孩报数前,先让first移动到startNo处
        for (int j = 0; j < startNo - 1; j++) {
            first = first.getNext();
            helper = helper.getNext();
        }
        // 让first和helper指针同时移动m-1次,然后出圈
        // 这里是一个循环操作,直到圈中只有一个节点
        while (true){
            if (helper == first){ // 说明圈中只剩最后一个节点
                break;
            }
            // 移动countNum - 1
            for (int j = 0; j < countNum - 1; j++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            // 这时first指向的节点就是要出圈的节点
            System.out.printf("小孩%d出圈 \n", first.getNo());
            // 将节点出圈
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.printf("最后留在圈中的小孩编号%d \n", first.getNo());
    }
}

// 节点类
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;
    }
}
  • 递归

三、总结

你可能感兴趣的:(数据结构,算法,链表,java)