Josephus问题
约瑟夫斯问题描述如下:
设编号为1,2,3,4,…,n的n个人围坐成一圈,约定编号是k(1<=k<=n)的人从1开始报数,数到m的那个人出列,下一位又从1开始报数,数到m的那个人又出列,以此类推,直到所有人出列为止,由此产生一个出队编号的序列,请输出这个序列。
链表LinkList和顺序表SeqList
链表和顺序表都是线性表,顺序表是把线性表中的元素按照逻辑顺序依次的存放在一组地址连续的存储单元里,而链表是把线性表中的元素通过指针串在一起,链表中的相邻元素没有存储在相邻的存储单元里,换句话说就是链表元素之间具有逻辑顺序相邻而没有物理地址相邻。
具体链表和顺序表的实现请看之前的文章:
【数据结构】使用Java实现顺序表类 SeqList
【数据结构】使用Java实现链表类LinkList
个人建议先看顺序表再看链表。
Josephus问题分析
这个问题的解决关键是让数据的首尾相连,当遍历到数组尾部的时候,下一元素应该是首部。如果使用顺序表的话,在数据遍历到尾部的时候将下标重置为0。如果使用链表的话,只需要将尾部的指针指向首部即可,将单链表变成循环链表。
出队操作相对会简单一些,使用链表的情况下,仅需要将出队的元素前一个元素指针指向出队元素的后一个元素即可,使用顺序表的情况下,调用remove函数,将出队元素移除,并让后面的元素整体前移一个单位。
顺序表的终止条件应该是当顺序表为空时终止,因为随着每一次出队操作,顺序表中元素都会减少一个。链表的终止条件是当遍历指针为空时终止。
代码实现
分析了以上关键点,我们接下来就是用代码来实现以上逻辑,首先是循序表实现:
package DS;
import java.util.Scanner;
/*
* SeqList API介绍
* setIncr(int inc): 设置顺序表每次增加时增量的大小
* setCapacity(int newSize): 设置新的最大容量
* getCapacity(): 获取数组的最大容量
* size(): 获取顺序表已经存放数据的数量,同length()
* get(int i): 获取顺序表下标为i的元素值
* set(int i,T x): 修改下标值为i的元素值,即为修改data[i]
* indexOf(int begin,int end,T value): 在begin和end之间查找值为value的元素下标值,使用顺序查找法
* indexOf(T o): 在整个线性表中查找元素o的下标值
* indexOf(int begin,T o): 在下标为begin到末尾的线性表中查找元素o的下标值
* add(int i,T x): 在i位置插入数据元素x
* add(T x): 在表尾添加数据元素x
* append(T x): 在表尾添加数据元素x
* addSort(T x): 以有序方式向顺序表中插入数据元素x
* sort(): 对顺序表从小到大排序
* remove(int i): 删除下标为i的元素,并返回被删除的元素
* remove(T value): 删除值为value的元素,并返回被删除的元素
* clear(): 清除整个顺序表
* toString(): 将顺序表转化成字符串,便于直接输出顺序表
* toArray(): 将顺序表转化成Object数组,并返回
* toArray(T[] a): 将顺序表转换为类型为E的数组
* iterator(): 返回一个迭代对象
* length(): 获取线性表长度
* isEmpty(): 判断线性表是否为空
*/
public class Josephus_Seq {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n, k, m;
System.out.print("请输入n,k,m的值:");
n = scan.nextInt();
k = scan.nextInt();
m = scan.nextInt();
// 定义一个顺序表代表这组人,1表示这个人还未出队,最初都是未出队状态
SeqList<Integer> seqList = new SeqList<Integer>();
for (int i = 0; i < n; i++)
seqList.add(i);
// 因为顺序表是从0开始到n-1的,而题之中给的的k是从1到n的,所以将k-1付给k
k--;
int flag = 0, i = k;
while (!seqList.isEmpty()) {
flag++;
if (flag == m) {
//如果要将当前元素移除,i的值就不用变,因为移除后seqList中元素整体向前平移一个单位
System.out.print((seqList.get(i) + 1) + " ");//输出出队编号,从1到n
seqList.remove(i);
if(seqList.isEmpty())
break;
flag = 0;
}
else {
//如果这个元素不动,则需要i++来访问下一个元素
i++;
}
if (i >= seqList.length)
i = 0;
}
}
}
链表实现:
package DS;
import java.util.Scanner;
/*
* LinkList API介绍
*
* LinkList(): 构造函数
* clear(): 删除整个列表
* removeAll(): 删除整个列表,调用clear函数来实现
* getNode(int i): 获得逻辑上i号节点
* get(int i): 获得逻辑上i号节点的值
* set(int i,T x): 修改i号节点的值
* add(int i,T x): 将值为x的节点插入到i号位置
* add(T key): 在链表尾部插入元素key
* addBack(T key): 在链表尾部插入元素key,和add(T key)函数的作用一样
* addFront(T key): 在链表首部插入元素key
* remove(int i): 删除i号节点,并返回i号节点对应的值
* remove(): 重载remove,删除头节点
* removeFront(): 删除链表头节点,与remove()函数同义
* removeBack(): 删除链表尾节点
* addSort(T value): 将值value按从小到大的排序方式插入链表
* sort(): 对链表按照从小到大的顺序进行排序
* indexOf(int begin,int end,T key): 在索引begin和end之间查找值key,返回逻辑编号
* search(T key): 功能同indexOf,遍历整个链表,一般不使用,主要用于实现字典
* contains(T key): 判断链表中是否存在值为key节点
* toString(): 将链表中的值转换成字符串
* toArray(): 将链表转换成Object数组
* toArray(E[] a): 将单链表转化为特定类型的数组,使用了函数泛型
* iterator(): 返回迭代器对象
*/
public class Josephus_Link {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n, k, m;
System.out.print("请输入n,k,m的值:");
n = scan.nextInt();
k = scan.nextInt();
m = scan.nextInt();
// 实例化LinkList对象来存储队伍信息
LinkList<Integer> linkList = new LinkList<Integer>();
// 初始化链表
for (int i = 0; i < n; i++) {
linkList.add(i);
}
// 将链表首位相连
linkList.last.next = linkList.first;
k--;
int flag = 0;
Lnode<Integer> pLnode = linkList.getNode(k);
Lnode<Integer> qLnode = linkList.getNode(0);
// System.out.print(linkList.first.data);
while (pLnode != null) {
flag++;
if (flag == m) {
System.out.print((pLnode.data + 1) + " ");
// 获取pLnode前一个节点qLnode
while (qLnode.next != pLnode) {
qLnode = qLnode.next;
}
qLnode.next = pLnode.next;
pLnode.next = null;
pLnode = qLnode.next;
flag = 0;
} else {
pLnode = pLnode.next;
}
}
}
}