约瑟夫环来自于一个故事,故事的大概就是几个人围成一圈从1开始数数,每数到一个数字(比如是3),那么这个人出列,从下一个人开始重新数数,下一个数到3的人出列,依次循环下去,直到所有人出列。
约瑟夫环的实现可以采用数组、环形链表、容器类解决,环形链表需要创建结点对象,容器类需要维护容器类,最佳实现方法就是使用数组,当然,使用数组的时候就不能和链表或者容器类一样真实删除(因为真是删除要移动元素形成新的数组),而是模拟删除。下面是两种实现方法。
使用数组实现约瑟夫环
给数组全部赋1,报数的方法是加上该位置的数值,也就是每个位置加1,当该位置删除之时赋0,,也就是该位置在数组中被遍历到了但是不进行报数,实现了模拟删除。
import java.util.Scanner;
/**
* 约瑟夫环
* @author XY
*/
public class Joseph {
public static void main(String[] args) {
// Scanner scanner=new Scanner(System.in);
// int cut=scanner.nextInt();//删除阈值
// int N=scanner.nextInt();//数组的大小
long time=System.currentTimeMillis();
int cut=4;
int N=10;//模拟输入值
int[] num=new int[N];
for (int i = 0; i < num.length; i++) {
num[i]=1;//置初始值为1
}
StringBuffer sb=new StringBuffer();
int res=N;//剩余元素的数量
int point=0;//遍历指标
int flag=0;//删除指标
while(res>0){
if(point==N) point=0;//实现数组首尾连接
flag+=num[point];//删除指标加1或者0
if(flag==cut){//达到删除阈值
sb.append(point+1+" ");
num[point]=0;//置0不增加删除指标
flag=0;
res--;
}
point++;
}
System.out.println((System.currentTimeMillis()-time)/1000.0);//运行时间
System.out.println(sb);
}
}
使用数组模拟删除操作,降低时间复杂度
上面的方法中,使用置0操作使得删除元素不参与报数,但是在数组中是被访问了,也就是说如果一个长度为1000的数组最后只剩下了2个位置参与报数的时候剩下的998个位置虽然不参与报数,但是程序还是访问了其他的998个位置,这就造成了很大的浪费。下面的方法就是避免这种不必要的数组访问,降低时间复杂度,在本文的程序中都设置了计时,在数组很小的时候分不出区别,但是在数组在1000000这个级别的时候区别就很明显。
避免无用的数组访问的方法也很简单,首先赋值如下:
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 0 |
在遍历的时候前驱结点pre=now(现结点),now=num[now]。因为上述遍历方式,现结点是通过上一个结点来访问的,那么删除操作就是num[pre]=num[now],这样就删除了now结点,下一次访问的时候通过pre直接到now的下一个结点,例如num[3]=num[4],下一次now=3的时候now=num[now]=5,不访问num[4],实现了删除操作。
import java.util.Scanner;
/**
* 使用数组实现的约瑟夫环,设置模拟删除,降低时间复杂度
* @author XY
*
*/
public class JosephB {
public static void main(String[] args) {
// Scanner scan=new Scanner(System.in);
// int N=scan.nextInt();
// int cut=scan.nextInt();
long time=System.currentTimeMillis();
int N=10;
int cut=4;//模拟输入值
int[] num=new int[N];
for (int i = 0; i < num.length; i++) {
num[i]=i+1;//存入的内容不同
}
num[N-1]=0;//循环
StringBuffer sb=new StringBuffer();
int pre=N-1,now=0,flag=0,res=N;//前驱结点,现结点,删除标,剩余长度
while(res>0){
++flag;
if(flag==cut){
num[pre]=num[now];//删除操作
flag=0;res--;
sb.append(now+1+" ");
}
pre=now;
now=num[now];//遍历
}
System.out.println((System.currentTimeMillis()-time)/1000.0);
System.out.println(sb);
}
}