ラベル Cによるアルゴリズム の投稿を表示しています。 すべての投稿を表示
ラベル Cによるアルゴリズム の投稿を表示しています。 すべての投稿を表示

2008年8月28日木曜日

【C算法】第6章 ソート (その8)

週の前半は、、、と毎週いっている。なんとかせねば。なんとか生きて乗り切った、という感じ。
さて、木構造が終わったので、6-8 ヒープソート。。。


  • 順序木  :兄弟の間に順序関係を有する木。
    無順序木 :兄弟の間に順序関係を有しない木。

    2進木  :度数がたかだか2の木。
    2分木  :兄弟に左右の区別がある2進木。

    完全2分木:根から下方へむかって、同一レベルでは左から順に詰められている2分木
    2分探索木:ノードの値とくらべて、左部分木の値は小さく、右部分木の値は大きい。
    ヒープ  :親の値が子の値以上になっている完全2分木。兄弟の大小関係は任意。
    半順序木 :ヒープの別名。

  • 完全2分木に配列の要素をわりあてたときの様相。

    a[i]の親はa[(i - 1) / 2]
    a[i]の左の子はa[i * 2 + 1]
    a[i]の右の子はa[i * 2 + 2]

    なんで??
  • ためす。

    1の親は、(1 - 1)/2 = 0
    1の左の子は、1 * 2 + 1 =3
    1の右の子は、1 * 2 + 2 =4
    2の親は、(2 - 1)/2 = 0
    2の左の子は、2 * 2 + 1 =5
    2の右の子は、2 * 2 + 2 =6
    3の親は、(3 - 1)/2 = 1
    3の左の子は、3 * 2 + 1 =7
    3の右の子は、3 * 2 + 2 =8
    4の親は、(4 - 1)/2 = 1
    4の左の子は、4 * 2 + 1 =9
    4の右の子は、4 * 2 + 2 =10

    たしかにそうなっている。なんでこうなるの?
  • まず、heapを階層毎にグループにわけると、
    {{0},{1,2},{3,4,5,6},{7,8,9,10,11,12,13,14},...}
    となる。法則性はある。。。かんがえる。。。
  • a[n]の子まで添字をふるには、まず、a[n]の階層の残りのノードと、既存のノード*2だけノードを処理する必要がある。
    ところで、木の左端の要素の系列は階層で上から下にならべると、a[0],a[1],a[3],a[7],a[15],...となる。これは(2^n) - 1である(n = 0,1,2,...)。
    一方各階層のノード数は、2^n (n=0,1,2,...)である。
    すると、a[(2^n) - 1]の子供に辿りつく前に添字をふるべきノードは同じ階層の残りだから、(2^n)-1個ある。ゆえに左端のノードについてはa[i]の子のノードがa[i*2 + (1 or 2)]というのはなりたっている。
    つぎに2^n個の階層のノードで、(2^n)-1から数えてたどりつくまでにp個のノードを経るような(p=[0,(2^n)-1])ノードに着目しよう。すなわち、(2^n)-1+pという添字のノードだ。
    さて子のノードに辿りつくまでに添字をふるべきノードの数は、同じ階層で、(2^n)-p個ある。ひとつしたの階層でもふるべきであり、同じ階層でその対象となるのは、p個だから、2*p個の添字をふる。よって、ふられる添字は、(2^n) - p + 2*p = (2^n)+p。
    さて、親の添字は、(2^n)-1+pであり、子の一歩のノードの添字は、(2^n)+p-2+(2^n)+p=2((2^n)-1+p)だから、これもa[i]の子がa[i*2 + ( 1 or 2)]というのがなりたつ。■
    これで親から子はわかった。
  • 子から親は、親から子を逆転させればOK。
  • これで、heapというデータ構造を配列で実現できることがわかった。
  • あとはちょいちょい。

おお!ついに「新版C言語によるアルゴリズムとデータ構造」を読了した!
これで、

  • 「例解UNIXプログラミング教室」
  • 「アルゴリズムC」
  • 「新コンピュータサイエンス講座 コンパイラ」
  • 「Operationg Systems Design and Implementation 3rd Ed.」

