この記事では、C言語を使って素数を1000個求めて出力するプログラムの作り方を解説します。
素数を見つけるための基本的なアルゴリズムである「試し割り法」と「エラトステネスの篩」の2つの方法を紹介し、それぞれの方法を使ったプログラムの設計と実装方法を詳しく説明します。
また、これらの方法のパフォーマンスを比較し、どちらが効率的かを実行時間の測定を通じて分析します。
素数を求めるアルゴリズム
素数を求めるためのアルゴリズムにはいくつかの方法があります。
ここでは、代表的な「試し割り法」と「エラトステネスの篩」について解説します。
試し割り法
試し割り法の基本的な考え方
試し割り法は、ある数が素数かどうかを判定するための基本的な方法です。
この方法では、対象の数を2からその数の平方根までの整数で割り、割り切れるかどうかを確認します。
割り切れる数が存在しない場合、その数は素数と判定されます。
例えば、17が素数かどうかを確認する場合、2から√17(約4.1)までの整数(2, 3, 4)で割り算を行います。
どの数でも割り切れないため、17は素数と判定されます。
試し割り法の効率化
試し割り法は基本的な方法ですが、効率を上げるための工夫も可能です。
以下のポイントを考慮すると、計算量を減らすことができます。
- 偶数の除外: 2以外の偶数は素数ではないため、最初に2をチェックし、その後は奇数のみをチェックします。
- 平方根までのチェック: 対象の数の平方根までの整数で割り算を行うことで、計算量を大幅に減らせます。
エラトステネスの篩
エラトステネスの篩の基本的な考え方
エラトステネスの篩(ふるい)は、古代ギリシャの数学者エラトステネスが考案した素数を効率的に求める方法です。
この方法では、ある範囲内の数から素数を見つけるために、以下の手順を踏みます。
- 2から始めて、リスト内の最小の数を素数とする。
- その数の倍数をリストから削除する。
- リスト内の次の最小の数を素数とし、同様にその倍数を削除する。
- これをリストの最後まで繰り返す。
この方法により、効率的に素数を見つけることができます。
エラトステネスの篩の実装方法
エラトステネスの篩をC言語で実装する方法を以下に示します。
- 配列の初期化: 2からNまでの数を格納する配列を用意し、すべての要素を真(素数候補)に設定します。
- 篩い落とし: 配列の最小の素数候補から始め、その倍数をすべて偽(素数ではない)に設定します。
- 次の素数候補: 配列内の次の真の要素を見つけ、同様にその倍数を偽に設定します。
- 終了条件: 配列の最後まで処理を繰り返し、真の要素が素数となります。
この方法を用いることで、効率的に素数を求めることができます。
C言語でのプログラム設計
ここでは、C言語を用いて素数を1000個求めて出力するプログラムの設計方法について解説します。
具体的には、必要なライブラリのインクルードから始まり、変数の宣言と初期化、素数判定関数の作成、メイン関数の設計、そして最終的な完成コードまでを順を追って説明します。
必要なライブラリのインクルード
まず、C言語でプログラムを作成する際に必要なライブラリをインクルードします。
標準入出力を行うためにstdio.h
を、メモリ操作を行うためにstdlib.h
をインクルードします。
#include <stdio.h>
#include <stdlib.h>
変数の宣言と初期化
次に、プログラムで使用する変数を宣言し、初期化します。
ここでは、素数を格納するための配列や、素数のカウントを行うための変数を用意します。
#define MAX_PRIMES 1000
int primes[MAX_PRIMES]; // 素数を格納する配列
int count = 0; // 素数のカウント
素数判定関数の作成
素数を判定するための関数を作成します。
ここでは、試し割り法とエラトステネスの篩の2つの方法を紹介します。
試し割り法による素数判定関数
試し割り法を用いた素数判定関数は、与えられた数が素数かどうかを判定します。
具体的には、2からその数の平方根までの整数で割り切れるかどうかをチェックします。
int is_prime_trial_division(int num) {
if (num <= 1) return 0;
for (int i = 2; i * i <= num; i++) {
if (num % i == 0) return 0;
}
return 1;
}
エラトステネスの篩による素数判定関数
エラトステネスの篩を用いた素数判定関数は、ある範囲内の全ての素数を効率的に求めることができます。
ここでは、1000個の素数を求めるために十分な範囲を設定します。
void sieve_of_eratosthenes(int limit, int *primes, int *count) {
int *is_prime = malloc((limit + 1) * sizeof(int));
for (int i = 0; i <= limit; i++) is_prime[i] = 1;
is_prime[0] = is_prime[1] = 0;
for (int p = 2; p * p <= limit; p++) {
if (is_prime[p]) {
for (int i = p * p; i <= limit; i += p) {
is_prime[i] = 0;
}
}
}
for (int p = 2; p <= limit && *count < MAX_PRIMES; p++) {
if (is_prime[p]) {
primes[(*count)++] = p;
}
}
free(is_prime);
}
メイン関数の設計
メイン関数では、素数を1000個求めるためのループと、求めた素数を出力する処理を行います。
素数を1000個求めるループ
試し割り法を用いる場合、素数を1000個求めるためのループを以下のように設計します。
int num = 2;
while (count < MAX_PRIMES) {
if (is_prime_trial_division(num)) {
primes[count++] = num;
}
num++;
}
エラトステネスの篩を用いる場合、適切な範囲を設定して素数を求めます。
int limit = 10000; // 適切な範囲を設定
sieve_of_eratosthenes(limit, primes, &count);
素数の出力方法
求めた素数を出力するための処理を以下のように設計します。
for (int i = 0; i < count; i++) {
printf("%d\n", primes[i]);
}
完成したコード
以上の設計を基に、完成したコードは以下のようになります。
#include <stdio.h>
#include <stdlib.h>
#define MAX_PRIMES 1000
int primes[MAX_PRIMES];
int count = 0;
int is_prime_trial_division(int num) {
if (num <= 1) return 0;
for (int i = 2; i * i <= num; i++) {
if (num % i == 0) return 0;
}
return 1;
}
void sieve_of_eratosthenes(int limit, int *primes, int *count) {
int *is_prime = malloc((limit + 1) * sizeof(int));
for (int i = 0; i <= limit; i++) is_prime[i] = 1;
is_prime[0] = is_prime[1] = 0;
for (int p = 2; p * p <= limit; p++) {
if (is_prime[p]) {
for (int i = p * p; i <= limit; i += p) {
is_prime[i] = 0;
}
}
}
for (int p = 2; p <= limit && *count < MAX_PRIMES; p++) {
if (is_prime[p]) {
primes[(*count)++] = p;
}
}
free(is_prime);
}
int main() {
// 試し割り法を用いる場合
int num = 2;
while (count < MAX_PRIMES) {
if (is_prime_trial_division(num)) {
primes[count++] = num;
}
num++;
}
// エラトステネスの篩を用いる場合
// int limit = 10000;
// sieve_of_eratosthenes(limit, primes, &count);
for (int i = 0; i < count; i++) {
printf("%d\n", primes[i]);
}
return 0;
}
このコードを実行すると、1000個の素数が出力されます。
試し割り法とエラトステネスの篩のどちらを使用するかは、コメントアウトを切り替えることで選択できます。
パフォーマンスの比較
素数を求めるアルゴリズムにはいくつかの方法がありますが、ここでは「試し割り法」と「エラトステネスの篩」の2つの方法について、そのパフォーマンスを比較します。
試し割り法とエラトステネスの篩の比較
まず、試し割り法とエラトステネスの篩の基本的な違いについて説明します。
- 試し割り法:
- 各数値が素数かどうかを判定するために、その数値の平方根までの全ての素数で割り切れるかどうかを確認します。
- 計算量はO(n√n)であり、大きな数値に対しては効率が悪くなります。
- エラトステネスの篩:
- 2から始めて、各素数の倍数を順次消していくことで素数を見つけます。
- 計算量はO(n log log n)であり、試し割り法に比べて大きな数値に対しても効率が良いです。
実行時間の測定方法
プログラムの実行時間を測定するためには、C言語の標準ライブラリであるtime.h
を使用します。
以下に、実行時間を測定するためのコード例を示します。
#include <stdio.h>
#include <time.h>
// 試し割り法による素数判定関数
int is_prime_trial_division(int num) {
if (num <= 1) return 0;
for (int i = 2; i * i <= num; i++) {
if (num % i == 0) return 0;
}
return 1;
}
// エラトステネスの篩による素数判定関数
void sieve_of_eratosthenes(int limit, int primes[]) {
for (int i = 0; i <= limit; i++) primes[i] = 1;
primes[0] = primes[1] = 0;
for (int i = 2; i * i <= limit; i++) {
if (primes[i]) {
for (int j = i * i; j <= limit; j += i) {
primes[j] = 0;
}
}
}
}
int main() {
int limit = 100000;
int primes[limit + 1];
// 試し割り法の実行時間測定
clock_t start = clock();
for (int i = 2; i <= limit; i++) {
is_prime_trial_division(i);
}
clock_t end = clock();
double trial_division_time = (double)(end - start) / CLOCKS_PER_SEC;
printf("試し割り法の実行時間: %f秒\n", trial_division_time);
// エラトステネスの篩の実行時間測定
start = clock();
sieve_of_eratosthenes(limit, primes);
end = clock();
double sieve_time = (double)(end - start) / CLOCKS_PER_SEC;
printf("エラトステネスの篩の実行時間: %f秒\n", sieve_time);
return 0;
}
結果の分析
上記のコードを実行すると、試し割り法とエラトステネスの篩の実行時間が表示されます。
一般的に、エラトステネスの篩の方が試し割り法よりも高速であることが確認できます。
例えば、以下のような結果が得られるかもしれません。
試し割り法の実行時間: 2.345678秒
エラトステネスの篩の実行時間: 0.123456秒
この結果から、エラトステネスの篩が試し割り法に比べて非常に効率的であることがわかります。
特に大きな数値に対しては、その差が顕著に現れます。
以上のように、素数を求める際にはアルゴリズムの選択が重要であり、エラトステネスの篩のような効率的な方法を使用することで、計算時間を大幅に短縮することができます。