Bài 9.2: Số chính phương và Số hoàn hảo trong C++

Thế giới của các con số luôn ẩn chứa những điều kỳ diệu và thú vị. Trong lập trình, chúng ta thường xuyên làm việc với các loại số có tính chất đặc biệt. Hôm nay, chúng ta sẽ cùng nhau khám phá hai khái niệm quen thuộc trong toán học và xem cách chúng ta có thể kiểm tra chúng bằng C++: đó là số chính phươngsố hoàn hảo.

Việc hiểu và có thể code để kiểm tra các tính chất số học cơ bản này không chỉ giúp bạn củng cố kiến thức về toán, mà còn rèn luyện kỹ năng tư duy thuật toán và làm việc với các phép toán trong C++.

Khám phá Số Chính Phương (Perfect Square)

Đầu tiên, chúng ta hãy nói về số chính phương. Một số chính phương là một số nguyên không âm mà căn bậc hai của nó cũng là một số nguyên. Nói cách khác, nó là bình phương của một số nguyên nào đó.

Ví dụ:

  • 0 là số chính phương vì $0 = 0^2$
  • 1 là số chính phương vì $1 = 1^2$
  • 4 là số chính phương vì $4 = 2^2$
  • 9 là số chính phương vì $9 = 3^2$
  • 16 là số chính phương vì $16 = 4^2$
  • ...
  • Các số như 2, 3, 5, 6, 7, 8, 10... không phải là số chính phương.

Làm thế nào để kiểm tra một số nguyên n có phải là số chính phương trong C++? Cách đơn giản nhất là tính căn bậc hai của nó. Nếu kết quả là một số nguyên, thì n là số chính phương.

Trong C++, chúng ta có thể sử dụng hàm sqrt() từ thư viện <cmath> để tính căn bậc hai. Hàm này trả về một giá trị kiểu double. Để kiểm tra xem kết quả có phải là số nguyên hay không, chúng ta có thể làm tròn kết quả về số nguyên gần nhất và kiểm tra xem bình phương của số nguyên đó có bằng số ban đầu hay không.

Tuy nhiên, việc làm việc với số thực (double) có thể gặp vấn đề về độ chính xác nhỏ. Một cách tiếp cận khác an toàn hơn là tính căn bậc hai, sau đó ép kiểu kết quả về số nguyên, và kiểm tra xem bình phương của số nguyên sau khi ép kiểu có bằng số ban đầu hay không.

Hãy cùng xem code C++:

#include <iostream> // Để sử dụng cout và cin
#include <cmath>    // Để sử dụng hàm sqrt()

// Hàm kiểm tra xem một số có phải là số chính phương hay không
bool isPerfectSquare(int n) {
    // Số âm không phải là số chính phương
    if (n < 0) {
        return false;
    }
    // 0 và 1 là số chính phương (0*0=0, 1*1=1)
    if (n == 0 || n == 1) {
        return true;
    }

    // Tính căn bậc hai của n
    double root = sqrt(n);

    // Ép kiểu kết quả về số nguyên
    int intRoot = static_cast<int>(root);

    // Kiểm tra xem bình phương của số nguyên sau khi ép kiểu có bằng n hay không
    // Đây là cách an toàn để tránh sai số làm tròn của số thực
    return (intRoot * intRoot == n);
}

int main() {
    cout << "Kiem tra mot vai so co phai la so chinh phuong:\n";
    cout << boolalpha; // In true/false thay vi 1/0

    cout << "0 la so chinh phuong? " << isPerfectSquare(0) << endl;     // true
    cout << "1 la so chinh phuong? " << isPerfectSquare(1) << endl;     // true
    cout << "4 la so chinh phuong? " << isPerfectSquare(4) << endl;     // true
    cout << "9 la so chinh phuong? " << isPerfectSquare(9) << endl;     // true
    cout << "7 la so chinh phuong? " << isPerfectSquare(7) << endl;     // false
    cout << "16 la so chinh phuong? " << isPerfectSquare(16) << endl;    // true
    cout << "25 la so chinh phuong? " << isPerfectSquare(25) << endl;    // true
    cout << "26 la so chinh phuong? " << isPerfectSquare(26) << endl;    // false
    cout << "-4 la so chinh phuong? " << isPerfectSquare(-4) << endl;   // false

    return 0;
}