のどれでも手をつけられるようになった。
どれからいこうかなぁ。シプサとの関連で、コンパイラにしとこうかな。

2008年8月24日日曜日

【C算法】第10章 木構造

だんだん終局が近づいてきた。

  • n進木:ノードの度数がたかだかnの木。
  • 2進木:ノードの度数がたかだか2の木。
  • 2分木:ノードの度数がたかだか2であり、子に左右の区別がある木。
  • 2分探索木:キー値について、どのノードにおいても、それの左部分木のノードは小さく、それの右部分木のノードは大きい2分木。
  • 2分探索木を深さ優先探索して通りがけ順でみると、キー値の昇順でノードが得られる。
  • いまさらながら、2分探索木はおなじメンツでも違う構成があることに気付いた。

      6
     / \
    2   7
     \
      3

        6
       / \
      3   7
     / 


  • 完全2分木

    • 最下層以外は全て「詰まって」いる。
    • 最下層は、左側から詰めていく。
    • 根から下流へ、各層を左から右へと添字をふっていくと、配列の添字に対応できる。

          0
         / \
        1   2
       / \
      3   4


  • ん? list10-2で

    else if (cond < 0)
    SearchNode(p->left, x);
    else
    SearchNode(p->right, x);

    は、次の間違いでは?

    else if (cond < 0)
    return (SearchNode(p->left, x));
    else
    return (SearchNode(p->right, x));


最後のほうがぐだぐだになったが木構造がおわった。
次回は6-8 ヒープソート。

2008年8月23日土曜日

【C算法】第8章 線形リスト (その2)

こつこつ。

  • 演習9-1。そうか、enumの要素と関数名も衝突するんだ。
  • ちょっとだけ、日本語を読み書きするようにC言語を読み書きする感覚がでてきた。ちょっとだけ。
  • ポインタで書かれたものを頭の中で追ったり頭の中でいじくりまわしたりするのが楽しくなってきた。

おお、線形リストがおわった。次回は「木構造」。

2008年8月22日金曜日

【C算法】第8章 線形リスト

シプサでだいぶエネルギーを使ってしまった。このままだと徹夜で出勤なのだが、ちょっとだけ。

  • list0910までの写経をした。
  • 線形リスト自体は「実践編」で一度やったので無問題。

こつこつ。

2008年8月21日木曜日

【C算法】第8章 文字列処理 (その4)

こつこつ。今回はBM法。

  • BM法アルゴリズムの自分なりのまとめ。
  • テキスト"ABCXDEZCABACABAC"かパターン"ABAC"を探索する。

    • テキスト照合位置Tとパターン照合位置Pという2つの変数を使う。
    • パターンの文字列長は4文字である。そこで、テキストの4文字目以降にあらわれる最初の"C"を探す。
    • "C"がなければパターンは含まれていない。
    • "C"があったら、テキストのその位置から前へ照合をはじめる。この例の場合は、T=7, P=3となる。TとPを減算していって、パターンの文字全てが照合して同じならば探索成功。Pのインデックスまたはポインタを返す。
    • 照合が途中で失敗した場合は後続するテキスト内にパターンが存在するかどうか、探索を継続する。
      このケースだと、

      01234567890123456
      ABCXDEZCABACABAC\0
      ABAC

      なので、"Z"と"A"でいきなり違うので照合はストップする。ストップ時はT=6, P=2。
    • ここで、最後に照合に失敗した"Z"を検討対象にする。"Z"の場合はパターンにあらわれないので、パターンの最終照合時に判明した未照合文字数3だけTを進める。T=9, P=2。
    • ここでP=2のパターン文字は"A"なので、T=9以降の"A"を探す。するとT=10となる。
    • 照合再開。照合はお尻から実施するので、P=3にする。あわせて、T=11とする。

  • う、ちょっとわかったが一般性がたりないかな。もう一度トライ。

    • テキストの長さをTN、パターンの長さをPNとする。
    • TN < PNならばマッチしないので終了。そうでなければ次へ。
    • テキスト照合位置TPとパターン照合位置PPという2つの変数を使う。
    • 初期値をTP= TN - PN, PP = PN - 1とする。
    • 照合ループ:
      PPがPN - 1でなければ、適量(Kとする)を加算してPN-1とする。TP += Kとする。
      TPとPPの位置にある文字が同じならば、TPとPPとを減算して比較する。
      PP=0まで同じならば、マッチ。TPの値を返して終了する。
      PP=0になるまでに不一致が発生したなら、ループを抜ける。
    • 次の照合位置を決める作業。
    • まずTPの位置にある文字を見る(不一致した文字)。その文字によってTPの移動量が異なる。例えば、その文字がパターン文字列に含まれない文字ならば、TP += PP + 1だけ移動する。逆にその文字がパターン文字列に含まれるが、この照合位置では不一致であった場合もある。この場合は、その文字にあわせた移動をする。その移動の幅の計算がなぜ本のようでいいのかがわからない。。。。それは将来Knuthをやったときとかに考えるとして、今は飲みこんでおく
    • さらにもう一回移動を勘案する。TP以降で現在のPPの位置の文字と一致するまでTPをインクレイメントする。一致せずTNにいたれば、一致不可能にて終了。一致した場合は、「照合ループへ」

  • いまいちすっきりしないが、これくらいの理解でCでの実装へ。list0814を写経、上の私の理解とは違うところがあるが、大筋はあってる。
  • おもうに、C言語を使いこなしている、というのは、このように自然言語でアルゴリズムを書いたり理解したりしようとしてくても、C言語のまま、それが自然言語であるがごとくアルゴリズムを書いたり読んだりできるようなことを言うのかもしれない。そういう感覚でもCを書いていってみたい。

