LoginSignup
1
0

More than 3 years have passed since last update.

[C言語アルゴリズム]2分木探索(binary search tree)

Posted at

C言語で2分探索木アルゴリズムを実装した

学習にあたって使用した環境

  • ideone.com (https://ideone.com/) ※オンライン上でプログラミング学習ができるサイト

参考資料

C言語による最新アルゴリズム辞典 (奥村晴彦著/1991年初版 技術評論社:206ページ)

2分探索木の概要

以下参考資料より引用

探索に用いる2分木。各ノードにデータと二つのポインタを持ち、左のポインタleftでつながる子孫のデータはすべて自分より小さく、右のポインタrightでつながる子孫のデータはすべて自分より大きい。
2分探索木による探索は、根から始め、探し物がそのノードより小さいか大きいかによって、左または右のポインタをたどっていく。
完全に釣り合いのとれた2分木ならn回の比較で、2のn-1乗個のものの中から目的のものを見つけることができる。

ソースコード

postfix.c
// 2分探索木 binary search tree

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 探索に使用するキーの型宣言
typedef char keytype[21];

// 2分木のノードの型宣言
typedef struct node {
    struct node *left, *right;  // 左右の子ノードへのポインタ
    keytype key;                // 探索のキー
} *nodeptr;     // ノードを指すポインタ

struct node nil;    // 木の末端を表すノード
nodeptr root = &nil;    // 根を指すポインタ

// 木への挿入(登録)関数
nodeptr insert(keytype insertkey){
    int cmp;
    nodeptr *p;     // ノードを指すポインタ
    nodeptr q;      // ノードの実体

    strncpy(nil.key,insertkey,sizeof(keytype));  // 番人のキーに引数のキーをコピー

    p = &root;  // 根のポインタを内部変数のポインタに代入

    // キーが一致するまでループ
    while((cmp = strncmp(insertkey, (*p)->key, sizeof(keytype))) != 0)
    {
        // insertkeyがポインタが指すノードのキーより小さい場合、左側の小さいノードに移動する
        // insertkeyがポインタが指すノードのキーより大きい場合、右側の大きいノードに移動する
        if(cmp < 0){
            // ポインタを左に移動する
            p = &((*p)->left);
        } 
        else{
            // ポインタを右に移動する
            p = &((*p)->right);
        }
    }

    // すでにキーが登録されている場合
    if(*p != &nil){
        return NULL;
    }

    // ノード分のメモリ領域を確保する
    if((q = malloc(sizeof(*q))) == NULL){
        printf("insert: メモリ不足によりメモリ領域確保失敗。\n");
        exit(EXIT_FAILURE);
    }

    // ノードのキーをコピーする
    strncpy(q->key, insertkey, sizeof(keytype));

    q->left = &nil;
    q->right = *p;
    *p = q;

    // 登録したノードを返す
    return q;

}

// 削除関数
// 削除できれば0、失敗なら1を返す
int delete(keytype deletekey){
    int cmp;

    nodeptr *p, *q;
    nodeptr r, s;

    strncpy(nil.key, deletekey, sizeof(keytype));   // 番人

    p = &root;

    // キーが一致するまでループ
    while((cmp = strncmp(deletekey, (*p)->key, sizeof(keytype))) != 0)
    {
        // insertkeyがポインタが指すノードのキーより小さい場合、左側の小さいノードに移動する
        // insertkeyがポインタが指すノードのキーより大きい場合、右側の大きいノードに移動する
        if(cmp < 0){
            p = &((*p)->left);
        }
        else{
            p = &((*p)->right);
        }
    }

    // キーが見つからない場合
    if(*p == &nil){
        return 1;
    }

    r = *p;

    if(r->right == &nil){
        *p = r->right;
    }else if(r->left == &nil){
        *p = r->right;
    } else{
        q = &(r->left);

        while((*q)->right != &nil){
            q = &((*q)->right);
        }

        s = *q;
        *q = s->left;
        s->left = r->left;
        s->right = r->right;
        *p = s;
    }

    // 領域を解放する
    free(r);

    return 0;

}

// 検索関数
// キーが見つかった場合にはキーを持つノードのポインタを返す
// 見つからなければNULLを返す
nodeptr search(keytype searchkey){
    int cmp;
    nodeptr p;

    strncpy(nil.key, searchkey, sizeof(keytype));
    p = root;

    while((cmp =strncmp(searchkey, p->key, sizeof(keytype))) != 0){
        if(cmp < 0){
            p = p->left;
        }else{
            p = p->right;
        }
    }

    if(p != &nil){
        // 見つかった場合
        return p;
    }else{
        // 見つからなかった場合
        return NULL;
    }

}

void printtree(nodeptr p){
    static int depth = 0;

    // 左側のノードを表示する
    if((p->left) != &nil){
        depth++;
        // 再帰呼び出し
        printtree(p->left);
        depth--;
    }

    // 引数で受けたノードを表示する
    printf("%*c%s\n", 5*depth, ' ', p->key);

    // 右側のノードを表示する
    if(p->right != &nil){
        depth++;
        // 再帰呼び出し
        printtree(p->right);
        depth--;
    }
}

int main(void) {

    char buf[22];
    short count;

    printf( "命令   Iabc:  abcを挿入\n"
            "       Dabc:  abcを削除\n"
            "       Sabc:  abcを検索\n");

    for(;;){
        printf("命令は? ");

        // bufサイズ分標準入力からデータを受け取る
        if(scanf("%21s%*[^\n]", buf) != 1){
            break;
        }

        switch(buf[0]){
            // 挿入処理
            case 'I': case'i':
                // 挿入関数
                if(insert(&buf[1])){
                    printf("%21s%*[^\n]:登録しました\n",buf);
                }else{
                    printf("%21s%*[^\n]:登録済みです\n",buf);
                }

                break;

            // 削除処理
            case 'D': case 'd':
                // 削除関数
                if(delete(&buf[1])){
                    printf("%21s%*[^\n]:削除されていません\n",buf);
                }else{
                    printf("%21s%*[^\n]:削除しました\n",buf);
                }

                break;

            // 検索処理
            case 'S': case 's':
                // 削除関数
                if(search(&buf[1])){
                    printf("%21s%*[^\n]:登録されています\n",buf);
                }else{
                    printf("%21s%*[^\n]:登録されていません\n",buf);
                }

                break;
            default:
                printf("使用できるのは I, D, S だけです\n");
                break;
        }

        if(root != &nil){
            printf("\n");
            printtree(root);
            printf("\n");
        }

        count++;

    }

    return EXIT_SUCCESS;
}

実行結果

stdin.txt(任意)
Iabc
Sabc
Dabc
stdout.txt(任意)
命令   Iabc:  abcを挿入
       Dabc:  abcを削除
       Sabc:  abcを検索
命令は? 登録しました

 abc

命令は? 
Success #stdin #stdout 0s 4368KB
命令   Iabc:  abcを挿入
       Dabc:  abcを削除
       Sabc:  abcを検索
命令は? 登録されていません
命令は? 
Success #stdin #stdout 0s 4516KB
命令   Iabc:  abcを挿入
       Dabc:  abcを削除
       Sabc:  abcを検索
命令は? 削除されていません
命令は? 

所感

  • ideoneで対話型プログラムをやろうとするとちょっとうまくいかないな。
  • 理解度が深まったら追記していこうと思う。
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0