Trước hết cũng nên nhắc lại khái niệm số nguyên tố (prime number): số nguyên tố là số nguyên dương chỉ chia hết cho 1 và chính nó. Theo định nghĩa này số nguyên tố là số tự nhiên và chỉ có hai ước số phân biệt, đó chính là số 1 và bản thân nó. Từ đó suy ra số 1 không phải là một số nguyên tố (có rất nhiều sinh viên mới học lập trình nhầm số 1 là số nguyên tố). Một kết quả khác cũng không kém quan trọng được rút ra đó là số 2 là số nguyên tố đầu tiên (nhỏ nhất) và cũng là số nguyên tố chẵn duy nhất.
Như vậy để kiểm tra một số nguyên có là số nguyên tố hay không, theo suy nghĩ trực quan của tất cả các lập trình viên hay thậm chí một người không hiểu biết gì về thuật toán thì chúng ta cần kiểm tra xem số đó có ước số nào khác 1 và chính nó hay không, nếu có thì đó là hợp số (combine number) còn nếu không có số nào thì đó chính là một số nguyên tố. Thuật toán đầu tiên đến với chúng ta đó là: kiểm tra các số có khả năng là ước số của n (số cần kiểm tra tính nguyên tố), các số này nằm trong khoảng từ 2 tới n – 1. Thuật toán được cài đặt bằng C đơn giản như sau:
int ktnguyento1(int n)
{
// hàm kiểm tra n có là số nguyên tố hay không
// kết quả: 1 nếu đúng, 0 nếu sai
int i;
int kq = 1; // gia sử n là số nguyên tố
for(i=2;i
if(n % i == 0)
{
// n có ước số là i, không cần kiểm tra tiếp các giá trị tiếp theo
kq = 0;
break;
}
return kq;
}
int ktnguyento1(int n)
{
// hàm kiểm tra n có là số nguyên tố hay không
// kết quả: 1 nếu đúng, 0 nếu sai
int i;
for(i=1;i<=n;i++)
if(n % i == 0) return 0;
return 1;
}
int ktnguyento1(int n)
{
// hàm kiểm tra n có là số nguyên tố hay không
// kết quả: 1 nếu đúng, 0 nếu sai
int i;
if (n==1)
return 0;
for(i=1;i<=n;i++)
if(n % i == 0) return 0;
return 1;
}
int ktnguyento0(int n)
{
// hàm kiểm tra n có là số nguyên tố hay không
// kết quả: 1 nếu đúng, 0 nếu sai
int i;
int dem = 0; // đếm tổng các ước của n
for(i=1;i<=n;i++)
if(n % i == 0) dem = dem + i;
if(dem==n+1) return 1;
return 0; // tương tự như else return 0;
}
Trong các cài đặt trên, nếu n là số nguyên tố thì vòng lặp for sẽ chạy tới khi i = n – 1 để có thể đưa ra kết luận cuối cùng. Tuy nhiên, suy nghĩ thêm một chút chúng ta sẽ thấy rằng không cần phải kiểm tra đến giá trị i = n – 1 mà thực chất chỉ cần tới n/2 (n div 2) vì không có ước số nào của n lớn hơn n/2. Vì vậy thuật toán 2 sẽ là như sau:
int ktnguyento2(int n)
{
// hàm kiểm tra n có là số nguyên tố hay không
// kết quả: 1 nếu đúng, 0 nếu sai
int i;
if (n==1)
return 0;
for(i=2;i<=n/2;i++)
if(n % i == 0) return 0;
return 1;
}
int ktnguyento3(int n)
{
int i;
if (n==1)
return 0;
for(i=2;i<=(int)sqrt(n);i++)
if(n % i == 0) return 0;
return 1;
}
Với cài đặt trên vẫn có thể có cải tiến được (hầu hết các sinh viên và những người mới học lập trình không để ý điều này), đó là thay vì mỗi lần lặp đều tính sqrt(n) ta chỉ cần tính một lần trước khi thực hiện vòng lặp:
int ktnguyento3(int n)
{
int i;
int m;
if (n==1)
return 0;
m = (int)sqrt(n);
for(i=2;i<=m;i++)
if(n % i == 0) return 0;
return 1;
}
Lại để ý rằng các số nguyên tố chỉ có thể là các số lẻ trừ số 2, và do đó chúng không thể chia hết cho các số chẵn nên ta chỉ cần kiểm tra các ước số (giá trị của i) là số lẻ, do vậy thuật toán tiếp theo (4) sẽ như sau:
int ktnguyento4(int n)
{
int i;
int m;
if(n == 2)
return 1;
if (n == 1||n % 2 == 0)
return 0;
m = (int)sqrt(n);
for(i=3;i<=m;i=i+2)
if(n % i == 0) return 0;
return 1;
}
Vừa rồi ta đã loại bỏ được một nửa số giá trị của i bằng cách xét ước số 2 có thể có của n. Tiếp theo ta sẽ cải tiến thuật toán bằng cách xét ước số nguyên tố tiếp theo có thể có của n là số 3. Nếu n chia hết cho 2 hoặc 3 thì dễ dàng kết luận nó là hợp số (tất nhiên n phải khác hai giá trị đó). Ngược lại n sẽ có ước số có dạng , , ta sẽ bắt đầu với giá trị i = 5 nên sẽ chọn công thức . Nhưng nếu chỉ kiểm tra i sẽ thiếu mất ứng cử viên ở dạng nên ta sẽ kiểm tra thêm giá trị i+2 ( ) cho đủ. Vậy thuật toán mới (5) sẽ là như sau:
int ktnguyento5(int n)
{
int i;
int m;
if(n == 2 || n == 3)
return 1;
if (n == 1||n % 2 == 0||n % 3 == 0)
return 0;
m = (int)sqrt(n);
for(i=5;i<=m;i=i+6)
if(n % i == 0 || n % (i+2) == 0) return 0;
return 1;
}
int ktnguyento51(int n)
{
int i;
int m, y;
if(n == 2 || n == 3)
return 1;
if (n == 1||n % 2 == 0||n % 3 == 0)
return 0;
m = (int)sqrt(n);
y = 2;
for(i=5;i<=m;i=i+y, y = 6 - y)
if(n % i == 0) return 0;
return 1;
}
const int MAX_N = 10000; // giá trị lớn nhất của n
int nt[MAX_N]; // mảng đánh dấu, nt[i] = 0 nếu i là số nguyên tố, 1 nếu ngược lại
void sangnt(int n)
{
// đánh dấu các số nguyên tố nhỏ hơn n
int i, k;
for (i=2; i<n; i++)
if (nt[i] == 0) // nếu i là số nguyên tố thì các bội của i sẽ là hợp số
{
k = 2;
// đánh dấu các bội số của i
while (k*i<n)
nt[i*k++] = 1;
}
}
Qua việc trình bày các thuật toán đơn giản để kiểm tra các số nguyên tố nhỏ, tôi hy vọng mang đến cho các bạn một thuật toán tốt để có thể sử dụng trong các cuộc thi lập trình hay các bài toán trong phạm vi nhỏ, đồng thời qua đó minh họa quá trình tinh chỉnh cài đặt một thuật toán. Có thể cùng một thuật toán nhưng với các cài đặt khác nhau, hiệu quả sẽ khác nhau, và nhiều khi hiệu quả đó lại dựa trên những chi tiết tưởng chừng như rất nhỏ nhặt, không đáng lưu tâm.
0 nhận xét:
Đăng nhận xét
Cảm ơn bạn đã đóng góp ý kiến thảo luận của mình tại http://phoenixsinh.blogspot.com. Mời bạn tham đến với blog mùa tóc rối tại địa chỉ http://muatocroi.com. chúc bạn vui vẻ