• ベストアンサー

ハノイの塔

★自分が理解している事 「(n-1)ハノイが解けると仮定するとnハノイも解けること」 は理解できます。 そして数学的帰納法によりすべての自然数についてハノイは「解ける」 ここまではわかります。 ★わからないのは、その「解き方」です。 「解ける」ことはわかるのですが「解き方」がわからないのです。 何故このプログラムで正解が表示されるのかが理解できないのです。 確かに、紙と鉛筆でプログラムの流れを追っていくと解けています。 しかし、何でこのプログラムで解けるのかがわからないのです。 棒xは出発点 棒yは目的地 棒zは作業棒 です。 (n-1)ハノイをひとかたまりと考え、作業棒Zに移す。 nハノイを目的地Yに移す。 (n-1)ハノイをひとかたまりと考え、目的地Yに移す。 そんな説明で確かに納得した気になりはします。 しかしこのプログラムでは(n-2)以下の場合についてはいっさい語っていません。 何かだまされてる気がします。 このプログラムでは(n-1)について語っていますが (n-2)以下については全く語っていません。 数学的帰納法により「解ける」ことは証明済みですが、 「その解き方」がこのプログラムでよいという「証明」はありますでしょうか? 理解のコツはありますでしょうか? よろしくお願いいたします。 #include<stdio.h> #include<stdlib.h> void hanoi(int n, char x, char y, char z) { if(n==0) {/* 何もしない */} else { hanoi(n-1,x,z,y); printf("%c->%c,",x,y); hanoi(n-1,z,y,x); } } int main(void) { int num; scanf("%d",&num); hanoi(num,'A','B','C'); return 0; }

質問者が選んだベストアンサー

  • ベストアンサー
  • fonera
  • ベストアンサー率52% (38/72)
回答No.2

恐れ入ります。 「(n-1)ハノイが解けると仮定すると、nハノイも解ける」が正しい場合、 「nハノイが解けると仮定すると、(n-1)ハノイも解ける」が正しいというのは理解されていますか? 「3ハノイが解けるときには、4ハノイが解ける」のが正しいときには常に 「4ハノイが解けるときには、3ハノイが解ける」は正しくなります。 # 判りづらければ、自然数の1からスタートしていると考えてみて下さい。 hanoi(3,'A','B','C');でスタートすると  hanoi(n-1,x,z,y);→hanoi(2, 'A', 'C', 'B')  printf("%c->%c,",x,y);→printf("A→C")  hanoi(n-1,z,y,x);→hanoi(2, 'C', 'B', 'A') となるのまでは理解されていると思います。 あとは、これを再帰(自分自身を呼び出す)していくだけです hanoi(3,'A','B','C');でスタートすると  hanoi(n-1,x,z,y);→hanoi(2, 'A', 'C', 'B')   hanoi(n-1,x,z,y);→hanoi(1, 'A', 'B', 'C')    hanoi(n-1,x,z,y);→hanoi(0, 'A', 'C', 'B')    printf("%c->%c,",x,y);→printf("A→B")    hanoi(n-1,z,y,x);→hanoi(0, 'C', 'B', 'A')   printf("%c->%c,",x,y);→printf("A→C")   hanoi(n-1,z,y,x);→hanoi(1, 'B', 'C', 'A')    hanoi(n-1,x,z,y);→hanoi(0, 'B', 'A', 'C')    printf("%c->%c,",x,y);→printf("B→C")    hanoi(n-1,z,y,x);→hanoi(0, 'A', 'C', 'B')  printf("%c->%c,",x,y);→printf("A→B")  hanoi(n-1,z,y,x);→hanoi(2, 'C', 'B', 'A')   hanoi(n-1,x,z,y);→hanoi(1, 'C', 'A', 'B')    hanoi(n-1,x,z,y);→hanoi(0, 'C', 'B', 'A')    printf("%c->%c,",x,y);→printf("C→A")    hanoi(n-1,z,y,x);→hanoi(0, 'B', 'A', 'C')   printf("%c->%c,",x,y);→printf("C→B")   hanoi(n-1,z,y,x);→hanoi(1, 'A', 'B', 'C')    hanoi(n-1,x,z,y);→hanoi(0, 'A', 'C', 'B')    printf("%c->%c,",x,y);→printf("A→B")    hanoi(n-1,z,y,x);→hanoi(0, 'C', 'B', 'A') 0ハノイは解ける→1ハノイは解ける→2ハノイは解ける→・・・→nハノイは解けるをやっているだけです。 nから逆に下って行ってるだけですね。

