Hallo,
ich möchte Hofstadters Q-Folge als Funktion sowohl iterativ als auch
rekursiv programmieren. Dabei möchte ich die n-te Zahl berechnen, die
main-Funktion stellt sicher, dass die Zahl n>0 ist.
http://de.wikipedia.org/wiki/Hofstadter-Folge
In dem Link etwa in der Mitte ist sie beschrieben.
Iterativ habe ich das ganze geschafft, der Code sieht so aus:
1
intIterativ(intn)
2
{
3
inti=0;
4
int*ptr;
5
ptr=(int*)malloc(n*sizeof(int));
6
7
ptr[0]=ptr[1]=1;
8
9
for(i=2;i<=n;++i){
10
ptr[i]=ptr[i-ptr[i-1]]+ptr[i-ptr[i-2]]}
11
12
returnptr[n-1];/* Feld beginnt bei 0 */
Rekursiv bekomme ich das ganze einfach nicht hin, wäre super, wenn mir
dabei jemand helfen könnte, ich bin fast am Verzweifeln.
MfG
Bert
Bert schrieb:
> int *ptr;> ptr = (int *) malloc( n * sizeof(int));>> ptr[0]=ptr[1]=1;>> for(i=2;i<=n;++i){
Hier hast du einen Fehler.
Du hast im malloc 1 Element zu wenig allokiert.
Faustregel: Wann immer du es mit Arrays zu tun hast und die
for-Schleife, die über die Array-Elemente iteriert, ein <= anstelle
eines < enthält, ist extreme Vorsicht angebracht.
Ausserdem hast du den allokierten Speicher nicht wieder freigegeben.
> Rekursiv bekomme ich das ganze einfach nicht hin, wäre super, wenn mir> dabei jemand helfen könnte, ich bin fast am Verzweifeln.
Wo liegt das Problem?
Rekursiv programmiert sich das fast von alleine mit der Definition der
Folge
Def:
Q(1) = 1
Q(2) = 1
Q(n) = Q( n - Q(n-1)) + Q( n - Q(n-2) )
Karl heinz Buchegger schrieb:
>> for(i=2;i<=n;++i){>> Hier hast du einen Fehler.> Du hast im malloc 1 Element zu wenig allokiert.
...bzw. einen Schleifendurchlauf zu viel programmiert, der für das
Ergebnis gar nicht benötigt wird. i<n als Abbruchbedingung reicht, dann
stimmt's auch mit dem malloc.
Hallo,
vielen Dank euch beiden!!
>Wo liegt das Problem?
Die beiden oberen Zeilen sind klar, mein Problem ist einfach, dass ich
mir nicht vorstellen kann, dass diese Zeile
1
returnQ(n-Q(n-1))+Q(n-Q(n-2));
die Q-Folge ergibt. Da tu ich mich ja schon beim Nachvollziehen mit
einem Beispiel, wenn ich nun etwa n= 5 im Kopf einsetze, extrem schwer.
Dass man das Ganze quasi aus der Definition "entnehmen" kann, wusste ich
nicht, ich werd mir das Schema dann mal einprägen. Danke nochmals!
MfG
Bert
Hmm.
Laut Wikipedia:
<quote>
Es ist insbesondere unbekannt, ob die Folge für alle n wohldefiniert
ist, das heißt, ob die Folge irgendwo abbricht, weil ihre
Produktionsregel versucht, sich auf Elemente zu beziehen, die sich
konzeptuell links vom ersten Element Q(1) befinden müssten.
</quote>
Also besser:
1
intQ(intn)
2
{
3
if(n<1){
4
printf("ungültiges n: %d\n",n);
5
return1;// man könnte hier auch einen exit() machen
Bert schrieb:
> Die beiden oberen Zeilen sind klar, mein Problem ist einfach, dass ich> mir nicht vorstellen kann, dass diese Zeile>
1
>returnQ(n-Q(n-1))+Q(n-Q(n-2));
>> die Q-Folge ergibt. Da tu ich mich ja schon beim Nachvollziehen mit> einem Beispiel, wenn ich nun etwa n= 5 im Kopf einsetze, extrem schwer.
Das wirst du auch im Kopf so nicht schaffen.
Aber mit Papier und Bleistift gehts. Und dann hast du natürlich auch
noch dein Programm. Niemand hindert dich daran, im Programm ein paar
printf einzubauen, so dass das Programm mitprotokolliert, wie das
berechnet wird :-)
So könnte zb eine mit Ausgaben versehene Funktion Q aussehen, die
mitprotokolliert, was sie tut. Der zusätzliche Parameter 'Level' ist
einfach nur die Rekusrionstiefe und wird dazu misbraucht die einzelnen
Ausgaben entsprechend der Rekursionstiefe einzurücken.
Wow, da hast du dir aber viel Mühe gemacht, da kann ich mich nur erneut
bedanken und dir ein dickes Lob aussprechen! Toll, wie du dich hier
engagierst! Ich werd den Code jetzt gleich mal durchdenken :-)
MfG
Bert
Bert schrieb:
> Wow, da hast du dir aber viel Mühe gemacht, da kann ich mich nur erneut> bedanken und dir ein dickes Lob aussprechen! Toll, wie du dich hier> engagierst! Ich werd den Code jetzt gleich mal durchdenken :-)
Da brauchst du nicht viel durchdenken.
Ist der gleiche Code wie vorher, nur ein paar printf sind mehr drinnen
und die Monsterberechnung
Q(n) = Q( n - Q(n-1)) + Q( n - Q(n-2) )
habe ich in 2 Teile aufgespalten
S1 = Q( n - Q(n-1) )
S2 = Q( n - Q(n-2) )
Q(n) = S1 + S2
damit ich mittendrinn, während der Berechnung, noch Gelegenheit für ein
paar printf habe.
Eigentlich sollte man das noch ein wenig mehr mit Zwischenvariablen und
printf aufdröseln. Aber das kannst ja dann du machen :-)
Oh da hat jemand mit dem Programmierpraktikum an der TU Munich Probleme.
Ich dachte dazu gibts Tutoren =)
Jetzt mal ein Tipp fürs rekursive: Ersetz einfach jede eckige Klammer
des Feldes durch einen rekursiven Funktionsaufruf.
ptr[i-ptr[i-1]] + ptr[i-ptr[i-2]]
also so:
rekursiv(i-rekursiv(i-1) + rekursiv(i-rekursiv(i-2))
Zu berechnen ist Q( 4 - Q( 3 ) ) + Q( 4 - Q( 2 ) )
3
4
Gesucht Q( 4 - Q( 3 ) )
5
Dazu brauche ich Q(3)
6
Berechne Q(3)
7
Zu berechnen ist Q( 3 - Q( 2 ) ) + Q( 3 - Q( 1 ) )
8
9
Gesucht Q( 3 - Q( 2 ) )
10
Dazu brauche ich Q(2)
11
Berechne Q(2)
12
trivial, das ist 1
13
Q(2) ergibt also 1
14
Und jetzt Q( 3 - 1 ) = Q(2)
15
Berechne Q(2)
16
trivial, das ist 1
17
Q( 3 - Q( 2 ) ) ergibt also 1
18
19
Gesucht Q( 3 - Q( 1 ) )
20
Dazu brauche ich Q(1)
21
Berechne Q(1)
22
trivial, das ist 1
23
Q(1) ergibt also 1
24
Und jetzt Q( 3 - 1 ) = Q(2)
25
Berechne Q(2)
26
trivial, das ist 1
27
Q( 3 - Q( 1 ) ) ergibt also 1
28
Q(3) = 1 + 1 = 2
29
Q(3) ergibt also 2
30
Und jetzt Q( 4 - 2 ) = Q(2)
31
Berechne Q(2)
32
trivial, das ist 1
33
Q( 4 - Q( 3 ) ) ergibt also 1
34
35
Gesucht Q( 4 - Q( 2 ) )
36
Dazu brauche ich Q(2)
37
Berechne Q(2)
38
trivial, das ist 1
39
Q(2) ergibt also 1
40
Und jetzt Q( 4 - 1 ) = Q(3)
41
Berechne Q(3)
42
Zu berechnen ist Q( 3 - Q( 2 ) ) + Q( 3 - Q( 1 ) )
43
44
Gesucht Q( 3 - Q( 2 ) )
45
Dazu brauche ich Q(2)
46
Berechne Q(2)
47
trivial, das ist 1
48
Q(2) ergibt also 1
49
Und jetzt Q( 3 - 1 ) = Q(2)
50
Berechne Q(2)
51
trivial, das ist 1
52
Q( 3 - Q( 2 ) ) ergibt also 1
53
54
Gesucht Q( 3 - Q( 1 ) )
55
Dazu brauche ich Q(1)
56
Berechne Q(1)
57
trivial, das ist 1
58
Q(1) ergibt also 1
59
Und jetzt Q( 3 - 1 ) = Q(2)
60
Berechne Q(2)
61
trivial, das ist 1
62
Q( 3 - Q( 1 ) ) ergibt also 1
63
Q(3) = 1 + 1 = 2
64
Q( 4 - Q( 2 ) ) ergibt also 2
65
Q(4) = 1 + 2 = 3
66
-> ergebnis 3
man erkennt auch den Nachteil der (simplen) rekursiven Lösung.
Das Programm ist strohdumm. Einzelne Werte werden wieder und immer
wieder berechnet. Das Programm weiß nicht, dass es (in diesem Fall) Q(3)
schon des öfteren berechnet hat.
Karl heinz Buchegger schrieb:
> ...
Hab' mir auch gerade ein paar Gedanken dazu gemacht. Ganz so einfach
ist es tatsächlich nicht ;-)
Aber die Folge hat einen interessanten Verlauf (s. Anhang).
Christopher D. schrieb:
> Ich behaupte wenn dus simpel rekursiv berechnest wird ohne dynamische> Programmierung irgendwo bei n = 50 Schluss sein :>
Alles eine Frage der Geduld.
Die Stackbelastung ist ja nicht sehr hoch.
D.h.: Solange man das Ergebnis erwarten kann, ist man im Rennen.
Karl heinz Buchegger schrieb:
> Christopher D. schrieb:>> Ich behaupte wenn dus simpel rekursiv berechnest wird ohne dynamische>> Programmierung irgendwo bei n = 50 Schluss sein :>>> Alles eine Frage der Geduld.> Die Stackbelastung ist ja nicht sehr hoch.> D.h.: Solange man das Ergebnis erwarten kann, ist man im Rennen.
Hehe, ja da kann man sich zeitlich irgendwo zwischen einem Kaffee holen
und 5 Häuser bauen einreihen :D
Christopher D. schrieb:
> Hehe, ja da kann man sich zeitlich irgendwo zwischen einem Kaffee holen> und 5 Häuser bauen einreihen :D
:-)
Habs gerade probiert.
10 geht sofort
20 ebenfalls
30 dauert schon ein paar Sekunden
40 noch keien Zigarettenlänge
50 hab ich grade gestartet
Karl heinz Buchegger schrieb:
> Christopher D. schrieb:>>> Hehe, ja da kann man sich zeitlich irgendwo zwischen einem Kaffee holen>> und 5 Häuser bauen einreihen :D>> :-)>> 50 hab ich grade gestartet
Werde dann heut nach unserem Johann König - DVD Abend wieder reinsehen
und das Ergebnis abwarten :D
Morgen gebe ich dann auch mal eine schicke Folge (im Sinne wenn man sie
sich als Plot ansieht) zum Besten
Karl heinz Buchegger schrieb:
> 10 Minuten sind durch> Und er rechnet und rechnet und rechnet ....
ich würde bei n=50 auf eine 2stellige stundenzahl < 1 Tag tippen
Christopher D. schrieb:
> Karl heinz Buchegger schrieb:>> 10 Minuten sind durch>> Und er rechnet und rechnet und rechnet ....>> ich würde bei n=50 auf eine 2stellige stundenzahl < 1 Tag tippen
20 sind durch.
:-)
Allerdings will ich um 8 ins Kino und ich hab keine Messfunktion ins
Programm eingebaut. Also hopp, hopp. In einer halben Stunde muss ein
Ergebnis her :-)
Ich kann natürlich auch durchlaufen lassen. Dann hab ich wenigstens eine
obere Schranke wenn er in der Zwischenzeit fertig werden sollte.
Karl heinz Buchegger schrieb:
> Christopher D. schrieb:>> Karl heinz Buchegger schrieb:>>> 10 Minuten sind durch>>> Und er rechnet und rechnet und rechnet ....>>>> ich würde bei n=50 auf eine 2stellige stundenzahl < 1 Tag tippen>> 20 sind durch.>> :-)> Allerdings will ich um 8 ins Kino und ich hab keine Messfunktion ins> Programm eingebaut. Also hopp, hopp. In einer halben Stunde muss ein> Ergebnis her :-)> Ich kann natürlich auch durchlaufen lassen. Dann hab ich wenigstens eine> obere Schranke wenn er in der Zwischenzeit fertig werden sollte.
ich wette dass er nicht vor morgen früh fertig ist :) wer steigt ein? :D
Christopher D. schrieb:
> Karl heinz Buchegger schrieb:>> Christopher D. schrieb:>>> Karl heinz Buchegger schrieb:>>>> 10 Minuten sind durch>>>> Und er rechnet und rechnet und rechnet ....>>>>>> ich würde bei n=50 auf eine 2stellige stundenzahl < 1 Tag tippen>>>> 20 sind durch.>>>> :-)>> Allerdings will ich um 8 ins Kino und ich hab keine Messfunktion ins>> Programm eingebaut. Also hopp, hopp. In einer halben Stunde muss ein>> Ergebnis her :-)>> Ich kann natürlich auch durchlaufen lassen. Dann hab ich wenigstens eine>> obere Schranke wenn er in der Zwischenzeit fertig werden sollte.>> ich wette dass er nicht vor morgen früh fertig ist :) wer steigt ein? :D
Ich!
Ich wette auf morgen mittag.
Ach ja. 40 Minuten sind durch :-)
(Ich hätte doch den Optimizer einschalten sollen :-)
Klaus Wachtler schrieb:
> ok, ihr rechnet offenbar ungeschickt....
:-)
Da läuft bewusst die völlig unoptimierte naive rekursive Version :-)
Schon klar, dass man da viel drehen kann. Das prinzipielle Problem,
warum da die Laufzeit explodiert, ist ja bekannt. Ich will gar nicht
wissen, wieviele Milliarden mal das Programm mittlerweile Q(3) als
Zwischenergebnis berechnet hat
Btw. die erste Stunde ist bald rum
Nötig ist dann noch ein dynamisches Feld aus vararr.h; siehe Anhang.
Daraus wird eine Methode og() verwendet, welche die bisherige
Obergrenze (also höchsten benutzten Index) liefert.
Ansonsten wird einfach mit [] aus Feldelemente zugegriffen und im
Hintergrund passend Platz dafür beschafft.
Klaus Wachtler schrieb:
> Nötig ist dann noch ein dynamisches Feld aus vararr.h; siehe Anhang.
Damit erklärt sich dann auch, was gemeinhin unter 'dynamischer
Programmierung' verstanden wird: Wenn im Programm Datenstrukturen
vorkommen, die zur Laufzeit wachsen bzw. schrumpfen.
Klaus Wachtler schrieb:
> Das hast du mit dem Stack doch sowieso... :-)
LOL
Welcher Stack?
(Hintergrund: Der C-Standard fordert keinen Stack. Unterprogramme müssen
wieder zum Aufrufer zurückfinden. Aber es ist nirgends spezifiziert, wie
dieser Mechanismus zu geschehen hat. Solange eine zuverlässige
Kristallkugel vorhanden ist, wäre die auch zulässig :-)
Ich geh mal davon aus, dass in meiner CPU winzigkleine Japaner wohnen,
die über ein Mords Gedächtnis verfügen :-)
1 Stunde 10.
Ich geh mal Schnee vom Auto schaufeln und dann ab ins Kino.
Avatar in 3D
Sieht so aus, als ob das wieder mal ein wunderschönes Beispiel ist für
* Time for Space
Investier ein bischen Speicher um die Laufzeit drastisch
zu drücken
* Beim Optimieren auf algorithmischer Ebene anfangen
Subtraktionen zu optimiern, hätte nicht viel gebracht
@khb:
Jetzt wirst du aber ziemlich pingelig.
Ob du es Stack nennst oder nichts davon wissen willst:
Bei rekursiver Programmierung brauchst du irgendwas dynamisches.
Aber im Ernst: Im OP wird ja auch für alle Werte ab 1 ein Feld
allokiert (iterative Version). Nichts anderes mache ich auch.
Nur halt aus Bequemlichkeit automatisch, dahinter steckt nichts
anderes als ein Feld mit allen Werten ab 1.
Du hattest ja mit deiner Aussage weiter oben vollkommen recht, daß
das wiederholte Berechnen der Zwischenwerte tötlich ist.
Diese zu cachen ist doch nicht illegitim, sondern einfach
sinnvoll m.E..
Selbst wenn du jetzt die Zeit hast, auf Q(50) zu warten, wirst du
halt bei Q(60) oder Q(100) scheitern mit dem päpstlichen Weg.
Klaus Wachtler schrieb:
> @khb:> Jetzt wirst du aber ziemlich pingelig.> Ob du es Stack nennst oder nichts davon wissen willst:> Bei rekursiver Programmierung brauchst du irgendwas dynamisches.
Schon klar.
Aber üblicherweise wird der Stack bei der Betrachtung von 'dynamischer
Programmierung' ausgeklammert.
Das würde ja sonst den ganzen Begriff ad absurdum führen. Denn jedes
Programm, welches über Funktionsaufrufe verfügt, hat so etwas.
> Du hattest ja mit deiner Aussage weiter oben vollkommen recht, daß> das wiederholte Berechnen der Zwischenwerte tötlich ist.> Diese zu cachen ist doch nicht illegitim, sondern einfach> sinnvoll m.E..
:-)
behauptet ja auch keiner, dass das illegitim wäre. Kann es sein, dass du
das in den falschen Hals gekriegt hast?
> Selbst wenn du jetzt die Zeit hast, auf Q(50) zu warten, wirst du> halt bei Q(60) oder Q(100) scheitern mit dem päpstlichen Weg.
Völlig klar.
Wenn ich demnächst einmal Zeit habe, baue ich mir eine Graphik wie die
Rechenzeit ansteigt. Das wird ein Graph werden!
Aber was solls.
Der Rechner läuft sowieso und fadisiert sich die halbe Zeit.
@Karl heinz:
Läuft das bei dir auf einem C64, oder hast du vielleicht noch die
Debug-Printfs noch drin? ;-)
Bei mir dauert es mit deiner Routine vom 08.01.2010 18:50 für etwas
größere n ziemlich genau
wobei c=32.5ns eine rechnerabhängige Konstante ist (Pentium 4 3,2GHz).
yalu schrieb:
> @Karl heinz:>> Läuft das bei dir auf einem C64, oder hast du vielleicht noch die> Debug-Printfs noch drin? ;-)
printf sind raus.
Debug Version, gebaut mit VC++ 6.0
Vis Studio ist offen, der Browser ist offen und da läuft auch sonst noch
was im Hintergrund :-)
Ohne die Gültigkeitsabfrage (n<1), die beiden Initialisierungen
zusammengefasst, das Ganze mit unsigned und mit -O3 statt -O2 kompiliert
(auf dem GCC), aber immer noch rekursiv, dauert es für n=50 noch 5m51.
1
#include<stdio.h>
2
3
unsignedintQ(unsignedintn){
4
if(n<=2)
5
return1;
6
returnQ(n-Q(n-1))+Q(n-Q(n-2));
7
}
8
9
intmain(void){
10
printf("%d\n",Q(50));
11
return0;
12
}
Ich muss jetzt nämlich ebenfalls die Biege machen ;-)
@Karl Heinz:
Hast gerade eben einen Lauf auf dem Forenrechner gestartet, oder warum
hat der für ein paar Minuten nicht mehr geantwortet?
Dynamische Programmierung bedeutet dass man sich Zwischenergebnisse in
einer Tabelle merkt und wieder verwendet ;)
EDIT: Um noch ein bisschen klug zu scheißern handelt es sich um Top-Down
DP auch Memoization genannt, daneben gibt es noch iteratives bottom-up
DP :) Habe ich vor 2 Wochen meiner Erstitutoriumsgruppe verklickert :)
So jetzt beginnt auch unser DVD abend.
Habs leider übersehen, wann der Rechner fertig wurde :-(
Auf jeden Fall kommt 25 raus :-)
>> Apfelmännchen im Seepferdchental der Mandelbrotmenge ist>> auch eine schöne Übung zum Zeitvertreib> Aber erst, wenn das einer als Hausaufgabe machen muß und> selber keine Lust dazu hat :-)
:-)
Ich liebe solche Fingerübungen. Kleine Programme, mit denen man so
herrlich spielen kann.
In meiner Anfangszeit hab ich mir den Spass gemacht, mit der Stoppuhr
neben dem Rechner einen Bubblesort zu 'trainieren'. Den konnte ich zum
Schluss morgens um halb 4 fehlerfrei im Schlaf runterrasseln.
(Ein bischen einen Vogel darf jeder haben :-)