Nhiều vấn đề trong thực tế, chẳng hạn như việc đếm số lượng cách sắp xếp các phần tử trong một tập hợp, lựa chọn tổ hợp của các phần tử, hoặc tìm tất cả các đường đi trong một đồ thị, đề
Trang 1Chương 1 – Thuật toán liệt kê
HỌC VIỆN CÔNG NGHỆ BƯU CHÍNH VIỄN THÔNG
MÔN TOÁN RỜI RẠC - 2024
-
BÀI TIỂU LUẬN MÔN TOÁN RỜI RẠC - 2024
Tìm hiểu bài toán liệt kê và cách giải bài toán
Giảng viên: Phạm Anh Thư
Trang 2Chương 1 – Thuật toán liệt kê
Bảng phân công công việc
Dương Hoài Nam (bàn
trưởng) Tìm hiểu bài toán liệt kê, phân chia công việc cho các thành viên, hỗ trợ
làm báo cáo
15/9/2024
Nguyễn Trọng Triệu Làm báo cáo, tìm hiểu các phương
pháp và hoạt động của các thuật toán
Trang 3Chương 1 – Thuật toán liệt kê
MỤC LỤC
I ĐẶT VẤN ĐỀ 4
1 Xuất xứ của bài toán, những ứng dụng phổ biến 4
2 Yêu cầu 5
3 Mục tiêu 6
4 Độ phức tập của thuật toán 6
II NỘI DUNG 6
1 Định nghĩa và phân loại bài toán liệt kê 6
2 Các thuật toán giải bài toán liệt kê 7
2.1 Thuật toán quay lui (Backtracking) 7
Ví dụ 8
Vidu1 8
Vidu2 10
Ưu điểm của thuật toán quay lui 17
Nhược điểm của phương pháp quay lui: 17
2.2 Thuật toán sinh 17
Ví dụ 18
Vidu1 18
Vidu2 21
Ưu điểm của phương pháp sinh: 23
Nhược điểm của phương pháp sinh: 23
KẾT LUẬN 25
So sánh giữa phương pháp sinh và quay lui 25
Tóm lại 25
TÀI LIỆU THAM KHẢO 25
Trang 4Chương 1 – Thuật toán liệt kê
I ĐẶT VẤN ĐỀ
1 Xuất xứ của bài toán, những ứng dụng phổ biến
Bài toán liệt kê xuất hiện phổ biến trong nhiều lĩnh vực của toán học, đặc biệt là trong tổ hợp và lý thuyết đồ thị Nhiều vấn đề trong thực tế, chẳng hạn như việc đếm số lượng cách sắp xếp các phần tử trong một tập hợp, lựa chọn tổ hợp của các phần tử, hoặc tìm tất cả các đường đi trong một đồ thị, đều là các dạng của bài toán liệt kê
Bài toán đếm: Xây dựng công thức tính nghiệm của bài toán
Bài toán liệt kê: Nghiệm của bài toán là gì?
Ví dụ: Liệt kê tập con m phần tử của tập n phần tử Cho X = { 1, 2, , n } Hãy liệt kê tất
cả các tập con k phần tử của X (k n)
Bài toán liệt kê được đúc kết và phát triển từ nhiều nhà khoa học tiêu biểu như:
Leonhard Euler (1707–1783): là một trong những nhà toán học đầu tiên nghiên cứu lý
thuyết tổ hợp Ông đã áp dụng lý thuyết đồ thị để giải quyết các bài toán liệt kê, đặt nền móng cho lý thuyết đồ thị hiện đại
Trang 5Chương 1 – Thuật toán liệt kê
Claude Shannon (1916–2001): Ông ứng dụng bài toán liệt kê vào việc mã hóa và
truyền thông, góp phần quan trọng trong lý thuyết mã hóa và thuật toán nén thông tin
Khó khăn chính của phương pháp này là sự tăng nhanh của các tổ hợp Để xây dựng chừng
1 tỷ cấu hình (con số này không phải là lớn đối với các bài toán tổ hợp), ta giả sử cần 1 giây
để liệt kê một cấu hình thì chúng ta cũng cần 31 năm mới giải quyết xong Tuy nhiên với
sự phát triển nhanh chóng của máy tính, bằng phương pháp liệt kê, nhiều bài toán khó của
lý thuyết tổ hợp đã được giải quyết và góp phần thúc đẩy sự phát triển của nhiều ngành toán học
2 Yêu cầu
Bài toán liệt kê yêu cầu phải xác định và liệt kê tất cả các khả năng hoặc phương án xảy
ra trong một tập hợp với các điều kiện cụ thể Chúng ta cần đưa ra giải pháp để đếm hoặc tìm kiếm tất cả các cấu hình có thể từ tập hợp đã cho Mỗi bài toán liệt kê đảm bảo 2 nguyên tắc:
+Không được lặp lại bất kỳ một cấu hình nào
+Không được bỏ sót bất kỳ một cấu hình nào
Trang 6Chương 1 – Thuật toán liệt kê
3 Mục tiêu
Tìm hiểu về bài toán liệt kê và các thuật toán phổ biến được sử dụng để giải quyết bài toán này Chúng ta sẽ trình bày chi tiết các thuật toán, thuật toán sinh, thuật toán quay lui cùng với các ví dụ cụ thể để minh họa cách áp dụng
4 Độ phức tập của thuật toán
Phân tích độ phức tạp của thuật toán liệt kê thường dựa trên số lượng phần tử cần sinh và
số thao tác cần thực hiện cho mỗi phần tử Các phương pháp như sinh và quay lui thường
có độ phức tạp cao, vì chúng phải liệt kê tất cả các khả năng có thể xảy ra
Đối với các bài toán hoán vị, phương pháp sinh có độ phức tạp là O(n!) (với n là số phần tử), còn đối với bài toán tổ hợp hoặc tập con, độ phức tạp là O(2^n) Điều này xảy ra do số lượng phần tử cần sinh tăng theo cấp số nhân khi số phần tử ban đầu tăng lên Phương pháp này có thể nhanh trong một số trường hợp, nhưng không hiệu quả khi phải xử lý các bài toán lớn vì lượng phần tử cần liệt kê quá lớn
Phương pháp quay lui cũng có độ phức tạp tương tự (O(n!) cho hoán vị và O(2^n) cho tổ hợp) Tuy nhiên, nhờ tính năng quay lui, phương pháp này có thể tránh được việc kiểm tra các trường hợp không khả thi, giúp giảm số thao tác cần thực hiện trong một số trường hợp Điều này làm cho quay lui trở thành lựa chọn tối ưu khi không gian tìm kiếm lớn và cần giảm thiểu các thao tác không cần thiết
II NỘI DUNG
1 Định nghĩa và phân loại bài toán liệt kê
Bài toán liệt kê có thể được hiểu là quá trình tìm ra hoặc đếm tất cả các đối tượng hoặc cấu trúc có thể xảy ra từ một tập hợp cho trước, với các ràng buộc hoặc quy tắc nhất định Các dạng bài toán liệt kê phổ biến bao gồm:
Tổ hợp (Combinations): Liệt kê tất cả các tập hợp con có kích thước k từ một tập
hợp n phần tử Ví dụ, từ tập {A, B, C, D}, liệt kê tất cả các tổ hợp con có 2 phần tử: {A, B}, {A, C}, {A, D}, {B, C}, {B, D}, {C, D}
Hoán vị (Permutations): Liệt kê tất cả các cách sắp xếp các phần tử trong một tập
hợp Ví dụ, với tập {1, 2, 3}, các hoán vị là: {1, 2, 3}, {1, 3, 2}, {2, 1, 3}, {2, 3, 1}, {3, 1, 2}, {3, 2, 1}
Trang 7Chương 1 – Thuật toán liệt kê
Tập con (Subsets): Liệt kê tất cả các tập hợp con của một tập hợp cho trước, không
quan tâm đến kích thước của các tập con Ví dụ, tập con của {A, B, C} là: {}, {A}, {B}, {C}, {A, B}, {A, C}, {B, C}, {A, B, C}
2 Các thuật toán giải bài toán liệt kê
2.1 Thuật toán quay lui (Backtracking)
Thuật toán quay lui là một phương pháp tìm kiếm tất cả các phương án hoặc cách sắp xếp bằng cách thử tất cả các khả năng Đặc điểm của thuật toán này là nếu phát hiện ra một lựa chọn không hợp lệ, nó sẽ "quay lui" (backtrack) để thử các lựa chọn khác
Ví dụ: Đối với bài toán sinh hoán vị, thuật toán quay lui sẽ cố gắng hoán đổi các phần tử
để tạo ra tất cả các cách sắp xếp khác nhau
Thuật toán quay lui:
1 Bắt đầu từ vị trí đầu tiên của danh sách
2 Thử từng khả năng một tại vị trí hiện tại
3 Nếu thỏa mãn điều kiện, tiếp tục với vị trí kế tiếp
4 Nếu không thỏa mãn, quay lui và thử phương án khác
Điểm quan trọng nhất của thuật toán là phải ghi nhớ lại mỗi bước đã đi qua, những khả năng nào đã được thử để tránh sự trùng lặp Để nhớ lại những bước duyệt trước đó, chương trình cần phải được tổ chức theo cơ chế ngăn xếp (Last in first out) Vì vậy, thuật toán quay lui rất phù hợp với những phép gọi đệ qui Thuật toán quay lui xác định thành phần thứ i
có thể được mô tả bằng thủ tục Try(i) như sau:
void Try( int i ) {
}
Trang 8Chương 1 – Thuật toán liệt kê
1 Sử dụng thuật toán quay lui để liệt kê các tổ hợp mà không quan tâm đến thứ tự
2 Chỉ xét các tập con khác nhau và tránh các hoán vị lặp lại Ví dụ, {A, B} và {B, A} được xem là một tổ hợp
Ý tưởng của thuật toán quay lui
1 Bắt đầu từ vị trí đầu tiên của tập hợp và chọn phần tử đầu tiên
2 Chuyển sang vị trí tiếp theo để chọn phần tử thứ hai
3 Khi đã chọn đủ k phần tử (ở đây k = 2), lưu lại tổ hợp và quay lui về vị trí trước
đó để thử chọn các phần tử khác
Triển khai từng bước cụ thể
Với tập {A, B, C, D} và k = 2, chúng ta sẽ liệt kê từng bước thực hiện thuật toán quay lui:
Bước 1: Khởi tạo
o Thêm phần tử A vào tổ hợp: combination = [A]
o Gọi đệ quy với start = 1 (vị trí tiếp theo)
2 Ở vị trí 1 với tổ hợp [A]:
o Thêm phần tử B vào tổ hợp: combination = [A, B]
o Đã đạt độ dài k = 2, in tổ hợp: {A, B}
o Quay lui (loại B khỏi tổ hợp), trở lại tổ hợp [A]
3 Tiếp tục ở vị trí 1 với tổ hợp [A]:
o Thêm phần tử C vào tổ hợp: combination = [A, C]
o Đã đạt độ dài k = 2, in tổ hợp: {A, C}
o Quay lui (loại C khỏi tổ hợp), trở lại tổ hợp [A]
Trang 9Chương 1 – Thuật toán liệt kê
4 Tiếp tục ở vị trí 1 với tổ hợp [A]:
o Thêm phần tử D vào tổ hợp: combination = [A, D]
o Đã đạt độ dài k = 2, in tổ hợp: {A, D}
o Quay lui (loại D khỏi tổ hợp), trở lại tổ hợp [] (trở lại từ A)
5 Quay lui về vị trí ban đầu với tổ hợp rỗng [] và chuyển sang vị trí 1:
o Thêm phần tử B vào tổ hợp: combination = [B]
o Gọi đệ quy với start = 2
o Quay lui (loại D khỏi tổ hợp), trở lại tổ hợp [] (trở lại từ B)
8 Quay lui về vị trí ban đầu với tổ hợp rỗng [] và chuyển sang vị trí 2:
o Thêm phần tử C vào tổ hợp: combination = [C]
o Gọi đệ quy với start = 3
9 Ở vị trí 3 với tổ hợp [C]:
o Thêm phần tử D vào tổ hợp: combination = [C, D]
o Đã đạt độ dài k = 2, in tổ hợp: {C, D}
o Quay lui (loại D khỏi tổ hợp), trở lại tổ hợp [] (trở lại từ C)
10 Kết thúc quá trình: Không còn phần tử nào để chọn tiếp, nên thuật toán hoàn tất
Giải thích từng bước quay lui
Mỗi khi đạt đến tổ hợp có độ dài bằng k, chúng ta in ra tổ hợp và quay lui (bỏ phần tử cuối cùng) để tiếp tục thử các phần tử khác Bằng cách này, chúng ta lần lượt kiểm tra tất
Trang 10Chương 1 – Thuật toán liệt kê
cả các tổ hợp của các phần tử trong tập hợp, đảm bảo không có tổ hợp nào bị bỏ sót và không bị lặp lại
Bài toán N quân hậu có thể phát biểu rõ ràng như sau: Với một bàn cờ kích thước NxN, bạn cần phải đặt N quân hậu sao cho không có hai quân hậu nào có thể "ăn" được nhau Điều đó có nghĩa là, mỗi quân hậu phải nằm trên một hàng, một cột, và không nằm trên cùng một đường chéo với bất kỳ quân hậu nào khác
Ví dụ, với N = 4, có hai cách đặt các quân hậu lên bàn cờ 4x4 thỏa mãn yêu cầu Hai cách này đảm bảo rằng không có quân hậu nào có thể tấn công quân khác:
Trang 11Chương 1 – Thuật toán liệt kê
Ý tưởng giải quyết
Nhận xét bài toán: Vì quân hậu có thể di chuyển theo hàng ngang, nên việc đặt quân hậu
vào các hàng khác nhau là bắt buộc Điều này đồng nghĩa với việc trên mỗi hàng chỉ có thể có một quân hậu Tuy nhiên, việc này mới chỉ giải quyết được một phần yêu cầu của bài toán, ta cần tiếp tục đảm bảo rằng các quân hậu không tấn công nhau theo cột và
đường chéo
Trang 12Chương 1 – Thuật toán liệt kê
Bài toán này có nhiều cách giải, nhưng phương pháp phổ biến và dễ hiểu nhất là phương pháp "thử" Cụ thể, ta sẽ thử đặt quân hậu lên từng hàng của bàn cờ, kiểm tra tính hợp lệ (không trùng cột, không trùng đường chéo), và nếu tìm thấy một cách sắp xếp hợp lệ, ta
sẽ lưu lại kết quả
Vì trên mỗi hàng chỉ có thể đặt một quân hậu, ta sẽ lần lượt đặt quân hậu vào các vị trí trên các hàng và sau đó kiểm tra hai điều kiện còn lại là:
Không trùng cột
Không trùng đường chéo chính và đường chéo phụ
Quy tắc kiểm tra cột và đường chéo:
Kiểm tra cột: Với mỗi lần đặt một quân hậu, ta phải đảm bảo rằng không có quân
hậu nào khác đã được đặt trên cùng một cột Điều này có thể thực hiện bằng cách
sử dụng một mảng đánh dấu để theo dõi các cột đã bị chiếm giữ
Kiểm tra đường chéo: Mỗi quân hậu có thể tấn công theo hai đường chéo, do đó
cần đảm bảo rằng không có quân hậu nào khác nằm trên cùng đường chéo với quân hậu hiện tại Đường chéo chính (từ trên trái xuống dưới phải) có đặc điểm là hiệu số giữa chỉ số hàng và chỉ số cột của các ô trên cùng đường chéo là cố định Tương tự, đường chéo phụ (từ trên phải xuống dưới trái) có tổng của chỉ số hàng
Trang 13Chương 1 – Thuật toán liệt kê Thuật toán giải quyết bài toán N quân hậu
Bài toán có thể giải bằng phương pháp quay lui (backtracking), một kỹ thuật thử và loại
bỏ rất hiệu quả cho các bài toán tìm kiếm tổ hợp Quá trình thực hiện thuật toán sẽ bao gồm hai công việc chính:
1 Thử đặt quân hậu: Với mỗi hàng, ta sẽ thử đặt quân hậu vào các cột khác nhau,
bắt đầu từ cột đầu tiên đến cột cuối cùng
2 Kiểm tra tính hợp lệ: Sau khi đặt một quân hậu, ta cần kiểm tra xem vị trí đó có
hợp lệ hay không bằng cách kiểm tra xem cột và hai đường chéo đã bị chiếm giữ chưa
Nếu tìm thấy một vị trí hợp lệ, ta tiếp tục đặt quân hậu ở hàng tiếp theo Ngược lại, nếu không tìm được vị trí hợp lệ cho một hàng nào đó, ta sẽ quay lại hàng trước đó, xóa bỏ quân hậu đã đặt và thử tiếp vị trí khác
Thuật toán mã giả:
- Duyệt từ hàng 1 đến hàng N:
- Với mỗi hàng, thử đặt quân hậu vào các cột từ 1 đến N
- Nếu vị trí đó hợp lệ (không trùng cột và đường chéo đã bị đánh dấu):
- Đặt quân hậu vào vị trí đó
- Đánh dấu cột và các đường chéo (chính và phụ)
- Chuyển qua xét hàng tiếp theo
- Nếu tất cả các hàng đều được đặt quân hậu hợp lệ, ghi nhận cách xếp này là một
nghiệm
- Nếu không thể đặt quân hậu nào trên một hàng, quay lui và thử vị trí khác ở hàng trước đó
Kiểm tra tính hợp lệ:
Để kiểm tra tính hợp lệ của một vị trí, ta cần đảm bảo rằng quân hậu không tấn công bất
kỳ quân hậu nào đã được đặt trước đó Cụ thể, ta cần:
1 Không trùng cột: Dùng một mảng col[] để đánh dấu các cột đã có quân hậu Nếu
quân hậu muốn đặt nằm trên cột đã bị đánh dấu, vị trí này không hợp lệ
2 Không trùng đường chéo chính: Dùng mảng diag1[] để đánh dấu hiệu số giữa
chỉ số hàng và chỉ số cột (hàng - cột) của các quân hậu đã được đặt
3 Không trùng đường chéo phụ: Dùng mảng diag2[] để đánh dấu tổng của chỉ số
hàng và chỉ số cột (hàng + cột) của các quân hậu đã được đặt
Nếu vị trí thoả mãn cả ba điều kiện trên, quân hậu có thể được đặt vào đó, và chương trình sẽ tiếp tục xét hàng tiếp theo
Trang 14Chương 1 – Thuật toán liệt kê
Ví dụ minh họa
Bước 1: Đặt quân hậu vào hàng 1
Giả sử ta bắt đầu từ hàng 1, thử đặt quân hậu tại cột 2 Vì đây là bước đầu tiên, không có quân hậu nào khác trên bàn cờ, nên vị trí này là hợp lệ
Bàn cờ sau bước 1:
Bước 2: Đặt quân hậu vào hàng 2
Chuyển sang hàng 2 và thử lần lượt các cột từ cột 1 đến cột 4
Thử cột 1: Vị trí này không bị tấn công bởi quân hậu ở hàng 1, do đó hợp lệ
Bàn cờ sau bước 2:
Bước 3: Đặt quân hậu vào hàng 3
Trang 15Chương 1 – Thuật toán liệt kê
Chuyển sang hàng 3 và thử các cột từ 1 đến 4
Thử cột 1: Vị trí này không bị tấn công bởi quân hậu nào, do đó hợp lệ
Bàn cờ sau bước 3:
Bước 4: Đặt quân hậu vào hàng 4
Chuyển đến hàng cuối cùng (hàng 4) và thử lần lượt các cột
Thử cột 1: Vị trí này bị tấn công theo cột bởi quân hậu ở hàng 3, nên không hợp
Trang 16Chương 1 – Thuật toán liệt kê
Bước 5: In ra kết quả
Khi quân hậu đã được đặt vào cả 4 hàng mà không có quân hậu nào có thể tấn công lẫn nhau, ta đã tìm được một cách sắp xếp hợp lệ
Kết quả cuối cùng:
Phân tích chi tiết quá trình quay lui
1 Hàng 1: Đặt quân hậu tại cột 2
2 Hàng 2: Đặt quân hậu tại cột 4
3 Hàng 3: Đặt quân hậu tại cột 1
4 Hàng 4: Đặt quân hậu tại cột 3
Nếu không thể tìm thấy vị trí hợp lệ ở một hàng nào đó, thuật toán sẽ quay lui
(backtracking) để thử các vị trí khác ở các hàng trước đó Ví dụ, nếu ở hàng 4 không có cột nào hợp lệ, thuật toán sẽ quay lại hàng 3 và thử vị trí khác
Thuật toán quay lui giúp tìm ra tất cả các cách sắp xếp quân hậu sao cho không có quân hậu nào tấn công lẫn nhau Với bàn cờ 4x4, ta đã tìm được cách sắp xếp chính xác như trên, và cách giải này có thể áp dụng cho các kích thước bàn cờ lớn hơn như 8x8 hoặc N tùy ý
Trang 17Chương 1 – Thuật toán liệt kê
Ưu điểm của thuật toán quay lui
a) Hiệu quả trong việc tránh lặp lại các tổ hợp không hợp lệ:
Thuật toán quay lui rất hiệu quả trong việc tìm các tổ hợp mà không tạo ra các tổ hợp không hợp lệ Bằng cách "quay lui" và thử các phần tử tiếp theo, thuật toán này đảm bảo rằng chỉ các tổ hợp hợp lệ được tạo ra và ghi lại
b) Dễ dàng áp dụng cho các bài toán có ràng buộc:
Với khả năng thử các lựa chọn một cách có điều kiện, thuật toán quay lui phù hợp cho các bài toán liệt kê có ràng buộc hoặc yêu cầu phức tạp Ví dụ, bài toán liệt kê chỉ các tổ hợp không chứa phần tử nhất định có thể dễ dàng được thực hiện bằng cách thêm điều kiện kiểm tra trước khi thêm phần tử vào tổ hợp
c) Tiết kiệm bộ nhớ và chỉ tạo ra kết quả cần thiết:
Thuật toán quay lui không cần lưu trữ tất cả các tổ hợp trong bộ nhớ cùng lúc, mà chỉ tạo
ra các tổ hợp khi cần thiết (khi đạt đủ số lượng phần tử yêu cầu) Điều này giúp tiết kiệm
bộ nhớ và tăng hiệu quả cho các bài toán có tập dữ liệu lớn
Dễ hiểu và dễ triển khai:
Thuật toán quay lui có mã giả và mã thực tế dễ hiểu và dễ viết, điều này giúp nó được sử dụng phổ biến trong các ứng dụng liệt kê và tổ hợp trong thực tế Phương pháp thử và loại
bỏ của nó cũng giúp thuật toán rõ ràng và dễ theo dõi trong quá trình thực hiện
Nhược điểm của phương pháp quay lui:
a) Có thể tốn thời gian khi không gian tìm kiếm lớn và ít điều kiện ràng buộc
b) Hiệu suất phụ thuộc vào quá trình kiểm tra tính hợp lệ, nếu mất nhiều thời gian sẽ giảm hiệu quả
c) Khó tối ưu hóa, dù có thể loại bỏ nhiều lựa chọn không khả thi, nhưng việc tìm cách chọn lọc tối ưu vẫn là thách thức
2.2 Thuật toán sinh
Thuật toán sinh tổ hợp giúp liệt kê tất cả các tập hợp con có k phần tử từ một tập hợp n phần tử Thuật toán này sử dụng đệ quy để tạo ra tất cả các tổ hợp con theo cách chọn từng phần tử và tiếp tục chọn từ các phần tử còn lại