ふー。次回はやっと線形リスト。。。

2008年8月18日月曜日

【C算法】第8章 文字列処理 (その3)

こつこつ。今回は「8-3 文字列探索」

  • brute forthとあるが、brute forceの誤字と思われる。
  • 少なからぬ時間、Cとアルゴリズムに取り組んで、自分についてわかってきたことがある。

    • 数に弱い。基数と序数にかかわる計算に弱いのだ。例えば、30 - 20 = 10ということと、[20,30]に含まれる整数の個数が11個、(20,30]なら10個、(20,30)なら9個ということがどう関係しているかわかっていないのだ。だから配列を取り扱うときに、インデックスの境界での振舞いをすぐに見失う。これは訓練するしかない。
    • 頭のメモリというかスタックというか何かが小さい。他人のことはわからないが、おそらく自分が漠然と予想していた自分の能力よりも遥に小さいのが現実であり、そのギャップに対処できていない。だから、アルゴリズムとその実装について考えるときに、追いきれなくて思考停止することが多い。小さいものは仕方ないから、大きくする努力とともに、小さいものでなんとかやっていく工夫が必要。

  • KMP法の演習をやるなかでの知見。

    • 適切な情報を表示させようとprint文を設置しようとすることは、アルゴリズムの理解に有効である。
    • それらprint文を#if DEBUGとかでON/OFFするようにしているのだが、Cのマクロ、見ためが汚ない。。。
    • ログ出力のよい方法はあるのだろうか? 調べてみるとlog4cというものがあるようだ。使用例がみあたらない。先々調査する。


次回はBoyer-Moore法。

2008年8月16日土曜日

【C算法】第8章 文字列処理 (その2)

今回は「文字列の比較」

  • 日々すこしづつポインタに慣れていっている気がする。

次回は「文字列探索」。こつこつ。

【C算法】第8章 文字列処理

こつこつ。

  • literal: 文字通りの, a literal translation | 直訳; 文字で表現された, a literal error | 誤字;
  • list0801:何故16進数表示の制度を(CHAR_BIT + 3) / 4とするのかがわからない。
  • なんか、文字列になっていきなり基本に戻ったような。。。
  • list0805:そうか、ポインタのポインタを渡すのは、ポインタ自身を書き換えたいんだけどポインタそのものを渡したら、スコープの問題で、関数内での変更が反映されないからか。いまさら、やっと理解した。

今回は「文字列からの文字の探索」まで。次回は「文字列の比較」。

