プログラミングの勉強を進めていくと、必ず出会うのが「アルゴリズム」ってやつですよね。
C言語の文法はなんとなくわかってきたけど、それをどう使って問題を解くプログラムを書けばいいのか…
そんな風に悩んでいる人もいるんじゃないでしょうか?
心配ご無用!この記事では、プログラミングの考え方の基本とも言えるアルゴリズムを、C言語で実際に形にする方法を、初心者さんにも分かりやすく解説していきます。
特に、よく使われる「探索(データを探す)」と「ソート(データを並べ替える)」の基本アルゴリズムを取り上げて、サンプルコード付きで紹介するから、手を動かしながら学べますよ。
この記事で学べること
- アルゴリズムをC言語で書くってどういうことか
- コードを書き始める前の考え方のコツ
- 基本的な探し方「線形探索」のC言語コード
- 基本的な並べ替え方「バブルソート」のC言語コード
- 実装で気をつけたいポイントとデバッグの初歩
C言語におけるアルゴリズム実装とは?初心者が知るべき基本
まず、アルゴリズムって言葉、ちょっと硬いですよね。
簡単に言うと、問題を解くための「手順」や「計算方法」のことです。
料理で例えるなら、カレーを作るための「レシピ」みたいなものかな。
そして、アルゴリズム実装というのは、その「レシピ(手順)」を、プログラミング言語を使ってコンピュータが理解できる形(コード)に書き起こす作業を指します。
C言語でアルゴリズムを実装するなら、C言語の文法ルールに従って、レシピをコードに翻訳していくイメージですね。
なんでC言語で学ぶのがいいの?って思うかもしれません。
C言語は、コンピュータの動きに近いレベルでプログラムを書けるので、アルゴリズムが実際にどうやって動いているのか、その仕組みを深く理解しやすいんです。
メモリをどう使うかとか、処理の速さとか、そういう基礎的な部分を意識する良いトレーニングになりますよ。
ここでしっかり基礎を固めておくと、他の言語を学ぶときにも、きっと役立つはずです!
C言語でアルゴリズムを実装する前の準備と考え方
さあ、コードを書くぞ!…と意気込む前に、ちょっと待ってください。
いきなりパソコンに向かうよりも、まず頭の中で設計図を描くのが、うまくいくコツなんです。
料理も、レシピを決めずに作り始めたら大変なことになるでしょ?
プログラミングも同じで、準備がとっても大事。
具体的には、こんなステップで考えてみましょう。
1. アルゴリズムを理解する
どんな手順で問題を解くのか、処理の流れをしっかり把握しましょう。2. データの準備を考える
プログラムで扱うデータは何か、どんな形で用意するかを考えます。3. 処理を細かく分ける
大きな問題を一気に解こうとすると大変なので、処理を小さなステップに分解します。例えば、「配列の中から一番大きい数字を見つける」というアルゴリズムを実装するとしたら…
- 手順(アルゴリズム) 配列の最初の値を「仮の最大値」として覚えておき、残りの値を順番に見ていって、仮の最大値より大きい値が見つかったら、そっちを新しい「仮の最大値」に更新する。最後まで見たら、その時の「仮の最大値」が答え。
- データ 配列(例 `int data[] = {3, 1, 4, 1, 5, 9, 2, 6};`)
- 処理分割 配列の準備 → 最大値を探すループ → 結果の表示
こんな風に、書き始める前に少し立ち止まって考えるだけで、後のコーディングがぐっと楽になります。
【実践】C言語で学ぶ!線形探索アルゴリズムの実装
ここからは、実際にC言語でアルゴリズムを実装してみましょう。
まずは、アルゴリズムの中でも超基本、「探索」から。
探索っていうのは、たくさんのデータの中から、目的のデータを見つけ出す作業のことです。
今回は、一番シンプルな探索アルゴリズム「線形探索(せんけいたんさく)」をC言語で書いてみます。
名前はちょっと難しそうだけど、やってることは超シンプルなので安心してくださいね!
線形探索アルゴリズムとは
線形探索は、データの先頭から順番に、一つずつ目的の値と一致するかどうかを比較していく方法です。
宝探しで、箱が横一列に並んでいて、左の箱から順番に開けて中身を確認していくイメージですね。
// 配列のイメージ [ 10 | 25 | 5 | 30 | 15 ] // 探したい値が「5」の場合 1. 最初の要素「10」を見る → 5じゃない 2. 次の要素「25」を見る → 5じゃない 3. 次の要素「5」を見る → あった!ここで探索終了。
メリットは、なんといっても単純明快で分かりやすいこと!
プログラムも比較的簡単に書けます。
デメリットは、データの数がめちゃくちゃ多いと、見つけるまでに時間がかかっちゃう可能性があること。 運悪く一番最後の要素が目的の値だったら、全部見ないといけないですからね。
でも、まずはこのシンプルな方法を理解して実装できることが第一歩です!
C言語による線形探索の実装コード例
では、実際にC言語で線形探索を書いてみましょう。
ここでは、整数の配列の中から、指定された整数を探す関数を実装してみます。
#include <stdio.h> // 線形探索を行う関数 // data: 探索対象の配列 // size: 配列の要素数 // target: 探したい値 // 戻り値: 見つかった場合はその要素のインデックス(添え字)、見つからなかった場合は -1 int linearSearch(int data[], int size, int target) { // 配列の先頭から末尾まで順番にチェック for (int i = 0; i < size; i++) { // 現在見ている要素(data[i])が探したい値(target)と一致したら if (data[i] == target) { // その要素のインデックス(i)を返す return i; } } // ループが終了しても見つからなかった場合 return -1; // 見つからなかった印として -1 を返す } int main(void) { // 探索対象のデータ(配列) int numbers[] = {10, 25, 5, 30, 15, 40, 50}; // 配列の要素数を計算 int size = sizeof(numbers) / sizeof(numbers[0]); // 探したい値 int targetValue = 15; // 線形探索関数を呼び出して結果を取得 int resultIndex = linearSearch(numbers, size, targetValue); // 結果の表示 if (resultIndex != -1) { // 見つかった場合 printf("%d は配列の %d 番目(インデックス %d)に見つかりました。\n", targetValue, resultIndex + 1, resultIndex); } else { // 見つからなかった場合 printf("%d は配列の中に見つかりませんでした。\n", targetValue); } // 別の値を探してみる targetValue = 100; resultIndex = linearSearch(numbers, size, targetValue); if (resultIndex != -1) { printf("%d は配列の %d 番目(インデックス %d)に見つかりました。\n", targetValue, resultIndex + 1, resultIndex); } else { printf("%d は配列の中に見つかりませんでした。\n", targetValue); } return 0; }
どうでしょう?意外と短く書けると思いませんか?
このコードをコピペして、お手元の環境で動かしてみるのが一番の勉強になりますよ!
コードの解説と実行結果
さて、さっきのコードが何をやっているのか、もう少し詳しく見ていきましょう。
`linearSearch` 関数
- `int linearSearch(int data[], int size, int target)`
この行は関数の宣言です。`int data[]`で整数の配列を、`int size`で配列の要素数を、`int target`で探したい値を受け取ります。戻り値は`int`型(見つかった場所の番号か、見つからなかった印)です。 - `for (int i = 0; i < size; i++)`
これが配列を順番に見ていくためのループです。`i`が0から始まり、`size - 1`まで1ずつ増えながら繰り返します。`i`は配列のインデックス(何番目の要素かを示す番号、0から始まる)に対応しています。 - `if (data[i] == target)`
ここが比較の心臓部!配列の`i`番目の要素`data[i]`と、探している値`target`が同じかどうかをチェックしています。 - `return i;`
もし値が一致したら、その場所を示すインデックス`i`を返して、関数はここで終了します。見つかった!ってことですね。 - `return -1;`
`for`ループが最後まで終わっても(つまり、配列の最後まで見ても)値が見つからなかった場合、この行が実行されます。見つからなかったよ、という合図として`-1`を返しています。(配列のインデックスは0から始まるので、-1はありえない値として使えます)
`main` 関数
- `int numbers[] = { ... };`
探索の対象となるサンプルデータ(配列)を用意しています。 - `int size = sizeof(numbers) / sizeof(numbers[0]);`
これはC言語で配列の要素数を計算する定番テクニック。配列全体のサイズを、配列の要素1つ分のサイズで割っています。 - `int targetValue = 15;`
最初に探す値を設定しています。 - `int resultIndex = linearSearch(numbers, size, targetValue);`
上で作った`linearSearch`関数を呼び出して、探索を実行し、結果(見つかったインデックス or -1)を`resultIndex`に保存しています。 - `if (resultIndex != -1) { ... } else { ... }`
`resultIndex`の値を見て、見つかったかどうかで表示するメッセージを切り替えています。見つかった場合は、インデックス(0始まり)に1を足して「〇番目」と表示すると、人間には分かりやすいですよね。
実行結果の例
15 は配列の 5 番目(インデックス 4)に見つかりました。 100 は配列の中に見つかりませんでした。
こんな風に表示されれば成功です!
`targetValue`の値を変えてみて、結果がどう変わるか試してみるのも面白いですよ。
【実践】C言語で学ぶ!バブルソートアルゴリズムの実装
探索の次は、もう一つの超基本アルゴリズム、「ソート」に挑戦しましょう!
ソートっていうのは、データを特定の順序(例えば、小さい順や大きい順)に並べ替えることです。
データが綺麗に並んでいると、後で特定のデータを探しやすくなったり(線形探索より効率的な探索法が使える!)、見やすくなったり、何かと便利なんです。
今回は、ソートアルゴリズムの中でも、考え方が比較的シンプルで理解しやすい「バブルソート」をC言語で実装してみます。
バブルソートアルゴリズムとは
バブルソートは、隣り合う要素を比較して、もし順序が逆だったら(例えば小さい順に並べたいのに左が大きい場合)、それらを入れ替えるという操作を繰り返していく方法です。
この比較と入れ替えを配列の端から端まで行うと、一番大きい(または小さい)要素が、まるで泡(バブル)が水面に上がっていくように、配列の端に移動します。
この操作を、配列全体が綺麗に並び終わるまで繰り返すのがバブルソートです。
// 配列 [ 5 | 1 | 4 | 2 | 8 ] を小さい順にソート // 1周目 (一番大きい「8」が右端へ) [ 5 | 1 | 4 | 2 | 8 ] → (5と1比較) → [ 1 | 5 | 4 | 2 | 8 ] [ 1 | 5 | 4 | 2 | 8 ] → (5と4比較) → [ 1 | 4 | 5 | 2 | 8 ] [ 1 | 4 | 5 | 2 | 8 ] → (5と2比較) → [ 1 | 4 | 2 | 5 | 8 ] [ 1 | 4 | 2 | 5 | 8 ] → (5と8比較) → [ 1 | 4 | 2 | 5 | 8 ] (変化なし) // 2周目 (次に大きい「5」が右から2番目へ) [ 1 | 4 | 2 | 5 | 8 ] → (1と4比較) → [ 1 | 4 | 2 | 5 | 8 ] (変化なし) [ 1 | 4 | 2 | 5 | 8 ] → (4と2比較) → [ 1 | 2 | 4 | 5 | 8 ] [ 1 | 2 | 4 | 5 | 8 ] → (4と5比較) → [ 1 | 2 | 4 | 5 | 8 ] (変化なし) // 8は確定してるので比較不要 // ...これを繰り返していくと... // 最終結果 [ 1 | 2 | 4 | 5 | 8 ]
メリットは、アルゴリズムの考え方がすごくシンプルで、実装しやすいこと。
デメリットは、線形探索と同じく、データの数が多いと、並べ替えに結構時間がかかってしまうことです。
でも、隣同士をひたすら比べる、愚直さが愛しいソートですよね!まずは、この動きをコードにする練習をしましょう。
C言語によるバブルソートの実装コード例
それでは、C言語でバブルソートを書いてみましょう。
整数の配列を小さい順(昇順)に並べ替える関数を実装します。
#include <stdio.h> // 配列の要素を交換する関数 void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } // バブルソートを行う関数 // data: ソート対象の配列 // size: 配列の要素数 void bubbleSort(int data[], int size) { // i は「何周目か」を表す (0周目からsize-2周目まで) // 各周で一番大きいものが右端に確定していくイメージ for (int i = 0; i < size - 1; i++) { // j は「隣と比較する要素の左側のインデックス」を表す // 各周の終わりでは、右端のi個の要素は既にソート済みなので比較不要 for (int j = 0; j < size - 1 - i; j++) { // 隣り合う要素を比較 (左 > 右 なら入れ替え) if (data[j] > data[j + 1]) { // 要素を交換する swap(&data[j], &data[j + 1]); } } } } // 配列の内容を表示する関数 void printArray(int data[], int size) { for (int i = 0; i < size; i++) { printf("%d ", data[i]); } printf("\n"); } int main(void) { // ソート対象のデータ(配列) int numbers[] = {5, 1, 4, 2, 8}; // 配列の要素数を計算 int size = sizeof(numbers) / sizeof(numbers[0]); printf("ソート前: "); printArray(numbers, size); // バブルソート関数を呼び出してソートを実行 bubbleSort(numbers, size); printf("ソート後: "); printArray(numbers, size); return 0; }
線形探索より少し複雑に見えますか?
特に二重の`for`ループがポイントですね。
ネストしたループがバブルソートのキモ!落ち着いて読み解けば大丈夫です。
コードの解説と実行結果
バブルソートのコードも詳しく見ていきましょう。
`swap` 関数
- `void swap(int *a, int *b)`
これは、2つの整数変数の値を入れ替えるための補助的な関数です。C言語で関数の外にある変数の値を変更するには、ポインタ(変数のメモリアドレス)を使う必要があります。`*a` はポインタ`a`が指す先の値、という意味です。`temp`という一時的な変数を使って、値をうまく入れ替えています。
`bubbleSort` 関数
- `for (int i = 0; i < size - 1; i++)`
外側のループです。配列の要素が`size`個ある場合、`size - 1`回の周回を行えば、全体がソートされることが保証されます(最後の1個は自動的に正しい位置に来るため)。`i`が増えるごとに、配列の右端からソート済み要素が確定していきます。 - `for (int j = 0; j < size - 1 - i; j++)`
内側のループです。ここで隣り合う要素の比較と交換を行います。`j`は比較するペアの左側のインデックスです。ループの範囲が`size - 1 - i`となっているのは、外側のループ(`i`)が進むにつれて、右端の要素は既にソート済みで比較する必要がなくなるからです。ちょっとした効率化ですね。 - `if (data[j] > data[j + 1])`
ここで大小比較!左側の要素`data[j]`が右側の要素`data[j + 1]`より大きいかどうかをチェックしています。(小さい順に並べたいので、左が大きいのはNG) - `swap(&data[j], &data[j + 1]);`
もし左側が大きければ、`swap`関数を呼び出して、`data[j]`と`data[j + 1]`の値を入れ替えます。`&`は変数のアドレス(ポインタ)を取得する記号です。
`printArray` 関数
- 配列の中身を順番に表示するための関数です。ソート前とソート後の状態を確認するために使います。
`main` 関数
- ソート対象の配列`numbers`を用意し、要素数`size`を計算します。
- `printArray`でソート前の状態を表示します。
- `bubbleSort`関数を呼び出して、配列`numbers`をソートします。
- 再度`printArray`でソート後の状態を表示します。
実行結果の例
ソート前: 5 1 4 2 8 ソート後: 1 2 4 5 8
このように、配列が小さい順に並び替えられていれば成功です!
`numbers`配列の中身を変えて、色々なパターンで試してみてくださいね。
C言語アルゴリズム実装における注意点
よし、コードも書けたし実行もできた!…と思ったら、なぜかうまく動かない、エラーが出る、なんてことも、プログラミングにはつきものです。
特にC言語は、自由度が高い反面、ちょっとしたミスが大きな問題につながることもあります。
ここで、初心者がアルゴリズムを実装するときに、特にハマりやすいポイントと、簡単な解決のヒントを紹介しておきますね。
配列の範囲外アクセス
これが一番よくあるミスかもしれません。C言語の配列は、例えば要素数が5個なら、インデックスは0から4までです。なのに、うっかり`data[5]`にアクセスしようとしたり、ループの条件を間違えて`i <= 5`(`i`が5になる)としてしまったりすると、範囲外にアクセスしてしまい、プログラムが異常終了したり、予期せぬ動作をしたりします。ループの終了条件の間違い
「あと1回足りなかった」とか「1回多くループしちゃった」というのもありがちです。比較や交換のロジックミス
ソートで、小さい順にしたいのに比較演算子(`>`や`<`)が逆だったり、探索で`==`(等しい)と書くべきところを`=`(代入)と書いてしまったり(これはコンパイル時に警告が出ることが多いですが)。変数の初期化忘れ
合計値を計算する変数などを、最初に0で初期化し忘れると、予期しないゴミの値が入ったまま計算が進んでしまうことがあります。変数は使う前に適切な値で初期化する習慣をつけましょう。じゃあ、もしプログラムがうまく動かなかったらどうするか?
パニックにならず、まずは落ち着いて原因を探りましょう。そのための原始的だけど強力な方法が「`printf`デバッグ」です。
これは、プログラムの途中で、怪しいと思われる変数の値を`printf`関数を使って表示させてみる方法です。
// 例:ループの中で変数の値を確認する for (int i = 0; i < size; i++) { printf("ループ %d 回目: i = %d, data[i] = %d\n", i + 1, i, data[i]); // ←追加 if (data[i] == target) { return i; } }
こんな風に`printf`を仕込んで実行すれば、「ループの何回目で、変数がどんな値になっているか」が丸見えになります。
自分が想定していた動きと違う値になっていれば、そこがバグ(プログラムの誤り)の原因である可能性が高いです。
エラーは友達!怖くない!
エラーメッセージを読んだり、`printf`デバッグをしたりして、原因を突き止める過程も、プログラミングのスキルアップには欠かせない経験ですよ。
【まとめ】C言語でのアルゴリズム実装力を伸ばすために
お疲れ様でした!
この記事では、C言語を使ったアルゴリズム実装の基本として、
- アルゴリズム実装とは何か
- 実装前の考え方のステップ
- 基本的な探索「線形探索」のコードと解説
- 基本的なソート「バブルソート」のコードと解説
- 実装時の注意点と簡単なデバッグ方法
といった内容を見てきました。
アルゴリズム実装は、「①アルゴリズムの動きを理解する → ②それをどうコードにするか考える(設計) → ③実際にコードを書く → ④動かしてテストする」というサイクルの繰り返しです。
そして、何よりの上達法は、とにかく自分の手を動かしてコードを書いてみること!
最初は、この記事のサンプルコードをそのまま書き写す(写経する)だけでもOKです。
動かしてみて、「なるほど、こう書くとこう動くのか」と体感することが大事です。
慣れてきたら、配列の中身を変えたり、少し処理を追加したりして、改造してみるのも面白いでしょう。
今回紹介したのは、数あるアルゴリズムの中のほんの入り口にすぎません。
もし興味が湧いたら、
- 他のソートアルゴリズム(選択ソート、挿入ソートなど)
- もう少し効率的な探索アルゴリズム(二分探索 ※データがソート済みである必要あり)
- 配列以外のデータの持ち方(データ構造:連結リスト、木構造など)
といった、さらに奥深い世界に挑戦してみるのも良いかもしれません。
難しそうに感じるかもしれませんが、一つ一つステップを踏んでいけば、必ず理解できるようになります。
この記事が、あなたのC言語でのアルゴリズム実装マスターへの道を歩み始める、きっかけとなれたら嬉しいです。
【関連記事】
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。