摘 要
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
本次课程设计便是在约瑟夫生死环问题的背景下,在一定的条件限制下(第九个人开始、数到的第十五个人结束)完成的。实现的主要方式是使用单向循环链表,再通过创建辅助指针进行链表的遍历和删除链结点完成出队操作。
关键词:约瑟夫生死环 单向循环链表 辅助指针 链结点的删除
第一章 绪 论
1.1 课设主要研究问题
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
此上问题可归为约瑟夫生死环问题,约瑟夫问题,是一个计算机咳血和数学中的问题,在计算机编程的算法中,类似问题又称为约瑟夫环,又称“丢手绢问题”。
本次课设就是在约瑟夫问题基础上进行改编的:30 个旅客同乘一条船,因为严重超载,加上风大浪高,危险万 分,因此船长告诉乘客,只有将全船一半的旅客投入海中,其余人才能幸免遇难。 无奈,大家只得同意这种办法,并议定 30 个人围成一圈,由第一个人开始,依 次报数,数到第 9 人,便把他投入大海中,然后从他的下一个人数起,数到第 9 人,再将他投入大海,如此循环,直到剩下 15 个乘客为止。问哪些位置是将被扔下大海的位置?
第二章 课设实现过程
2.1 需求分析
2.1.1 存储结构
按照课程设计要求,首先确定使用的存储结构,本次课程设计选择的是单向循环链表的存储结构。
2.1.2 程序所能达成的内容
用循环链表模拟30个人围坐一圈,用链表中每一个结点代表一个人,从第一个结点开始,对该链表进行遍历,直到第9个结点,通过辅助指针删除该节点,完成出队即题目中要求的“扔下船”。重复上述过程,直至剩下15个乘客为止。
2.1.3 建立的模块及功能要求
根据课程设计要求,可将要建立的功能模块分为3部分,分别为辅助指针的创建、循环链表的创立、循环链表的遍历及删除。
其中每个模块的功能要求如下
- 辅助指针的创立:能传回结点的编号即分辨是第几个人,辅助链表的创建、遍历和删除等功能
- 循环链表的创建:首先创建第一个结点,并形成环形,再借助辅助指针和辅助结点完成循环链表的创建
- 循环链表的遍历及删除:借助辅助指针完成循环链表的遍历和删除
2.2概要分析
为了实现上述的功能要求,应用循环链表来模拟整个过程。
- 本次课程设计使用的是借助辅助指针完成循环链表的创建,首先先创一个boy类作为辅助指针,其中包括getNo()、setNo()、getNext()、setNext()方法,来分辨结点位于链表中的位置(即分辨这个人是第几个人)、实现链表的遍历以此完成链表的创建。
2、创建一个循环链表,采取的方法是先创建第一个节点,让first指向该节点,并形成环形;后面每当我们创建一个新的节点,就借助辅助指针,将该节点加入到已有的环形链表中。
3、循环链表的删除(即题目中的将选中的人扔下船)主要用first和helper两个指针完成,其中first指针在链表创建时创建,指向链表中的第一个节点,helper指针指向最后一个节点,当报数时,让first和helper指针通过while语句同时移动8次,就可通过辅助指针的操作完成出圈的操作。
第三章 详细设计
3.1详细设计
一、辅助指针类的创建
class Boy{
private int no;
private Boy next;
public Boy(int no){
this.no=no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
二、创建循环链表
class CircleSingleLinkedList{
private Boy first=null;
//添加小孩节点,构成环形链表
public void addBoy(int nums) {
//nums做一个数据检验
if (nums < 1) {
System.out.println("nums的值不正确");
return;
}
Boy curBoy = null;//辅助指针,构建环形链表
//使用for循环创建链表
for (int i = 1; i <= nums; i++) {
//根据编号,创建小孩节点
Boy boy = new Boy(i);
//如果是第一个小孩
if (i == 1) {
first = boy;
first.setNext(first);//构成环
curBoy = first;//让curBody指向第一个小孩
} else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
//遍历当前的环形链表
public void showBoy(){
//判断链表是否为空
if(first==null) {
System.out.println("没有任何小孩");
return;
}
//因为first不能动,因此我们仍然使用一个辅助指针完成遍历
Boy curBoy=first;
while (true){
//System.out.printf("小孩的编号%d/n",curBoy.getNo());
if (curBoy.getNext()==first) {
//说明已经遍历完毕
break;
}
curBoy=curBoy.getNext();//curBoy后移
}
}
三、创建循环链表
public void countBoy(int startNo,int countNum,int nums){
//对数据进行检验
if(first==null||startNo<1||startNo>nums) {
System.out.println("参数有误,请重新输入");
return;
}
//创建辅助指针帮助小孩出圈
Boy helper=first;
//需求创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点
while (true) {
if (helper.getNext() == first) {//说明helper指向最后小孩节点
break;
}
helper = helper.getNext();
}
//小孩报数前,先让first和helper移动k-1次
for(int j=0;j<startNo-1;j++) {
first = first.getNext();
helper = helper.getNext();
}
//当小孩报数时,让first 和 helper 指针同时 的移动 m - 1 次, 然后出圈
//这里是一个循环操作,知道圈中只有一个节点
while (true) {
if (helper == first) {//说明中只有一个节点
break;
}
//
//当小孩报数时,让first 和 helper 指针同时 的移动 m - 1 次, 然后出圈
//这里是一个循环操作,知道圈中只有一个节点
for (int j = 0; j < countNum - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
//这时first指向的节点,就是要出圈的小孩节点
System.out.printf("小孩%d出圈\n", first.getNo());
first = first.getNext();
helper.setNext(first);
}
程序代码
//整体思路
//先创建环形链表
//出队列
public class Josepfu {
public static void main(String args[]){
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(30);// 加入30个小孩节点
circleSingleLinkedList.showBoy();
circleSingleLinkedList.countBoy(1, 9, 30); //
}
}
//创建环形链表
class CircleSingleLinkedList{
//创建first节点,当前没有编号
private Boy first=null;
//添加小孩节点,构成环形链表
public void addBoy(int nums) {
//nums做一个数据检验
if (nums < 1) {
System.out.println("nums的值不正确");
return;
}
Boy curBoy = null;//辅助指针,构建环形链表
//使用for循环创建链表
for (int i = 1; i <= nums; i++) {
//根据编号,创建小孩节点
Boy boy = new Boy(i);
//如果是第一个小孩
if (i == 1) {
first = boy;
first.setNext(first);//构成环
curBoy = first;//让curBody指向第一个小孩
} else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
//遍历当前的环形链表
public void showBoy(){
//判断链表是否为空
if(first==null) {
System.out.println("没有任何小孩");
return;
}
//因为first不能动,因此我们仍然使用一个辅助指针完成遍历
Boy curBoy=first;
while (true){
//System.out.printf("小孩的编号%d/n",curBoy.getNo());
if (curBoy.getNext()==first) {
//说明已经遍历完毕
break;
}
curBoy=curBoy.getNext();//curBoy后移
}
}
//根据用户的输入,计算出小孩出圈的顺序
public void countBoy(int startNo,int countNum,int nums){
//对数据进行检验
if(first==null||startNo<1||startNo>nums) {
System.out.println("参数有误,请重新输入");
return;
}
//创建辅助指针帮助小孩出圈
Boy helper=first;
int sum=0;
//需求创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点
while (true) {
if (helper.getNext() == first) {//说明helper指向最后小孩节点
break;
}
helper = helper.getNext();
}
//小孩报数前,先让first和helper移动k-1次
for(int j=0;j<startNo-1;j++) {
first = first.getNext();
helper = helper.getNext();
}
//当小孩报数时,让first 和 helper 指针同时 的移动 m - 1 次, 然后出圈
//这里是一个循环操作,知道圈中只有一个节点
while (true) {
if (helper == first) {//说明中只有一个节点
break;
}
//
//当小孩报数时,让first 和 helper 指针同时 的移动 m - 1 次, 然后出圈
//这里是一个循环操作,知道圈中只有一个节点
for (int j = 0; j < countNum - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
sum++;
//这时first指向的节点,就是要出圈的小孩节点
if(sum<16) {
System.out.printf("小孩%d出圈\n", first.getNo());
first = first.getNext();
helper.setNext(first);
}
}
}
}
class Boy{
private int no;
private Boy next;
public Boy(int no){
this.no=no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}