2008年8月15日金曜日

【C算法】第7章 集合 (その2)

今日はビットベクトル。

  • BsetMemNum、頭いいなぁ。こういうのを自分で発案できるようになると思えない。
  • list0705は、bsetlib.hにlist0704の関数のプロトタイプを書いとかないとコンパイルできなかった。gccの使い方がわるいのかな。
  • 演習0705。BsetIsSubsetとBsetIsPropserSubsetを作成。これ、やっぱりコーディング前にどれだけアルゴリズムを確認できるかが作業本体だった。アルゴリズムをCでどう書くか考える部分よりも、そもそものアルゴリズムを考える部分が大きい。
  • 自分の環境でintやunsigned longのサイズを確認しようとして、limits.hを見てみた。しかし、どのifマクロが効いて、結果どのUMAXなどが有効になるのかを追うのが面倒くさそう。そこで、gdb上でp sizeof(int)などとした。やはりgdbはREPL的。
  • 演習0706の可変ビットベクトルの実装に偉い時間がかかった。教訓。

    • ポインタにだんだん慣れてきたが、まだ弱い。ポインタが引数の関数に対して、呼び出しもとでオブジェクトを作ってその&を渡してあげるときに、呼び出しもとでもポインタをつくって渡しちゃったりする。それではまった。
    • ひとつの関数をつくるのと、ビットベクトルのライブラリを作るように、関数群をつくるのとを比べると、関数群の設計の訓練が足りていないことがわかる。これはどうやって訓練すべきか。

  • gdb上で関数を再定義して、そのまま走らせられれば便利なんだけどな。できるのかな?

次は文字列処理。こつこつ。

2008年8月14日木曜日

【C算法】第7章 集合

ちょっと体調悪し。

  • 演習7-2 で二分探索を活用するところでグダグダになった。
  • どうしても、再帰的に考えてしまって、Cらしい書きぶりを組めない。
  • あきらめて、再帰でやったらすんなり書けた。Cらしいアルゴリズムは徐々に、ということにするしかないかな。
  • 多値を使えれば関数のインターフェイスがきれいにできるところがあったが、Cでの多値のやりかたをしらないので諦めた。

こつこつ。次回は「7-3 ビットベクトルによる集合」だ。

2008年8月12日火曜日

【C算法】第6章 ソート (その7)

今回はヒープソート。

  • heap: a heap of bricks | れんがの山; in a heap | 山のように;
  • ああ、だめだ。本で推奨されているように、第10章で木構造をやってから、やろう。

というわけで、度数ソート。

  • 度数ソートを考えた人、えらい頭いいな。。。

次回は集合。こつこつ。

2008年8月11日月曜日

【C算法】第6章 ソート (その6)

こつこつ。今日はマージソート。

  • 私の環境では"mergesort"がstdlibにあるので衝突した。あり? stdlibにもいろいろあるのかな?
  • ファイル有効範囲の変数(Symbol?)にStaticをつけると、それは別のソースファイルから見えない(shadow)されるのか。

演習無しの4ページ進むのでお腹いっぱいという感じ。Cでアルゴリズムを書く練習と、Cで書かれたアルゴリズム実装を読む練習が足りてないからだろう。なので、今、練習になっていて、いっぱいいっぱいなのだろう。

【C算法】第6章 ソート (その5)

