关于约瑟夫环这个问题,我前一篇文章给出的算法,时间复杂度已经达到O[n],已经是一个相当不错的算法了。
然而我在网上看到Tank_long网友的博文,他在O[n]的基础上把时间复杂度又进一步下降了,而且在某些条件下极大的降低了复杂度。
虽然不知道是不是这位网友原创的算法,但是这么优秀的算法,我们又岂能错过呢?那么下面就听我缓缓道来~
我就在上一篇文章的基础之上继续往下扩展(忘记了的朋友可以回头看一下哈,链接)
这个优化算法的出发点呢,是基于当 k 小于 n 的时候,就会有这样一个情况:
0,1,...,k-1,k,...,2k-1,2k,...,3k-1,3k,...,n-1
在这个数列一遍遍历完后,我们发现已经从中剔除了 n/k 个数据,假如我们能够找到 F[n] 与 F[n-n/k] 之间的关系,那就能够大大减少计算量了。结论当然是肯定的喽,那我们就来找找他们之间的关系。
我们先假设 m=n/k ,把 m 个数选中的数(k-1,2k-1,...,mk-1)从数列中剔除,以mk为起始点重新排成长度为 n-m 的数列,如下:
mk,...,n-1,0,1,..,k,...,2k,...,mk-2
我们假设已经知道F[n-m]的值,即 F[n-m]=P 。我们只需要找到上面数列的第 P 位的值,就是我们需要的 F[n]。
可能会想到(mk+P)mod n 这个之前算法给出的计算方式,但实际上由于中间缺了(k-1,2k-1,...,(m-1)k-1)实际上 第P对应的值可能会比(mk+P)mod n 多上几位,所以正确的公式是这样的:
F[1]=0;
m=n/k;
if((F[n-m]+m*k-n)>0)
F[n]=(F[n-m]+m*k-n)/(k-1) +F[n-m]+m*k-n;
else
F[n]=F[n-m]+m*k;
直接看可能有点蒙,我稍微解释一下,由于当 P< n mod k(n mod k=n-mk)时,不会遇到缺失的几个数值,所以直接加上mk即可。
用k-1而不是k,因为每次数数去掉第k个数后剩下就是k-1个数了啊。
希望你们能理解,看不懂也可以参考Tank_long网友的博文。上代码:
int Fun(int n, int k) {
if (k < n) {
int res = Fun3(n - n / k, k);
if (res < n%k) {
res = res + n - n%k;
}
else {
res = res + (res - n%k) / (k - 1) +k*(int)(n/k)-n;
}
return res;
}
else {
return Fun2( k,n);
}
}
int Fun2(int k,int n) {
int x = 0;
for (int i = 0; i < n; i++) {
x = (x + k) % (i+1);
}
return x;
}
int x = 0;
for (int i = 0; i < n; i++) {
x = (x + k) % (i+1);
}
return x;
}
代码量很少,上面的公式也解释的很清楚了,我也不再加注释了哦。
注意一下,这个仅仅是在k<n的情况下才能用到,但也大大提高了运算速度。当k>=n时,还是按照前一篇文章的算法(也就是函数Fun2)来计算,可以自行考虑下为什么。
如果你有什么更好的方法,希望也能够分享出来,谢谢阅读~~
————————————————————————————————————————————————————
尝试对k>=n 的情况做优化,但是实际效果并不是很理想,可以大大减少时间复杂度,但是中间的过程花费的计算时间和空间占有量增大了。得不偿失。有兴趣的朋友也可以研究一下~顺便有机会也可以互相探讨一下~
————————————————————————————————————————————————————
研究k>=n 解决方案时,顺便又在网上搜罗了一番,又找到了一种优化算法。优化效果和上面给出的方法差不多,我这里简单介绍一下,具体可以参考这篇转载的文章。
实际上是对我在上面写的Fun2函数的优化。具体思路是考虑到其中的一行代码:
x = (x + k) % (i+1);
当(x+k)< (i+1) 时,x=x+k。而同时可能会存在(x+ak)< (i+a)的情况,假如我们直接能够算出a,那么就可以少循环a-1次。这样也就能进一步提高运算速度。思路类似与上面的Fun函数,这个好处在于不需要用递归来做,相对更容易理解。
通过 (x+ak)=(i+a),我们可以求出a的值:
a=(i-x)/(k-1)
注意1,k=1时,(k-1)=0,所以我们要先判断 k>1,把1的情况拿出来单独处理。
注意2,a算出来不一定是整数,我们把它转成整型即可。
注意3,i+a 可能大于 n ,这时我们需要比较一下大小,把大于n的部分去掉:
if(i+a>n){
a=n-i;
}
好了,废话不多说,上代码!
int Fun3(int n,int k) {
if (k > 1) {
int x = 0;
for (int i = 1; i < n; i++) {
if ((x + k) < (i + 1)) {
int a = (i - x) / (k - 1);
if (a + i >= n) {
a = n - i;
}
x = (x + k*a) % (i + j);
i += a - 1;
}
else {
x = (x + k) % (i + 1);
}
}
return x;
}
else {
return n-1;
}
}
实际上可以看出来,具体的思路和前面的是一样的,只是实现方式不同罢了。
嘛,,,同样没有解决k>n的情况。没关系,算法这玩意,也不是一时半会就能优化的。继续努力~~