Giới thiệu
Trong nội dung bài này, chúng ta cùng nhau tìm hiểu một số bài toán cơ bản về phương pháp sinh.
Phương pháp sinh có thể áp dụng để giải bài toán liệt kê tổ hợp.
Hai điều kiện để có thể áp dụng phương pháp sinh:
- Xác định được một thứ tự trên tập các cấu hình tổ hợp. Từ đó suy ra cấu hình đầu tiên và cấu hình cuối cùng.
- Xây dựng được thuật toán từ một cấu hình trung gian. Từ đó sinh ra cấu hình kế tiếp.
Những bài toán được tìm hiểu trong bài này:
- Sinh các dãy nhị phân độ dài
n.
- Liệt kê các tập con
kphần tử của tập{1, 2, …, n}theo thứ tự từ điển.
- Liệt kê các hoán vị của
{1, 2, …, n}theo thứ tự từ điển.
Thiết kế chương trình
Bước 1.
Chúng ta tạo một PyDev Project trong Eclipse IDE và đặt tên là PythonAlgorithmGenerationMethod.
Chúng ta tiếp tục tạo package main và class Main.py cùng phương thức main() mặc định.

Bước 2.
Chúng ta tạo package algorithm và class GenerationAlgorithm.py.
Chúng ta cũng định nghĩa những phương thức chính để thực hiện các bài toán đặt ra.

Phương thức generateBinarySequences()
Phương thức generateBinarySequences() được đặc tả để sinh một dãy nhị phân độ dài n.
Một dãy nhị phân độ dài n là một dãy x[1…n] trong đó x[i] ∈ {0, 1} (∀i : 1 ≤ i ≤ n).
Ví dụ: Khi n = 3, chúng ta có 8 dãy nhị phân độ dài 3 được liệt kê lần lượt như sau:
{000; 001; 010; 011; 100; 101; 110; 111}
Như vậy dãy đầu tiên sẽ là 00…0 và dãy cuối cùng sẽ là 11…1.
Ý tưởng giải thuật:
- Xét từ cuối dãy về đầu (xét từ hàng đơn vị lên), tìm số 0 gặp đầu tiên.
- Nếu thấy thì thay số 0 đó bằng số 1 và đặt tất cả các phần tử phía sau vị trí đó bằng 0.
- Nếu không thấy thì thì toàn dãy là số 1, đây là cấu hình cuối cùng.
Bước 1 – 2.
Chúng ta định nghĩa một mảng gồm n phần tử.
Chúng ta gán giá trị 0 cho từng phần tử trong mảng.

Bước 3.
Chúng ta thực hiện thuật toán sinh trong vòng lặp while() {}.
Nội dung chính của thuật toán bao gồm các bước từ 3.1 đến 3.4.

Bước 3.1.
Tại từng bước lặp, chúng ta nhận được một dãy nhị phân.
Chúng ta thêm dãy nhị phân này vào danh sách sequences.

Bước 3.2.
Chúng ta duyệt các phần tử từ cuối dãy trở lại.

Bước 3.3.
Chúng ta thực hiện điều chỉnh giá trị của x[i] và những phần tử đứng sau trên dãy.

Bước 3.4.
Thuật toán sinh dừng lại khi đã sinh ra dãy 11…1.

Thử nghiệm
Chúng ta thử nghiệm phương thức generateBinarySequences() trong class Main.py.

Phương thức generateSubSets()
Phương thức generateSubSets() được đặc tả để liệt kê các tập con k phần tử của tập {1, 2, …, n} theo thứ tự từ điển.
Ví dụ: với n = 5, k = 3, chúng ta ta phải liệt kê đủ 10 tập con:
1.{1, 2, 3} 2.{1, 2, 4} 3.{1, 2, 5} 4.{1, 3, 4} 5.{1, 3, 5} 6.{1, 4, 5} 7.{2, 3, 4} 8.{2, 3, 5} 9.{2, 4, 5} 10. {3, 4, 5}
Như vậy tập con đầu tiên là {1, 2, …, k}.
Tập con kết thúc là {n - k + 1, n - k + 2, …, n}.
Ý tưởng giải thuật:
- Tìm từ cuối dãy lên đầu cho tới khi gặp một phần tử
x[i]chưa đạt giới hạn trênn - k + i.
- Nếu tìm thấy: (i) Tăng
x[i]đó lên 1; (ii) Gán tất cả các phần tử saux[i]bằng giới hạn dướix[i-1] + 1.
- Nếu không tìm thấy tức là mọi phần tử đã đạt giới hạn trên, đây là tập con cuối cùng.
Bước 1.
Chúng ta định nghĩa một mảng gồm k phần tử.