ちょっと、アルゴリズムを学ぶことが、なんだかわからなくなっている。

  • クイックソートのアルゴリズムで、なぜ分割がうまくいくのか、がわからない。
  • そういう方法で分割できる、ということは覚えれば、覚えられる。
  • それでいいのか、どうか。
  • それじゃ足りないとしたら、分割がわかる、というのはどういうことなのか。
  • アルゴリズムを理解する、ということ自体がわからない。。。
  • この方法(アルゴリズム)で、分割できることの証明を構築できればいいのかなぁ。
  • アルゴリズムを文章で書いてみる。

    • 配列aがある。大きさはN。型はint。値がすべての要素に入っている。
    • pl=0, pr=N-1という2つのインデックスポインタをつくる。
    • aの中から一つ要素を選ぶ。その値をxと呼ぶ。
    • plはa[pl]>=xが成立する要素が見つかるまで、右側に走査する。
    • prはa[pr]<=xが成立する要素が見付かるまで、左側に走査する。
    • 走査がおわったとき、pl > prならば、処理完了。そこで、インデックスが0 〜 pl-1がx以下の要素のグループ、pr+1 〜 N-1がx以上のグループ、pl>pr+1ならばpr+1〜pl-1がxに等しいグループの3つに分割されている。
    • 走査がおわったとき、pl <= prならば、a[pl]とa[pr]の値を交換して再度走査を開始する。

  • うーん。これを数学的に証明するのはちょっと面倒そうだ。場合わけが。。。
  • そして、それはこの本の学習でやるべきことじゃなく思えてきた。それをやるなら、Knuth本とかじゃないだろうか舞台は。
  • というわけで、クイックソートの数学的理解は、今はやめておこう。
  • 直感的にはわかるんだけどねぇ。

  • 演習6-14でひっかかる。ポインタがからむとやっぱりひっかかる。前より楽になったけど。
  • GDBをもうちょっと便利につかいたい。UNIXプログラミングの道具箱をみる。

    • breakpointは関数名引数で設定すべし。
    • 停止毎に表示確認する式はdisplayで登録すべし。
    • 中身をよくみる構造体はdefineで手続化すべし。
    • 繰返しGDBを起動するような状況なら、作業ディレクトリに.gdbinitをおいて、必要なものはそこに書くべし。
    • または、-x fileをgdbにわたして、起動時に実行させるべし。
    • または、source fileでプロンプトから読み込むべし。

  • てな感じ。演習6-14では、こんなんにして、sourceで読み込むことにした。

    # gdbinit file for prac0614.c

    display left
    display right
    display pivot
    display pl
    display pr

    define hookpost-next
    set $n = 0
    while $n < nmemb
    print *((int *)base + $n)
    set $n = $n + 1
    end
    end

    #break quick
    #displayの設定は、そのシンボルが存在するcontextに入らないとできない。
    #なので、displayを読ませるには、先にrunする必要あり。
    # runする前にbreak quickする必要あり。
    #よってbreak quickはプロンプトでやることにする。

  • バグバグな状態をGDBで調べてみる。
  • 判明、あんまりよく考えないで書いていたからだけだった。。。うむー。
  • 演習6-15。う、関数ポインタやNULLポインタをつかって、高階関数を作るときは、その関数の中身が再帰でかかれているよりも非再帰でかかれていたほうががかなり楽。
  • 演習6-16。また間違えた。変数の値をswapしたいときにポインタをつかっていて、アドレスをコピーしちゃってて値をコピーしていない。

うーん。やっとクイックソートがおわった。クイックソートじゃなくて、ポインタに慣れてきた。それはそれでよし。

2008年8月7日木曜日

【C算法】第6章 ソート (その4)


  • クイックソートの分割が何故うまくいくのかが理解できない。。。
  • 一日かけて、こつこつ考えてみることにする。

2008年8月5日火曜日

【C算法】第6章 ソート (その3)

