单向环形链表——约瑟夫问题求解

单向环形链表应用场景

Josephu(约瑟夫、约瑟夫环) 问题
Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
思路:用循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
单向环形链表

单向环形链表

代码实现

输入n,k,m
n:表示有n个小孩
k:表示从第k个小孩开始计数
m:表示每次计数值为m
输出:小孩出列顺序
用例:
输入:5,2,3;输出:3 5 2 1 4
输入:6,1,4;输出:4 2 1 3 6 5

import java.util.ArrayList;
import java.util.List;

/**
 * @author 陈治远
 * @create 2019-07-27 21:02
 */
public class JosephuQuestion {

    public static void main(String[] args) {
        List<Node> nodeList = solve(6, 1, 5);
        if (nodeList != null) {
            for (Node node : nodeList) {
                System.out.print(node.getNum() + "\t");
            }
        }
    }

    /**
     * 解决约瑟夫问题
     * @param n n个节点
     * @param k 从第k个节点开始计数
     * @param m 每次计数m次
     * @return 返回一个按删除顺序保存的节点的List
     */
    public static List<Node> solve(int n, int k, int m) {

        // 初始化一个长度为n的环形链表
        Node firstNode = init(n);
        if (firstNode == null) {
            return null;
        }
        // 找到链表中编号为k的节点的前一个节点,用before表示
        // 第一个节点的编号是1,所以前一个节点的编号就是k-1
        // 因为遍历是从0开始,0表示第一个节点,则k-1-1表示前一个节点
        // 当k==1时,k-1=-1,所以应该有一个取模操作
        Node before;
        Node node = firstNode;
        for (int i = 0; i < (k - 2 + n) % n; i++) {
            node = node.getNext();
        }
        before = node;

        // 按删除顺序保存所有节点
        List<Node> nodeList = new ArrayList<>();
        while (true) {
            // 得到第一个计数的节点,temp变量是辅助节点
            Node temp = before.getNext();
            // 开始计数,计数为m,需要循环m-1次
            for (int i = 1; i < m; i++) {
                if (i == m - 1) {
                    // 重置下一轮计数的before
                    before = temp;
                }
                temp = temp.getNext();
            }
            // for循环结束后,temp就是计数为m的那个节点,添加到nodeList
            nodeList.add(temp);
            // 删除这个节点
            before.setNext(temp.getNext());

            // 当只有一个节点时,结束while循环
            if (temp.getNext() == temp) {
                break;
            }
        }
        return nodeList;
    }

    /**
     * 初始化一条节点数为n的单向环形链表,并返回第一个节点
     * @param n 链表的节点数
     * @return 返回第一个节点
     */
    public static Node init(int n) {
        if (n < 1) {
            return null;
        }
        // 初始化第一个节点
        Node firstNode = new Node(1);
        // 保存当前节点
        Node curNode = firstNode;
        for (int i = 2; i <= n; i++) {
            Node node = new Node(i);
            curNode.setNext(node);
            curNode = node;
        }
        // 首尾对接
        curNode.setNext(firstNode);

        return firstNode;
    }


    // 创建一个内部类表示节点(一个节点对象就是一个小孩)
    private static class Node {
        private int num;    // 节点编号
        private Node next;  // 后节点指针

        public Node(int num) {
            this.num = num;
        }

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        public Node getNext() {
            return next;
        }

        public void setNext(Node next) {
            this.next = next;
        }

    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值