karasu4649
質問者

お礼

>「(n-1)ハノイが解けると仮定すると、nハノイも解ける」が正しい場合、 >「nハノイが解けると仮定すると、(n-1)ハノイも解ける」が正しいというのは理解されていますか? はい。というか、すべての自然数nにおいてハノイは解けることが 証明済みですので(n-1)ハノイも解けるのは当たり前です。 僕が知りたいのは hanoi(n-1,x,z,y); hanoi(n-1,z,y,x); この引数の変え方で正解をだせることの証明がほしいのです。 確かに、n=4ぐらいで紙と鉛筆でやってみると確かに正解は出ます。 しかし、n=100でもこの引数の変え方でよいのはどこで担保されているのか、その証明がほしいのです。

その他の回答 (4)

  • Oh-Orange
  • ベストアンサー率63% (854/1345)
回答No.5

★アドバイス ・n=3、n=4のハノイが理解出来ているとすると  n=4は上に3枚、下には4枚目があることになる。  この状態で4枚目を移動しないと考えると上の3枚までは  ハノイの移動があなたでも正しく移動できることを証明できますよね。 ・なら上の3枚が移動した状態で4枚目を移動するには  4枚目を3枚ある以外の残りの場所に移動します。  その後に3枚のハノイ移動を行うとn=4のハノイ移動が完了します。 ・移動回数を列挙すると  n=3…7回  n=4…15回  n=5…31回  n=6…63回  n=7…127回  n=8…255回  n=9…511回  n=10…1,023回  :  n=20…1,048,575回  :  n=30…1,073,741,823回  :  n=50…1,125,899,906,842,623回  :  n=100…1,267,650,600,228,229,401,496,703,205,375回  となります。  ここでnの数と回数に規則的な法則があります。分かりますか?  『回数』=『(2のn乗)から1引く』  このような関係があるのです。 ・だから  n=10のハノイ移動を考えるときは上に9枚、下に10枚目  n=9のハノイ移動を考えるときは上に8枚、下に9枚目  n=8のハノイ移動を考えるときは上に7枚、下に8枚目  n=7のハノイ移動を考えるときは上に6枚、下に7枚目  n=6のハノイ移動を考えるときは上に5枚、下に6枚目  n=5のハノイ移動を考えるときは上に4枚、下に5枚目  n=4のハノイ移動を考えるときは上に3枚、下に4枚目  n=3のハノイ移動を考えるときは上に2枚、下に3枚目  n=2のハノイ移動を考えるときは上に1枚、下に2枚目  と考えていけばよいので >hanoi(n-1,x,z,y); >hanoi(n-1,z,y,x); >この引数の変え方で正解をだせることの証明がほしいのです。  この引数の変え方だけで正解を出せることになるのです。  上記の考えこそが『証明』なのです。 ※本当は図形で考えれば分かりやすいんですよ。 ※書くよりも。直感で分かるから。 参考にして下さい。

  • asuncion
  • ベストアンサー率33% (2126/6288)
回答No.4

> 「解ける」ことが証明済みであることと > 「解き方」がわかることは全く別なのですが。 解き方がわかるかわからないかは、その人の理解力の話ですね。

karasu4649
質問者

お礼

いや、ですから、 hanoi(n-1,x,z,y); hanoi(n-1,z,y,x); この引数の変え方で正解をだせることの証明がほしいのです。 あなたはせいぜいn=4ぐらいで満足しているのではないのですか? それともすべての自然数nにおいて、この引数の入れ替え方でOKであることの 証明を持っているのでしょうか? n=4か5ぐらいで満足することに何の価値もありません。 一般的、つまり∀nについて成り立つ証明にこそ、価値があるのです。 ハノイに関しては5冊ぐらい読みました。 しかし∀nに関しての証明が書いてある本は皆無でした。 そして私には理解力がない、だから質問をさせていただいているのです。

karasu4649
質問者

補足

ちなみに私は6回ぐらい「やっと理解できた」という感覚にいたりました。 しかし、数日後、「いや、やはり理解できていない」 「このプログラムで正解が出るのはたまたまなのではないか?」 「偶然なのではないか?必然性はあるのだろうか?」 と思ってしまうのです。 「これを理解するには証明が無ければダメだ」 そう思っているのです。

  • asuncion
  • ベストアンサー率33% (2126/6288)
回答No.3