Bước 2.
Chúng ta gán giá trị là chỉ số vị trí tương ứng cho từng phần tử trong mảng.

Bước 3.
Chúng ta thực hiện thuật toán sinh trong vòng lặp while() {}.
Nội dung chính của thuật toán bao gồm các bước từ 3.1 đến 3.4.

Bước 3.1.
Tại từng bước lặp, chúng ta nhận được một tập con.
Chúng ta thêm tập con này vào danh sách sequences.

Bước 3.2.
Chúng ta duyệt các phần tử từ cuối dãy trở lại.

Bước 3.3.
Chúng ta thực hiện điều chỉnh giá trị của x[i] và những phần tử đứng sau trên dãy.

Bước 3.4.
Thuật toán sinh dừng lại khi tất cả các phần tử đã đạt giới hạn trên.

Thử nghiệm
Chúng ta thử nghiệm phương thức generateSubSets() trong class Main.py.

Phương thức generatePermutation()
Phương thức generatePermutation() được đặc tả để liệt kê các hoán vị của tập {1, 2, …, n} theo thứ tự từ điển.
Ví dụ với n = 4, ta phải liệt kê đủ 24 hoán vị:
1.1234 2.1243 3.1324 4.1342 5.1423 6.1432 7.2134 8.2143 9.2314 10.2341 11.2413 12.2431 13.3124 14.3142 15.3214 16.3241 17.3412 18.3421 19.4123 20.4132 21.4213 22.4231 23.4312 24.4321
Như vậy hoán vị đầu tiên sẽ là <1, 2, …, n>.
Hoán vị cuối cùng là <n, n-1, …, 1>.
Ý tưởng giải thuật:
- Xác định đoạn cuối giảm dần dài nhất.
- Xác định chỉ số
icủa phần tửx[i]đứng liền trước đoạn cuối đó.
- Nếu tìm thấy chỉ số
inhư trên: (i) tìm phần tửx[k]nhỏ nhất thoả mãn điều kiệnx[k] > x[i]; (ii) Đảo giá trịx[k]vàx[i]; (iii) Lật ngược thứ tự đoạn cuối giảm dần (từx[i+1]đếnx[k]) trở thành tăng dần.
- Nếu không tìm thấy tức là toàn dãy đã sắp giảm dần, đây là cấu hình cuối cùng.
Bước 1.
Chúng ta định nghĩa một mảng gồm n phần tử.

Bước 2.
Chúng ta gán giá trị là chỉ số vị trí tương ứng cho từng phần tử trong mảng.

Bước 3.
Chúng ta thực hiện thuật toán sinh trong vòng lặp while() {}.
Nội dung chính của thuật toán bao gồm các bước từ 3.1 đến 3.4.

Bước 3.1.
Tại từng bước lặp, chúng ta nhận được một hoán vị.
Chúng ta thêm hoán vị này vào danh sách sequences.

Bước 3.2.
Chúng ta duyệt các phần tử từ cuối dãy trở lại.

Bước 3.3.
Chúng ta thực hiện xem xét nếu chưa gặp phải hoán vị cuối.
Việc kiểm tra được thực hiện từ Bước 3.3.1 đến 3.3.3.

Bước 3.3.1.
Chúng ta duyệt các phần tử từ cuối dãy trở lại.

Bước 3.3.2.
Chúng ta lùi dần k.

Bước 3.3.3.
Chúng ta thực hiện đổi chỗ x[k] và x[i].

Bước 3.3.4.
Chúng ta điều chỉnh những phần tử sau x[i].

Bước 3.4.
Thuật toán sinh dừng lại khi toàn bộ dãy giảm dần.

Thử nghiệm
Chúng ta thử nghiệm phương thức generatePermutation() trong class Main.py.

Kết luận
Trong nội dung bài này, chúng ta đã cùng tìm hiểu một số bài toán con đối với thuật toán sinh để liệt kê một danh sách theo yêu cầu cho trước.
Hi vọng chúng ta có thể hiểu thêm về tư duy thuật toán cũng như một số các kỹ thuật lập trình Python để áp dụng cho các bài khác.























































































































































































