约瑟夫问题 (Java)

1,407 阅读3分钟

一、问题描述

人们站在一个等待被处决的圈子里。 计数从圆圈中的指定点开始,并沿指定方向围绕圆圈进行。 在跳过指定数量的人之后,执行下一个人。 对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放。

问题即,给定人数、起点、要跳过的数字,选择初始圆圈中的位置以避免被处决。

二、思路及实现

常见的做法是使用循环链表来解决约瑟夫问题。

首先需要找到第一个报数的结点。

跳过指定的人数后,找到要处决的人的前一个结点(删除操作只能通过前一个结点进行),删除要删除的人。

循环链表中只剩一个人时,此人就被释放。

循环链表实现

Node.java

/**
 * @author liyanan
 * @date 2020/1/2 13:44
 */
/**
 * 模拟结点
 */
public class Node {
    /**
     * 存储数据
     */
    public int data;
    /**
     * 指向下一个节点
     */
    public Node next;

    public Node() {
    }

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

CircleLinkedList.java

/**
 * 循环链表的实现(特殊的单链表)
 *
 * @author liyanan
 * @date 2020/1/2 13:16
 */
public class CircleLinkedList {
    /**
     * 指向循环链表的第一个结点
     */
    private Node head;
    /**
     * 循环链表的长度
     */
    private int length;


    public CircleLinkedList() {
        head = null;
        length = 0;
    }

    public Node getHead() {
        return head;
    }

    public int getLength() {
        return length;
    }

    /**
     * 尾部插入
     *
     * @param newNode
     */
    public void insert(Node newNode) {
        if (head == null) {
            // 循环链表为空时,head 指向新的节点
            head = newNode;
        } else {
            Node temp = head;
            // 找到循环链表最后一个节点
            while (temp.next != head) {
                temp = temp.next;
            }
            // 插入新的节点
            temp.next = newNode;
        }
        // 循环链表新的结点指向 NULL
        newNode.next = head;
        // 循环链表长度加 1
        length++;
    }

    /**
     * 将新的结点插入到指定结点后
     *
     * @param preNode 指定节点
     * @param newNode 新的节点
     */
    public void insert(Node preNode, Node newNode) {
        newNode.next = preNode.next;
        preNode.next = newNode;
        length++;
    }

    /**
     * 删除数据域为指定值的元素
     *
     * @param e
     */
    public void del(int e) {
        Node temp = head;
        while (length >= 0) {
            // 找到数据域为 e 的结点
            if (temp.data == e) {
                if (temp.next == head) {
                    // 第一个节点
                    head = null;
                } else {
                    // 最后一个节点
                    temp.next = temp.next.next;
                }
                // 循环链表长度减 1
                length--;
                return;
            }
            // 找到下一个结点
            temp = temp.next;
        }
    }

    /**
     * 给定被删除元素的前一个结点,删除指定元素
     *
     * @param preNode
     */
    public Node del(Node preNode) {
        Node delNode = preNode.next;
        if (delNode == head) {
            // 维护 head 的指向
            head = head.next;
        }
        // 删除
        preNode.next = preNode.next.next;
        length--;
        return delNode;
    }

    /**
     * 随机访问第 k 个结点
     *
     * @param k
     * @return
     */
    public Node find(int k) {
        if (k <= 0) {
            throw new RuntimeException("输入的参数 k 必须大于 0");
        }
        int cnt = 1;
        Node temp = head;
        // 找到第 k 个元素
        while (cnt != k) {
            temp = temp.next;
            cnt++;
        }
        return temp;
    }

    /**
     * 打印循环链表所有有效节点
     *
     * @return
     */
    public String printCircleLinkedList() {
        StringBuilder sb = new StringBuilder();
        int cnt = 0;
        Node temp = head;
        while (head != null && cnt < getLength()) {
            sb.append(temp.data);
            sb.append(" -> ");
            temp = temp.next;
            cnt++;
        }
        sb.append(", length = ");
        sb.append(length);
        return sb.toString();
    }
}

约瑟夫问题解决

package demo.linkedlist;

/**
 * 使用单向循环链表解决约瑟夫问题
 *
 * @author liyanan
 * @date 2020/1/2 17:05
 */
public class JosephProblem {

    private CircleLinkedList circleLinkedList = new CircleLinkedList();

    /**
     * @param total 总人数
     * @return 链表头结点
     */
    public Node createCircle(int total) {
        for (int i = 1; i <= total; i++) {
            Node node = new Node(i);
            circleLinkedList.insert(node);
        }
        return circleLinkedList.getHead();
    }

    /**
     * 处决
     *
     * @param start 开始计数的人,如果只有一个, start = 1
     * @param num   每次跳过的人树
     * @return
     */
    public Node run(int start, int num, int total) {
        createCircle(total);
        // 找到开始报数的人
        Node startNode = circleLinkedList.find(start);
        Node temp = startNode;
        while (circleLinkedList.getLength() > 1) {
            // 找到被删除人的前一个结点
            for (int i = 1; i < num - 1; i++) {
                temp = temp.next;
            }
            // 处决要删除的人
            Node del = circleLinkedList.del(temp);
            // 下一个人开始报数
            temp = temp.next;
            System.out.print(del.data + " ");
        }
        System.out.println();
        // 返回最后幸存的人
        return circleLinkedList.getHead();
    }

    public static void main(String[] args) {
        JosephProblem joseph = new JosephProblem();
        Node node = joseph.run(1, 5, 9);
        System.out.println("最后幸存的人:" + node.data);
    }
}