最近刚学完环形单向链表,并用来解决了算法中的经典问题-——约瑟夫环问题,下面来对环形单向链表及其解决约瑟夫环问题的一些要点做一个小结
1.单项环形链表的简介
单向环形链表和单链表大体基本相似,他的不同就在于他是首尾相连的单链表,也就是说它是将单链表的最后一个节点的next指向了第一个节点或者头节点,(ps:如果不清楚什么是单链表请参考我的上一篇博客)如图所示:
2.约瑟夫问题
Josephu问题为:设编号为 1,2,… n的 n个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1开始报数,数到
m的那个人出列,它的下一位又从 1开始报数,数到 m的那个人又出列,依次类推,直到所有人出列为止,由此
产生一个出队编号的序列。
3.关于约瑟夫环的思路
用一个不带头结点的循环链表来处理 Josephu问题:先构成一个有 n个结点的单循环链表,然后由 k结点起从 1开
始计数,计到 m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从 1开始计数,直到最后一个
4.代码实现
首先我们得创建一个小孩的类
package circlelinkedlist;
public class JosefuBoy {
private int boynum;//小孩编号
private JosefuBoy next;
public JosefuBoy(int num) {
this.boynum=num;
}
public int getBoynum() {
return boynum;
}
public void setBoynum(int boynum) {
this.boynum = boynum;
}
public JosefuBoy getNext() {
return next;
}
public void setNext(JosefuBoy next) {
this.next = next;
}
}
接着我们来编写环形单向链表,首先给出first节点
package circlelinkedlist;
//创建环形单向链表
public class Doublelinkedlist {
//first节点
JosefuBoy first=new JosefuBoy(-1);
}
向单项环形链表添加结点,这里必须要注意,每添加一个新的结点都要讲新结点的next指向first这样才能构成环形链表
//添加节点,构成环形链表,num为要添加的节点数量
public void add(int num) {
if(num<1) {
System.out.println("添加的值不正确");
return;
}
//辅助节点
JosefuBoy temp=null;
//for循环创建链表
for(int i=1;i<=num;i++) {
//根据编号创建节点
JosefuBoy boy=new JosefuBoy(i);
//第一个节点
if(i==1) {
first=boy;
first.setNext(first);
temp=first;
}else {
temp.setNext(boy);
boy.setNext(first);
temp=boy;
}
}
}
接下来关键来了,出圈,怎么样出圈呢,这时我们需要把辅助结点temp放在尾节点上,因此我们在出圈前要先遍历一遍链表把temp放在尾节点,接着根据用户输入的从第几个小孩开始报数startnum,我们将first和temp同时向后移startnum-1个节点,这个时候temp节点应该是紧紧的跟着first节点,而first节点已经指向了开始的小孩节点,然后根据输入的要数多少次countnum,然后将first节点和temp向后移countnum-1个节点(为什么要移countnum-1次,因为节点本身要报一次数,所以要减去自己)这时,first指向了要出圈的小孩,此时,我们只需要将first指向出圈小孩的下一个节点,再让temp指向first(也就是说让出圈小孩的上一个节点指向出圈小孩的下一个节点)则完成出圈,下面上代码
//小孩出圈
/**
*
* @param startnum 表示从第几个孩子开始数数
* @param countnum 表示数多少个
* @param boynum 表示圈内最初有多少个孩子
*/
public void count(int startnum,int countnum,int boynum) {
//参数校验
if(startnum>boynum||startnum<1||first==null) {
System.out.println("参数输入有误");
}
//创建辅助节点temp并指向最后一个节点
JosefuBoy temp =first;
while(true) {
if(temp.getNext()==first) {
break;
}
temp=temp.getNext();
}
//报数前先将first和temp移动到用户输入的位置上
for(int i=0;i<startnum-1;i++) {
first=first.getNext();
temp=temp.getNext();
}
//报数时,让first和temp移动countnum-1次,然后出圈
//循环操作直至最后一个人
while(true) {
if(first==temp) {//只有一个人
break;
}
for(int j=0;j<countnum-1;j++) {
first=first.getNext();
temp=temp.getNext();
}
//此时,first指向的就是要出圈的小孩
System.out.println("小孩"+first.getBoynum()+"出圈了");
first=first.getNext();
temp.setNext(first);
}
System.out.println("最后留下的小孩编号是"+first.getBoynum());
}
测试代码
package circlelinkedlist;
public class Josefudemo {
public static void main(String[] args) {
Doublelinkedlist boy=new Doublelinkedlist();
boy.add(5);
boy.count(1, 2, 5);
}
}
测试结果