いまのやり方だと仕事との両立が早晩きつくなるような。昨日も朝までやってたし。。。

  • 今日は「6-5 シェルソート」。
  • だめだ。シェルソートのアルゴリズムがどういものだかはすぐわかるのだが、それのCによる実装が例示されているソースでよいことがわからない。
  • 変数の動きを追ってみる。

    --------------------------------------------------------
    n: 2
    0 1
    h: n/2
    i: h
    j: i-h

    --------------------------------------------------------
    n: 3
    0 1 2
    h: n/2
    i: h
    j: i-h
    i: i++
    j: i-h

    --------------------------------------------------------
    n: 4
    0 1 2 3
    h: n/2
    i: h
    j: i-h
    i: i++
    j: i-h

    h: n/2/2
    i: h
    j: i-h
    i: i++
    j: i-h
    j: i-2h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h

    --------------------------------------------------------
    n: 5
    0 1 2 3 4
    h: n/2
    i: h
    j: i-h
    i: i++
    j: i-h
    i: i++
    j: i-h
    j: i-2h

    h: n/2/2
    i: h
    j: i-h
    i: i++
    j: i-h
    j: i-2h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h
    j: i-4h
    --------------------------------------------------------
    n: 8
    0 1 2 3 4 5 6 7
    h: n/2
    i: h
    j: i-h
    i: i++
    j: i-h
    i: i++
    j: i-h
    i: i++
    j: i-h

    h: n/2/2
    i: h
    j: i-h
    i: i++
    j: i-h
    i: i++
    j: i-h
    j: i-2h
    i: i++
    j: i-h
    j: i-2h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h

    h: n/2/2/2
    i: h
    j: i-h
    i: i++
    j: i-h
    j: i-2h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h
    j: i-4h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h
    j: i-4h
    j: i-5h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h
    j: i-4h
    j: i-5h
    j: i-6h
    i: i++
    j: i-h
    j: i-2h
    j: i-3h
    j: i-4h
    j: i-5h
    j: i-6h
    j: i-7h

    --------------------------------------------------------

  • ここでhは、「h離れたものどもでソート」であり、幅のようなもの。ここでは4,2,1という数列。
  • iは[h,n)における1刻みの数列を作る。i(q) = h + q, {q= 0, 1, ..., n - p - 1}
    これは幅hで離れたもの「ども」の尻尾にあたる要素を列挙しているのだが、それだけではない。そのモノドモごとに、単純挿入ソートをおこなうのだから、1つのモノドモをソートしきるには、そのモノドモに含まれる要素数と同じ回数だけモノドモ(の部分)をなめなければならない。iが[h,n)というように結構多いなぁという数なのはそれを尽くすためである。
  • よって、その尽くすためのi達おのおのから幅hで減算していく数列が単純挿入ソートの対象添字セットであり、それがj。
  • ああ、やっとわかった。
  • わかりにくい原因は、「シェルソートのイメージはモノドモ毎にソートを完了していくというものだが、Cの実装ではソートがモノドモをまたがってインターリーブされている」というところだ。
  • わかったこと。

    • このCの実装は「成すべき処理を、いかに効率よく生成するか」というものである。
    • なので、アルゴリズムの結果として成すべき処理が一覧されたとして、それをどう組替えるともっとも単純にそれら処理を生成できるものになるか、ということ。
    • そして、Cでこのように考える、ということは、おそらく「慣れ」の問題。ただし、その慣れを得るのにCでいろいろやるのがいいか、離散数学を勉強するのがいいかは要判断。
    • CLからプログラミングを学びはじめたので、私はこの手の思考に弱い。
    • ここでやった分析は手で実施したのだが、これ自体をプログラムに出力させればよい。手でやるのはアホ。

  • 演習6-9で、(*s)++とすべきところを、*s++としてしまって、ちょっとはまった。いかんなぁ。
  • 演習6-9。1000個の要素に0〜9999をランダムに入れてソートさせると2の倍数だと15000強の交換、3倍して1足す方だと14000強、というかんじだった。

こつこつ。

2008年8月4日月曜日

【C算法】第6章 ソート (その2)

