【数据结构】应用循环链表解决约瑟夫问题(无头节点)

什么是约瑟夫问题?

首先我们先看个故事:

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

声明一个类代表人,因为是单项链表,所以在该类中需要有一个指向下一个节点的指针

/**
 * 创建一个Boy类,表示一个节点
 */
class Boy{
    private int no;//编号
    private Boy next;//指向下一个节点,默认null

    public Boy (int no) {
        this.no = no;
    }
    //此处getter和setter方法省略
}

添加元素

  • 1.初始状态下,循环链表只有一个指针,我们声明这个指针为first,因为没有元素,所以这个指针是null

  • 2.第一次添加元素时,假定这个元素是 (1-关羽),那么我们就应该让first指针指向 (1-关羽) 这个元素,因为是循环链表,所以元素 (1-关羽) 的指针(假定定义为next指针)是指向自己的,如下图:
    在这里插入图片描述

  • 3.第二次添加元素时,假定这个元素是 (2-张飞) 需要一下步骤:

    • 第一步:张飞的next指针=关羽的next指针
    • 第二步:关羽的next指针=张飞

在这里插入图片描述

4.第三次在添加元素时,假定这个元素是 (3-赵云) ,那么此时就按照第3步,就ok

总结:添加元素主要分为两部分:1.添加第一元素,自己构成环;2.继续添加元素时,加到环上即可。

/**
 * 创建一个环形的单项链表
 */
class CircleSingleLinkedList{
    //创建一个first节点
    private Boy first;


    /**
     * 添加小孩节点,构建成一格环形的链表
     * @param nums
     */
    public void addBoy(int nums){
        //一、对参数进行校验
        if(nums < 1 ){
            System.out.println("nums的值不正确");
            return;
        }
        //因为first指针不能动,所以定义一个指针,来代替first指针
        Boy curBoy = null;
        //使用for来创建我们的环形链表
        for (int i = 1; i <= nums; i++) {
            //根据编号,创建小孩节点
            Boy boy = new Boy(i);
            //情况一:当前链表中无元素
            if(i == 1){
                first = boy;
                first.setNext(first);//构建环状
            } else {
            //情况二:当前链表中有元素
                curBoy.setNext(boy);
                boy.setNext(first);
            }
            curBoy = boy;
        }
    }
}

约瑟夫核心问题

因为约瑟夫的人数一共是39+约瑟夫+它的朋友,一共是41个,人数太多画图很复杂,所以以5人为例进行研究。

  • 1.首先准备五个人,寻找一个开始节点 (这里我们就以三国蜀国五虎上将之首关羽作为第一个节点)

在这里插入图片描述

  • 2.因为需要取出元素,而且链表是单向链表,所以必须定义一个辅助变量表示尾指针 (定义为last指针) ,否则无法获取到元素的前一个节点
    ·在这里插入图片描述

  • 3.首先是关羽喊1号,张飞喊2号,然后赵云喊3号,(喊号就是移动last和head指针,只不过,需要移动喊号的次数-1,因为指针本身就在关羽这里) 此时的head指针指向赵云,last指针指向张飞

    • 第一步:赵云出队列
    • 第二步:让张飞的next=赵云的next,即:last.next=head.next;
    • 第三步:继续喊号,即:last指针和head指针继续移动
      在这里插入图片描述
  • 4.依次循环,直到last.next=head.next,此时证明链表中就只有一个元素了,此时就结束了


/**
 * 创建一个环形的单项链表
 */
class CircleSingleLinkedList{
    //创建一个first节点
    private Boy first;


    /**
     * 根据用户的输入,加算出小孩出圈的顺序
     * @param startNo 表示从第几个小孩开始数数
     * @param countNum 表示数几下
     * @param nums 表示初始有多少小孩在权重
     */
    public void countBoy(int startNo , int countNum , int nums){
        //一、参数校验
        if(first == null || startNo < 1 || startNo > nums || countNum > nums){
            System.out.println("参数有误,请重新输入");
            return;
        }
        //定义一个辅助指针last,并辅助赋值,让他指向循环链表的末尾元素
        Boy last = first;
        while (last.getNext() != first){
            last = last.getNext();
        }


        //定义一个临时变量,指向首元素
        Boy head = first;
        //因此此时head是第一个元素,last是最后一个元素,所以只要让这两个元素同时移动,即可保证相对位置是head在前,last元素在其后面
        for (int i = 0; i < startNo-1; i++) {
            head = head.getNext();
            first = first.getNext();

        }


        //遍历要数的次数,直到遍历剩下一个元素为止
        while (head != last){
            for (int i = 0; i < countNum-1 ; i++) {
                head = head.getNext();
                last = last.getNext();
            }
            System.out.println("出队列的人:"+head.getNo());

            //让head元素向后移动
            head = head.getNext();
            last.setNext(head);
        }
        System.out.println("最后剩下的人:"+head.getNo());
    }
}
  • 5.编写一个测试类测试一下:
public class JosepfuDome {
    public static void main(String[] args) {
        //测试添加和遍历是否ok
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(41);
     	//第一个形参是:从那个元素开始报数
     	//第二个形参是:第几次报数的时候开始出队列
     	//第三本形参是:一共有多少人
        circleSingleLinkedList.countBoy(1,3,41);
    }
}

D:\JAVA\jdk-8u131-windows-x64\bin\java.exe -javaagent:D:\InteIIiJ_IDEA\IntelliJ_IDEA_2019.2.3\lib\idea_rt.jar=57698:D:\InteIIiJ_IDEA\IntelliJ_IDEA_2019.2.3\bin -Dfile.encoding=UTF-8 -classpath D:\JAVA\jdk-8u131-windows-x64\jre\lib\charsets.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\deploy.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\access-bridge-64.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\cldrdata.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\dnsns.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\jaccess.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\jfxrt.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\localedata.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\nashorn.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\sunec.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\sunjce_provider.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\sunmscapi.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\sunpkcs11.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\ext\zipfs.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\javaws.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\jce.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\jfr.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\jfxswt.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\jsse.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\management-agent.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\plugin.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\resources.jar;D:\JAVA\jdk-8u131-windows-x64\jre\lib\rt.jar;G:\java_study\DataStructuresArithmetic\out\production\DataStructuresArithmetic com.atguigu.linkedlist.JosepfuDome
打印出来的孩子是:3
打印出来的孩子是:6
打印出来的孩子是:9
打印出来的孩子是:12
打印出来的孩子是:15
打印出来的孩子是:18
打印出来的孩子是:21
打印出来的孩子是:24
打印出来的孩子是:27
打印出来的孩子是:30
打印出来的孩子是:33
打印出来的孩子是:36
打印出来的孩子是:39
打印出来的孩子是:1
打印出来的孩子是:5
打印出来的孩子是:10
打印出来的孩子是:14
打印出来的孩子是:19
打印出来的孩子是:23
打印出来的孩子是:28
打印出来的孩子是:32
打印出来的孩子是:37
打印出来的孩子是:41
打印出来的孩子是:7
打印出来的孩子是:13
打印出来的孩子是:20
打印出来的孩子是:26
打印出来的孩子是:34
打印出来的孩子是:40
打印出来的孩子是:8
打印出来的孩子是:17
打印出来的孩子是:29
打印出来的孩子是:38
打印出来的孩子是:11
打印出来的孩子是:25
打印出来的孩子是:2
打印出来的孩子是:22
打印出来的孩子是:4
打印出来的孩子是:35
打印出来的孩子是:16
最后留在圈中的小孩是:31

Process finished with exit code 0

我们看到最后的结果的确是:剩下16和31,所以最后站在16和31号的约瑟夫和它的朋友存活了下来。

怎么样?要不要来个可乐庆祝一下,又学废了。。。
在这里插入图片描述

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
循环链表是一种特殊的链表,它的最后一个节点指向第一个节点,形成一个环。使用循环链表可以很方便地实现约瑟夫环问题。 具体实现步骤如下: 1. 定义一个节点结构体,包含一个数据域和一个指向下一个节点的指针域。 2. 构建循环链表,将每个节点连接起来形成一个环。 3. 定义一个计数器,从指定位置开始遍历链表,每遍历到一个节点就将计数器加1,当计数器的值等于指定的数n时,将该节点链表中删除。 4. 重复步骤3,直到链表中只剩下一个节点为止。 下面是一个C语言实现的例子: ```c #include <stdio.h> #include <stdlib.h> // 定义节点结构体 typedef struct node { int data; struct node *next; } Node; // 构建循环链表 Node *buildList(int n) { Node *head = NULL, *tail = NULL; for (int i = 1; i <= n; i++) { Node *newNode = (Node *)malloc(sizeof(Node)); newNode->data = i; newNode->next = NULL; if (head == NULL) { head = newNode; tail = newNode; } else { tail->next = newNode; tail = newNode; } } tail->next = head; // 将最后一个节点指向头节点,形成循环链表 return head; } // 删除节点 Node *deleteNode(Node *head, int n, int m) { Node *p = head, *pre = NULL; int count = 0; while (p->next != p) { // 当链表中只剩下一个节点时结束循环 count++; if (count == m) { // 找到要删除的节点 printf("%d ", p->data); // 输出该节点的值 pre->next = p->next; // 将该节点链表中删除 Node *temp = p; p = p->next; free(temp); count = 0; } else { pre = p; p = p->next; } } printf("%d\n", p->data); // 输出最后剩下的节点的值 free(p); return NULL; } int main() { int n, m; printf("请输入总人数n和报数m:"); scanf("%d %d", &n, &m); Node *head = buildList(n); printf("出列顺序为:"); head = deleteNode(head, n, m); return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值