Algorithmensammlung: Zahlentheorie: Fibonacci-Folge

Algorithmensammlung: Zahlentheorie

Programme zur Berechnung des n-ten Wertes der Fibonacci-Folge aus den Startwerten und und dem rekursiven Bildungsgesetz

  für   .

Algorithmen

Bearbeiten

Eine direkte Implementierung durch die Rekursion

F(n) = F(n-1) + F(n-2)

wäre unsinnig, weil die Rechenzeit mit steigendem n extrem stark ansteigt. Für n > 1 würde die Funktion (in Pseudocode)

function fib(n)
     if n < 2
         return n
     return fib(n-1) + fib(n-2)

(Fn + Ln - 1) mal aufgerufen, dabei ist Ln das n-te Glied der Lucas-Folge, die wie die Fibonacci-Folge berechnet wird, nur mit anderen Startwerten   und  .

Für n = 43 (F43 = 433494437) wären das 1402817465, also über 1,4 Milliarden Aufrufe.

Besser sind z. B. iterative Algorithmen, wie

function fib(n)
    if n < 2
        return n
    f2 = 0    // vorletzter Wert
    f1 = 1    // letzter Wert
    while n > 1
        n = n - 1
        f = f1 + f2
        f2 = f1
        f1 = f
    return f

.

Basic C64

Bearbeiten
 5 REM FIBONACCI 
 10 A = 1
 20 B = 1
 30 FOR X = 1 TO 20
 40 PRINT A
 50 PRINT B
 60 A = A + B
 70 B = A + B
 80 NEXT

Gibt es ein Programm mit nur einer Variablen A ?

 10 PHI = (1 + 5^0.5) / 2
 20 FOR N = 1 TO 20
 30 FIB = INT(((PHI^N - (1-PHI)^N) / 5^0.5)+0.5)
 40 PRINT N,FIB
 50 NEXT

Wegen Rundungsfehlern ist die Folge nicht ganz korrekt.

Eine Variante mit einem Array

 10 REM FIBONACCI-FOLGE
 20 CLS
 30 REM DER ARRAY F WIRD MIT DEN FIBONACCI ZAHLEN GEFUELLT
 40 DIM F(50) 
 50 F(0) = 0
 60 F(1) = 1
 70 N = 1
 80 LET F(N+1) = F(N) + F(N-1)
 90 LET N = N + 1
 100 PRINT F(N);", ";
 110 REM STOP NACH 50 ZAHLEN
 120 IF N < 50 THEN GOTO 80

Basic X11

Bearbeiten
 REM FIBONACCI 
 A = 1 
 B = 1 
 FOR X = 1 TO 20 
 PRINT A 
 PRINT B 
 A = A + B 
 B = A + B 
 NEXT

Visual Basic for Applications (VBA)

Bearbeiten
Public Function Fibonacci(n As Long) As Variant
    Dim i As Long, a, b
    
    a = 0 ' =F(0)
    b = 1 ' =F(1)
    
    For i = 2 To n Step 2
        ' Abwechselnd a und b erhöhen
        a = a + b
        b = a + b
    Next i
    
    ' Zutreffenden Wert zurückgeben
    Fibonacci = IIf(n Mod 2 = 0, a, b)
End Function

Der Rückgabewert ist je nach Größe vom Datentyp Long oder Double. Die Funktion enthält keine Fehlerbehandlung bei n < 0.

Werden Matrizen der Form

 

addiert oder multipliziert, ergibt sich erneut eine Matrix dieser Form. Für die Darstellung solcher Matrizen ist die zweite Zeile redundant. Außerdem ist

 .
data M r = M !r !r deriving (Eq, Show)

instance Num r => Num (M r) where
    fromInteger n = M (fromInteger n) 0
    M a b + M c d = M (a+c) (b+d)
    M a b - M c d = M (a-c) (b-d)
    M a b * M c d = M (a*c+b*d) (a*d+b*c+b*d)
    abs = undefined
    signum = undefined
    
fib :: Num r => Integer -> r
fib n = b where
    M a b = M 0 1 ^ n

