下面两个博客的内容给了我很大的帮助,基本上都是参考他们的内容。
http://blog.csdn.net/u013850478/article/details/23037423
http://blog.sina.com.cn/s/blog_3cb8b94501017lpr.html
首先约瑟夫的基本问题里,可以推导出递推公式,有一个递推方法比较详细,忘了在哪个博客上看到的,我只是复述一遍,过程如下图:
有了递推公式,解决基本问题(最后一个人的编号,过程不需要输出)就是轻而易举的事。
下面代码包括几个方法,分别是:
解决基本问题,O(n)时间复杂度,编号从0开始;
解决基本问题,O(n)时间复杂度,编号从1开始;
解决基本问题,O(NM)时间复杂度,编号从0开始,采用环模拟报数的过程,不过这种方法可以输出依次出队的编号(我有个疑问是,如果要依次输出出队编号的话,还有什么更有效的方法);
扩展问题1,当首次报到m出队和之后报到k出队的m和k不同时;
扩展问题2,第P个出队的编号是多少;
扩展问题3,结合扩展问题1和扩展问题2的情况;
扩展问题4,当M=2时,这种特殊情况下,复杂度可降到O(logN),不过我的代码用了递归,没有直接用迭代,不知道用迭代怎么把这个问题写出来;
1 package Source; 2 class circleNode{ 3 int data; 4 circleNode next; 5 public circleNode(int _data, circleNode n){ 6 data = _data; 7 next = n; 8 } 9 } 10 /** 11 * 从0开始编号的地推公式:f(n,m) = [f(n-1,m)+m]%n ; f(1,m) = 0; 12 * 从1开始编号的递推公式:f(n,m) = [f(n-1,m)+m -1]%n +1; f(1,m) = 1; 13 * @author Echo 14 * 15 */ 16 public class CircleNM { 17 /** 18 * 约瑟夫问题的基本问题:n个人围成一圈,从0开始编号到n-1,从1报数到m,数到m的出队,然后从下一个开始数1,报到m的输出队列, 19 * 求最后一个人的编号。 20 * 这里采用数学方式f(n,m) = [f(n-1,m)+m]%n O(n) 21 * 如果编号是从1开始,则最后输出的时候加1 22 * 采用递推的方法解决 23 * @param n 24 * @param m 25 * @return 26 */ 27 public static int firstMathMethod(int n,int m) 28 { 29 if(n<=0 || m<=0) 30 return -1;//输入参数不对 31 int lastVal = 0; 32 for(int i=2;i<=n;i++) 33 lastVal = (lastVal+m)%i; 34 return lastVal; 35 } 36 public static int firstMathMethod2(int n,int m) 37 { 38 if(n<=0 || m<=0) 39 return -1;//输入参数不对 40 int lastVal = 1; 41 for(int i=2;i<=n;i++) 42 lastVal = (lastVal+m-1)%i +1; 43 return lastVal; 44 } 45 /** 46 * 问题同上,但是用链表 47 * 这种方法可以输出依次出队的编号,目前我不知道除了模拟方法,还有什么更有效率的方法可以依次输出这个顺序?? 48 * 采用环模拟删除过程 O(nm) 49 * @param n 50 * @param m 51 * @return 52 */ 53 public static int secondLinkedListMethod(int n, int m) 54 { 55 //创建环形 56 circleNode head = new circleNode(0,null); 57 circleNode pointer =head; 58 for(int i=1;i<n;i++) 59 { 60 pointer.next = new circleNode(i,null); 61 pointer = pointer.next; 62 } 63 pointer.next = head; 64 //模拟删除过程 65 pointer = head; 66 int index = 1; 67 while(pointer.next!=pointer) 68 { 69 if(index == m-1) 70 { 71 pointer.next = pointer.next.next; 72 pointer = pointer.next; 73 index = 1; 74 }else 75 { 76 index++; 77 pointer = pointer.next; 78 } 79 } 80 return pointer.data; 81 82 } 83 84 /** 85 * 这算是约瑟夫问题的一个扩展:n 个人按顺时针围成一圈从1开始按顺序顺序编号,首先先把第m号的人出队, 86 * 然后从m+1号开始按1、2、3 、....、k按顺时针报数,报 k 者退出圈外, 87 * 其余的人再从1、2、3 、....、k 报数,报 k 的人再退出圈外,依次类推。请输出最后一个人的原序号。 88 * 其实整个思维过程和基本问题是一制的。 89 * @param n 如果编号从1开始,则 最后输出的数+1 即可 90 * @param k 91 * @param m 92 * @return 93 */ 94 public static int genericNMK(int n, int k, int m) 95 { 96 if(n<=0 || m<=0) 97 return -1;//输入参数不对 98 int lastValue = 0; 99 for(int i=2;i<n;i++) 100 lastValue = (lastValue + k)%i; 101 return (lastValue +m)%n; 102 } 103 142 /** 143 * 扩展问题2:第P轮出圈的编号,这个r初始的时候为什么是0,我不懂??? 144 * 这里是从1开始编号 145 * @param n 146 * @param m 147 * @param p 148 * @return 149 */ 150 public static int NMP(int n,int m, int p) 151 { 152 int r = 0; 153 for(int i=n-p+1;i<=n;i++) 154 { 155 r = (r+m-1)%i +1; 156 } 157 return r; 158 } 159 /** 160 * 扩展问题3,首次出队的报道数和之后的不同 161 * @param n 从1开始编号到n 162 * @param m 除了首次外都按照 163 * @param k 首次出队的编号是报道k的 164 * @param p 第p个出圈 165 * @return 166 */ 167 public static int NMKP(int n,int m,int k, int p) 168 { 169 int r = 0; 170 for(int i=n-p+1;i<n;i++) 171 { 172 r = (r+m-1)%i +1; 173 } 174 return (r+k-1)%n+1; 175 } 176 /** 177 * 一种特殊情况下,即m=2时,可以得到O(logN)的算法 178 * 可以再想下直接迭代怎么做?? 179 * 采用递归的方式解决上面的特殊问题 180 * @param n 181 * @return 182 */ 183 public static int N2recu(int n) 184 { 185 if(n<=0) 186 return -1; 187 if(n == 1) 188 return 1; 189 if(n%2 == 0) 190 return 2*N2recu(n/2)-1; 191 else 192 return 2*N2recu(n/2)+1; 193 } 194 /** 195 * @param args 196 */ 197 public static void main(String[] args) { 198 // TODO Auto-generated method stub 199 //System.out.println(firstMathMethod(10,11)); 200 //System.out.println(firstMathMethod2(10,11)); 201 //System.out.println(secondLinkedListMethod(8,3)); 202 203 System.out.println(N2recu(11)); 204 } 205 206 }