データ構造とアルゴリズム勉強会 #3

# 内容
・線形探索
・二分探索
・双方向連結リスト

# 線形探索
問題：http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_4_A&lang=jp
for文を回すだけなので省略します。

# 二分探索
計算量O(Nlog(2)N)のアルゴリズムです。
与えられた値と配列の中の値で一致するものがあるかを探しますが、その際配列が昇順に並んでいる場合、決め打ちができます。leftを左端、rightを右端、真ん中をmidとします。例えば以下のような感じです。

~~~
// 5が配列に含まれるか探したい
vector = [1, 3, 4, 5, 7]
=> left = 1, right = 7, mid = (1+7)/2 = 4
~~~

ここで`mid`が5以上か未満かを判定します。
`mid`が５以上なら`right`の値が`mid`になり、次の探索は左端から`mid`までになります。
逆に`mid`が５未満ならそこを`left`にし、それを繰り返すことで探索範囲を狭めていきます。そして探索の幅が1以下になった時、`right`が探したい値となっています。

例えば上の例では1回目のサーチで`mid = 4 < 5`より、`left = 4, right = 7`です。
次に`mid = (4+7)/2 = 5`となり、`mid = 5 >= 5`だから、`right = 5`
`right - left = 1`より、探索の幅が1になったので終了、となります。

実装は以下のようになります。

~~~c++
bool binarySearch(vector<int> S, int key){
    int left = 0;
    int right = S.size()-1;
    int mid;

    while(right - left > 1){
        mid = (left+right)/2;
        if(S[mid] >= key) right = mid;
        else left = mid;
    }

    return S[right] == key;
}
~~~

# 双方向連結リスト
問題:http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_3_C&lang=jp
ここが一番重いのでメインで説明します。
まず、双方向連結リストの構造ですが、ポインタをキーに持っていて、キーを参照して値を見つけます。
そして構造体として前後の値のキーを持っているため、操作上は相互に繋がっているような構造になります。
イメージとしては ① ⇄ ② ⇄ ③ ⇄ … のようにキーを相互に参照できます。
これを実現する構造体をまず定義します。

~~~c++
struct Node{
    int key;
    Node *next, *prev;
};
~~~

`Node`は整数型のキー(実際の値)と、前後の値のポインタを持ちます。これによって相互に参照ができます。
問題の解説ではなく双方向連結リストの理解が目的なので、以下ではこれの基本的な操作の実装のみ解説していきます。

まず、基準となるところがないとリストは機能しないので、初期化を行います。

~~~c++
Node *nil;

void initialize(){
    nil = (Node *)malloc(sizeof(Node)); // malloc:メモリを確保,sizeof:Nodeを定義するのに必要なメモリを確保
    nil->next = nil; // (*nil).next = nil
    nil->prev = nil; // ->は参照？
}
~~~

グローバル変数として`Node *nil`を定義し、次に初期化の関数を実装します。
`(Node *)malloc(sizeof(Node))`は`Node`を格納するのに十分なメモリを確保しているというイメージで大丈夫です。詳しく理解する必要はありませんが、直接メモリを操作する関数なので危険ですし、あまり使う機会はないです。`->`はアロー演算子と呼ばれ、ここでは`nil.next`と同じです。構造体のポインタを参照するときにこれを用います。
ちょっと不思議ですが、この実装により前後に自身を持つリストができました。ここに要素をつなげていきます。

まず、`nil`が最初の要素なので、新たなデータは`nil`の次に入れることになります。
もともと`nil ⇄ 1`というリストがあったとしましょう。ここに`x`が入力されました。つまり、`nil ⇄ x ⇄ 1`とリストを更新したいのです。そのような実装は以下のようになります。

~~~c++
void insert(int key){
    Node *x = (Node *)malloc(sizeof(Node));
    x->key = key;

    x->next = nil->next; // nilの次が最初だから
    nil->next->prev = x;
    nil->next = x;
    x->prev = nil;
}
~~~

引数として`key`を受け取ります。これはデータです。新たに`x`という名前のNode構造体のメモリを確保します。そしてそこに受け取った値を格納します。
ここでもう一度確認しますが、もともと`nil ⇄ 1`があり、`nil ⇄ x ⇄ 1`の矢印部分を4つの操作で完成させます。
`x->next = nil->next`: x → 1
`nil->next->prev = x`: x ← 1 (ここでx ⇄ 1が完成)
`nil->next = x`: nil → x
`x->prev = nil`: nil ← x(ここでnil ⇄ xが完成)
ということで、4つの操作によりxがリストに挿入されました。

次に、要素の探索を実装します。nilの次から順番に総当たりしていきます。

~~~c++
Node* listSearch(int key){
    Node *cur = nil->next;
    while(cur != nil && cur->key != key) cur = cur->next;
    // curがnilでなく、キーが見つからなければそこのキーを返し、見つかれば次のcurを返す
    return cur;
}
~~~

`cur`はNodeです。最初はnil->nextがもつポインタを参照し、nilの次のNodeを格納します。そこからwhile文で引数を`key`に持つ`cur`に当たるまで、順番に総当たりしていきます。そして`cur->key`と引数`key`が一致したらその値を返します。

最後に、Nodeのもつ要素を消す関数を定義します。

~~~c++
// Nodeを消す
void deleteNode(Node *t){
    if(t == nil) return; // tがなければ何もしない
    //2つの操作でtの前とあとを相互に繋げる
    t->prev->next = t->next;
    t->next->prev = t->prev;
    free(t); // メモリ解放
}
~~~

ここは比較的わかりやすいのですが、もともと`1 ⇄ t ⇄ 2`となっていたところを、ポインタの参照を`1 ⇄ 2`となるようにし、`t`が格納されていた部分のメモリ領域を`free(t)`で解放することでtを消します。

以上で双方向連結リストの定義、データの追加・探索・削除ができるようになりました。基本的な考え方は以上となります。