约瑟夫环问题的算法优化——学习笔记

关于约瑟夫环这个问题,我前一篇文章给出的算法,时间复杂度已经达到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的情况。没关系,算法这玩意,也不是一时半会就能优化的。继续努力~~

猜你喜欢

转载自blog.csdn.net/jjwwwww/article/details/81023865