约瑟夫环以及约瑟夫生死者游戏的C/Java代码实现

简介: 约瑟夫环以及约瑟夫生死者游戏的C/Java代码实现

前言

约瑟夫环问题,是一个经典的循环链表问题,题意是:已知 n 个人(分别用编号 1,2,3,…,n 表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 开始,还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,直到圆桌上剩余一个人。

如图所示,假设此时圆周周围有 5 个人,要求从编号为 3 的人开始顺时针数数,数到 2 的那个人出列:
出列顺序依次为:
编号为 3 的人开始数 1,然后 4 数 2,所以 4 先出列;
4 出列后,从 5 开始数 1,1 数 2,所以 1 出列;
1 出列后,从 2 开始数 1,3 数 2,所以 3 出列;
3 出列后,从 5 开始数 1,2 数 2,所以 2 出列;
最后只剩下 5 自己,所以 5 胜出。
约瑟夫环问题有多种变形,比如顺时针转改为逆时针等,虽然问题的细节有多种变数,但解决问题的中心思想是一样的,即使用循环链表,而循环链表与普通链表没有较大区别,只是将尾指针指向头指针而已。

一、概念了解

由上面提出的概念可以知道,约瑟夫环问题要求指定一个开始位置,从这个开始位置,顺时针或逆时针的循环读取下一个数据,且数到2的数据要求被移除,而其之后的数据接上来。因此我们需要做到:

1:构建一个链表且这个链表是首尾相连

2:若数据数到2,那么这个数据被移除

3:一个数据被删除后另一个数据要接上去

4:当链表只有一个数据时,结束

特殊情况:如果从编号1开始,且念到1的人出列,那么应该是上面情况

二、实验设计

1.链表初始化

#include <cstdio>
#include <iostream>
typedef struct Joseph
{
  int number;  //编号
  struct Joseph* next; //下一个地址
}Ring;
Ring* InitRing(int n)  //n为所需要构造的数量
{
  Ring* head,*tail,*p;  //头指针 新建指针 临时指针
  p= head = (Ring*)malloc(sizeof(Joseph)); //指针初始化
  head->number = 1; //编号1的元素
  head->next = NULL; //暂时指向空
  for (int i = 2; i <= n; i++)
  {
    tail = (Ring*)malloc(sizeof(Joseph)); //开辟新空间,存放编号
    tail->number = i; //赋予编号
    tail->next = NULL; //暂时指向空
    p->next = tail; //衔接
    p = p->next;  //p指向下一个节点,以便下一次p的next能指向新节点
  }
  p->next = head; //首尾相接
  return head;  //返回头指针
}

2.功能实现

void MoveRing(Ring* head, int start, int num)
{
  Ring* p, * tail; //tail用来指向要删除的数据的前一个位置,保证能数据删除后还能形成完整链条
  p= tail = head; 
  while (tail->next != head)//特殊处理,如果要删除的数据就是头指针指向的数据
    tail = tail->next;//那么为了保证删除该数据后还能形成一个环而作的处理
  while (p->number != start)//找到要删除的序号的位置
  {     
    tail = p;   //保证tail是要删除数据的前一位
    p = p->next;  //p为要删除的数据的位置
  }
  while (p->next != p)//最后的情况是tail->next=p->next,p=tail->next,因此最后p->next==p
  {
    for(int i = 1;i < num; i++)//达到了要删除的编号
    {
      tail = p;  //保证tail是要删除的前一位
      p = p->next;//p为要删除的数据
    }
    tail->next = p->next;//tail的下一位指向的是被删除的数据的下一位
    cout << "出列的序号是" << p->number << endl;;
    free(p);//释放被删除数据的空间
    p = tail->next; //使得p指向被删除数据的下一位
  }
  cout << "胜利者的序号是" << p->number << endl;
  free(p);//函数全部结束,直接释放还没释放的空间,防止空间浪费
}

3.完整代码

#include <cstdio>
#include <iostream>
#include <algo.h>
typedef struct Joseph
{
  int number;  //编号
  struct Joseph* next; //下一个地址
}Ring;
Ring* InitRing(int n)  //n为所需要构造的数量
{
  Ring* head,*tail,*p;  //头指针 新建指针 临时指针
  p= head = (Ring*)malloc(sizeof(Joseph)); //指针初始化
  head->number = 1; //编号1的元素
  head->next = NULL; //暂时指向空
  for (int i = 2; i <= n; i++)
  {
    tail = (Ring*)malloc(sizeof(Joseph)); //开辟新空间,存放编号
    tail->number = i; //赋予编号
    tail->next = NULL; //暂时指向空
    p->next = tail; //衔接
    p = p->next;  //p指向下一个节点,以便下一次p的next能指向新节点
  }
  p->next = head; //首尾相接
  return head;  //返回头指针
}
void MoveRing(Ring* head, int start, int num)
{
  Ring* p, * tail; //tail用来指向要删除的数据的前一个位置,保证能数据删除后还能形成完整链条
  p= tail = head; 
  while (tail->next != head)//特殊处理,如果要删除的数据就是头指针指向的数据
    tail = tail->next;//那么为了保证删除该数据后还能形成一个环而作的处理
  while (p->number != start)//找到要删除的序号的位置
  {     
    tail = p;   //保证tail是要删除数据的前一位
    p = p->next;  //p为要删除的数据的位置
  }
  while (p->next != p)//最后的情况是tail->next=p->next,p=tail->next,因此最后p->next==p
  {
    for(int i = 1;i < num; i++)//达到了要删除的编号
    {
      tail = p;  //保证tail是要删除的前一位
      p = p->next;//p为要删除的数据
    }
    tail->next = p->next;//tail的下一位指向的是被删除的数据的下一位
    cout << "出列的序号是" << p->number << endl;;
    free(p);//释放被删除数据的空间
    p = tail->next; //使得p指向被删除数据的下一位
  }
  cout << "胜利者的序号是" << p->number << endl;
  free(p);//函数全部结束,直接释放还没释放的空间,防止空间浪费
}
int main()
{
    printf("输入圆桌上的人数n:");
    int n;
   scanf("%d", &n);
    Ring* head = InitRing(n);
    printf("从第k人开始报数(k>1且k<%d):", n);
    int k;
   scanf("%d", &k);
   printf("数到m的人出列:");
    int m;
   scanf("%d", &m);
    MoveRing(head, k, m);
    return 0;
}

4.效果

三、约瑟夫生死者游戏

1.概念

30个旅客同乘一条船,因为严重超载,必须将全船一半的旅客投入海中,其余人才能幸免遇难。大家同意一种办法:30人围成一圈,由第一个人数起,依次报数,数到第9人,便把他投入大海中,然后再从他的下一个人数起,数到第9人,再将他扔进大海中,如此循环地进行,直到剩下15个乘客为止。

要求实现如下功能:

1.建立一个具有30个结点的单循环链表

2.生者与死者的选择:从位置为1的结点开始数,数到第8个结点,将其下一个结点从单循环链表中删除,然后,再从被删除结点的下一个结点开始数,数到第8个结点,再将其下一个结点删除,如此下去,直到剩下15个结点为止;

3.输出所有生者的位置。

2.解决

可以发现约瑟夫生死者游戏只不过是再约瑟夫环上增加了一些限制条件,例如:

1.最后需要剩余人数为15人

2.数到9的人下船

3.报出幸存者的编号

知道了这些,我们只需要再约瑟夫环的代码上进行一些修改就可以得到约瑟夫生死者游戏的代码

3.C实现

#include <cstdio>
#include<cstdlib>
#include<iomanip>
typedef int Status;
typedef int ElemType;
typedef char cElemType;
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define MAXSIZE 20
#define make (struct student*)malloc(sizeof(struct student));
using namespace std;
typedef struct Joseph
{
  int remain; //剩余人数
  int number;  //编号
  struct Joseph* next; //下一个地址
}Ring;
Ring* InitRing(int n)  //n为所需要构造的数量
{
  Ring* head,*tail,*p;  //头指针 新建指针 临时指针
  p= head = (Ring*)malloc(sizeof(Joseph)); //指针初始化
  head->remain = 30; //初始剩余人数30
  head->number = 1; //编号1的元素
  head->next = NULL; //暂时指向空
  for (int i = 2; i <= n; i++)
  {
    tail = (Ring*)malloc(sizeof(Joseph)); //开辟新空间,存放编号
    tail->number = i; //赋予编号
    tail->next = NULL; //暂时指向空
    p->next = tail; //衔接
    p = p->next;  //p指向下一个节点,以便下一次p的next能指向新节点
  }
  p->next = head; //首尾相接
  return head;  //返回头指针
}
void MoveRing(Ring* head, int start, int num)
{
  Ring* p, * tail; //tail用来指向要删除的数据的前一个位置,保证能数据删除后还能形成完整链条
  p= tail = head; 
  while (tail->next != head)//特殊处理,如果要删除的数据就是头指针指向的数据
    tail = tail->next;//那么为了保证删除该数据后还能形成一个环而作的处理
  while (p->number != start)//找到要删除的序号的位置
  {     
    tail = p;   //保证tail是要删除数据的前一位
    p = p->next;  //p为要删除的数据的位置
  }
  while (head->remain>15)//最后的情况是tail->next=p->next,p=tail->next,因此最后p->next==p
  {
    for(int i = 1;i < num; i++)//达到了要删除的编号
    {
      tail = p;  //保证tail是要删除的前一位
      p = p->next;//p为要删除的数据
    }
    tail->next = p->next;//tail的下一位指向的是被删除的数据的下一位
    cout << "出列的序号是" << p->number << endl;
    free(p);//释放被删除数据的空间
    p = tail->next; //使得p指向被删除数据的下一位
    head->remain -= 1; //有人被移除,剩余人数减1
  }
  for (int j = 1; j <= 15; j++)
  {
    cout << "幸存者的序号是" << p->number << endl; //输出胜利者的序号
    p = p->next; //让p指向下一个地址
  }
  free(head);//函数全部结束,直接释放还没释放的空间,防止空间浪费
}
int main()
{
    printf("输入圆桌上的人数n:");
    int n;
   scanf("%d", &n);
    Ring* head = InitRing(n);
    printf("从第k人开始报数(k>1且k<%d):", n);
    int k;
   scanf("%d", &k);
   printf("数到m的人出列:");
    int m;
   scanf("%d", &m);
    MoveRing(head, k, m);
    return 0;
}

4.Java实现

public class JosephusRing{
    /**
     *
     * @param numOfPeople 总人数
     * @param perpleOrder 出去的人叫道的号
     * @param startNum  从第几个人开始
     */
    public static void josephusRing(int numOfPeople,int perpleOrder,int startNum){
        Queue<Integer>queue=new Queue<Integer>();
        for(int i=0;i<numOfPeople;i++){ //把这几个人入队列
            queue.enqueue(i);
        }
        for(int i=0;i<startNum;i++){//找到开始的那个人的编号 把他放在队头
            queue.enqueue(queue.dequeue());
        }
        while(numOfPeople>0){
            for(int i=1;i<perpleOrder;i++){//假设叫道3的出去,那么从1开始 1 2 3 循环两次 然后队头就是要出去的
                queue.enqueue(queue.dequeue());
            }
            System.out.println("出去的人的编号是:"+queue.dequeue());
            numOfPeople--;
        }
    }
      public static void main(String[] args) {
        new JosephusRing().josephusRing(5,3,0);
    }
}



相关文章
|
2天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
16天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
26 5
Java反射机制:解锁代码的无限可能
|
12天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
42 3
|
18天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
50 10
|
13天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
12天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
20天前
|
搜索推荐 Java 数据库连接
Java|在 IDEA 里自动生成 MyBatis 模板代码
基于 MyBatis 开发的项目,新增数据库表以后,总是需要编写对应的 Entity、Mapper 和 Service 等等 Class 的代码,这些都是重复的工作,我们可以想一些办法来自动生成这些代码。
28 6
|
20天前
|
Java
通过Java代码解释成员变量(实例变量)和局部变量的区别
本文通过一个Java示例,详细解释了成员变量(实例变量)和局部变量的区别。成员变量属于类的一部分,每个对象有独立的副本;局部变量则在方法或代码块内部声明,作用范围仅限于此。示例代码展示了如何在类中声明和使用这两种变量。
|
21天前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
41 3
|
21天前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
26 1