Giải thích code:

  1. Chúng ta sử dụng <iostream> để nhập xuất và <cmath> để dùng hàm sqrt().
  2. Hàm isPerfectSquare(int n) nhận vào một số nguyên n.
  3. Chúng ta xử lý các trường hợp đặc biệt: số âm (không phải SNT), 0 và 1 (là SNT).
  4. sqrt(n) tính căn bậc hai của n và trả về kiểu double.
  5. static_cast<int>(root) ép kiểu giá trị double của căn bậc hai về kiểu int. Điều này sẽ bỏ đi phần thập phân.
  6. Logic chính nằm ở intRoot * intRoot == n. Chúng ta kiểm tra xem bình phương của phần nguyên căn bậc hai có bằng số ban đầu n hay không. Nếu có, n là số chính phương.
  7. Trong main(), chúng ta gọi hàm isPerfectSquare với nhiều giá trị khác nhau để kiểm tra. boolalpha giúp in kết quả true hoặc false dễ đọc hơn.

Bạn cũng có thể viết một chương trình nhỏ để người dùng nhập số từ bàn phím và kiểm tra:

#include <iostream>
#include <cmath>

// Sử dụng lại hàm isPerfectSquare ở trên
bool isPerfectSquare(int n) {
    if (n < 0) return false;
    if (n == 0 || n == 1) return true;
    double root = sqrt(n);
    int intRoot = static_cast<int>(root);
    return (intRoot * intRoot == n);
}

int main() {
    int num;
    cout << "Nhap mot so nguyen de kiem tra so chinh phuong: ";
    cin >> num;

    if (isPerfectSquare(num)) {
        cout << num << " la so chinh phuong." << endl;
    } else {
        cout << num << " khong phai la so chinh phuong." << endl;
    }

    return 0;
}

Khám phá Số Hoàn Hảo (Perfect Number)

Tiếp theo, chúng ta sẽ tìm hiểu về số hoàn hảo. Số hoàn hảo là một số nguyên dương mà tổng các ước số dương thật sự (tức là các ước số dương nhỏ hơn nó) bằng chính số đó.

Ví dụ:

  • Số 6: Các ước số dương thật sự là 1, 2, 3. Tổng: 1 + 2 + 3 = 6. Vậy 6 là số hoàn hảo.
  • Số 28: Các ước số dương thật sự là 1, 2, 4, 7, 14. Tổng: 1 + 2 + 4 + 7 + 14 = 28. Vậy 28 là số hoàn hảo.
  • Số 12: Các ước số dương thật sự là 1, 2, 3, 4, 6. Tổng: 1 + 2 + 3 + 4 + 6 = 16. 16 khác 12, vậy 12 không phải số hoàn hảo.

Các số hoàn hảo tiếp theo là 496, 8128, 33550336,... Chúng khá hiếm!

Để kiểm tra một số n có phải là số hoàn hảo, chúng ta cần thực hiện các bước sau:

  1. Tìm tất cả các ước số dương của n, ngoại trừ chính n.
  2. Tính tổng các ước số đó.
  3. So sánh tổng với n. Nếu bằng nhau, n là số hoàn hảo.

Làm thế nào để tìm các ước số dương thật sự? Chúng ta có thể duyệt qua các số từ 1 đến n/2. Nếu một số i chia hết cho n (tức là n % i == 0), thì i là một ước số dương thật sự của n (vì nếu i > n/2i là ước số, thì i phải là n, nhưng chúng ta loại trừ n).

Dưới đây là hàm C++ để kiểm tra số hoàn hảo:

#include <iostream> // Để sử dụng cout

// Hàm kiểm tra xem một số có phải là số hoàn hảo hay không
bool isPerfectNumber(int n) {
    // Số hoàn hảo là số nguyên dương, lớn hơn 1.
    // Các số <= 1 không phải là số hoàn hảo.
    if (n <= 1) {
        return false;
    }

    int sumOfDivisors = 0; // Biến để lưu tổng các ước số thật sự

    // Duyệt từ 1 đến n/2 để tìm các ước số thật sự
    // Duyệt đến n/2 là đủ vì nếu i > n/2 là ước số của n (và i < n),
    // thì n/i sẽ là một ước số nhỏ hơn 2, điều này chỉ xảy ra khi n <= 1,
    // mà trường hợp đó ta đã loại bỏ.
    for (int i = 1; i <= n / 2; ++i) {
        if (n % i == 0) { // Nếu i là ước của n
            sumOfDivisors += i; // Cộng i vào tổng
        }
    }

    // So sánh tổng các ước số thật sự với n
    return (sumOfDivisors == n);
}

