Wat bedoel je dan met sneller werken zoals (x+y)^n; die werkt namelijk in dezelfde tijd, tenzij je eerst x+y uitwerkt en dan pas die macht. Als je eerst die macht wilt uitwerken, moet je dus langs die binomiaalcoëfficiënten. Op net dezelfde manier gaat die (x+y)^n ook traag worden voor grote n (juist omdat je die coëfficiënten eerst nodig hebt.
Dingen die je kan toepassen om het sneller te maken is gewoon de expliciete formule gaan gebruiken, maar opletten dat je daar de nodige aanpassingen maakt:
!} )
. Dat hoort nu eenmaal ook bij programmeren dat je gaat optimaliseren daarin. Ik geef maar een voorbeeld
(en vermits je geen programmeertaal aangeeft, doe ik maar Python):
Code:
def fac(n):
product = 1
for i in range(1,n+1):
product *= i
return product
def binom(n,k):
return fac(n)/(fac(k)*(fac(n-k))
Normaal zou je zelf al moeten merken dat dat niet handig gaat zijn. Het recursieve algoritme dat op WikiPedia gebruikt wordt, zal zo op het eerste gezicht al veel sneller zijn. Mijn best gok is trouwens dat die in O(n/2+1) gaat werken, wat redelijk zuinig is (het algoritme hierboven in O(2n)).
Een versnelling in mijn algoritme hierboven kan je al maken door het wiskundig te gaan bekijken en dan na te gaan waarmee je eigenlijk bezig bent, zodoende kom je op iets dergelijks:
Code:
def product(a,b):
prod = 1
for i in range(a,b+1):
prod *= i
return prod
def fac(n):
return product(1,n)
def binom(n,k):
return product(k+1,n)/fac(k)
Als je hiernaar kijkt, hoe snel hij loopt: O

. Op net dezelfde manier kan je een symmetrische variant maken die sneller loopt voor kleine waarden van k (vermits bovenstaande beter loopt voor grote waarden van k). Je zou dan eventueel beide algoritmes kunnen combineren (door die looptijd te analyseren, kan je zien voor welke waardes van n i.f.v. k het rendabel is om een van beide te kiezen.
Het wikipedia-algoritme is dus O(n ), en volgens mij vrijwel het meest sneller dat je gaat vinden. Voor grote n (ik denk aan duizend bv.), wordt dat wat trager; al zou dat voor een computer nog geen probleem mogen zijn. Voor een rekenmachine echter, is het een ander paar mouwen; maar daar mag je zeker niet vergeten dat die dingen echt niet snel kunnen rekenen. Maar je kan nu eenmaal niet iets veel vereenvoudigen; op een bepaald moment kom je aan de ondergrens.
Verder bestaan er nog genoeg trucjes om die dingen te doen: stel dat je een programma maakt waar je heel vaak die waardes nodig hebt (voor de faculteit of voor binomiaalcoëfficiënten), dan kan je ook steeds afwegen: hoe veel is mijn dataopslag of geheugengebruik waard tegenover mijn prestaties. Een gekende truc is bv. caching. Dit houdt in dat je eenmaal de hele berekening maakt en dat je het resultaat ergens mooi bijhoudt (in een bestand als je dat vaak nodig hebt; gewoon in het geheugen als het best meevalt). De eerste keer dat die berekening gemaakt zal worden, zal je even lang moeten wachten als normaal; maar elke volgende keer dat je die berekening maakt; krijg je direct je resultaat). Het nadeel natuurlijk is dat je hiermee RAM-geheugen opoffert voor prestaties op rekenkracht.
Om maar een voorbeeld te geven:
Code:
cache = {0: 1, 1: 1}
def fac(n):
if cache.has_key(n):
return cache[n]
else:
f = n * fac(n-1)
cache[f] = n
return f
In dit geval zal het ervoor zorgen dat we steeds pas beginnen tellen bij het hoogste getal waarvan we reeds een faculteit berekend hebben. Dit houdt dus ook in dat als we fac(1000) laten berekenen, hij 1000 resultaten zal bijhouden voor volgend gebruik. Daarop valt natuurlijk te verbeteren (door niet recursief maar in een lus te werken en slechts de gevraagde faculteit zelf te berekenen, of die slechts te berekenen als die maar enkele getallen groter is dan een gekende of slechts faculteit van veelvouden van 10 bij te houden (zodat je met een volle cache O(9) werkt en daarvoor O(n ); natuurlijk met het extra geheugengebruik en wel het gegeven dat je ook eerst die cache moet vullen (wat dus nog steeds zelfde tijdsorde is)).
En bovenstaande Python-code is zonder te testen of zelfs maar de intentie om robuust te zijn (dus negatieve waarden of iets dergelijks en alles kan de soep in lopen). Net zozeer kan het hier of daar wat afgeroomd worden, eventueel wat versneld. Het gaat mij vooral om het concept.