这篇文章涉及我最喜欢的三个主题-数学,通过经验传递知识(教程单元测试)和研究的重要性。
大多数开发人员都通过面试来了解斐波那契数列 。
为了简要回顾该系列,定义了:
F( n )= F( n-1 )+ F( n-2 ),n> 2
F(1)= F(2)= 1
有一个变体定义:
F( n )= F( n-1 )+ F( n-2 ),n> 1
F(1)= 1
F(0)= 0
白板问题“编写代码以计算F(n)”有四个众所周知的解决方案。
递归 –您需要提及这一点以表明您对递归感到满意,但还必须提及这是一个非常糟糕的主意,因为它需要O(2 n )时间和空间堆栈,因为您需要为每个n加倍工作。
带记忆的递归 –如果您指出这是一个很好的概括,那么这可能是一个好方法。 基本上是递归,但是您需要维护一个缓存(备注),因此您只需要进行一次递归调用-后续的递归调用只查找缓存的值。 这是一种灵活的技术,因为它可用于任何纯递归函数。 (也就是说,一个仅依赖于其输入并且没有副作用的递归函数。)第一次调用需要O(n)时间,堆栈和堆空间。 我不记得是否先对较小或较大的值进行递归调用是否重要。
如果您有持久性缓存,则后续调用需要O(1)时间,堆栈空间和O(n)堆空间。
迭代 –如果您无法缓存值(或仅想有效地初始化缓存),则可以使用迭代方法。 它需要O(n)时间,但只需要O(1)堆栈和堆空间。
直接逼近 –最后使用φ或使用sqrt(5)进行变体是众所周知的逼近。 对于时间,堆栈空间和堆空间,它为O(1) 。 如果您1)将查找表用于最小值,并且2)确保n不太大,则这是一种好方法。
最后一点经常被忽略。 只要您不超过浮点数的精度,近似值就起作用。 F( 100,000 )应该很好。 F( 1,000,000,000,000 )可能不是。 对于如此庞大的数字,迭代方法不切实际。
研究
您是否知道还有另外两个在时间和空间上具有O(lg(n))性能的解决方案(根据Wikipedia)? (我不认为它是O(lg(n)),因为它不是分治法-两个递归调用不会将它们的初始工作分割开来-但是有了备忘录,它肯定小于O(n) 。我怀疑,但无法Swift证明它是O(lg 2 (n)) 。)
根据维基百科,我们知道:
F( 2n-1 )= F 2 ( n )+ F 2 ( n-1 )
F( 2n )= F( n )(F( n )+ 2F( n-1 ))
将其重写为F( n )的递归方法很简单。
还有一个考虑三种情况的属性-F( 3n-2 ),F( 3n-1 )和F( 3n )。 有关详细信息,请参见代码。
这些位点提供了斐波那契和相关卢卡斯序列的许多其他属性。 很少有开发人员会需要了解这些属性,但是在极少数情况下,一小时的研究可以节省数天的工作。
实作
现在,我们可以使用我们的研究为斐波那契和卢卡斯序列实施合适的方法。
斐波那契计算
(此代码未显示对n足够小的未缓存值使用直接逼近的优化。)
/**
* Get specified Fibonacci number.
* @param n
* @return
*/
@Override
public BigInteger get(int n) {
if (n < 0) {
throw new IllegalArgumentException("index must be non-negative");
}
BigInteger value = null;
synchronized (cache) {
value = cache.get(n);
if (value == null) {
int m = n / 3;
switch (n % 3) {
case 0:
value = TWO.multiply(get(m).pow(3))
.add(THREE.multiply(get(m + 1)).multiply(get(m))
.multiply(get(m - 1)));
break;
case 1:
value = get(m + 1).pow(3)
.add(THREE.multiply(get(m + 1)
.multiply(get(m).pow(2))))
.subtract(get(m).pow(3));
break;
case 2:
value = get(m + 1).pow(3)
.add(THREE.multiply(get(m + 1).pow(2)
.multiply(get(m))))
.add(get(m).pow(3));
break;
}
cache.put(n, value);
}
}
return value;
}
斐波那契迭代器
/**
* ListIterator class.
* @author bgiles
*/
private static final class FibonacciIterator extends ListIterator {
private BigInteger x = BigInteger.ZERO;
private BigInteger y = BigInteger.ONE;
public FibonacciIterator() {
}
public FibonacciIterator(int startIndex, FibonacciNumber fibonacci) {
this.idx = startIndex;
this.x = fibonacci.get(idx);
this.y = fibonacci.get(idx + 1);
}
protected BigInteger getNext() {
BigInteger t = x;
x = y;
y = t.add(x);
return t;
}
protected BigInteger getPrevious() {
BigInteger t = y;
y = x;
x = t.subtract(x);
return x;
}
}
卢卡斯计算
/**
* Get specified Lucas number.
* @param n
* @return
*/
public BigInteger get(int n) {
if (n < 0) {
throw new IllegalArgumentException("index must be non-negative");
}
BigInteger value = null;
synchronized (cache) {
value = cache.get(n);
if (value == null) {
value = Sequences.FIBONACCI.get(n + 1)
.add(Sequences.FIBONACCI.get(n - 1));
cache.put(n, value);
}
}
return value;
}
Lucas迭代器
/**
* ListIterator class.
* @author bgiles
*/
private static final class LucasIterator extends ListIterator {
private BigInteger x = TWO;
private BigInteger y = BigInteger.ONE;
public LucasIterator() {
}
public LucasIterator(int startIndex, LucasNumber lucas) {
idx = startIndex;
this.x = lucas.get(idx);
this.y = lucas.get(idx + 1);
}
protected BigInteger getNext() {
BigInteger t = x;
x = y;
y = t.add(x);
return t;
}
protected BigInteger getPrevious() {
BigInteger t = y;
y = x;
x = t.subtract(x);
return x;
}
}
教育
教育其他开发人员有关这些意外关系的存在的最佳方法是什么? 代码,当然!
对其他开发人员进行有关存在可证明这些关系的代码的教育的最佳方法是什么? 当然,单元测试!
编写单元测试可以同时验证我们的实现并告知其他开发人员可以用来改进代码的技巧,这很简单。 关键是提供指向其他信息的链接。
斐波那契数列
public class FibonacciNumberTest extends AbstractRecurrenceSequenceTest {
private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
/**
* Constructor
*/
public FibonacciNumberTest() throws NoSuchMethodException {
super(FibonacciNumber.class);
}
/**
* Get number of tests to run.
*/
@Override
public int getMaxTests() {
return 300;
}
/**
* Verify the definition is properly implemented.
*
* @return
*/
@Test
@Override
public void verifyDefinition() {
for (int n = 2; n < getMaxTests(); n++) {
BigInteger u = seq.get(n);
BigInteger v = seq.get(n - 1);
BigInteger w = seq.get(n - 2);
Assert.assertEquals(u, v.add(w));
}
}
/**
* Verify initial terms.
*/
@Test
@Override
public void verifyInitialTerms() {
verifyInitialTerms(Arrays.asList(ZERO, ONE, ONE, TWO, THREE, FIVE, EIGHT));
}
/**
* Verify that every third term is even and the other two terms are odd.
* This is a subset of the general divisibility property.
*
* @return
*/
@Test
public void verifyEvenDivisibility() {
for (int n = 0; n < getMaxTests(); n += 3) {
Assert.assertEquals(ZERO, seq.get(n).mod(TWO));
Assert.assertEquals(ONE, seq.get(n + 1).mod(TWO));
Assert.assertEquals(ONE, seq.get(n + 2).mod(TWO));
}
}
/**
* Verify general divisibility property.
*
* @return
*/
@Test
public void verifyDivisibility() {
for (int d = 3; d < getMaxTests(); d++) {
BigInteger divisor = seq.get(d);
for (int n = 0; n < getMaxTests(); n += d) {
Assert.assertEquals(ZERO, seq.get(n).mod(divisor));
for (int i = 1; (i < d) && ((n + i) < getMaxTests()); i++) {
Assert.assertFalse(ZERO.equals(seq.get(n + i).mod(divisor)));
}
}
}
}
/**
* Verify the property that gcd(F(m), F(n)) = F(gcd(m,n)). This is a
* stronger statement than the divisibility property.
*/
@Test
public void verifyGcd() {
for (int m = 3; m < getMaxTests(); m++) {
for (int n = m + 1; n < getMaxTests(); n++) {
BigInteger gcd1 = seq.get(m).gcd(seq.get(n));
int gcd2 = BigInteger.valueOf(m).gcd(BigInteger.valueOf(n))
.intValue();
Assert.assertEquals(gcd1, seq.get(gcd2));
}
}
}
/**
* Verify second identity (per Wikipedia): sum(F(i)) = F(n+2)-1
*/
@Test
public void verifySecondIdentity() {
BigInteger sum = ZERO;
for (int n = 0; n < getMaxTests(); n++) {
sum = sum.add(seq.get(n));
Assert.assertEquals(sum, seq.get(n + 2).subtract(ONE));
}
}
/**
* Verify third identity (per Wikipedia): sum(F(2i)) = F(2n+1)-1 and
* sum(F(2i+1)) = F(2n)
*/
@Test
public void verifyThirdIdentity() {
BigInteger sum = ZERO;
for (int n = 0; n < getMaxTests(); n += 2) {
sum = sum.add(seq.get(n));
Assert.assertEquals(sum, seq.get(n + 1).subtract(ONE));
}
sum = ZERO;
for (int n = 1; n < getMaxTests(); n += 2) {
sum = sum.add(seq.get(n));
Assert.assertEquals(sum, seq.get(n + 1));
}
}
/**
* Verify fourth identity (per Wikipedia): sum(iF(i)) = nF(n+2) - F(n+3) + 2
*/
@Test
public void verifyFourthIdentity() {
BigInteger sum = ZERO;
for (int n = 0; n < getMaxTests(); n++) {
sum = sum.add(BigInteger.valueOf(n).multiply(seq.get(n)));
BigInteger x = BigInteger.valueOf(n).multiply(seq.get(n + 2))
.subtract(seq.get(n + 3)).add(TWO);
Assert.assertEquals(sum, x);
}
}
/**
* Verify fifth identity (per Wikipedia): sum(F(i)^2) = F(n)F(n+1)
*/
public void verifyFifthIdentity() {
BigInteger sum = ZERO;
for (int n = 0; n < getMaxTests(); n += 2) {
BigInteger u = seq.get(n);
BigInteger v = seq.get(n + 1);
sum = sum.add(u.pow(2));
Assert.assertEquals(sum, u.multiply(v));
}
}
/**
* Verify Cassini's Identity - F(n-1)F(n+1) - F(n)^2 = -1^n
*/
@Test
public void verifyCassiniIdentity() {
for (int n = 2; n < getMaxTests(); n += 2) {
BigInteger u = seq.get(n - 1);
BigInteger v = seq.get(n);
BigInteger w = seq.get(n + 1);
BigInteger x = w.multiply(u).subtract(v.pow(2));
Assert.assertEquals(ONE, x);
}
for (int n = 1; n < getMaxTests(); n += 2) {
BigInteger u = seq.get(n - 1);
BigInteger v = seq.get(n);
BigInteger w = seq.get(n + 1);
BigInteger x = w.multiply(u).subtract(v.pow(2));
Assert.assertEquals(MINUS_ONE, x);
}
}
/**
* Verify doubling: F(2n-1) = F(n)^2 + F(n-1)^2 and F(2n) =
* F(n)(F(n-1)+F(n+1)) = F(n)(2*F(n-1)+F(n).
*/
@Test
public void verifyDoubling() {
for (int n = 1; n < getMaxTests(); n++) {
BigInteger u = seq.get(n - 1);
BigInteger v = seq.get(n);
BigInteger w = seq.get(n + 1);
BigInteger x = v.multiply(v).add(u.pow(2));
Assert.assertEquals(seq.get((2 * n) - 1), x);
x = v.multiply(u.add(w));
Assert.assertEquals(seq.get(2 * n), x);
x = v.multiply(v.add(TWO.multiply(u)));
Assert.assertEquals(seq.get(2 * n), x);
}
}
/**
* Verify tripling.
*/
@Test
public void verifyTripling() {
for (int n = 1; n < getMaxTests(); n++) {
BigInteger u = seq.get(n - 1);
BigInteger v = seq.get(n);
BigInteger w = seq.get(n + 1);
BigInteger x = TWO.multiply(v.pow(3))
.add(THREE.multiply(v).multiply(u).multiply(w));
Assert.assertEquals(seq.get(3 * n), x);
x = w.pow(3).add(THREE.multiply(w).multiply(v.pow(2)))
.subtract(v.pow(3));
Assert.assertEquals(seq.get((3 * n) + 1), x);
x = w.pow(3).add(THREE.multiply(w.pow(2)).multiply(v)).add(v.pow(3));
Assert.assertEquals(seq.get((3 * n) + 2), x);
}
}
}
卢卡斯序列
public class LucasNumberTest extends AbstractRecurrenceSequenceTest {
private static final FibonacciNumber fibonacci = new FibonacciNumber();
/**
* Constructor
*/
public LucasNumberTest() throws NoSuchMethodException {
super(LucasNumber.class);
}
/**
* Get number of tests to run.
*/
@Override
public int getMaxTests() {
return 300;
}
/**
* Verify the definition is properly implemented.
*
* @return
*/
@Test
@Override
public void verifyDefinition() {
for (int n = 2; n < getMaxTests(); n++) {
BigInteger u = seq.get(n);
BigInteger v = seq.get(n - 1);
BigInteger w = seq.get(n - 2);
Assert.assertEquals(u, v.add(w));
}
}
/**
* Verify initial terms.
*/
@Test
@Override
public void verifyInitialTerms() {
verifyInitialTerms(Arrays.asList(TWO, ONE, THREE, FOUR, SEVEN, ELEVEN,
BigInteger.valueOf(18), BigInteger.valueOf(29)));
}
/**
* Verify Lucas properties.
*/
@Test
public void verifyLucas() {
// L(n) = F(n-1) + F(n+1)
for (int n = 2; n < getMaxTests(); n++) {
Assert.assertEquals(seq.get(n),
fibonacci.get(n - 1).add(fibonacci.get(n + 1)));
}
}
/**
* F(2n) = L(n)F(n)
*/
@Test
public void verifyLucas2() {
for (int n = 2; n < getMaxTests(); n++) {
Assert.assertEquals(fibonacci.get(2 * n),
seq.get(n).multiply(fibonacci.get(n)));
}
}
/**
* F(n) = (L(n-1)+ L(n+1))/5
*/
@Test
public void verifyLucas3() {
for (int n = 2; n < getMaxTests(); n++) {
Assert.assertEquals(FIVE.multiply(fibonacci.get(n)),
seq.get(n - 1).add(seq.get(n + 1)));
}
}
/**
* L(n)^2 = 5 F(n)^2 + 4(-1)^n
*/
@Test
public void verifyLucas4() {
for (int n = 2; n < getMaxTests(); n += 2) {
Assert.assertEquals(seq.get(n).pow(2),
FIVE.multiply(fibonacci.get(n).pow(2)).add(FOUR));
}
for (int n = 1; n < getMaxTests(); n += 2) {
Assert.assertEquals(seq.get(n).pow(2),
FIVE.multiply(fibonacci.get(n).pow(2)).subtract(FOUR));
}
}
}
结论
显然,除非开发人员正在处理Project Euler问题或进行工作面试,否则开发人员几乎不需要计算斐波那契数。 这段代码不会直接使用。
同时,即使您确定已经了解了所有需要知道的知识,这也充分证明了投资一两个小时进行研究的价值。 您可能不需要BigInteger实现,但是有些人可能认为O(lg(n))方法优于使用φ的幂进行估计,或者可以充分利用MathWorld和Wikipedia页面上讨论的关系。
源代码
好消息是我已经为此发布了源代码……坏消息是当我处理Project Euler问题时,这是正在进行的涂鸦的一部分。 (这里没有解决方案-完全是对问题启发的想法的探索。因此,代码有些粗略,不应用于决定是否邀请我参加面试(除非给您留下深刻的印象): http ://github.com/beargiles/projecteuler 。
翻译自: https://www.javacodegeeks.com/2014/06/fibonacci-and-lucas-sequences.html