Tìm Tất Cả Các Ước Số - Từ Cơ Bản Đến Tối Ưu Hóa
Tìm các ước số của một số là một bài toán cơ bản trong toán học và lập trình. Từ việc phân tích số học đến các ứng dụng trong mật mã học, việc hiểu cách tìm ước số hiệu quả là rất quan trọng. Trong bài viết này, chúng ta sẽ khám phá nhiều cách tiếp cận khác nhau, từ đơn giản đến tối ưu.
Bạn sẽ học được cách cải thiện thuật toán từ O(n) xuống O(√n), hiểu được tại sao và khi nào nên áp dụng từng phương pháp. Đây là nền tảng quan trọng cho nhiều bài toán nâng cao hơn.
Hiểu Về Ước Số
Định Nghĩa Ước Số
Ước số (divisor/factor) của một số nguyên dương n là những số nguyên dương chia hết cho n.
Ví dụ:
- Ước số của 12: 1, 2, 3, 4, 6, 12
- Ước số của 16: 1, 2, 4, 8, 16
- Ước số của 7: 1, 7 (số nguyên tố chỉ có 2 ước số)
Tính Chất Quan Trọng
Nếu a là ước số của n, thì n/a cũng là ước số của n. Điều này giúp chúng ta chỉ cần kiểm tra đến √n thay vì đến n.
Các Phương Pháp Tìm Ước Số
1. Phương Pháp Brute Force - O(n)
Cách đơn giản nhất là kiểm tra tất cả các số từ 1 đến n:
Cài đặt bằng C++:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> findDivisorsBruteForce(int n) {
vector<int> divisors;
cout << "Tim uoc so cua " << n << ":" << endl;
cout << "Kiem tra tu 1 den " << n << endl;
for (int i = 1; i <= n; i++) {
if (n % i == 0) {
divisors.push_back(i);
cout << i << " la uoc so cua " << n << endl;
}
}
return divisors;
}
void printDivisors(const vector<int>& divisors) {
cout << "Cac uoc so: ";
for (int i = 0; i < divisors.size(); i++) {
cout << divisors[i];
if (i < divisors.size() - 1) cout << ", ";
}
cout << endl;
cout << "Tong so uoc so: " << divisors.size() << endl;
}
int main() {
int n;
cout << "Nhap so nguyen duong n: ";
cin >> n;
vector<int> divisors = findDivisorsBruteForce(n);
printDivisors(divisors);
return 0;
}
Phân tích:
- Ưu điểm: Đơn giản, dễ hiểu, đảm bảo tìm được tất cả ước số
- Nhược điểm: Chậm với n lớn, độ phức tạp O(n)
2. Phương Pháp Tối Ưu - O(√n)
Sử dụng tính chất của ước số để giảm số lần kiểm tra:
Cài đặt bằng Python:
import math
def find_divisors_optimized(n):
"""Tìm ước số với độ phức tạp O(√n)"""
divisors = []
sqrt_n = int(math.sqrt(n))
print(f"Tìm ước số của {n}:")
print(f"Chỉ cần kiểm tra từ 1 đến {sqrt_n} (√{n})")
for i in range(1, sqrt_n + 1):
if n % i == 0:
divisors.append(i)
print(f"{i} là ước số")
# Thêm ước số tương ứng n/i (nếu khác i)
if i != n // i:
divisors.append(n // i)
print(f"{n // i} cũng là ước số (vì {n} ÷ {i} = {n // i})")
# Sắp xếp kết quả
divisors.sort()
return divisors
def analyze_divisors(n, divisors):
"""Phân tích thống kê về ước số"""
print(f"\n=== PHÂN TÍCH ƯỚC SỐ CỦA {n} ===")
print(f"Tất cả ước số: {divisors}")
print(f"Số lượng ước số: {len(divisors)}")
print(f"Tổng các ước số: {sum(divisors)}")
# Phân loại ước số
proper_divisors = [d for d in divisors if d != n]
print(f"Ước số thực sự (không kể {n}): {proper_divisors}")
# Kiểm tra số hoàn hảo
if sum(proper_divisors) == n:
print(f"🎉 {n} là số hoàn hảo!")
elif sum(proper_divisors) > n:
print(f"📈 {n} là số thừa (abundant)")
else:
print(f"📉 {n} là số thiếu (deficient)")
def main():
n = int(input("Nhập số nguyên dương: "))
if n <= 0:
print("Vui lòng nhập số nguyên dương!")
return
divisors = find_divisors_optimized(n)
analyze_divisors(n, divisors)
if __name__ == "__main__":
main()
3. Cài Đặt Với Theo Dõi Hiệu Suất
Cài đặt bằng Java:
import java.util.*;
import java.util.stream.Collectors;
public class DivisorFinder {
public static class Result {
List<Integer> divisors;
long timeNanos;
int comparisons;
Result(List<Integer> divisors, long timeNanos, int comparisons) {
this.divisors = divisors;
this.timeNanos = timeNanos;
this.comparisons = comparisons;
}
}
public static Result findDivisorsBruteForce(int n) {
long startTime = System.nanoTime();
List<Integer> divisors = new ArrayList<>();
int comparisons = 0;
for (int i = 1; i <= n; i++) {
comparisons++;
if (n % i == 0) {
divisors.add(i);
}
}
long endTime = System.nanoTime();
return new Result(divisors, endTime - startTime, comparisons);
}
public static Result findDivisorsOptimized(int n) {
long startTime = System.nanoTime();
Set<Integer> divisorSet = new HashSet<>();
int comparisons = 0;
int sqrt = (int) Math.sqrt(n);
for (int i = 1; i <= sqrt; i++) {
comparisons++;
if (n % i == 0) {
divisorSet.add(i);
divisorSet.add(n / i);
}
}
List<Integer> divisors = new ArrayList<>(divisorSet);
Collections.sort(divisors);
long endTime = System.nanoTime();
return new Result(divisors, endTime - startTime, comparisons);
}
public static void comparePerformance(int n) {
System.out.println("=== SO SÁNH HIỆU SUẤT ===");
System.out.println("Số cần tìm ước: " + n);
System.out.println();
Result bruteForce = findDivisorsBruteForce(n);
System.out.println("BRUTE FORCE:");
System.out.println("Ước số: " + bruteForce.divisors);
System.out.println("Thời gian: " + bruteForce.timeNanos + " nanoseconds");
System.out.println("Số phép so sánh: " + bruteForce.comparisons);
System.out.println();
Result optimized = findDivisorsOptimized(n);
System.out.println("OPTIMIZED:");
System.out.println("Ước số: " + optimized.divisors);
System.out.println("Thời gian: " + optimized.timeNanos + " nanoseconds");
System.out.println("Số phép so sánh: " + optimized.comparisons);
System.out.println();
double speedup = (double) bruteForce.timeNanos / optimized.timeNanos;
double comparisonReduction = (double) bruteForce.comparisons / optimized.comparisons;
System.out.println("TỐI ƯU HÓA:");
System.out.printf("Nhanh hơn: %.2fx%n", speedup);
System.out.printf("Giảm phép so sánh: %.2fx%n", comparisonReduction);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Nhập số cần tìm ước (thử với số lớn để thấy sự khác biệt): ");
int n = scanner.nextInt();
comparePerformance(n);
scanner.close();
}
}
Các Trường Hợp Đặc Biệt
1. Xử Lý Số Chính Phương
Với số chính phương, cần chú ý không thêm √n hai lần:
Cài đặt bằng C++:
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
vector<int> findDivisorsPerfectSquare(int n) {
vector<int> divisors;
int sqrtN = sqrt(n);
cout << "So " << n;
if (sqrtN * sqrtN == n) {
cout << " la so chinh phuong (" << sqrtN << "²)" << endl;
} else {
cout << " khong phai so chinh phuong" << endl;
}
for (int i = 1; i <= sqrtN; i++) {
if (n % i == 0) {
divisors.push_back(i);
// Chỉ thêm n/i nếu i khác √n
if (i != sqrtN || sqrtN * sqrtN != n) {
divisors.push_back(n / i);
}
}
}
sort(divisors.begin(), divisors.end());
return divisors;
}
void demonstratePerfectSquares() {
vector<int> testNumbers = {16, 25, 36, 15, 20, 24};
for (int n : testNumbers) {
cout << "\n--- Test với n = " << n << " ---" << endl;
vector<int> divisors = findDivisorsPerfectSquare(n);
cout << "Uoc so: ";
for (int i = 0; i < divisors.size(); i++) {
cout << divisors[i];
if (i < divisors.size() - 1) cout << ", ";
}
cout << endl;
}
}
int main() {
demonstratePerfectSquares();
return 0;
}
2. Tìm Ước Số Nguyên Tố
Cài đặt bằng Python:
def is_prime(n):
"""Kiểm tra n có phải số nguyên tố"""
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
for i in range(3, int(n**0.5) + 1, 2):
if n % i == 0:
return False
return True
def find_prime_divisors(n):
"""Tìm tất cả ước số nguyên tố"""
all_divisors = []
sqrt_n = int(n**0.5)
for i in range(1, sqrt_n + 1):
if n % i == 0:
all_divisors.append(i)
if i != n // i:
all_divisors.append(n // i)
all_divisors.sort()
prime_divisors = [d for d in all_divisors if is_prime(d)]
return all_divisors, prime_divisors
def prime_factorization(n):
"""Phân tích thành thừa số nguyên tố"""
factors = []
d = 2
while d * d <= n:
while n % d == 0:
factors.append(d)
n //= d
d += 1
if n > 1:
factors.append(n)
return factors
def main():
n = int(input("Nhập số cần phân tích: "))
all_divisors, prime_divisors = find_prime_divisors(n)
prime_factors = prime_factorization(n)
print(f"\n=== PHÂN TÍCH SỐ {n} ===")
print(f"Tất cả ước số: {all_divisors}")
print(f"Ước số nguyên tố: {prime_divisors}")
print(f"Phân tích thừa số nguyên tố: {' × '.join(map(str, prime_factors))}")
# Kiểm tra tính chất
if len(prime_divisors) == 1 and prime_divisors[0] == n:
print(f"🔹 {n} là số nguyên tố")
elif len(all_divisors) == 2:
print(f"🔹 {n} là số nguyên tố")
else:
print(f"🔹 {n} là hợp số")
if __name__ == "__main__":
main()