int main() {
    cout << "Kiem tra mot vai so co phai la so hoan hao:\n";
    cout << boolalpha; // In true/false thay vi 1/0

    cout << "6 la so hoan hao? " << isPerfectNumber(6) << endl;     // true
    cout << "28 la so hoan hao? " << isPerfectNumber(28) << endl;   // true
    cout << "12 la so hoan hao? " << isPerfectNumber(12) << endl;   // false
    cout << "10 la so hoan hao? " << isPerfectNumber(10) << endl;   // false
    cout << "496 la so hoan hao? " << isPerfectNumber(496) << endl; // true
    cout << "1 la so hoan hao? " << isPerfectNumber(1) << endl;     // false
    cout << "-5 la so hoan hao? " << isPerfectNumber(-5) << endl;   // false

    return 0;
}

Giải thích code:

  1. Chúng ta sử dụng <iostream> để nhập xuất.
  2. Hàm isPerfectNumber(int n) nhận vào một số nguyên n.
  3. Số hoàn hảo phải là số nguyên dương và lớn hơn 1, nên chúng ta loại bỏ các trường hợp n <= 1.
  4. Biến sumOfDivisors được khởi tạo bằng 0 để tích lũy tổng các ước số.
  5. Vòng lặp for duyệt từ i = 1 đến n / 2.
  6. Bên trong vòng lặp, n % i == 0 kiểm tra xem i có phải là ước của n hay không (phép chia lấy dư bằng 0).
  7. Nếu i là ước, nó được cộng vào sumOfDivisors.
  8. Cuối cùng, hàm trả về true nếu sumOfDivisors bằng n, ngược lại trả về false.
  9. Trong main(), chúng ta kiểm tra vài số ví dụ.

Vì số hoàn hảo khá hiếm, bạn có thể muốn tìm các số hoàn hảo trong một phạm vi nhất định. Chương trình sau sẽ tìm và in ra tất cả các số hoàn hảo nhỏ hơn hoặc bằng một giới hạn mà người dùng nhập vào:

#include <iostream>

// Sử dụng lại hàm isPerfectNumber ở trên
bool isPerfectNumber(int n) {
    if (n <= 1) return false;
    int sumOfDivisors = 0;
    for (int i = 1; i <= n / 2; ++i) {
        if (n % i == 0) {
            sumOfDivisors += i;
        }
    }
    return (sumOfDivisors == n);
}

int main() {
    int limit;
    cout << "Nhap gioi han tren de tim so hoan hao: ";
    cin >> limit;

    cout << "Cac so hoan hao trong pham vi tu 2 den " << limit << " la:\n";

    // Duyet tu 2 len den gioi han (so hoan hao nho nhat la 6)
    for (int i = 2; i <= limit; ++i) {
        if (isPerfectNumber(i)) {
            cout << i << endl;
        }
    }

    return 0;
}

Giải thích code:

  1. Chương trình yêu cầu người dùng nhập một giới hạn trên (limit).
  2. Vòng lặp for duyệt qua tất cả các số nguyên từ 2 đến limit. (Chúng ta bắt đầu từ 2 vì các số nhỏ hơn không thể là số hoàn hảo).
  3. Với mỗi số i, chúng ta gọi hàm isPerfectNumber(i) để kiểm tra.
  4. Nếu hàm trả về true, tức là i là số hoàn hảo, chúng ta in số đó ra màn hình.

Qua bài này, chúng ta đã cùng tìm hiểu về định nghĩa của số chính phươngsố hoàn hảo, cũng như cách triển khai các hàm kiểm tra chúng trong C++ bằng cách sử dụng các phép toán và vòng lặp cơ bản. Đây là những ví dụ tuyệt vời cho thấy cách chúng ta có thể áp dụng kiến thức toán học vào giải quyết các bài toán lập trình.

Comments

There are no comments at the moment.