Hier wird eine Num-Instanz für Matrizen der angegebenen Form definiert. Diese wird von der vordefinierten Exponentiation (^) :: (Integral b, Num a) => a -> b -> a benutzt, welche ihrerseits als   binäre Exponentiation implementiert ist.


Eine Lösung mit Listengenerierung per Rekursion:

fibs = 0:1:zipWith (+) fibs (tail fibs)

Die Funktion fibs ruft man dann ohne Parameter auf. Zurückgegeben wird die endlose Fibonacci-Folge. Ausgeben kann man die nicht, aber zuweisen oder einen Wert herauspicken fibs!!100, oder einen Teil der Liste extrahieren take 101 fibs.

Dass das überhaupt funktioniert, kann verblüffen. Möglich ist das nur wegen Haskells Konzept der späten Auswertung (Lazy Evaluation), welches hier schamlos ausgenutzt wird. Eine detaillierte Erklärung, was intern abläuft, kann man finden, wenn man die Funktion als Suchbegriff in eine Suchmaschine eingibt.

Die folgende Implementierung ist rekursiv:

# Getestet unter Python 3.4, sollte mit allen 3.x-Versionen funktionieren
def fib(n):
    if n > 1:
        return fib(n-1) + fib(n-2)
    else:
        return n

Diese Funktion stimmt direkt mit der Definition der Folge überein und allenfalls für Lernzwecke geeignet. Die Geschwindigkeit ist allerdings nicht ausreichend für einen praktischen Einsatz: die Laufzeit steigt in etwa proportional zum Funktionswert (nicht zum Wert des Parameters n). Dieser Mangel lässt sich z. B. mit   Memoisation teilweise ausmerzen. Sobald die Zahlen allerdings etwas größer werden, ist die direkte Berechnung der Werte über die   Formel von Moivre-Binet weitaus schneller:

# Getestet unter Python 3.4, sollte mit allen 3.x-Versionen funktionieren
import math


def moivre_binet(n):
    näherung = (1/math.sqrt(5)) * (((1+math.sqrt(5))/2)**n - ((1-math.sqrt(5))/2)**n)
    return int(abs(näherung))
    # nötig wegen möglicherweise irritierender Rundungsfehler durch mangelhafte 
    # maschineninterne Darstellung der irrationalen Wurzel aus 5

Geschwindigkeitstechnisch lässt sich da auch noch dran feilen, dazu lese man den dazugehörigen Wikipedia-Artikel, für die ersten Schritte sollte der Quelltext aber genügen.

Weitere Implementierungen

def fibonacciIterativ(n: int) -> int:
    assert n >= 0, "n must be a non-negative integer"
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
    return a


def fibonacciFunktional(n: int) -> int:
    assert n >= 0, "n must be a non-negative integer"
    return reduce(lambda x, _: (x[1], x[0] + x[1]), range(n), (0, 1))[0]


def fibonacciEnd(n: int) -> int:
    assert n >= 0, "n must be a non-negative integer"
    return _fibonacciEnd(n, 0, 1)

def _fibonacciEnd(n: int, a: int, b: int) -> int:
    if n == 0:
        return a
    else:
        return _fibonacciEnd(n - 1, b, a + b)


def fibonacciMatrix(n: int) -> int:
    assert n >= 0, "n must be a non-negative integer"
    from numpy import array
    from numpy.linalg import matrix_power
    return matrix_power(array([[1, 1], [1, 0]]), n).item((0, 1))


def fibonacciTuple(n: int) -> int:
    assert n >= 0, "n must be a non-negative integer"
    return _fibonacciTuple(n)[0]

def _fibonacciTuple(n: int) -> Tuple[int, int]:
    if n == 0:
        return (0, 1)
    else:
        a, b = _fibonacciTuple(n - 1)
        return (b, a + b)


def fibonacciDoubling(n: int) -> int:
    assert n >= 0, "n must be a non-negative integer"
    return _fibonacciDoubling(n)[0]

