茶娱饭后(六)之Josepfu(约瑟夫问题)环形链表实现

约瑟夫问题。

统称处理环状排队出列逻辑,例如一圈小孩围成一个圈,任意指定哪一个小孩开始报数,报指定数之后指向的小孩退出,接下来退出小孩的下一个小孩开始报数,固定数目之后指向的小孩退出,依次循环,直到最后一个小孩。

约瑟夫问题是一个经典的算法问题,其解决过程涉可能用到的数据结构有数组、链表,涉及的算法包括模拟、递归、递推、动态规划等等,因此非常适合做一道面试题。

 

问题描述:

首先n个候选人围成一个圈,依次编号为0..n-1。然后指定一个正整数k,并0号候选人开始按从1到k的顺序依次报数,n-1号候选人报数之后,又再次从0开始。当有人报到K时,这个人被淘汰,从圈里出去。下一个人从1开始重新报数。重复上述过程,直至剩下一个人,求问最后这一人在最开始的圈中的编号。

 

 

实现

我们用(java)循环链表实现约瑟夫问题

代码如下:


/**
 * @date 19-9-21
 * @auther jackliang
 * @description TODO
 */
public class Josepofu {

    private Boy first = null;


    public static void main(String[] args) {
        Josepofu circleLinkedList = new Josepofu();
        circleLinkedList.addBoy(25);//这里数目可以自己定
        circleLinkedList.list();
        circleLinkedList.countBoy(1, 2, 25);//从第一个小孩开始报数,数2下,指针往后移动一位,圈里边有25个小孩
    }


    //添加小孩节点构成一个环形链表
    public void addBoy(int numi) {
        if (numi < 2) {//环形链表里边至少应该有两个值
            System.out.println("numi 值不正确");
        }
        //for 循环创建循环链表

        Boy curBoy = null; //辅助指针

        for (int i = 1; i <= numi; i++) {
            //根据编号创建一个小孩节点
            Boy boy = new Boy(i);
            if (i == 1) {
                first = boy;
                first.next = first;//构成环状
                curBoy = first;//辅助指针定义,为了后面的小孩出圈操作,指定头指针前的指针指向
            } else {
                curBoy.next = boy;
                boy.next = first;//这也是为了构成新节点和first节点的指针闭环
                curBoy = boy;//指针移动至下一个节点以便下一次新节点的前一个节点可以指向新节点
            }
        }
    }


    //根据用户的输入形成小孩出圈的顺序

    /**
     * @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("参数输入有误");
        }

        Boy helper = first;
        // 创建一个辅助变量helper,事先应该指向环形链表的最后这个节点
        while (true) {
            if (helper.next == first) {
                break;
            }
            helper = helper.next;
        }

        //小孩报数前先让,first and helper 移动k -1 次,以便确定从那个小孩开始报数
        for (int j = 0; j < startNo - 1; j++) {
            first = first.next;
            helper = helper.next;
        }

        //当小孩开始报数,让first 和 helper 指针同时移动m  - 1 次,然后出圈,
        while (true) {
            if (helper == first) {
                break;
            }
            //让first 和 helper 指针同时移动 countNum 次
            for (int j = 0; j < countNum - 1; j++) {
                first = first.next;
                helper = helper.next;
            }
            System.out.printf("小孩%d出圈\n", first.no);
            /*指定小孩出圈,出圈小孩前一个小孩的指针指向出圈小孩的下一个小孩,并且指向出圈小孩的头指针指向下一个小孩*/
            first = first.next;
            helper.next = first;
        }

        System.out.printf("最后留在圈中的小孩为%d号", helper.no);

    }


    public void list() {
        //判断链表是否为空
        if (first == null) {
            System.out.println("链表为空");
            return;
        }
        Boy curBoy = first;
        while (true) {
            System.out.printf("打印小孩编号=%d\n", curBoy.no);
            if (curBoy.next == first) {
                System.out.println("遍历完毕");
                break;
            }

            curBoy = curBoy.next;
        }
    }


}


class Boy {

    public int no;
    public Boy next;

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

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

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值