问题描述:
Josephus问题是下面的游戏:N个人编号从1到N,围坐成一个圆圈。从1号开始传递一个热土豆。经过M次传递后拿着热土豆的人被清除离座,围坐的圆圈紧缩,由坐在被清除的人后面的人拿起热土豆继续进行游戏。最后剩下的人取胜。
提供两种方案
package com.light.springboot.algorithm; import java.util.ArrayList; import java.util.Date; import java.util.Iterator;
import org.apache.poi.ss.formula.functions.T; /** * Josephus问题是下面的游戏:N个人编号从1到N,围坐成一个圆圈。从1号开始传递一个热土豆。 * 经过M次传递后拿着热土豆的人被清除离座,围坐的圆圈紧缩,由坐在被清除的人后面的人拿起热土豆继续进行游戏。最后剩下的人取胜。 * @author qiaozhong */ public class Josephus { /** * 方案一:通过循环找list下标进行删除的方法解决约瑟夫问题 * @param person * @param num * @return */ public static String josephus(ArrayList<String> person, int num){ int i=0; while(person.size() > 1){ //循环num次,确定i for(int j=0; j < num; j++){ i++; } //当i大于当前总人数时,i减去总人数,使i小于总人数 while(i >= person.size()){ i = i - person.size(); } person.remove(i); } return person.get(0); }
/** * 泛型写法 * @param person * @param num * @return */ public static <T> T josephusGeneric(ArrayList<T> person, int num){ int i=0; while(person.size() > 1){ //循环num次,确定i for(int j=0; j < num; j++){ i++; } //当i大于当前总人数时,i减去总人数,使i小于总人数 while(i >= person.size()){ i = i - person.size(); } person.remove(i); } return person.get(0); }
/** * 方案二:通过list的迭代器iterator解决约瑟夫问题,速度更快 * @param person * @param num * @return */ public static String iteratorJosephus(ArrayList<String> person, int num){ Iterator<String> iterator = person.iterator(); int count = 0; while (person.size()>1) { //步骤1:如果到链表尾部,就重新获取迭代器,以重头开始 if(!iterator.hasNext()){ iterator = person.iterator(); } //步骤2:只要没有到链表结尾,就一直循环查下一个元素 while(iterator.hasNext() && count++ <= num){ iterator.next(); } //步骤3:上边的while结束有两种可能,一种是count循环了num次,那么count++应该大于num,此种情况该删除当前元素 //另一种是查找到了链表尾部,此种情况不删除当前元素,应该回到步骤1重新执行iterator = person.iterator()继续查找 if (count > num) { count = 0; iterator.remove(); } } return person.get(0); } //测试两种方案效率 public static void main(String args[]){ ArrayList<String> person = new ArrayList(); for (int i = 0; i < 800000; i++) { person.add(String.valueOf("第" + (i+1) + "位客人")); } long start = new Date().getTime(); System.out.println(josephus(person, 7) + "获胜!"); System.out.println("方案一消耗时间:" + (new Date().getTime() - start)); long iteratorStart = new Date().getTime(); System.out.println(iteratorJosephus(person, 7) + "获胜!"); System.out.println("方案二消耗时间:" + (new Date().getTime() - iteratorStart)); } }
测试结果:
第194525位客人获胜! 方案一消耗时间:43501 第194525位客人获胜! 方案二消耗时间:1
分析:
方案一:我们的代码设计部分,我们每次调用remove时,都重新遍历了一遍表,调用n次remove,消耗时间为O(N2)。
方案二:通过迭代器能够有效地利用List的删除效率高的特性。因为迭代器的时间是O(1),迭代器的remove时间也是O(1),整体的运行时间理论上是O(N)。