数据结构——单向环形链表 | 约瑟夫问题

约瑟夫问题的来历,先讲一个小故事

据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

简而言之,所谓的约瑟夫问题就是n个人围成一圈,从指定编号的人从1开始报数,数到m的人出列,他的下一个又从1开始报数,数到m的那个人继续出列,以此类推,直到所有人出列为止。
Tips

使用一个不带头结点的单向循环链表来处理约瑟夫问题,先构成一个有n个节点的单向循环链表,然后从i节点起从1开始计数,计到m时,对应节点从链表中删除,然后从被删除节点的下一节点开始继续上述操作,直到最后一个节点从链表中删除

首先创建一个Person节点
//创建一个Person类,表示一个节点
public class Person {
    private int no;//编号
    private Person next;//指向下一个节点,默认为null

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

    public int getNo() {
        return no;
    }

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

    public Person getNext() {
        return next;
    }

    public void setNext(Person next) {
        this.next = next;
    }
}
创建单向环形链表:
//创建一个环形的单向链表
public class CircleSingleLinkedList {
    //先创建一个first节点
    private Person first = null;

    /**
     * 添加节点,构建成一个环形的链表
     *
     * @param nums 当前链表中的节点个数
     */
    public void addBoy(int nums) {
        //nums 做一个数据校验
        if (nums < 1) {
            System.out.println("数值错误!");
            return;
        }
        //创建辅助节点,帮助构建环形链表
        Person curPerson = null;
        //利用for循环来创建环形链表
        for (int i = 1; i <= nums; i++) {
            //根据编号添加节点
            Person person = new Person(i);
            //判断是不是第一个节点
            if (i == 1) {
                first = person;
                first.setNext(first);//构成环状
                curPerson = first;//让curPerson指向第一个节点
            } else {
                curPerson.setNext(person);//将当前节点的下一节点指向新加入的节点
                person.setNext(first);//将新添加入的节点的下一节点指向first节点,再次构成环状
                curPerson = person;//将辅助节点后移
            }
        }
    }

    /**
     * 根据用户的输入,计算出出圈的顺序
     *
     * @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;
        }
        //创建辅助节点,完成出圈任务
        Person helper = first;
        //当出这个while循环时,辅助节点helper即指向了最后一个节点
        while (true) {
            //当辅助节点的下一节点指向first节点时,则说明辅助节点指向了最后一个节点
            if (helper.getNext() == first) {
                break;
            }
            helper = helper.getNext();//将辅助节点向后移动
        }
        //在报数前,应先将first和helper移动startNo - 1 次
        for (int i = 0; i < startNo - 1; i++) {
            first = first.getNext();//此时first就指向了开始报号的节点
            helper = helper.getNext();//此时helper就指向了开始报号的节点的前一个节点
        }
        //报数时,让first和helper同时移动countNum - 1次,然后出圈(这里是一个循环操作,直到链表中还剩下一个节点)
        System.out.print("出圈的顺序为:");
        while (true) {
            if (helper == first) { // 说明圈中只有一个节点
                break;
            }
            for (int i = 0; i < countNum - 1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            //此时first指向的节点就是要出圈的节点
            System.out.print(first.getNo() + "->");
            //这时将first指向的节点出圈
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.print(+ first.getNo());
    }

    //遍历当前环形链表
    public void showList() {
        //判断链表是否为空
        if (first == null) {
            System.out.println("链表为空!");
            return;
        }
        //创建一个辅助节点,来帮助完成遍历
        Person curPerson = first;
        System.out.print("链表顺序为:");
        while (true) {
            //如果当前节点的下一节点指向first,则说明遍历完毕
            if (curPerson.getNext() == first) {
                System.out.print(curPerson.getNo());
                break;
            }
            System.out.print(curPerson.getNo()+"->");
            curPerson = curPerson.getNext();//将当前节点后移
        }
        System.out.println();
    }
}
测试代码如下:
public class CircleSingleLinkedListTest {

    public static void main(String[] args) {
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);//添加5个节点
        circleSingleLinkedList.showList();//打印一下该循环链表
        circleSingleLinkedList.countBoy(2,3,5);//从第二个节点开始报数,每数3次出一个节点,链表中的节点数共有5个。
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值