ちょっと焦っているのが、よくないと思えてきた。焦ってやったことは、後々役に立つようにはならない。自分に力量が無いから、思うように進まず、焦るのだ。そして、焦ってしびれをきらして、無理して速く進んだとしても、それは身の丈にあっていないわけで、力量不相応なことをしており、力量が無いことのなんの解決行為にもなっていない。力量の無さを受け止めた上で、こつこつやるしかないのだ。

  • 「6-3 単純選択ソート」から。
  • 離れた場所の要素を交換すると安定性が崩れるようだ。
  • 「6-4 単純挿入ソート」。シャトルソート! なんだかかっこいいかんじ。
  • 単純ってstraightの訳なのね。
  • そういやトランプって最近やってないなぁ。こんどやってみよう。
  • これはデータ構造として、配列ではなくて線形リストとかに向いたアルゴリズムじゃないかなぁ。
  • 演習6-7のbinary insertion sortの実装にやたら時間がかかる。再帰を使えばすぐにかけるのです。せっかくCなので、再帰を使わないで、配列と繰返しでやろうとすると、途端に、値の振舞と添字の振舞がいろんなケースでどうなるのか、ということが追えなくなるのです。やっぱり、数学に弱い、とくに離散数学に弱いのが原因だと思う。
    さて、ここで開きなおります。C上で直接アルゴリズムを考えるのではなく、離散数学の問題の解答として再帰的表現をなるべく使わないものを立案してから、それをCで実装することにする。
  • ふむ。そのようにやったら、デバッグなしで一発で正しく書けた。しかしながら、その結果としてのCによる実装のあり様はある意味きたないというか、それが正しく動くものかどうか、ぱっと見で判断できないようなものだ。これは何を意味しているのか。
    CLとかSchemeのときは、再帰とか高階関数とかマクロとかをつかいながら、アルゴリズムを考える過程としてそれを書き進めることができるが、Cではそうやってはいけないまたはできないということかもしれない。アルゴリズムを考えてから、それのCによる実装を考えるという二段階を踏むべきということか。単なる慣れの問題かなぁ。

こつ、こつ。

2008年8月3日日曜日

【C算法】第6章 ソート

こつこつやってたら、半分まで来た。こんな調子でプログラマになれるのか?という疑問もあるが、とにかくこつこつやるしかない。

  • 内部ソートと外部ソートの存在をしった。
  • ソートの三大要素は交換・選択・挿入である。
  • バブルソート、シェーカーソートとかは交換なのね。

次回は「6-3 単純選択ソート」。

2008年8月1日金曜日

【C算法】第5章 再帰的アルゴリズム (その2)

こつこつ。

  • ハノイの塔。非再帰的にも書けたが、いまいちしっくりこない。フレームスタックをもっときっちり作ってしまう手法の方がすっきりするかも。
  • う、8王妃列挙の再帰アルゴリズムですら理解できない。。。考える。。。がなんだかもやもや。再帰といいつつfor文もつかっているからわかりにくいのかな、それともbranchingという再帰の使い方になれてないからかな? 再度考える。

    • 3王妃列挙に縮小して考える。
    • 3王妃列挙では、一行で、とある配置を表現する。例えば、"111"など。これは全列とも行1に王妃がいる配置を表す。
    • 理解するためにset(i=0)をどんどん展開していく。

      {i=0; for(j0=0 to 2) {pos[i]=j0; set(i=1);}}

      {i=0; for(j0=0 to 2) {pos[i]=j0;
      {i=1; for(j1=0 to 2) {pos[i]=j1; set(i=2);}}}}

      {i=0; for(j0=0 to 2) {pos[i]=j0;
      {i=1; for(j1=0 to 2) {pos[i]=j1;
      {i=2; for(j2=0 to 2) {pos[i]=j2; print()}}}}}}

      {i=0; for(j0=0 to 2) {pos[i]=j0;
      {i=1; for(j1=0 to 2) {pos[i]=j1;
      {i=2; for(j2=0 to 2) {pos[i]=j2; for(k=0 to 2) {print pos[k]}}}}}}}

      {i=0; for(j0=0 to 2) {pos[i]=j0;
      {i=1; for(j1=0 to 2) {pos[i]=j1;
      {i=2; for(j2=0 to 2) {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}}}

      {i=0; for(j0=0 to 2) {pos[i]=j0;
      {i=1; for(j1=0 to 2) {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}}}}

      {i=0; for(j0=0 to 2) {pos[i]=j0;
      {i=1;
      {j1=0; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}}}};
      {j1=1; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}}}};
      {j1=2; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}}}}}
      {i=0;
      {j0 = 0;{pos[i]=j0;
      {i=1;
      {j1=0; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}};
      {j1=1; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}};
      {j1=2; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}}}}}
      {j0 = 1;{pos[i]=j0;
      {i=1;
      {j1=0; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}};
      {j1=1; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}};
      {j1=2; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}}}}}
      {j0 = 2;{pos[i]=j0;
      {i=1;
      {j1=0; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}};
      {j1=1; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}};
      {j1=2; {pos[i]=j1;
      {i=2;
      {j2=0; {pos[i]=j2; print pos[0]pos[1]pos[2]}};
      {j2=1; {pos[i]=j2; print pos[0]pos[1]pos[2]]};
      {j2=2; {pos[i]=j2; print pos[0]pos[1]pos[2]]}}}}}}}}

    • これをみて分析すると。
    • まず、列iは再帰によって繰り返しを生じている。
    • 列iの繰り返しと行j*の繰返しは交互に生じている。
    • j0は列0の行配置、j1は列1の行配置、j2は列2の行配置、を生成している。
    • 理解したいのはset(i)の作業を日本語で何というか、だ。
    • まずset(i)は列iに関する作業であるといえる。
    • ただし、それは列i+1に関する作業も含んでいる。
    • その連鎖によって、set(0)は列0、列1、列2のすべての作業を含んでいる。
    • ところで、「列iに関する作業」というのは具体的に何かというと、列iの各行jのどこに王妃がいるかを印字すること。
    • うーむ。よくわからない。
    • そうか!set(i=0)を列0に関する作業、と考えるからいけないのだ。これは「列0を起点に、分岐して、王妃を全部列挙する作業」と考えるのか。
    • すると、
      {i=0; for(j0=0 to 2) {pos[i]=j0; set(i=1);}}

      は、列0については、0〜2の通りがあるけど、それはそれぞれの通りについて、「列1を起点に、分岐して、王妃を全部列挙する作業」があるもんだよ、といっている。
    • これを繰返していく、と。
    • なるほど、再帰による分岐は、階乗とかの再帰とかとはニュアンスがちょっと違う。勉強になった。

  • 分割統治法って、再帰的である分割に限るのかな?
  • うーん。8王妃の分岐限定法アルゴリズムを完全に理解できたわけではないが、分岐限定法自体の考え方は多少わかるので、先に進もう。

