いろいろありますけど(講義編)

大学であったことなどを中心に書いていきます。統計学とC言語の講義が主です。

ハノイの塔 第11回

2005-08-08 11:36:42 | アルゴリズム
ではプログラムです。


#include <stdio.h>
#include <math.h>

#define PEGS 4
#define MAX 100

int hanoi4(int, int, int, int, int, int);
int hanoi3(int, int, int, int);
void move(int, int);
void initPeg(int);
void printPeg(void);
int isqrt(int);

int peg[PEGS][MAX + 1];

int main(void) {
    int size, count, h;
    int xlist[10];
    
    fprintf(stderr, "ハノイの塔の大きさは(1-%d):", MAX);
    scanf("%d", &size);
    
    initPeg(size);
    printf("大きさ%dの%dペグのハノイの塔の移動方法は以下の通りです。\n---\n", size, PEGS);
    printPeg();
    count = hanoi4(size, 0, 3, 1, 2, 0);

    printf("---\n以上%d手です。\n", count);
    return(0);
}

int hanoi4(int n, int from, int to, int other1, int other2, int flag) {
    int count = 0;
    int h3, h4;

    /* 最適分割数の計算 */
    h3 = (isqrt(1 + 8 * n) - 1) / 2;
    if((flag == 1) && (h3 * (h3 + 1) != 2 * n)) { h3++; }
    h4 = n - h3;
    
    if(h4 > 0) {
        count += hanoi4(h4, from, other1, other2, to, flag);
    }
    count += hanoi3(h3, from, to, other2);
    if(h4 > 0) {
        count += hanoi4(h4, other1, to, other2, from, flag);
    }

    return(count);
}

int hanoi3(int n, int from, int to, int other) {
    int count = 1;
    if(n == 1) {
        move(from, to);
    } else {
        count += hanoi3(n - 1, from, other, to);
        move(from, to);
        count += hanoi3(n - 1, other, to, from);
    }
    return(count);
}

void move(int from, int to) {
    int n = peg[from][peg[from][0]];
    
    printf("%04dを%cから%cへ動かす\n", n, (char)(0x41 + from), (char)(0x41 + to));
    peg[to][++peg[to][0]] = n;
    peg[from][peg[from][0]--] = 0;
    printPeg();
}

void initPeg(int n) {
    int i, j;
    
    for(j = 0; j <= n; j ++) {
        peg[0][j] = n - j + 1;
        for(i = 1; i < PEGS; i ++) {
            peg[i][j] = 0;
        }
    }
    peg[0][0] = n;
}

void printPeg(void) {
    int i, j, max = 0;
    
    printf("     ");
    for(i = 0; i < PEGS; i++) {
        printf("  |   ");
        if(max < peg[i][0]) {
            max = peg[i][0];
        }
    }
    for(j = max; j > 0; j--) {
        printf("\n     ");
        for(i = 0; i < PEGS; i++) {
            if(peg[i][j] == 0) {
                printf("  |   ");
            } else {
                printf("%05d ", peg[i][j]);
            }
        }
    }
    printf("\n     ");
    for(i = 0; i < PEGS; i++) {
        printf("--%c-- ", (char)(0x41 + i));
    }
    printf("\n\n");
}

int isqrt(int x) {
    int i;
    
    for(i = 0; i * i <= x; i++);
    return(--i);
}



続きは次回に

ハノイの塔 第10回

2005-08-05 10:40:41 | アルゴリズム
手順数の数式自身は求まりました。


x=「( -1+√(1+8n) ) / 2」の整数部分
m=n-x(x+1)/2
としたとき
b(n)=(x+m-1) 2^x+1

この結果を考えてみます。
ここで、xは√nの式ですから、xのオーダは√nとなります。したがって、最終的なb(n)のオーダは√n×2^√nということになります。3本の普通のハノイの塔の場合は、a(n)=2^n-1でしたから、オーダは2^nです。これに比べれば、かなり小さくなった
ことがわかります。ここまで式を作る前には4本のハノイの塔の場合は2^√nくらいと予想していたのですが、前に余分な√n がついているのです(ゆっくり考えたら、ここにnの式が出てくることは自然なこととわかりました)。
こうなると、次は5本の場合のハノイの塔の手順数のオーダが気になります。いまの結果を考えると、

  1. n^(3/4) × 2^(nの4乗根)
  2. n^(3/4) × 2^(nの3乗根)

