C言語による基礎アルゴリズム実装:成績評価・桁和計算・べき乗・素数探索・ハノイの塔・組み合わせ・最大公約数

本稿では、C言語を用いた代表的なアルゴリズム課題を再構成し、各関数の設計意図と改善点を技術的に解説します。コード例は意図的に構造・変数名・制御フローを変更し、教育的かつ実用的な書き直しを行っています。

成績マッピング関数(文字列評価)

整数スコアを10点刻みで分類し、対応する等級記号を返す関数です。入力範囲に応じてA~Eの5段階評価を実施します。

#include <stdio.h>

char evaluate_grade(int raw_score) {
    const int threshold = raw_score / 10;
    
    switch (threshold) {
        case 10: case 9: return 'A';
        case 8: return 'B';
        case 7: return 'C';
        case 6: return 'D';
        default: return 'F'; // 59以下はF(修正:E→F、国際標準に準拠)
    }
}

int main() {
    int input;
    while (scanf("%d", &input) == 1) {
        printf("得点: %d → 評価: %c\n", input, evaluate_grade(input));
    }
    return 0;
}

整数の桁和計算(反復 vs 再帰)

正の整数の各桁の数字の合計を求める処理を、反復版と再帰版で実装しています。再帰版は末尾再帰ではなく、自然な分割統治スタイルを採用。

// 反復実装
int digit_sum_iterative(int value) {
    int total = 0;
    int abs_val = (value < 0) ? -value : value; // 符号無視
    
    do {
        total += abs_val % 10;
        abs_val /= 10;
    } while (abs_val > 0);
    
    return total;
}

// 再帰実装(ベースケース:1桁以下)
int digit_sum_recursive(int value) {
    int abs_val = (value < 0) ? -value : value;
    if (abs_val < 10) return abs_val;
    return (abs_val % 10) + digit_sum_recursive(abs_val / 10);
}

効率的なべき乗計算(二分累乗法)

指数nが大きい場合でも対数時間でxnを計算する再帰アルゴリズム。偶数指数では中間結果を再利用し、計算量をO(log n)に削減。

long long fast_power(long long base, unsigned int exp) {
    if (exp == 0) return 1;
    if (exp == 1) return base;
    
    long long half = fast_power(base, exp / 2);
    if (exp % 2 == 0) {
        return half * half;
    } else {
        return half * half * base;
    }
}

双子素数の列挙(最適化済み判定)

100未満の双子素数ペア(p, p+2)を検出し、その総数をカウントします。素数判定には√nまで試し割りを行い、不要なループを排除。

#include <stdbool.h>

bool is_prime_optimized(int candidate) {
    if (candidate < 2) return false;
    if (candidate == 2) return true;
    if (candidate % 2 == 0) return false;
    
    for (int i = 3; i * i <= candidate; i += 2) {
        if (candidate % i == 0) return false;
    }
    return true;
}

void list_twin_primes_under_100() {
    int pair_count = 0;
    printf("100未満の双子素数ペア:\n");
    
    for (int p = 3; p + 2 < 100; p += 2) {
        if (is_prime_optimized(p) && is_prime_optimized(p + 2)) {
            printf("(%d, %d)\n", p, p + 2);
            pair_count++;
        }
    }
    printf("合計: %d 組\n", pair_count);
}

ハノイの塔シミュレータ(移動回数計測付き)

再帰的手順でディスク移動を可視化し、総移動回数を正確に集計。各ステップで「ディスク番号: 出発杭 → 目標杭」の形式で出力します。

int simulate_hanoi(unsigned int disks, char source, char auxiliary, char target) {
    static int move_counter = 0;
    
    if (disks == 1) {
        printf("%u: %c → %c\n", disks, source, target);
        move_counter++;
        return 1;
    }
    
    int steps = 0;
    steps += simulate_hanoi(disks - 1, source, target, auxiliary);
    printf("%u: %c → %c\n", disks, source, target);
    steps += 1;
    steps += simulate_hanoi(disks - 1, auxiliary, source, target);
    
    return steps;
}

組み合わせ数計算(二項係数)

n個からm個を選ぶ組み合わせ数 C(n,m) を、数値オーバーフローを回避する工夫で実装。再帰版はパスカルの三角形の漸化式 C(n,m) = C(n−1,m) + C(n−1,m−1) を直接表現。

// 安全な反復実装(乗算/除算を交互に実行)
unsigned long long binomial_iterative(unsigned int n, unsigned int m) {
    if (m > n || m < 0) return 0;
    if (m == 0 || m == n) return 1;
    
    // C(n,m) = C(n,n-m) を利用して計算量削減
    if (m > n - m) m = n - m;
    
    unsigned long long result = 1;
    for (unsigned int i = 0; i < m; ++i) {
        result = result * (n - i) / (i + 1);
    }
    return result;
}

// 再帰実装(メモ化なし、小規模入力向け)
unsigned long long binomial_recursive(unsigned int n, unsigned int m) {
    if (m > n || m < 0) return 0;
    if (m == 0 || m == n) return 1;
    return binomial_recursive(n - 1, m) + binomial_recursive(n - 1, m - 1);
}

三数の最大公約数(ユークリッド拡張)

3つの整数a,b,cのGCDを求める関数。単純な試し割りではなく、gcd(gcd(a,b),c) の性質を利用した効率的な実装。

int gcd_two(int x, int y) {
    while (y != 0) {
        int temp = y;
        y = x % y;
        x = temp;
    }
    return x;
}

int gcd_three(int a, int b, int c) {
    return gcd_two(gcd_two(a, b), c);
}

タグ: c-language Recursion Algorithm mathematical-computing prime-numbers

6月14日 23:58 投稿