> すべての自然数nにおいてハノイは解けることが > 証明済みですので(n-1)ハノイも解けるのは当たり前です。 これがおわかりなのでしたら、nがいくらであってもOKですよね。

karasu4649
質問者

お礼

「解ける」ことが証明済みであることと 「解き方」がわかることは全く別なのですが。

  • php504
  • ベストアンサー率42% (926/2160)
回答No.1

再帰関数なので最初の呼び出しから見たら関数が呼ばれるたびに hanoi(n - 1, ) hanoi(n - 2, ) . . . hanoi(1, ) hanoi(0, ) となります。

関連するQ&A

  • ハノイの塔

    次のc言語で書かれたハノイの塔のプログラムをZ80で動作させたいのですが、アセンブルするとどうなるのでしょうか??教えてください。 void move(char n,char a,char b){ if(n>1)move(n-1,a,6-a-b); if(n>1)move(n-1,6-a-b,b); } int main(){ char n=5; move(n,1,2); }

  • C言語 ハノイの塔

    #include<stdio.h> void hanoi(int n,char a,char b,char c); int main(void) { int n=3; printf("円板の枚数 ⇒ " ); hanoi(n,'a','b','c'); return 0; } void hanoi(int n,char a,char b,char c) { if(n > 0){ hanoi(n - 1,a,c,b); printf("%d番の板を %c から %c に移動\n",n,a,b); hanoi(n - 1,c,b,a); } } このときの再帰の処理がわかりません。。 再帰の間にprintfがあるのでどこがつながっているのか順番がわかりません。 具体的な数値の手順を教えてください。

  • perl ハノイの塔の解に順番付けする方法

    プログラミング初心者です。 サブルーチンを用いて、ハノイの塔の解を求める課題で詰まっています。 解自体は求めることができたのですが、それぞれの解の順番(輪を移動させる順番)を一緒に表示させよという指示が出ており、この方法がわかりません。 表示順に上から番号を振るという考え方でよろしいのでしょうか。 まず考え方自体が違うようであれば、そこから指摘して頂きたいと思います。 よろしくお願いします。 ちなみに現状ではこのようになっております。 #!/usr/bin/perl sub hanoi { my ($no , $x , $y , $z) = @_; if( $no = $no){ hanoi ($no-1,$x,$z,$y); print "Move", $no ,"from" , "$x" , "to" , "$z" , "\n"; hanoi ($no-1,$y,$x,$z); } } print"入力された枚数のハノイの塔の解(順番付き)\n"; $data=<>*1; hanoi($data , "A" , "B" , "C");

  • ハノイの塔 関数内の進み方

    ■プログラムは後半に書いてます。よろしくお願いします。      no x y ――――――― 10行目 3 1 3 10行目 2 1 2 10行目 1 1 3 printf ■この後の5回(できれば10回)の進み方を教え貰えると嬉しいです。 ■わからない理由 printfのとき、(no, x, y) = (1, 1, 3)でno = 1。 そのため13行目は行われない。 しかし、returnもbreakもないためまた6行目に戻るのでしょうか?(答えを見ると6行目に行きそうです。) (no, x, y) はどのような値で関数が始まるのでしょうか? 9行目があるのにno=2or3で表示される理由がわからないです。 01|#include <stdio.h> 02|#include <conio.h> 03| 04|#define N 3 05| 06|void move(int no,int x,int y) 07|{ 08| static int i=1; 09| if(no>1) 10| move(no-1,x,6-x-y); 11| printf("%dを%d軸から%d軸へ移動 : %d\n",no,x,y,i++); 12| if(no>1) 13| move(no-1,6-x-y,y); 14|} 15| 16|int main(void) 17|{ 18| move(N,1,3); 19| 20| _getch(); 21| return(0); 22|}

  • 関数についての質問です。

    c言語を勉強しています。関数で戻り値がありますが、関数の処理の仕組みが理解できません。本等で勉強し僕の考え方が間違っていたら教えてください。 お願いいたします。 #include <stdio.h> int buy(int x,int y ) { int z; //1 printf("%d万円と%d万円の車を買いました。");//2 z=x+y; //3 return z; //4 } int main(void) { int num1,num2,num;//5 printf("いくらの車をかいますか?\n");//6 scanf("%d",&num1);//7 printf("いくらの車をかいますか?\n");//8 scanf("%d",&num2);//9 sum=buy(num1,num2);//10 printf("合計で%d万円です。\n",sum);//11 return 0;//12 } まず最初に 整数型、int num1,num2,numを読みます。 次に//7と//8で数値を入力します。 そして//10で値を格納し int buy( int x,int y)に値をわたします。 ※正確には関数buyに渡す。 そして計算をし計算結果 zをreturn で int buy(int x,int y )の buyに値を渡します。 そのあと呼び出し元の//10のbuyに値をかえします。 そしてプログラムが終了します。 間違っていたら教えてください。

  • オブジェクト(メモリ)のアドレスについて

    ■C++言語を勉強中です。 ■ポインター関係でオブジェクトのアドレスを求めています。 ■参考にC言語とC++言語で求めてみました。 ■ところが、C++プログラムでchar型のアドレスが表示されません。 ■C言語では表示されます 「質問」理由が分かりません、C++初心者です、宜しくお願いします。 //オブジェクトのアドレス //C++言語 #include <iostream> using namespace std; int main() { char x;      int y; double z; cout << "xのアドレス :" << &x << '\n'; cout << "yのアドレス :" << &y << '\n'; cout << "zのアドレス :" << &z << '\n'; return 0; } /* //C言語 #include <stdio.h> int main() { char x; int y; double z; printf("xのアドレス :%p \n",&x); printf("yのアドレス :%p \n",&y); printf("zのアドレス :%p \n",&z); return 0; } */  

  • C言語によるハノイの塔のプログラムの実行の流れ

    解きながら学ぶC言語の問題8-9のハノイの塔のプログラムについです。ソースコードは #include<stdio.h> #define N 3 void move(int no,int x,int y) { if(no>1) move(no-1,x,6-x-y); printf("%dを%d軸から%d軸へ移動\n",no,x,y); if(no>1) move(no-1,6-x-y,y); } int main(void) { move(N,1,3); return 0; } ============================== 実行結果 1を1軸から3軸へ移動 2を1軸から2軸へ移動 1を3軸から2軸へ移動 3を1軸から3軸へ移動 1を2軸から1軸へ移動 2を2軸から3軸へ移動 1を1軸から3軸へ移動 ============================== 最近大学の講義でこのプログラムの実行の流れを習ったのですが、どうにもよく理解することができません。 まず、move(no,x,y)にはそれぞれno=3,x=1,y=3の数が入っていますよね。 そして、no>1なので、move(no-1,6-x-y,y)の式に代入してmove(3-1,1,6-1-3)となるとno=2,x=1,y=2となるのでmove(2-1,1,6-1-2)となるのでこのときno=1,x=1,y=3。 この時点でno>1ではなくなりprintf関数によって、1を1軸から3軸へ移動、2を1軸から2軸へ移動と表示されると言うことだと思っているのですが。 ここから先がさっぱりです、なぜ1を3軸から2軸へ移動となるのでしょうか? 一つもどってno=2,x=1,y=2のときmove(no-1,6-x-y,y)の式に代入されるということなのでしょうか? どうして急にmove(no-1,x,6-x-y)の式で計算していたものがmove(no-1,6-x-y,y)の式で計算するようになるのか、がわかりません。 どちらも条件分岐はif(no>1)ですし、このif文が何のためにあるのかもよくわからないのです。 自分でも書いていて要点がはっきりしませんが、どなたかお願いいたします。

  • ARMプロセッサ,NEONの並列化について

    現在組み込み化プログラムの勉強をしております. ARMプロセッサのNEONを用いて並列化を試みているのですがうまくいきません... 一般的なCソースに対して,ループ内の配列に,"__restrict"をつけて, 下記のコマンドを用いております. すると,__restrictをつけた方がなぜか遅くなる始末... また-Sで出力したアセンブラを見てもNEON固有の命令 (先頭にVがつくもの)が無いようです. どなたかお分かりになりますでしょうか... よろしくお願いします. <(_ _)> コマンド arm-none-linux-gnueabi-gcc -Wall -O3 -march=armv7-a -mtune=cortex-a8 -ftree-vectorize -mhard-float -mfloat-abi=softfp -mfpu=neon -mvectorize-with-neon-quad -fno-strict-aliasing -o output_file input_file /********************ここからソース********************/ //NEONの自動化を検証するプログラム #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <arm_neon.h> #define NUM_VAL 10 #define LOP_VAL 100000000 char* __restrict vmla(char* __restrict a, char* __restrict x, char* __restrict y, char* __restrict z); int main(void); char* __restrict vmla(char* __restrict a, char* __restrict x, char* __restrict y, char* __restrict z){ //ポインタの初期化 char p = 0; a = &p; x = &p; y = &p; z = &p; int i, j; for(j = 0; j < LOP_VAL; j ++){ for(i = 0; i < NUM_VAL; i++){ a[i] = x[i] * y[i] * z[i]; } } return a; } int main(void){ char an = 0; char a_val[NUM_VAL]; char x_val[NUM_VAL]; char y_val[NUM_VAL]; char z_val[NUM_VAL]; for(an = 0; an < NUM_VAL; an ++){ a_val[an] = 0; x_val[an] = 0; y_val[an] = 0; z_val[an] = 0; } time_t time_start; time_t time_stop; printf("Start\n"); time_start = clock(); vmla(a_val, x_val, y_val, z_val); time_stop = clock(); printf("Processing time is %f\n", (double)(time_stop - time_start)); return 0; }

  • opencvで特定の座標を指定しその座標の色変換

    #include <cv.h> #include <highgui.h> #include <stdio.h> /* グローバル変数 */ CvFont font; IplImage *img; int num1=0; int num2=0; int num3=0; int num4=0; /* プロトタイプ宣言 */ void on_trackbar1 (int val1); void on_trackbar2 (int val2); void on_trackbar3 (int val3); void on_trackbar4 (int val4); int main (int argc, char *argv[]) { img = cvLoadImage ( "bgr.jpg", CV_LOAD_IMAGE_COLOR); cvInitFont (&font, CV_FONT_HERSHEY_DUPLEX, 1.0, 1.0); cvNamedWindow ("Image", CV_WINDOW_AUTOSIZE); cvCreateTrackbar ("X", "Image", 0, 255, on_trackbar1); cvCreateTrackbar ("Y", "Image", 0, 255, on_trackbar2); cvCreateTrackbar ("COLOR", "Image", 0, 255, on_trackbar3); cvCreateTrackbar ("-", "Image", 0, 100, on_trackbar4); cvShowImage ("Image", img); int x,y; uchar p[3]; for (y = 0; y < img->height; y++) { for (x = 0; x < img->width; x++) { p[0] =((uchar*)(img->imageData + img->widthStep*num2))[num1*3]; // B p[1] =((uchar*)(img->imageData + img->widthStep*num2))[num1*3+1]; // G p[2] =((uchar*)(img->imageData + img->widthStep*num2))[num1*3+2]; // R p[0] = num3; p[1] = num3; p[2] = num3; } } cvWaitKey (0); cvDestroyWindow ("Image"); cvReleaseImage (&img); return 0; } /* コールバック関数 */ void on_trackbar1 (int val1) { num1 = val1; char str[64]; printf ( "X=%d Y=%d C=%d -=%d\n", val1, num2, num3, num4); } void on_trackbar2 (int val2) { num2 = val2; char str [64]; printf ( "X=%d Y=%d C=%d -=%d\n", num1, val2, num3, num4); } void on_trackbar3 (int val3) { num3 = val3; char str [64]; printf ( "X=%d Y=%d C=%d -=%d\n", num1, num2, val3, num4); } void on_trackbar4 (int val4) { num4 = val4; char str [64]; printf ( "X=%d Y=%d C=%d -=%d\n", num1, num2, num3, val4); } このプログラムで特定の座標の色変換をしたいのですがうまくいきません。 トラックバー1でX座標 トラックバー2でY座標 トラックバー3で変更後の色 を指定します。 実行すると画像は表示されますがトラックバーを動かしても変化がありません。トラックバーを動かした時にmain関数に戻れば良いと思い、思いつく限り試しましたが上手くいきません。 どなたかアドバイスお願いします。

  • c言語の難しい問題について

    (c言語の問題) 下記のプログラムを完成させ、キーボードから文字列を読み込み、-1文字ずらすことによって暗号化を行うプログラムを作りなさい。ただし、ピリオド、空白などはそのままにするようにすること。 例)this is a pen. sghr hr @ qdm. #include<stdio.h> #define CHAR_NUM 256 void angou( I ) { II } int main(void) { unsigned char text[CHAR_NUM]; char moji; int i; puts("暗号化する文字を入力しなさい。"); while((moji=getchar()) !=EOF){ text[i]=moji; i++; } angou(text i); printf("%s",text); return(0); } I、IIに入る文を書きなさい。 私はIには「char x[],int y」 IIには 「if('A'<x[i]<'Z' && 'a'<x[i]<'z') int j; for(j=0;j<y;j++) x[j]=x[j]-1 else」 といれたのですが、出力がうまくでません。どうすればいいのですか?

専門家に質問してみよう