のどちらかだと思います。一般的にペグがk本のときには

  1. n^((k-3)/(k-2)) × 2^(nの2^(k-3)乗根)
  2. n^(1-(1/2)^(k-3)) × 2^(nの2^(k-3)乗根)
  3. n^((k-3)/(k-2)) × 2^(nの1/(k-3)乗根)
  4. n^(1-(1/2)^(k-3)) × 2^(nの1/(k-3)乗根)

となるでしょう。どちらの場合もk→∞のときには、nになるものです。これは間違いありません。ペグが無限にあるときには、n枚のディスクに対しては、一度すべてバラバラにして逆順に乗せていけばいいのですから常に2n-1手で終了します。オーダはnです。さて、どちらの予想が正しいのでしょうか。


続きは次回に

ハノイの塔 第9回

2005-08-04 12:36:06 | アルゴリズム
前回やっと数式ができました。


さて、前回の二つの式を比べてみましょう。この式は最適なxがただ一つに決まらない場合として作りました。
b(n)=(2x-k) 2^x+1
b(n)=(x+m-1) 2^x+1
ここのkとmの間には、k+m=x+1の関係がありますから、
2x-k=2x-(x+1-m)=x+m-1
となることから、実は二つの式は同じものであることがわかります。
したがって、まとめれば次のようになります。
x=「( -1+√(1+8n) ) / 2」の整数部分
m=n-x(x+1)/2
としたとき
b(n)=(x+m-1) 2^x+1
となります。
では、最適なxがただ一つに決まる場合はどうでしょうか。このときにはm=0ということになりますから、第7回の
b(n)=(x-1) 2^x+1
に一致します。
さらに、n=1のときはx=1, m=0となりますから、
b(1)=(1+0-1) 2^1+1=1
となって、一致します。このことから、次の式はすべてのnについて成り立つことがわかります。

x=「( -1+√(1+8n) ) / 2」の整数部分
m=n-x(x+1)/2
としたとき
b(n)=(x+m-1) 2^x+1

これで計算してみます。4本の場合と3本の場合と並べてみます。
n = 1 : 1 : 1
n = 2 : 3 : 3
n = 3 : 5 : 7
n = 4 : 9 : 15
n = 5 : 13 : 31
n = 6 : 17 : 63
n = 7 : 25 : 127
n = 8 : 33 : 255
n = 9 : 41 : 511
n = 10 : 49 : 1023
n = 11 : 65 : 2047
n = 12 : 81 : 4095
n = 13 : 97 : 8191
n = 14 : 113 : 16383
n = 15 : 129 : 32767
n = 16 : 161 : 65535
n = 17 : 193 : 131071
n = 18 : 225 : 262143
n = 19 : 257 : 524287
n = 20 : 289 : 1048575
n = 30 : 1025 : 1073741823
n = 40 : 2817 : 1099511627775
n = 50 : 6657 : 1125899906842623
n = 60 : 14337 : 1152921504606846975
n = 70 : 28673 : 1180591620717411303423
n = 80 : 53249 : 1208925819614629174706175
n = 90 : 94209 : 1237940039285380274899124223
n = 100 : 172033 : 1267650600228229401496703205375
こうして見ると、3本の場合に比べてかなり手数が減っていることがわかります。


続きは次回に

ハノイの塔 第8回

2005-08-03 11:14:39 | アルゴリズム
すこしずつ一般化した場合を考えてみます。


