![見出し画像](https://assets.st-note.com/production/uploads/images/103188785/rectangle_large_type_2_923ac9b7aac35d0159c89f5795acccb6.png?width=1200)
C言語教室 第22回 - 素数を求める
だいぶ前に
素数の魅力
という記事を書いたのですが、この記事のPVが毎週コンスタントにあり、全体としてもかなり上位のPVを持つページになっています。素数って人気があるんですね。
これも、少し前になるのですが、お世話になっている AyumiKatayama さんの
Prime numbers sieve C言語化編
を読んで、この話に参加しようと思っていました。やってみたかったことはリストを使うので、リストの説明が済んだので、ようやく書けるところまで来ました。もっとも、この記事の主旨とは少しずれているかもしれませんが、そこは悪しからず。
ということで、今回は素数を求めるプログラムを書いてみます。素数を求めるには、エラトステネスの篩という方法を使うのが簡単です。
エラトステネスの篩
自然数の一覧を用意して、最も小さな数から、その数の倍数を捨てて、数を増やしつつ、最終的に残った数が素数になるよという方法です。
![](https://assets.st-note.com/production/uploads/images/103188764/picture_pc_4d5cb70d5414b423e11b388dc3bbb3f1.gif)
これをC言語で書くのに、自然数の一覧を配列にするのが最も簡単です。
#include <stdio.h>
#define MAX 1000
void main() {
int i, j;
int n[MAX + 1];
/* init */
for (i = 0; i <= MAX; i++)
n[i] = -1;
/* check */
for (i = 2; i * i <= MAX; i++)
for (j = i * i; j < MAX; j += i)
n[j] = 0;
/* output */
for (i = 2; i < MAX; i++)
if (n[i])
printf("%d\t", i);
printf("\n");
}
アルゴリズムを素直に展開すると平方根が出てくるのですが、そんな計算をすると遅くなるだけなので、自乗と比較するのがポイントと言えばポイントです。
この短いコードでちゃんと以下の結果が得られます。
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997
配列を使うのが素直で、メモリを節約するために配列を char にしたり、ビットフィールドは使いにくいのですが、上手にビット演算を行って、素数かどうかを示すのに1ビットまで圧縮する方法もあるとは思います。いずれにせよ、求める数だけの領域が必要になり、このやり方で領域を分割するのは難しそうです。
そういう訳で、リストを使って篩を実装してみようと思い立ちました。今回は単方向リストで書いてみます。
#include <stdio.h>
#include <stdlib.h>
#define MAX 1000
// list
typedef struct node {
int val;
struct node *next;
} Node;
void main() {
int n = MAX;
int i;
// generate list
Node *head = NULL;
for (i = n; i >= 2; i--) {
Node *node = malloc(sizeof(Node));
node->val = i;
node->next = head;
head = node;
}
// check
Node *current = head;
while (current->val * current->val <= n) {
// Remove nonPrime
Node *prev = current;
Node *node = current->next;
while (node != NULL) {
if (node->val % current->val == 0) {
prev->next = node->next;
free(node);
node = prev->next;
} else {
prev = node;
node = node->next;
}
}
current = current->next;
}
// output
current = head;
while (current != NULL) {
printf("%d\t", current->val);
current = current->next;
}
printf("\n");
// destroy remain
current = head;
while (current != NULL) {
Node *next = current->next;
free(current);
current = next;
}
}
コードをコンパクトにしたいので、処理の中にリスト操作も埋め込んでしまいました。最初に自然数の一覧をリストとして生成し、この中から倍数(余りが0)をみつけたら、リストから外していきます。これで調べる数の自乗が最も大きな数を超えたら、リストに残った数が素数です。
配列とやっていることはほとんど(ループの回し方だけ少し違う)同じですし、配列よりもリストのほうがメモリはたくさん使うのですが、計算が進むにつれリストは短くなっていくので、メモリも減っていきます。
この先のコードはまだ検討していないのですが、次に求める領域をリストに追加して、リストの最初から繰り返せば続きの計算ができるのでは、なんて思っています。もしかしたらもう一捻りが必要かもしれませんが。
python のイテレータも実装は確認していませんが、基本的にはこういう構造で、filterにかけている処理こそが篩の部分になっているような気はしています。
今回は特に課題という形にせず、自由演習ということで解答は用意しないと思いますが、リストを使ったコードを、前回までの教室で学んだ自身のリスト関数を使って書いてみてください。余力があれば、より大きな数字に対して素数を調べる方法なんて研究していただければ幸い。とても大きな数を相手にする場合は、途中からファイルに吐き出す必要も出てくるとは思うのですが、まだファイル操作やってないので、続きはその頃かな。
次回は、スタック、キューなどを取り上げる予定です。
#C言語 #プログラミング講座 #素数 #エラトステネスの篩 #リスト
いいなと思ったら応援しよう!
![kzn](https://assets.st-note.com/production/uploads/images/111925158/profile_dffba6e25229ee04f46397fb91bbfc20.jpg?width=600&crop=1:1,smart)