2008年7月31日木曜日

【C算法】第5章 再帰的アルゴリズム

Cで再帰。楽しみだ!

  • う、ユークリッドの互除法自体がわからない。考えてみる。

    • 最大公約数の定義は、整数x,yがあって、x=az, y=bzとなるzのうち最大のもの。z = gcd(x, y)とあらわす。
    • ここで、gcd(x, y) = gcd(y, x)。どうでもいいけど。
    • さらに定義として、gcd(x, 0) = x。
    • で、ユークリッド互除法とは、gcd(x, y) = gcd(y, x % y) ということ。なんで?
    • 便宜上、x > yとする。すると、x % y = 0ならば、yがgcdであることは自明。
    • つづいて、x % y != 0とする。するとyがgcdでないことは自明。
    • このとき、とあるAにて、x = Ay + x % y となっている。ここで、Ayの部分はもちろんgcdで割り切れるものである。よって、x % yもgcdで割り切れなければならないことがあらたにわかる。x > y > x % y だから、x,yのgcdを考えることは、より小さな問題である y,x%yを考える問題と等価であるといえる。よって、gcd(x,y)=gcd(y,x%y)。
    • で、x < yであっても、gcd(x,y) = gcd(y, x%y)するなかで、大きい方が左の引数に寄っていくので同じ結果となる。
    • という感じかなぁ。

  • とりあえず5-2まで完了。
  • なんか、再帰を楽しむというより、再帰は悪って感じだなぁ。。。

こつこつ。

2008年7月30日水曜日

【C算法】第4章 スタックとキュー (その2)

今日はキューから。

  • リングバッファは、実践編ですでに検討済みだったのですんなり。
  • うむ。だんだん演習がごつくなってきて時間がかかるようになってきた。柴田さんの本は、入門編、実践編につづき三冊目だが、この本が一番レベルが高いかな。これをちゃんと終えれば、C言語入門者を卒業してC言語初級者を名乗ってもいいかもしれない!がんばろう。
  • うは、デックの実装に時間がかった。両方から出し入れできると、はじめに予想していた以上に先端ポインタと末端ポインタの兼ね合いの振舞がでてくんだな。C言語でやる場合の、作業量見積がまだできない状態ともいえる。

体調悪いが、こつこつ。