最適なxがただ一つに決まらない場合について考えたいと思います。つまり、x(x+1)/2<n<(x+1)(x+2)/2のときです。このときの最適な分割数はx or x+1となります。 b(n)=2b(n-x)+a(x)
この漸化式を解くわけですが、問題はb(n-x)にあります。n<(x+1)(x+2)/2ですから、(x+1)(x+2)/2-n=k(整数)とおくと、
x(x+1)/2-(n-x)=k-1
となって、しまいますから、この漸化式を一度使うたびごとに、上側の値に近づいていくことになります。例えば、n=8のときには、x=3です。このとき、(x+1)(x+2)/2=10ですから、10-8=2回漸化式で変換すると、前回特別な形になります。実際、
b(8)=2b(5)+a(3)=2(2b(3)+a(2))+a(3)=4b(3)+a(3)+2a(2)
で、このb(3)は前回の特別な形の場合b'(2)になります。
このことをまとめると、(x+1)(x+2)/2-n=kとおいたとき、
b(n)=2^k b(n-x-(x-1)-...-(x-k+1))+a(x)+2a(x-1)+...+2^(k-1) a(x-k+1)
となって、n-x-(x-1)-...-(x-k+1)=(x-k+1)(x-k+2)/2を満たしますから、
b(n)=2^k b'(x-k+1)+a(x)+2a(x-1)+...+2^(k-1) a(x-k+1)
となります。ここで、a(x)=2^x-1, b'(x)=(x-1) 2^x+1を代入して整理すれば、
b(n)=(2x-k) 2^x+1
となります。実際、n=8のときにはx=3, k=2でしたから、
b(8)=(2×3-2)×2^3+1=4×8+1=33
となって、あっています。
同様に、x+1を使った場合を考えれば、n-x(x+1)/2=mとおくと
(n-(x+1))-x(x-1)/2=m-1
となりますから、1回ごとに下の値に近づいていることがわかります。したがって
b(n)=2^k b(n-(x+1)-x-...-(x-m+2))+a(x+1)+2a(x)+...+2^(m-1) a(x-m+2)
となって、n-(x+1)-x-...-(x-m+2)=(x-m)(x-m+1)/2を満たしますから、
b(n)=2^m b'(x-m)+a(x+1)+2a(x)+...+2^(m-1) a(x-m+2)
となります。ここで、a(x)=2^x-1, b'(x)=(x-1) 2^x+1を代入すれば、
b(n)=(x+m-1) 2^x+1
となります。実際、n=8のときにはx=3, m=2でしたから、
b(8)=(3+2-1)×2^3+1=4×8+1=33
となって、あっています。
これでなんとか式ができたようです。


続きは次回に

ハノイの塔 第7回

2005-08-01 16:36:13 | アルゴリズム
まずは簡単に求めることができる場合を考えてみます。


最適なxがただ一つに決まる場合について考えます。つまり、n=x(x+1)/2のときです。
このときにb(n)=b'(x)とします。このとき、以下の漸化式が得られます。
  • b'(1)=1
  • b'(x)=2b'(x-1)+a(x)

a(x)=2^n-1ですから、これは簡単に解けそうです。
b'(x)=2b'(x-1)+2^x-1
b'(x)-x*2^x-1=2{b'(x-1)-(x-1)*2^(x-1)-1}
と変換できますから、b'(n)-x*2^x-1は初項1-1*2^1-1=-2公比2の等比数列になります。したがって、

b'(x)-x*2^x-1=-2*2^(x-1)=-2^x
b'(x)=x*2^x-2^x+1
b'(x)=(x-1)*2^x+1

となって手数が求まります。
検算してみます。
  • x=2→n=3→b(3)=b'(2)=(2-1)*2^2+1=1*4+1=5
  • x=3→n=6→b(6)=b'(3)=(3-1)*2^3+1=2*8+1=17
  • x=4→n=10→b(10)=b'(4)=(4-1)*2^4+1=3*16+1=49
  • x=5→n=15→b(15)=b'(5)=(5-1)*2^5+1=4*32+1=129
  • x=6→n=21→b(21)=b'(6)=(6-1)*2^6+1=5*64+1=321
  • x=7→n=28→b(28)=b'(7)=(7-1)*2^7+1=6*128+1=769
  • x=8→n=36→b(36)=b'(8)=(8-1)*2^8+1=7*256+1=1793
  • x=9→n=45→b(45)=b'(9)=(9-1)*2^9+1=8*512+1=4097

あっていますね。これで、まず第1段階の数式化ができました。ではこれを一般化していきたいと思います。はたして、うまくできるのでしょうか?


続きは次回に