java实现环形链表解决约瑟夫环问题

什么是环形链表?

环形链表就是单向链表的基础上让链表的首尾相连,形成一个环,这就是一个循环链表。
在这里插入图片描述

什么是约瑟夫环问题?

约瑟夫环如下:
在这里插入图片描述
约瑟夫问题是个著名的问题:N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。
例如只有三个人,把他们叫做A、B、C,他们围成一圈,从A开始报数,假设报2的人被杀掉。

●首先A开始报数,他报1。侥幸逃过一劫。
●然后轮到B报数,他报2。非常惨,他被杀了
●C接着从1开始报数
●接着轮到A报数,他报2。也被杀死了。
●最终胜利者是C
这就是典型的约瑟夫问题,那怎么解决这个问题呢?我们可以使用环形链表去实现,接下来我们就用java实现环形链表,然后来解决这个问题。

定义一个boy类,表示一个节点。

no表示当前节点的编号,next表示指向下一个节点。

//创建一个boy类,表示一个节点
class Boy {
    public int no;//编号
    public Boy next;//指向下一个节点

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

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

有了这个节点类,我们就可以去创建一个环形的单向链表了。

创建环形的单向链表

环形单向链表需要我们创建一个first节点,没有数据的时候为null,有数据的时候first永远指向第一个插入的节点,用来指向环形链表的起始位置。

    //创建一个first节点,当前没有编号
    private Boy first = null;

往链表中添加数据:
我们传进去一个数,这个数是几,就是创建几个节点的环形链表,我们首先需要判断传入的长度是否合法,然后我们需要定义一个辅助接点,指向最后一个节点。当第一个节点A1时候我们需要把他给first以及cur,然后让A1.next=A1,形成一个环。大于第一个的节点X我们只需要把他赋给cur.next并把节点X的next指向first,然后让cur往后移动一位即可。

    //添加节点,传入一个num,指定构建多长的环
    public void addBoy(int num) {
        //首先判断传入的长度是否非法
        if (num < 1) {
            System.out.println("传入的数不正确,num需要大于1.");
            return;
        }
        //定义一个辅助变量
        Boy cur = null;
        for (int i = 1; i <= num; i++) {
            //构建节点
            Boy boy = new Boy(i);
            //当第一个的时候我们需要将first指向这个位置,记录第一个位置。
            if (i == 1) {
                first = boy;
                cur = boy;
                boy.next = boy;
            } else {
                cur.next = boy;
                boy.next = first;
                cur = cur.next;
            }
        }
    }

展示环形链表:
我们仍然需要定义一个临时节点cur指向当前的节点,首先我们判断环形链表是否为空,不为空我们从first开始遍历输出,然后看cur.next是否等于first,相等表示遍历完成,结束循环,不相等让cur往后移动。

    //展示环形链表
    public void showBoy() {
        //定义一个临时变量
        Boy cur = first;
        //判断环形链表是否为空
        if (cur == null) {
            System.out.println("空链表~");
            return;
        }
        //然后循环输出,退出条件为cur.next = first 证明遍历完了,否则cur=cur.next;
        while (true) {
            System.out.println("小孩的编号:" + cur);
            if (cur.next == first) {
                break;
            }
            cur = cur.next;
        }
    }

此时我们构成了一个简单的环形链表,我们测试一下看看:

    public static void main(String[] args) {
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.showBoy();
        }

结果为:

小孩的编号:Boy{no=1}
小孩的编号:Boy{no=2}
小孩的编号:Boy{no=3}
小孩的编号:Boy{no=4}
小孩的编号:Boy{no=5}

我们构建的环形链表可以使用,接下来我们就来解决约瑟夫环问题。
我们需要定义一个解决这个问题的方法,方法需要三个参数,startNo表示从第几个人开始数数,countNum表示数几下淘汰一个人,nums表示环形链表中有多少个人。
具体实现:
1、进行数据校验,如果数据不正确,我们直接return。
2、创建一个辅助节点helper吧first赋给它,然后遍历环形链表,让这个辅助节点指向环形链表的最后一个节点,条件为helper.next == first。
3、因为定义的从那个位置开始数数,所以在出链表之前,我们需要让first节点和helper节点移动到指定startNo位置,移动的次数是startNo-1。
4、让人们报数,同时让first和helper 节点同时的移动countNum-1 次,然后出圈,出圈之后我们需要让first后移,然后让helper.next指向first,这样就把这个节点删除了,直到环形链表里只有一个节点first==helper,此时就是最后一个节点,我们输出后退出方法。
具体实现:

    /**
     * @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;
        //辅助指针事先指向环形链表的最后这个节点
        while (true) {
            if (helper.next == first) {
                break;
            }
            helper = helper.next;
        }
        //小孩报数前,需要让first和helper移动到指定的startNo
        for (int i = 0; i < startNo - 1; i++) {
            first = first.next;
            helper = helper.next;
        }
        //当小孩报数时,让first和helper 指针同时的移动m -1 次,然后出圈
        //这是一个循环操作,直到圈中只有一个节点
        while (true) {
            //只有一个节点
            if (first == helper) {
                System.out.printf("小孩%d出圈\n",first.no);
                break;
            }
            //让first和helper同时移动,移动的次数就是countNum - 1
            for (int j = 0; j < countNum - 1; j++) {
                first = first.next;
                helper = helper.next;
            }
            //此时first就是需要出圈的孩子
            System.out.printf("小孩%d出圈\n",first.no);
            //然后让first后移
            first = first.next;
            helper.next = first;
        }
    }

结果:

小孩2出圈
小孩4出圈
小孩1出圈
小孩5出圈
小孩3出圈

完整代码:

package com.haot.linkedlist;

public class Josephus {
    public static void main(String[] args) {
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.showBoy();
        circleSingleLinkedList.countBoy(1,2,5);
    }
}

//创建一个环形的单向链表
class CircleSingleLinkedList {
    //创建一个first节点,当前没有编号
    private Boy first = null;

    //添加节点,传入一个num,指定构建多长的环
    public void addBoy(int num) {
        //首先判断传入的长度是否非法
        if (num < 1) {
            System.out.println("传入的数不正确,num需要大于1.");
            return;
        }
        //定义一个辅助变量
        Boy cur = null;
        for (int i = 1; i <= num; i++) {
            //构建节点
            Boy boy = new Boy(i);
            //当第一个的时候我们需要将first指向这个位置,记录第一个位置。
            if (i == 1) {
                first = boy;
                cur = boy;
                boy.next = boy;
            } else {
                cur.next = boy;
                boy.next = first;
                cur = cur.next;
            }
        }
    }

    //展示环形链表
    public void showBoy() {
        //定义一个临时变量
        Boy cur = first;
        //判断环形链表是否为空
        if (cur == null) {
            System.out.println("空链表~");
            return;
        }
        //然后循环输出,退出条件为cur.next = first 证明遍历完了,否则cur=cur.next;
        while (true) {
            System.out.println("小孩的编号:" + cur);
            if (cur.next == first) {
                break;
            }
            cur = cur.next;
        }
    }

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

    /**
     * @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;
        //辅助指针事先指向环形链表的最后这个节点
        while (true) {
            if (helper.next == first) {
                break;
            }
            helper = helper.next;
        }
        //小孩报数前,需要让first和helper移动到指定的startNo
        for (int i = 0; i < startNo - 1; i++) {
            first = first.next;
            helper = helper.next;
        }
        //当小孩报数时,让first和helper 指针同时的移动m -1 次,然后出圈
        //这是一个循环操作,直到圈中只有一个节点
        while (true) {
            //只有一个节点
            if (first == helper) {
                System.out.printf("小孩%d出圈\n",first.no);
                break;
            }
            //让first和helper同时移动,移动的次数就是countNum - 1
            for (int j = 0; j < countNum - 1; j++) {
                first = first.next;
                helper = helper.next;
            }
            //此时first就是需要出圈的孩子
            System.out.printf("小孩%d出圈\n",first.no);
            //然后让first后移
            first = first.next;
            helper.next = first;
        }
    }
}

//创建一个boy类,表示一个节点
class Boy {
    public int no;//编号
    public Boy next;//指向下一个节点

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

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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是Java实现链表求解约瑟夫环问题的代码,其中包括了输出环顺序的部分: ``` import java.util.Scanner; public class Josephus { private Node head; private Node tail; private int size; private class Node { private int data; private Node prev; private Node next; public Node(int data) { this.data = data; } } public Josephus() { head = null; tail = null; size = 0; } public void add(int data) { Node newNode = new Node(data); if (head == null) { head = newNode; tail = head; } else { tail.next = newNode; newNode.prev = tail; tail = newNode; } size++; } public void remove(Node node) { if (node == head) { head = node.next; } else if (node == tail) { tail = node.prev; } else { node.prev.next = node.next; node.next.prev = node.prev; } size--; } public void solve(int m) { Node p = head; while (size > 1) { for (int i = 0; i < m - 1; i++) { p = p.next; } Node temp = p.next; remove(p); p = temp; } System.out.println("The order of the Josephus circle is:"); System.out.print(head.data + " "); } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("Please enter the number of people:"); int n = scanner.nextInt(); System.out.println("Please enter the value of m:"); int m = scanner.nextInt(); Josephus josephus = new Josephus(); for (int i = 1; i <= n; i++) { josephus.add(i); } josephus.solve(m); scanner.close(); } } ``` 在这个程序中,我们首先定义了一个双链表类`Josephus`,其中包括了`Node`节点类和相应的链表操作,比如`add`、`remove`等。 在`solve`方法中,我们使用一个指针`p`来遍历链表,每次移动`m-1`个位置,然后将`p`指向的节点删除。直到链表中只剩下一个节点,输出该节点的值。 最后,在`main`方法中,我们输入人数`n`和`m`,然后创建一个`Josephus`对象,并将人数添加到双链表中。最后调用`solve`方法求解约瑟夫环问题,并输出环顺序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值