Bài giảng Toán rời rạc (Phần I: Lý thuyết tổ hợp): Chương 3 (tt) - Nguyễn Đức Nghĩa

ppt
Số trang Bài giảng Toán rời rạc (Phần I: Lý thuyết tổ hợp): Chương 3 (tt) - Nguyễn Đức Nghĩa 142 Cỡ tệp Bài giảng Toán rời rạc (Phần I: Lý thuyết tổ hợp): Chương 3 (tt) - Nguyễn Đức Nghĩa 2 MB Lượt tải Bài giảng Toán rời rạc (Phần I: Lý thuyết tổ hợp): Chương 3 (tt) - Nguyễn Đức Nghĩa 0 Lượt đọc Bài giảng Toán rời rạc (Phần I: Lý thuyết tổ hợp): Chương 3 (tt) - Nguyễn Đức Nghĩa 15
Đánh giá Bài giảng Toán rời rạc (Phần I: Lý thuyết tổ hợp): Chương 3 (tt) - Nguyễn Đức Nghĩa
4.6 ( 18 lượt)
Nhấn vào bên dưới để tải tài liệu
Để tải xuống xem đầy đủ hãy nhấn vào bên trên
Chủ đề liên quan

Nội dung

Phần thứ nhất LÝ THUYẾT TỔ HỢP Combinatorial Theory Fall 2009 Toán rời rạc 1 Nội dung Chương 0. Mở đầu Chương 1. Bài toán đếm Chương 2. Bài toán tồn tại Chương 3. Bài toán liệt kê tổ hợp Chương 4. Bài toán tối ưu tổ hợp Toán rời rạc 2 Chương 3 BÀI TOÁN LIỆT KÊ Toán rời rạc 3 NỘI DUNG 1. Giới thiệu bài toán 2. Thuật toán và độ phức tạp 3. Phương pháp sinh 4. Thuật toán quay lui Toán rời rạc 4 Giíi thiÖu bµi to¸n Bài toán đưa ra danh sách tất cả cấu hình tổ hợp thoả mãn một số tính chất cho trước được gọi là bài toán liệt kê tổ hợp.  Do số lượng cấu hình tổ hợp cần liệt kê thường là rất lớn ngay cả khi kích thước cấu hình chưa lớn:  • Số hoán vị của n phần tử là n! • Số tập con m phần tử của n phần tử là n!/(m!(n-m)!  Do đó ần có quan niệm thế nào là giải bài toán liệt kê tổ hợp 5 Giíi thiÖu bµi to¸n  Bài toán liệt kê tổ hợp là giải được nếu như ta có thể xác định một thuật toán để theo đó có thể lần lượt xây dựng được tất cả các cấu hình cần quan tâm.  Một thuật toán liệt kê phải đảm bảo 2 yêu cầu cơ bản: • Không được lặp lại một cấu hình, • không được bỏ sót một cấu hình. 6 Chương 3. Bài toán liệt kê 1. Giới thiệu bài toán 2. Thuật toán và độ phức tạp 3. Phương pháp sinh 4. Thuật toán quay lui Toán rời rạc 7 Khái niệm bài toán tính toán      Định nghĩa. Bài toán tính toán F là ánh xạ từ tập các xâu nhị phân độ dài hữu hạn vào tập các xâu nhị phân độ dài hữu hạn: F : {0, 1}*  {0, 1}*. Ví dụ: Mỗi số nguyên x đều có thể biểu diễn dưới dạng xâu nhị phân là cách viết trong hệ đếm nhị phân của nó. Hệ phương trình tuyến tính Ax = b có thể biểu diễn dưới dạng xâu là ghép nối của các xâu biểu diễn nhị phân của các thành phần của ma trận A và vectơ b. Đa thức một biến: P(x) = a0 + a1 x + ... + an xn, hoàn toàn xác định bởi dãy số n, a0, a1, ..., an, mà để biểu diễn dãy số này chúng ta có thể sử dụng xâu nhị phân. 8 Khái niệm thuật toán Định nghĩa. Ta hiểu thuật toán giải bài toán đặt ra là một thủ tục xác định bao gồm một dãy hữu hạn các bước cần thực hiện để thu được đầu ra cho một đầu vào cho trước của bài toán.  Thuật toán có các đặc trưng sau đây:  • Đầu vào (Input): Thuật toán nhận dữ liệu vào từ một tập nào • • đó. Đầu ra (Output): Với mỗi tập các dữ liệu đầu vào, thuật toán đưa ra các dữ liệu tương ứng với lời giải của bài toán. Chính xác (Precision): Các bước của thuật toán được mô tả chính xác. 9 Khái niệm thuật toán • Hữu hạn (Finiteness): Thuật toán cần phải đưa được đầu ra sau một số hữu hạn (có thể rất lớn) bước với mọi đầu vào. • Đơn trị (Uniqueness): Các kết quả trung gian của từng bước thực hiện thuật toán được xác định một cách đơn trị và chỉ phụ thuộc vào đầu vào và các kết quả của các bước trước. • Tổng quát (Generality): Thuật toán có thể áp dụng để giải mọi bài toán có dạng đã cho. 10 Độ phức tạp của thuật toán  Độ phức tạp tính toán của thuật toán được xác định như là lượng tài nguyên các loại mà thuật toán đòi hỏi sử dụng.  Có hai loại tài nguyên quan trọng đó là thời gian và bộ nhớ.  Việc tính chính xác được các loại tài nguyên mà thuật toán đòi hỏi là rất khó. Vì thế ta quan tâm đến việc đưa ra các đánh giá sát thực cho các đại lượng này.  Trong giáo trình này ta đặc biệt quan tâm đến đánh giá thời gian cần thiết để thực hiện thuật toán mà ta sẽ gọi là thời gian tính của thuật toán. 11 Độ phức tạp của thuật toán  Rõ ràng: Thời gian tính phụ thuộc vào dữ liệu vào.  Ví dụ: Việc nhân hai số nguyên có 3 chữ số đòi hỏi thời gian khác hẳn so với việc nhân hai số nguyên có 3*109 chữ số!  Định nghĩa. Ta gọi kích thước dữ liệu đầu vào (hay độ dài dữ liệu vào) là số bít cần thiết để biểu diễn nó.  Ví dụ: Nếu x, y là đầu vào cho bài toán nhân 2 số nguyên, thì kích thước dữ liệu vào của bài toán là n = log |x| + log |y| .  Ta sẽ tìm cách đánh giá thời gian tính của thuật toán bởi một hàm của độ dài dữ liệu vào. 12 Phép toán cơ bản  Đo thời gian tính bằng đơn vị đo nào?  Định nghĩa. Ta gọi phép toán cơ bản là phép toán có thể thực hiện với thời gian bị chặn bởi một hằng số không phụ thuộc vào kích thước dữ liệu.  Để tính toán thời gian tính của thuật toán ta sẽ đếm số phép toán cơ bản mà nó phải thực hiện. 13 Các loại thời gian tính  Chúng ta sẽ quan tâm đến: • Thời gian tối thiểu cần thiết để thực hiện thuật toán với mọi bộ dữ liệu đầu vào kích thước n. Thời gian như vậy sẽ được gọi là thời gian tính tốt nhất của thuật toán với đầu vào kích thước n. • Thời gian nhiều nhất cần thiết để thực hiện thuật toán với mọi bộ dữ liệu đầu vào kích thước n. Thời gian như vậy sẽ được gọi là thời gian tính tồi nhất của thuật toán với đầu vào kích thước n. • Thời gian trung bình cần thiết để thực hiện thuật toán trên tập hữu hạn các đầu vào kích thước n. Thời gian như vậy sẽ được gọi là thời gian tính trung bình của thuật toán. 14 Ký hiệu tiệm cận Asymptotic Notation  , O,   Được xác định đối với các hàm nhận giá trị nguyên không âm  Dùng để so sánh tốc độ tăng của hai hàm  Được sử dụng để mô tả thời gian tính của thuật toán  Thay vì nói chính xác, ta có thể nói thời gian tính là, chẳng hạn, (n2) 15 Ký hiệu  Đối với hàm g(n) cho trước, ta ký hiệu (g(n)) là tập các hàm (g(n)) = {f(n) | tồn tại các hằng số c1, c2 và n0 sao cho 0  c1g(n)  f(n)  c2g(n), với mọi n  n0 } Ta nói rằng g(n) là đánh giá tiệm cận đúng cho f(n) 16 Ví dụ  10n2 - 3n = (n2)  Với giá trị nào của các hằng số n0, c1, và c2 thì bất đẳng thức sau đây là đúng với n ≥ n0: c1n2 ≤ 10n2 - 3n ≤ c2n2  Ta có thể lấy c1 bé hơn hệ số của số hạng với số mũ cao nhất, còn c2 lấy lớn hơn hệ số này, chẳng hạn: c1=9 < 10 < c2 = 11, n0 = 10.  Tổng quát, để so sánh tốc độ tăng của các đa thức, cần nhìn vào số hạng với số mũ cao nhất 17 Ký hiệu O Đối với hàm g(n) cho trước, ta ký hiệu O(g(n)) là tập các hàm O(g(n)) = {f(n) | tồn tại các hằng số dương c và n0 sao cho: f(n)  cg(n) với mọi n  n0 } Ta nói g(n) là cận trên tiệm cận của f(n) 18 Ví dụ: Ký hiệu O lớn  Chứng minh: f(n) = n2 + 2n + 1 là O(n2) • Cần chỉ ra: n + 2n + 1 ≤ c*n2 với c là hằng số nào đó và khi n > n0 nào đó 2  Ta có:  2n2 ≥ 2n khi n ≥ 1 và n2 ≥ 1 khi n ≥ 1 Vì vậy n2 + 2n + 1 ≤ 4*n2 với mọi n ≥ 1 Như vậy hằng số c = 4, và n0=1  19 Ví dụ: Ký hiệu O lớn    Rõ ràng: Nếu f(n) là O(n2) thì nó cũng là O(nk) với k > 2. Chứng minh: f(n) = n2 + 2n + 1O(n). Phản chứng. Giả sử trái lại, khi đó phải tìm được hằng số c và số n0 để cho: n2 + 2n + 1 ≤ c*n khi n ≥ n0  Suy ra n2 < n2 + 2n + 1 ≤ c*n với mọi n ≥ n0  Từ đó ta thu được: n < c với mọi n ≥ n0 ?! 20 Ký hiệu  Đối với hàm g(n) cho trước, ta ký hiệu (g(n)) là tập các hàm: (g(n)) = {f(n)| tồn tại các hằng số dương c và n0 sao cho cg(n)  f(n) với mọi n  n0 } Ta nói g(n) là cận dưới tiệm cận cho f(n) 21 Ví dụ: Ký hiệu   Chứng minh: f(n) = n2 - 2000n là (n2) • Cần chỉ ra: n2 - 2000n  c*n2 với c là hằng số nào đó và khi n > n0 nào đó Ta có: n2 - 2000n  0.5*n2 với mọi n ≥ 10000 (vì n2 - 2000n - 0.5*n2 = 0.5*n2 - 2000n = n(0.5*n -2000)  0 khi n ≥ 10000)  Như vậy hằng số c = 1, và n =1 0  22 Ví dụ: Ký hiệu     Rõ ràng: Nếu f(n) là (n2) thì nó cũng là (nk) với k < 2. Chứng minh: f(n) = n2 - 2000n  (n3). Phản chứng. Giả sử trái lại, khi đó phải tìm được hằng số c và số n0 để cho: n2 – 2000n  c*n3 khi n ≥ n0  Suy ra n2  n2 – 2000n  c*n3 khi n ≥ n0  Từ đó ta thu được: 1/c  n với mọi n ≥ n0 ?! 23 Liên hệ giữa , , O  Đối với hai hàm bất kỳ g(n) và f(n), f(n) = (g(n)) khi và chỉ khi f(n) = O(g(n)) và f(n) = (g(n)). tức là (g(n)) = O(g(n))(g(n)) 24 Chú ý  Giá trị của n0 và c không phải là duy nhất trong chứng minh công thức tiệm cận.  Chứng minh rằng 100n + 5 = O(n2) • 100n + 5 ≤ 100n + n = 101n ≤ 101n với mọi n ≥ 5 2 n0 = 5 và c = 101 là các hằng số cần tìm • 100n + 5 ≤ 100n + 5n = 105n ≤ 105n 2 với mọi n ≥ 1 n0 = 1 và c = 105 cũng là các hằng số cần tìm  Chỉ cần tìm các hằng c và n0 nào đó thoả mãn bất đẳng thức trong định nghĩa công thức tiệm cận. 25 Ký hiệu tiệm cận trong các đẳng thức  Được sử dụng để thay thế các biểu thức chứa các toán hạng với tốc độ tăng chậm  Ví dụ, 4n3 + 3n2 + 2n + 1 = 4n3 + 3n2 + (n) = 4n3 + (n2) = (n3)  Trong các đẳng thức, (f(n)) thay thế cho một hàm nào đó g(n)  (f(n)) • Trong ví dụ trên, (n ) thay thế cho 3n 2 2 + 2n + 1 26 So sánh các hàm số fg  ab f (n) = O(g(n))  a  b f (n) = (g(n))  a  b f (n) = (g(n))  a = b f (n) = o(g(n))  a < b f (n) = (g(n))  a > b 27 Cách nói về thời gian tính  Nói “Thời gian tính là O(f(n))” hiểu là: Đánh giá trong tình huống tồi nhất (worst case) là O(f(n)). Thường nói: “Đánh giá thời gian tính trong tình huống tồi nhất là O(f(n))” •  Nghĩa là thời gian tính trong tình huống tồi nhất được xác định bởi một hàm nào đó g(n)O(f(n)) “Thời gian tính là (f(n))” hiểu là: Đánh giá trong tình huống tốt nhất (best case) là (f(n)). Thường nói: “Đánh giá thời gian tính trong tình huống tốt nhất là (f(n))” • Nghĩa là thời gian tính trong tình huống tốt nhất được xác định bởi một hàm nào đó g(n)  (f(n)) 28 Đồ thị của một số hàm cơ bản 29 Tên gọi của một số tốc độ tăng Hàm Tên gọi log n log n tuyến tính n log n n log n n2 bình phương n3 bậc 3 2n hàm mũ nk (k là hằng số dương) đa thức 30 Ví dụ phân tích thuật toán Ví dụ. Xét thuật toán tìm kiếm tuần tự để giải bài toán Đầu vào: n và dãy sốs1, s2, . . . , sn. Đầu ra: Vị trí phần tử có giá trị key hoặc là n+1 nếu không tìm thấy. function Linear_Search(s,n,key); begin i:=0; repeat i:=i+1; until (i>n) or (key = si); Linear_Search := i; end; Toán rời rạc 31 Ví dụ phân tích thuật toán  Cần đánh giá thời gian tính tốt nhất, tồi nhất, trung bình của thuật toán với độ dài đầu vào là n. Rõ ràng thời gian tính của thuật toán có thể đánh giá bởi số lần thực hiện câu lệnh i:=i+1 trong vòng lặp repeat.  Nếu s1 = key thì câu lệnh i:=i+1 trong thân vòng lặp repeat thực hiện 1 lần. Do đó thời gian tính tốt nhất của thuật toán là (1).  Nếu key không có mặt trong dãy đã cho, thì câu lệnh i:= i+1 thực hiện n lần. Vì thế thời gian tính tồi nhất của thuật toán là O(n). Toán rời rạc 32 Ví dụ phân tích thuật toán     Cuối cùng, ta tính thời gian tính trung bình của thuật toán. • Nếu key tìm thấy ở vị trí thứ i của dãy (key = si) thì câu lệnh i := i+1 phải thực hiện i lần (i = 1, 2, ..., n), • Nếu key không có mặt trong dãy đã cho thì câu lệnh i := i+1 phải thực hiện n lần. Từ đó suy ra số lần trung bình phải thực hiện câu lệnh i := i+1 là [(1 + 2 + . . . + n) + n] /(n+1) = [n(n+1)/2 + n]/(n+1). Ta có n/4  [n(n+1)/2 + n]/(n+1)  n. với mọi n  1. Vậy thời gian tính trung bình của thuật toán là (n). Toán rời rạc 33 Chương 3. Bài toán liệt kê 1. Giới thiệu bài toán 2. Thuật toán và độ phức tạp 3. Phương pháp sinh 4. Thuật toán quay lui Toán rời rạc 34 3. PHƯƠNG PHÁP SINH 3.1. Sơ đồ thuật toán 3.2. Sinh các cấu hình tổ hợp cơ bản • Sinh xâu nhị phân độ dài n • Sinh tập con m phần tử của tập n phần tử • Sinh hoán vị của n phần tử 35 SƠ ĐỒ THUẬT TOÁN  Phương pháp sinh có thể áp dụng để giải bài toán liệt kê tổ hợp đặt ra nếu như hai điều kiện sau được thực hiện: 1) Có thể xác định được một thứ tự trên tập các cấu hình tổ hợp cần liệt kê. Từ đó có thể xác định được cấu hình đầu tiên và cấu hình cuối cùng trong thứ tự đã xác định. 2) Xây dựng được thuật toán từ cấu hình chưa phải là cuối cùng đang có, đưa ra cấu hình kế tiếp nó.  Thuật toán nói đến trong điều kiện 2) được gọi là Thuật toán Sinh kế tiếp 36 Thuật toán sinh procedure Generate; Begin ; Stop:=false; while not stop do begin <Đưa ra cấu hình đang có>; if (cấu hình đang có chưa là cuối cùng) then i. Dãy mới thu được sẽ i j là dãy cần tìm. 43 Ví dụ    Xét dãy nhị phân độ dài 10: b = 1101011111. Ta có i = 5. Do đó, đặt b5 = 1, và bi = 0, i = 6, 7, 8, 9, 10, ta thu đươc xâu nhị phân kế tiếp là 1101100000. 1101011111 1 1101100000 44 Thuật toán sinh xâu kế tiếp procedure Next_Bit_String; (* Sinh xâu nhị phân kế tiếp theo thứ tự từ điển của xâu đang có b1 b2 ... bn  1 1 ... 1 *) begin i:=n; while bi = 1 do begin bi = 0; i:=i-1; end; bi := 1; end; 45 Sinh các tập con m phần tử của tập n phần tử 46 Sinh các tập con m phần tử của tập n phần tử  Bµi to¸n ®Æt ra lµ: Cho X = {1, 2, ... , n}. H·y liÖt kª c¸c tËp con m phÇn tö cña X.  Mçi tËp con m phÇn tö cña X cã thÓ biÓu diÔn bëi bé cã thø tù gåm m thµnh phÇn a = (a1, a2, ... , am) tho¶ m·n 1  a1 < a2 < ... < am  n. 47 Thứ tự từ điển  Ta nãi tËp con a = (a1, a2,..., am) ®i tr­íc tËp con a' = (a'1, a'2, ... , a'm) trong thø tù tõ ®iÓn vµ ký hiÖu lµ a  a', nÕu tìm ®­îc chØ sè k (1  k  m) sao cho a1 = a'1 , a2 = a'2, . . . , ak-1 = a'k1, ak < a'k . 48 Ví dụ  Các tập con 3 phần tử của X = {1, 2, 3, 4, 5} được liệt kê theo thứ tự từ điển như sau 1 1 1 1 1 1 2 2 2 3 2 2 2 3 3 4 3 3 4 4 3 4 5 4 5 5 4 5 5 5 49 Thuật toán sinh kế tiếp    Tập con đầu tiên là (1, 2, ... , m) Tập con cuối cùng là (n-m+1, n-m+2, ..., n). Giả sử a=(a1, a2, ... , am) là tập con đang có chưa phải cuối cùng, khi đó tập con kế tiếp trong thứ tự từ điển có thể xây dựng bằng cách thực hiện các quy tắc biến đổi sau đối với tập đang có: • Tìm từ bên phải dãy a1, a2,..., am phần tử ai  n-m+i, • Thay a • Thay a i bởi ai + 1; j bởi ai + j - i, với j = i+1, i+2,..., m. 50 Ví dụ  Ví dụ: n = 6, m = 4. Giả sử đang có tập con (1, 2, 5, 6), cần xây dựng tập con kế tiếp nó trong thứ tự từ điển.  Ta có i=2: (1, 2, 5, 6) (3, 4, 5, 6) thay a2 = 3, và a3 = 4, a4 = 5, ta được tập con kế tiếp (1, 3, 4, 5). 51 51 Sinh m-tập kế tiếp procedure Next_Combination; (* Sinh m-tập con kế tiếp theo thứ tự từ điển của tập con (a1, a2,..., am)  (n-m+1,...,n) *) begin i:=m; while ai = n-m+i do i:=i-1; ai:= ai + 1; for j:=i+1 to m do aj := ai + j - i; end; 52 52 Sinh các hoán vị của tập n phần tử 53 Sinh các hoán vị của tập n phần tử  Bµi to¸n: Cho X = {1, 2, ... , n}, h·y liÖt kª c¸c ho¸n vÞ tõ n phÇn tö cña X.  Mçi ho¸n vÞ tõ n phÇn tö cña X cã thÓ biÓu diÔn bëi bé cã thø tù gåm n thµnh phÇn a = (a1, a2, ... , an) tho¶ m·n ai  X , i = 1, 2,..., n , ap  aq, p  q. 54 Thứ tự từ điển  Ta nãi ho¸n vÞ a = (a1, a2,..., an) ®i tr­íc ho¸n vÞ a' = (a'1, a'2, ... , a'n) trong thø tù tõ ®iÓn vµ ký hiÖu lµ a  a', nÕu tìm ®­îc chØ sè k (1  k  n) sao cho: a1 = a'1 , a2 = a'2, ... , ak-1 = a'k-1, ak < a'k . 55 Ví dụ  C¸c ho¸n vÞ tõ 3 phÇn tö cña X ={1, 2, 3} ®­îc liÖt kª theo thø tù tõ ®iÓn nh­ sau 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 56 Thuật toán sinh kế tiếp    Hoán vị đầu tiên: (1, 2, ... , n) Hoán vị cuối cùng: (n, n-1, ..., 1). Giả sử a = (a1, a2, ... , an) là hoán vị chưa phải cuối cùng, khi đó hoán vị kế tiếp nó có thể xây dựng nhờ thực hiện các biến đổi sau: • Tìm từ phải qua trái hoán vị đang có chỉ số j đầu tiên thoả mãn aj < aj+1 (nói cách khác: j là chỉ số lớn nhất thoả mãn aj < aj+1); • • • Tìm ak là số nhỏ nhất còn lớn hơn aj trong các số ở bên phải aj ; Đổi chỗ aj với ak ; Lật ngược đoạn từ aj+1 đến an . 57 Ví dụ  Giả sử đang có hoán vị (3, 6, 2, 5, 4, 1), cần xây dựng hoán vị kế tiếp nó trong thứ tự từ điển.  Ta có chỉ số j = 3 (a3 =2 < a4 = 5).  Số nhỏ nhất còn lớn hơn a3 trong các số bên phải của a3 là a5 = 4. Đổi chỗ a3 với a5 ta thu được (3, 6, 4, 5, 2, 1),  Cuối cùng, lật ngược thứ tự đoạn a4 a5 a6 ta thu được hoán vị kế tiếp (3, 6, 4, 1, 2, 5). 58 Sinh ho¸n vÞ kÕ tiÕp procedure Next_Permutation; (*Sinh hoán vị kế tiếp (a1, a2, ..., an)  (n, n-1, ..., 1) *) begin (* Tìm j là chỉ số lớn nhất thoả aj < aj+1 *) j:=n-1; while aj > aj+1 do j:=j-1; (* Tìm ak là số nhỏ nhất còn lớn hơn aj ở bên phải aj *) k:=n; while aj > ak do k:=k-1; Swap(aj, ak); (* đổi chỗ aj với ak *) (* Lật ngược đoạn từ aj+1 đến an *) r:=n; s:=j+1; while r>s do begin Swap(ar, as); (* đổi chỗ ar với as *) r:=r-1; s:= s+1; end; end; 59 Chương 3. Bài toán liệt kê 1. Giới thiệu bài toán 2. Thuật toán và độ phức tạp 3. Phương pháp sinh 4. Thuật toán quay lui 60 Chương 3. Bài toán liệt kê 3. THUẬT TOÁN QUAY LUI Backtracking Algorithm 61 NỘI DUNG 3.1. Sơ đồ thuật toán 3.2. Liệt kê các cấu hình tổ hợp cơ bản • Liệt kê xâu nhị phân độ dài n • Liệt kê tập con m phần tử của tập n phần tử • Liệt kê hoán vị 3.3. Bài toán xếp hậu 62 SƠ ĐỒ THUẬT TOÁN  Thuật toán quay lui (Backtracking Algorithm) là một thuật toán cơ bản được áp dụng để giải quyết nhiều vấn đề khác nhau.  Bài toán liệt kê (Q): Cho A1, A2,..., An là các tập hữu hạn. Ký hiệu X = A1 A2  ... An = { (x1, x2, ..., xn): xi  Ai , i=1, 2, ..., n}. Giả sử P là tính chất cho trên X. Vấn đề đặt ra là liệt kê tất cả các phần tử của X thoả mãn tính chất P: D = { x = (x1, x2, ..., xn)  X: x thoả mãn tính chất P }.  Các phần tử của tập D được gọi là các lời giải chấp nhận được. 63 Ví dụ  TÊt c¶ c¸c bµi to¸n liÖt kª tæ hîp c¬ b¶n ®Òu cã thÓ ph¸t biÓu d­íi d¹ng bµi to¸n (Q)  Bµi to¸n liÖt kª x©u nhÞ ph©n ®é dµi n dÉn vÒ viÖc liÖt kª c¸c phÇn tö cña tËp Bn = {(x1, ..., xn): xi  {0, 1}, i=1, 2, ..., n}.  Bµi to¸n liÖt kª c¸c tËp con m phÇn tö cña tËp N = {1, 2, ..., n} ®ßi hái liÖt kª c¸c phÇn tö cña tËp: S(m,n) = {(x1,..., xm)Nm: 1 ≤ x1 < ... < xm ≤ n }.  TËp c¸c ho¸n vÞ cña c¸c sè tù nhiªn 1, 2, ..., n lµ tËp n = {(x1,..., xn)  Nn: xi ≠ xj ; i ≠ j }. 64 Lời giải bộ phận  Định nghĩa. Ta gọi lời giải bộ phận cấp k (0≤k≤ n) là bộ có thứ tự gồm k thành phần (a1, a2, ..., ak), trong đó ai  Ai , i = 1, 2, ..., k.  Khi k = 0, lời giải bộ phận cấp 0 được ký hiệu là () và còn được gọi là lời giải rỗng.  Nếu k = n, ta có lời giải đầy đủ hay đơn giản là một lời giải của bài toán. 65 Ý tưởng chung Thuật toán quay lui được xây dựng dựa trên việc xây dựng dần từng thành phần của lời giải.  Thuật toán bắt đầu từ lời giải rỗng (). Trên cơ sở tính chất P ta xác định được những phần tử nào của tập A1 có thể chọn vào vị trí thứ nhất của lời giải. Những phần tử như vậy ta sẽ gọi là những ứng cử viên (viết tắt là UCV) vào vị trí thứ nhất của lời giải. Ký hiệu tập các UCV vào vị trí thứ nhất của lời giải là S1. Lấy a1  S1, bổ sung nó vào lời giải rỗng đang có ta thu được lời giải bộ phận cấp 1: (a1).  66 Bước tổng quát  Tại bước tổng quát, giả sử ta đang có lời giải bộ phận cấp k-1: (a1, a2, ..., ak-1).  Trên cơ sở tính chất P ta xác định được những phần tử nào của tập Ak có thể chọn vào vị trí thứ k của lời giải.  Những phần tử như vậy ta sẽ gọi là những ứng cử viên (viết tắt là UCV) vào vị trí thứ k của lời giải khi k-1 thành phần đầu của nó đã được chọn là (a1, a2, ..., ak-1). Ký hiệu tập các ứng cử viên này là Sk. 67 Xét hai tình huống  Tình huống 1: Sk ≠ . Khi đó lấy ak  Sk, bổ sung nó vào lời giải bộ phận cấp k-1 đang có (a1, a2, ..., ak-1) ta thu được lời giải bộ phận cấp k: (a1, a2, ..., ak-1, ak).  Khi đó • Nếu k = n thì ta thu được một lời giải, • Nếu k < n, ta tiếp tục đi xây dựng thành phần thứ k+1 của lời giải. 68 Tình huống ngõ cụt  Tình huống 2: Sk=. Điều đó có nghĩa là lời giải bộ phận (a1, a2, ..., ak-1) không thể tiếp tục phát triển thành lời giải đầy đủ. Trong tình huống này ta quay trở lại tìm ứng cử viên mới vào vị trí thứ k-1 của lời giải.  Nếu tìm thấy UCV như vậy, thì bổ sung nó vào vị trí thứ k-1 rồi lại tiếp tục đi xây dựng thành phần thứ k.  Nếu không tìm được thì ta lại quay trở lại thêm một bước nữa tìm UCV mới vào vị trí thứ k-2, ... Nếu quay lại tận lời giải rỗng mà vẫn không tìm được UCV mới vào vị trí thứ 1, thì thuật toán kết thúc. 69 Thuật toán quay lui procedure Bactrack(k: integer); begin Xây dựng Sk; for y  Sk do (* Với mỗi UCV y từ Sk *) begin ak := y; if k = n then else Backtrack(k+1); end; end; Lệnh gọi để thực hiện thuật toán quay lui là: Bactrack(1) 70 Hai vấn đề mấu chốt  Để cài đặt thuật toán quay lui giải các bài toán tổ hợp cụ thể ta cần giải quyết hai vấn đề cơ bản sau: • Tìm thuật toán xây dựng các tập UCV S . • Tìm cách mô tả các tập này để có thể cài đặt thao k tác liệt kê các phần tử của chúng (cài đặt vòng lặp qui ước for y  Sk do).  Hiệu quả của thuật toán liệt kê phụ thuộc vào việc ta có xác định được chính xác các tập UCV này hay không. 71 Chú ý  Nếu chỉ cần tìm một lời giải thì cần tìm cách chấm dứt các thủ tục gọi đệ qui lồng nhau sinh bởi lệnh gọi Backtrack(1) sau khi ghi nhận được lời giải đầu tiên.  Nếu kết thúc thuật toán mà ta không thu được một lời giải nào thì điều đó có nghĩa là bài toán không có lời giải. 72 Chú ý   Thuật toán dễ dàng mở rộng cho bài toán liệt kê trong đó lời giải có thể mô tả như là bộ (a1, a2, ..., an,...) độ dài hữu hạn, tuy nhiên giá trị của độ dài là không biết trước và các lời giải cũng không nhất thiết phải có cùng độ dài. Khi đó chỉ cần sửa lại câu lệnh if k = n then else Backtrack(k+1); thành if <(a1, a2, ..., ak) là lời giải> then  else Backtrack(k+1); Cần xây dựng hàm nhận biết (a1, a2, ..., ak) đã là lời giải hay chưa. 73 Cây liệt kê lời giải theo thuật toán quay lui Gốc (lời giải rỗng) Tập UCV S1 x1 (x1) Tập UCV S2 khi đã có (x1) x2 (x1, x2) Tập UCV S2 khi đã có (x1, x2) 74 LiÖt kª x©u nhÞ ph©n ®é dµi n 75 LiÖt kª x©u nhÞ ph©n ®é dµi n   Bµi to¸n liÖt kª x©u nhÞ ph©n ®é dµi n dÉn vÒ viÖc liÖt kª c¸c phÇn tö cña tËp Bn = {(x1, ..., xn): xi  {0, 1}, i=1, 2, ..., n}. Ta xÐt c¸ch gi¶i quyÕt hai vÊn ®Ò c¬ b¶n ®Ó cµi ®Æt thuËt to¸n quay lui: • Râ rµng ta cã S1 = {0, 1}. Gi¶ sö ®· cã x©u nhÞ ph©n cÊp k-1 (b1, ..., bk-1), khi ®ã râ rµng Sk = {0,1}. Nh­vËy, tËp c¸c UCV vµo c¸c vÞ trÝ cña lêi gi¶i ®­îc ®· x¸c ®Þnh. • Để cài đặt vòng lặp liệt kê các phần tử của Sk, dễ thấy là ta có thể sử dụng vòng lặp for trên PASCAL: for y:= 0 to 1 do hoặc trên C: for (y=0;y<=1;y++) 76 Chương trình trên Pascal var n: integer; b: array[1..20] of 0..1; count: word; procedure Ghinhan; var i: integer; begin count := count+1; write(count:5, '. '); for i := 1 to n do write(b[i]:2); writeln; end; procedure Xau(i: integer); var j: integer; begin for j := 0 to 1 do begin b[i] := j; if i = n then Ghinhan else Xau(i+1); end; end; BEGIN {Main program} write('n = '); readln(n); count := 0; Xau(1); write('Gõ Enter để kết thúc... '); readln END. 77 Chương trình trên C include #include #include int n, count; int b[20]; void Ghinhan() { int i, j; count++; printf(“Xau thu %i. ",count); for (i=1 ; i<= n ;i++) { j=b[i]; printf("%i ", j); } printf("\n"); } void Xau(int i){ int j; for (j = 0; j<=1; j++) { b[i] = j; if (i==n) Ghinhan(); else Xau(i+1); } } int main() { printf(“n = "); scanf("%i ",&n); printf("\n"); count = 0; Xau(1); printf(“Count = %d ", count); getch(); } 78 C©y liÖt kª d·y nhÞ ph©n ®é dµi 3 79 Liệt kê các m-tập con của n-tập 80 Liệt kê các m-tập con của n-tập  Bài toán: Liệt kê các tập con m phần tử của tập N = {1, 2, ..., n}. Bài toán dẫn về: Liệt kê các phần tử của tập: S(m,n)={(x1,..., xm)Nm: 1 ≤ x1<... #include #include int n, m, count; int a[20]; void Ghinhan() { int i, j; count++; printf("Tap con thu %i. ",count); for (i=1 ; i<= m ;i++) { j=a[i]; printf("%i ", j); } printf("\n"); } void MSet(int i){ int j; for (j = a[i-1] +1; j<= n-m+i; j++) { a[i] = j; if (i==m) Ghinhan(); else MSet(i+1); } } int main() { printf("n, m = "); scanf("%i %i",&n, &m); printf("\n"); a[0]=0; count = 0; MSet(1); printf(“Count = %d ", count); getch(); } 84 Cây liệt kê S(5,3) 85 Liệt kê hoán vị 86 Liệt kê hoán vị TËp c¸c ho¸n vÞ cña c¸c sè tù nhiªn 1, 2, ..., n lµ tËp n = {(x1,..., xn)  Nn: xi ≠ xj , i ≠ j }. Bài toán: Liệt kê tất cả các phần tử của n 87 Giải quyết 2 vấn đề mấu chốt  Râ rµng S1 = N. Gi¶ sö ta ®ang cã ho¸n vÞ bé phËn (a1, a2, ..., ak-1), tõ ®iÒu kiÖn ai ≠ aj, víi mäi i ≠ j ta suy ra Sk = N \ { a1, a2, ..., ak-1}.  Nh­vËy ta ®· cã c¸ch x¸c ®Þnh ®­îc tËp c¸c UCV vµo c¸c vÞ trÝ cña lêi gi¶i.  Mô tả S ? k 88 Mô tả Sk Xây dựng hàm nhận biết UCV: function UCV(j,k: integer): boolean; (* UCV nhận giá trị true khi và chỉ khi j  Sk *) var i: integer; begin for i:=1 to k-1 do if j = a[i] then begin UCV:= false; exit; end; UCV:= true; end; 89 Mô tả Sk  Câu lệnh qui ước “for y  Sk do ” có thể cài đặt như sau for y:=1 to n do if UCV(y,k) then begin (* y là UCV vào vị trí k *) ... end 90 Chương trình trên Pascal var n, count: integer; a: array[1..20] of integer; procedure Ghinhan; var i: integer; begin count := count+1; write(count:5, '. '); for i := 1 to n do write(a[i]:3); writeln; end; function UCV(j,k: integer): boolean; var i: integer; begin for i:=1 to k-1 do if j = a[i] then begin UCV:= false; exit; end; UCV:= true; end; 91 procedure Hoanvi(i: integer); var j: integer; begin for j := 1 to n do if UCV(j, i) then begin a[i] := j; if i = n then Ghinhan else Hoanvi(i+1); end; end; BEGIN {Main program} write('n = '); readln(n); count := 0; Hoanvi(1); write(‘Press Enter to finish... '); readln; END. 92 #include #include #include int n, count; int b[20]; int Ghinhan() { int i, j; count++; printf("Hoan vi thu %i. ",count); for (i=1 ; i<= n ;i++) { printf("%i ", b[i]); } printf("\n"); } int UCV(int j, int k) { int i; for (i=1; i<=k-1; i++) if (j == b[i]) return 0; return 1; } 93 int Hoanvi(int i){ int j; for (j = 1; j<=n; j++) if (UCV(j,i)==1) { b[i] = j; if (i==n) Ghinhan(); else Hoanvi(i+1); } } int main() { printf(“=========\n"); printf("n = "); scanf("%i",&n); printf("\n"); count = 0; Hoanvi(1); printf("Count = %d ", count); getch(); } 94 Cây liệt kê hoán vị của 1, 2, 3 95 The n-Queens Problem 96 The n-Queens Problem Giả sử ta có 8 con hậu...  ...và bàn cờ quốc tế  97 The n-Queens Problem Có thể xếp các con hậu sao cho không có hai con nào ăn nhau hay không ? 98 The n-Queens Problem Hai con hậu bất kỳ không được xếp trên cùng một dòng ... 99 The n-Queens Problem Hai con hậu bất kỳ không được xếp trên cùng một cột ... 100 The n-Queens Problem Hai con hậu bất kỳ không được xếp trên cùng một đường chéo! 101 The n-Queens Problem n con hậu Kích thước n*n n dò ng n cột 102 The n-Queens Problem Xét bài toán xếp n con hậu lên bàn cờ kích thước n x n. 103 Bài toán xếp hậu  Liệt kê tất cả các cách xếp n quân Hậu trên bàn cờ nn sao cho chúng không ăn được lẫn nhau, nghĩa là sao cho không có hai con nào trong số chúng nằm trên cùng một dòng hay một cột hay một đường chéo của bàn cờ. 104 Biểu diễn lời giải Đánh số các cột và dòng của bàn cờ từ 1 đến n. Một cách xếp hậu có thể biểu diễn bởi bộ có n thành phần (a1, a2 ,..., an), trong đó ai là toạ độ cột của con Hậu ở dòng i.  Các điều kiện đặt ra đối với bộ (a , a ,..., a ): 1 2 n  • • ai  aj , với mọi i  j (nghĩa là hai con hậu ở hai dòng i và j không được nằm trên cùng một cột); | ai – aj |  | i – j |, với mọi i  j (nghĩa là hai con hậu ở hai ô (ai, i) và (aj, j) không được nằm trên cùng một đường chéo). 105 Phát biểu bài toán  Như vậy bài toán xếp Hậu dẫn về bài toán liệt kê các phần tử của tập: D={(a1, a2, ..., an)Nn: ai ≠ aj và |ai – aj| ≠ |i – j|, i ≠ j }. 106 Hàm nhận biết ứng cử viên int UCVh(int j, int k) { // UCVh nhận giá trị 1 // khi và chỉ khi j  Sk int i; for (i=1; i #include int n, count; int a[20]; int Ghinhan() { int i; count++; printf("%i. ",count); for (i=1; i<=n;i++) printf("%i printf("\n"); } ",a[i]); int UCVh(int j, int k) { /* UCVh nhận giá trị 1 khi và chỉ khi j  Sk */ int i; for (i=1; i
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.