0965303260
TP. Hồ Chí Minh

[C]. Số Hoàn Hảo

Trong bài học này các bạn cùng tìm hiểu cách kiểm tra số Hoàn Hảo hay số Hoàn Thiện, mình sẽ hướng dẫn nhiều cách từ cơ bản tới nâng cao và bạn có thể lựa chọn cách tiếp cận phù hợp với mình.

NỘI DUNG : 

  • Thuật Toán Cơ Bản
  • Định Lý Euclid - Euler 
  • Video Tutorial

main

1. Thuật Toán Cơ Bản

Số hoàn hảo là số mà tổng các ước thực sự của nó (không tính chính nó) bằng chính nó. Ví dụ N = 28 có các ước 1, 2, 4, 7, 14, 28 thì các ước thực sự là 1, 2, 4, 7, 14 có tổng cũng bằng 28 nên 28 là số hoàn hảo

Để kiểm tra N là số hoàn hảo bạn chỉ cần tính tổng ước của N sau đó loại bỏ ước N và so sánh với N 

Code 1 : 

#include "stdio.h"
#include "math.h"

int is_perfect(int n){
    int tong = 0;
    for(int i = 1; i <= sqrt(n); i++){
        if(n % i == 0){
            tong += i;
            if(i != n / i){
                tong += n / i;
            }
        }
    }
    if(tong - n == n){
        return 1;
    }
    return 0;
}

int main(){
    int n = 1000;
    for(int i = 1; i <= n; i++){
        if(is_perfect(i)){
            printf("%d ", i);
        }
    }
    return 0;
}

Output : 

6 28 496

Code trên tính tổng ước của N sau đó trừ đi chính nó vì ta đang muốn tính các ước mà không xét chính nó.

Bạn cũng có thể bỏ qua ước 1 để không xét ước N như sau, nhưng chú ý khi N = 1

Code 2 : 

#include "stdio.h"
#include "math.h"

int is_perfect(int n){
    int tong = 1; 
    for(int i = 2; i <= sqrt(n); i++){
        if(n % i == 0){
            tong += i;
            if(i != n / i){
                tong += n / i;
            }
        }
    }
    if(tong == n && n != 1){
        return 1;
    }
    return 0;
}

int main(){
    int n = 1000;
    for(int i = 1; i <= n; i++){
        if(is_perfect(i)){
            printf("%d ", i);
        }
    }
    return 0;
}


2. Định Lý Euclid - Euler

Để kiểm tra số hoàn hảo bạn có thể sử dụng code ở mục 1, tuy nhiên nó chỉ phù hợp khi số N bạn cần kiểm tra ≤ 1012. Trong trường hợp cần kiểm tra số N lên tới 1018 thì code trên chạy rất lâu mới ra kết quả. 

Bạn thử viết hàm kiểm tra số hoàn hảo như mục 1 với số long long và thử chạy với  N = 1018 để kiểm tra.

Phương pháp tối ưu hơn để kiểm tra số hoàn hảo đó là sử dụng định lý Euclid - Euler. Định lý này phát biểu như sau

Nếu p là số nguyên tố và 2p - 1 cũng là số nguyên tố thì : 2p-1 * (2p - 1) sẽ là một số hoàn hảo.

Ví dụ :

p = 2 là số nguyên tố, và 22 - 1 cũng là số nguyên tố nên : 22-1 * (22 - 1) = 6 là số hoàn hảo

p = 3 là số nguyên tố, và 23 - 1 cũng là số nguyên tố nên : 23-1 * (23 - 1) = 4 * 7 = 28 là số hoàn hảo

Vậy để kiểm tra được hết các số hoàn hảo từ 1 tới giới hạn lớn nhất của số long long (9.1018) thì bạn cần duyệt các số nguyên tố p lớn tới đâu ?

Nhận thấy tích 2p-1 * (2p - 1) ~ 22p sẽ phải lên tới 9.1018 ~ 263 => 2p ~ 63 nên bạn chỉ cần duyệt p tới 32, 33 là đủ rồi.

Code này sẽ sinh ra tất cả các số hoàn hảo từ 1 tới 9.1018 và so sánh với N

Code : 

#include "stdio.h"
#include "math.h"

int prime(long long n){
    if(n < 2){
        return 0;
    }
    for(int i = 2; i <= sqrt(n); i++){
        if(n % i == 0 ) return 0;
    }
    return 1;
}

int is_perfect(long long n){
    for(int p = 1; p <= 33; p++){
        if(prime(p)){
            long long tmp1 = pow(2, p) - 1;
            if(prime(tmp1)){
                long long tmp2 = pow(2, p - 1);
                if(tmp1 * tmp2 == n){
                    return 1;
                }
            }
        }
    }
    return 0;
}

int main(){
    int n = 1000;
    for(int i = 1; i <= n; i++){
        if(is_perfect(i)){
            printf("%d ", i);
        }
    }
    return 0;
}

Output : 

6 28 496


Bạn cũng có thể in ra các số hoàn hảo từ 1 tới 9.1018

#include "stdio.h"
#include "math.h"

int prime(long long n){
    if(n < 2){
        return 0;
    }
    for(int i = 2; i <= sqrt(n); i++){
        if(n % i == 0 ) return 0;
    }
    return 1;
}

void print_perfect(){
    for(int p = 1; p <= 33; p++){
        if(prime(p)){
            long long tmp1 = pow(2, p) - 1;
            if(prime(tmp1)){
                long long tmp2 = pow(2, p - 1);
                printf("%lld\n", tmp1 * tmp2);
            }
        }
    }
}

int main(){
    print_perfect();
    return 0;
}

Output : 

6
28
496
8128
33550336
8589869056
137438691328
2305843008139952128

Nhận xét : từ 1 tới 9.1018 cũng chỉ có 8 số hoàn hảo.



Xem thêm bài giảng về Số Hoàn Hảo của mình :

VJYDLSc7zYg