约瑟夫环问题
- 有1~n个人围坐成一个圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出圈,出圈的那个人的后一个人又重新从1开始报数,下一次数到m的那个人出圈,以此类推,直到所有的人都出圈为止,由此产生一个出圈的编号序列。
问题解决
思路
- 用不带头节点的单向链表处理,构造一个有n个节点的单链表,然后让第n个节点的next指向第一个节点,从而构成一个单向循环链表。然后由第k个节点起从1开始计数,数到m时,对应的节点从链表中删除,然后再从被删除节点的下一个节点又开始从1计数,直到最后一个节点从链表中删除。
- 创建环形单链表需要辅助变量first和current,创建环形单链表时,first恒定指向第一个节点,用于帮助最后一个节点指向first,以形成环,current则指向环的最后一个节点随着新节点插入环,current则指向新节点,保持着始终指向环形单链表的最后一个节点的状态。
- 如创建好第一个节点时,就通过first让其next指向自己
- 创建第2个节点时,通过current令第一个节点的next指向第2个节点,然后令current指向第2个节点,通过current令第2个节点的next指向第一个节点,这样就完成了第2个节点的插入,并构成环。
- 后续的节点插入同理,直到插入完要求的节点数量。
- 节点出圈除了辅助变量first,还要创建一个辅助变量helper帮助节点出圈。
- 准备工作 helper在开始出圈前就指向环形单链表的最后一个节点,然后在报数出圈之前,先让first和helper移动k-1次,以便让first和helper分别指向目标节点和目标节点的前一个结点。
- 当开始报数时,就让first和helper同时移动m-1次,此时first指向的节点就是要出圈的节点,通过first得到当前节点的下一个节点,然后令first指向该节点,然后通过helper令出圈节点的前一个位置的节点的next指向first,这样就让出圈节点脱离了环形单链表,该出圈节点因为没有引用会被垃圾回收机制回收。
- 图示(以n=5,k=1,m=2的环形链表举例,展示出圈一个节点的步骤)
代码
public class Node {
private int no;
private String data;
private Node next;
public Node(int no,String data) {
this.no=no;
this.data=data;
}
public Node() {
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public String toString() {
return "no:"+no+" data:"+data;
}
}
public class Josephu {
private Node first=null;//永远指向第一个节点,用于标识第一个节点,不能移动
public void createCircle(int num) {
if(num<2) {
System.out.println("num请大于等于2");
return;
}
Node current=null;//设置辅助变量用于增加节点,可移动
for(int i=1;i<=num;i++) {
Node node =new Node(i, ""+i);
if(i==1) {//如果i==1说明是第一个节点,令辅助变量first指向该节点,并设置其下一个节点为其本身,形成环
first=node;
first.setNext(first);
current=first;//令辅助变量current指向第一个节点
//此时的环形单链表只有一个节点,因此自己指向自己
//而current永远处于指向环形链表的最后一个节点,所以current指向第一个节点
}else {
current.setNext(node);//让当前节点的next指向新节点
node.setNext(first);//让新节点的next指向第一个节点,形成环
current=node;//令current变量指向当前最新的节点
}
}
}
public void show() {
Node current=null;
if(first==null) {
System.out.println("链表为空");
return;
}
while(true) {
if(current==null) {//判断是否从第一个节点开始遍历
current=first;//是,则令current指向第一个节点
}
System.out.println(current);//输出每个current目前指向的节点
if(current.getNext()==first) {//如果当前节点的next指向了第一个节点,说明环形单链表到头了
break;//跳出循环
}
current=current.getNext();//继续向下遍历
}
}
public void count(int startNum,int countNum,int nodeNum) {
if(first==null) {
System.out.println("链表为空");
}
if(startNum<1||startNum>nodeNum) {
System.out.println("参数错误请重新输入");
}
Node helper=first;
while(true) {//让helper指向环形链表的最后一个节点
if(helper.getNext()==first) {
break;
}
helper=helper.getNext();
}
//让first和helper移动startNum-1次
for(int i=0;i<startNum-1;i++) {
first=first.getNext();
helper=helper.getNext();
}
while(true) {
for(int i=0;i<countNum-1;i++) {//让helper和first移动countNum-1次
first=first.getNext();
helper=helper.getNext();
}
System.out.printf("出圈的节点是%d \n",first.getNo());
//输出出圈节点的序号
first=first.getNext();//令first指向出圈节点的下一个位置的节点
helper.setNext(first);
//通过helper令出圈节点的前一个位置的节点的next指向
//出圈节点的下一个位置的节点
if(first==helper) {
break;
}
//如果first和helper指向同一个节点,说明环形单链表只剩下一个节点了
}
System.out.printf("最后一个节点是%d\n",first.getNo());
}
public static void main(String[] args) {
Josephu josephu=new Josephu();
josephu.createCircle(5);
josephu.show();
josephu.count(1, 2, 5);
}
}
运行结果
no:1 data:1
no:2 data:2
no:3 data:3
no:4 data:4
no:5 data:5
出圈的节点是2
出圈的节点是4
出圈的节点是1
出圈的节点是5
最后一个节点是3