环形链表,约瑟夫环问题【Java实现】

一、问题描述

设编号为 1 ~ n( n > 0 )的 n 个人按顺时针方向围成一圈,约定编号为 k 的人从 1 开始顺时针报数,报 m 的人( m 为正整数),令其出列。然后从他的下一个人开始,重新从 1 顺时针报数,报 m 的人,再令其出列,如此下去,直到圈中所有人都出列为止。求出列编号序列。

二、示例

三、思路及代码实现

1. 构造单向环形列表

思路:

  1. 先创建第一个节点,让 first 指向该节点,并构建成环形
  2. 后面每创建一个新的节点,将该节点加入到已有的环形链表中即可

图示:

代码如下:

/**
 * 构建单向环形链表
 * @param nums 队列人数
 */
public void circle(int nums) {
    //数据校验,如果传入的数据不合法,提示并结束方法
    if (nums < 1) {
        System.out.println("nums的值不正确!无法创建环!");
        return;
    }
    //初始化中间节点
    Node temp = null;
    for (int i = 1; i <= nums; i++) {
        //用来存放每次新创建的编号节点
        Node node = new Node(i);
        //如果是第一个节点,则直接赋值给first,且构建成环状
        if (i == 1) {
            first = node;
            first.next = first;
            temp = first;
            continue;
        }
        //如果不是第一个节点,则将其添加进环中
        temp.next = node;
        node.next = first;
        temp = node;
    }
}

2. 遍历环形链表

思路:

  1. 先让一个辅助节点 temp 指向 first节点
  2. 然后通过一个 while 循环遍历该环形链表即可(如果 temp.next==first 则结束循环)

代码如下:

/**
 * 遍历打印环形链表
 */
public void list() {
    //如果第一个节点没有数据,则链表为空,提示并结束方法
    if (first == null) {
        System.out.println("链表为空!");
        return;
    }
    //借助辅助节点遍历
    Node temp = first;
    while (temp.next != first) {
        System.out.println(temp);
        temp = temp.next;
    }
    System.out.println(temp);
}

3. 约瑟夫环问题解决

思路:

  1. 先创建一个辅助节点 temp 指向当前链表的最后一个节点
  2. 在开始报数前,先让 temp 与 first 同时移动 k-1 次
  3. 当开始报数时,让 temp 与 first 同时移动 m-1 次
  4. 将 first 指向的编号出列,first 指向出列节点的下一个节点

图示:

代码如下:

/**
 * 计算约瑟夫环的出列序列
 * @param k 第k个节点开始
 * @param m 开始后第m个节点出列
 * @param n 一共有n个节点
 */
public void joseph(int k, int m, int n) {
    //数据校验,如果数据不合法则提示且结束方法
    if (first == null || k < 1 || k > n) {
        System.out.println("数据不合法!");
        return;
    }
    //辅助节点
    Node temp = first;
    //找到环形链表的最后一个节点(下一个节点为 first 节点的节点),并赋值给辅助节点
    while (temp.next != first) {
        temp = temp.next;
    }
    //在开始报数前,先让 temp 与 first 同时移动 k-1 次
    for (int i = 0; i < k - 1; i++) {
        first = first.next;
        temp = temp.next;
    }
    //当 temp == first 时,环形链表中只剩下一个节点,结束循环
    while (temp != first) {
        //找到报数为 m 的节点
        for (int i = 0; i < m - 1; i++) {
            first = first.next;
            temp = temp.next;
        }
        //输出当前节点编号,且将当前节点出列
        System.out.printf("%d-->", first.id);
        first = first.next;
        temp.next = first;
    }
    //输出最后节点编号
    System.out.println(first.id);
}

四、测试

代码如下:

/**
 * 测试结果:
 * Node{id=1}
 * Node{id=2}
 * Node{id=3}
 * Node{id=4}
 * Node{id=5}
 * -----------------------
 * 4-->1-->3-->2-->5
 */
public static void main(String[] args) {
    JosephRing ring = new JosephRing();
    //构建环形列表
    ring.circle(5);
    //遍历环形链表
    ring.list();
    System.out.println("-----------------------");
    //打印约瑟夫环编号序列
    ring.joseph(3, 2, 5);
}
约瑟夫环问题是一个经典的计算机科学问题,它涉及到在一个环形链表上,每个节点有一个整数值,按照一定的步长(如7),从第一个节点开始计数,每到指定步长就删除当前节点,并将下一个节点提升到链表头部。如果步长为1,那么问题就是找到链表中的最后一个节点。 在Java中,我们可以使用LinkedList和循环来实现这个过程。首先创建一个Node类作为链表的基本元素,然后定义一个方法`josephus(int step)`,输入步长,开始遍历链表: ```java class Node { int value; Node next; // 构造函数 public Node(int val) { this.value = val; this.next = null; } } public class JosephusRing { private Node head; // 添加节点 public void addNode(Node node) { if (head == null) { head = node; } else { node.next = head; head = node; } } // 约瑟夫环问题 public Node josephus(int step) { if (head == null || step <= 0) return null; // 链表为空或步长无效 Node slow = head, fast = head; for (int i = 0; i < step - 1; i++) { fast = fast.next; // 快指针前进step-1次 if (fast == null) { // 如果快指针到了链表尾部,则回到头 fast = head; } } while (fast != null) { // 当快指针非空时,进行删除操作 slow = slow.next; fast = fast.next; } return slow; // 返回新的头节点 } } ``` 在这个实现中,我们使用了两个指针,一个慢指针每次移动一步,另一个快指针初始时也是一步,但在达到指定步长后会跳过一个节点继续前进。当快指针到达链表尾部时,链表长度已经减少了一个步长,此时慢指针的位置就是新的起始点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值