def _fibonacciDoubling(n: int) -> Tuple[int, int]:
    if n == 0:
        return (0, 1)
    else:
        x, y = _fibonacciDoubling(n // 2)
        a = x * (y * 2 - x)
        b = x * x + y * y
        if n % 2 == 0:
            return (a, b)
        else:
            return (b, a + b)

FreePascal

Bearbeiten

Der Vollständigkeit halber, wie es schnurstracks geradewegaus rekursiv implementiert wird:

{**
	implements fibonacci sequence recursively
	
	\param n the index of the Fibonacci number to retrieve
	\returns the Fibonacci value at n
}
function fibonacci(const n: byte): qword;
begin
	// optimization: then part gets executed most of the time
	if n > 1 then
	begin
		fibonacci := fibonacci(n - 2) + fibonacci(n - 1);
	end
	else
	begin
		// since the domain is restricted to non-negative integers
		// we can bluntly assign the result to n
		fibonacci := n;
	end;
end;

Natürlich will man lieber iterativ rechnen.

{**
	implements Fibonacci sequence iteratively
	
	\param n the index of the Fibonacci number to calculate
	\returns the Fibonacci value at n
}
function fibonacci(const n: longword): qword;
type
	/// more meaningful identifiers than simple integers
	relativePosition = (previous, current, next);
var
	/// temporary iterator variable
	i: longword;
	/// holds preceding fibonacci values
	f: array[relativePosition] of qword;
begin
	f[previous] := 0;
	f[current] := 1;
	
	// note, in Pascal for-loop-limits are inclusive
	for i := 1 to n do
	begin
		f[next] := f[previous] + f[current];
		f[previous] := f[current];
		f[current] := f[next];
	end;
	
	// assign to previous, bc f[current] = f[next] for next iteration
	fibonacci := f[previous];
end;

Es ist eigentlich lächerlich, die Fibonacci-Sequenz in Assembler zu programmieren, weil moderne Compiler bereits gut optimieren. Zum Beispiel setzt FPC die Summenbildung auf x86-64-Platformen mittels der Zeiger-Arithmetik-Instruktion lea (“load effective address”) um. Zu Demonstrationszwecken aber trotzdem mal. Die folgende Implementation beachtet insbesondere einen Ganzzahlüberlauf (was mit lea nicht möglich ist, da diese Instruktion keine flags setzt).

{**
	calculates Fibonacci number iteratively in ALU
	
	\param n the index of the Fibonacci number to calculate
	\returns the Fibonacci number at n
*}
function fibonacci(const n: longword): qword; assembler;
{$goto on}
label
	fibonacci_iterate, fibonacci_done;
{$asmMode intel}
asm
	// instructions that modify flags before the cmp
	xor rax, rax // rax := 0
	xor r10, r10 // r10 := 0
	// used by cmov in loop, bc cmov can not handle immediates
	mov r12, 1   // r12 := 1
	xor r13, r13 // r13 := 0
	
	cmp n, rax   // rax = n [n = 0]
	// ecx is used as counter variable by loop-instruction
	mov ecx, n   // ecx := n
	mov r11, 1   // r11 := 1
	je fibonacci_done // if n = 0 then goto done

fibonacci_iterate:
	mov rax, r10 // rax := r10        | effectively:
	add rax, r11 // rax := rax + r11  | rax := r10 + r11
	
	// xchg would be slower, since we wanna _drop_ one of the values
	mov r10, r11 // r10 := r11
	// possibly save to restore last valid value
	cmovnc r11, rax // if not CF then r11 := rax
	
	// destroy loop on overflow:
	// set ecx to 1, so decrementing hits zero (loop abort condition)
	cmovc ecx, r12 // if CF then ecx := 1
	cmovc r10, r13 // if CF then r10 := 0
	loop fibonacci_iterate // dec(ecx)
	// if ecx <> 0 then goto iterate
	
	
	// F_{93} is the largest value a qword can hold
	cmp n, 93      // 93 = n ?
	cmove r10, r11 // r10 := r11  | restore last value
	
fibonacci_done:
	// the @result macro represents the function's return value
	mov @result, r10 // result := r10
end;