Xây dựng khung thuật giải

Chương 2 của luận văn trình bày về kiến trúc khung thuật giải và việc vận dụng các mẫu thiết kế hướng đốitượng để xây dựng nó ở mục 2.1.Từ đó sử dụng mô hình này để đặc tả lại một số nhóm thuật giải quan trọng như thuật giải chia để trị, thuật giải quay lui, thuật giải tham lam cùng kèm theo một số ví dụ minh họa.

pdf14 trang | Chia sẻ: vietpd | Lượt xem: 1680 | Lượt tải: 0download
Bạn đang xem nội dung tài liệu Xây dựng khung thuật giải, để tải tài liệu về máy bạn click vào nút DOWNLOAD ở trên
30 Chương 2 Xây dựng khung thuật giải Chương 2 của luận văn trình bày về kiến trúc khung thuật giải và việc vận dụng các mẫu thiết kế hướng đối tượng để xây dựng nó ở mục 2.1. Từ đó sử dụng mô hình này để đặc tả lại một số nhóm thuật giải quan trọng như thuật giải chia để trị, thuật giải quay lui, thuật giải tham lam … cùng kèm theo một số ví dụ minh họa. 2.1 Xây dựng khung thuật giải tổng quát Trước khi xây dựng khung thuật giải, ta xem qua phương pháp xây dụng khung phần mềm. Ở tài liệu tham khảo [3], các tác giả đã chỉ ra hai nguyên lý xây dựng khung phần mềm: nguyên lý hợp nhất và nguyên lí phân tán. Nguyên lý hợp nhất xây dựng khung phần mềm dựa trên mẫu thiết kế ℎ . Trong đó, những điểm tương đồng sẽ được giữ trong phương thức mẫu của lớp cơ sở và những điểm khác biệt sẽ được thi công ở các hàm nhượng quyền ℎ tại các lớp con. Còn nguyên lý phân tán dựa trên mẫu thiết kế , trong đó những điểm tương đồng sẽ được giữ trong các phương thức cụ thể của client còn những điểm khác biệt sẽ được mô tả trong lớp trừu tượng và được thi công ở các lớp 31 con của nó. Trong khuôn khổ của luận văn này, nguyên lý phân tán được sử dụng để xây dựng khung thuật giải tổng quát. Bắt đầu với việc xây dựng khung thuật giải cho việc giải bài toán bằng thuật giải, ta bắt đầu với ba khái niệm cốt lõi trong việc giải bài toán là: bài toán đặt ra, phương pháp để giải bài toán và kết quả đạt được. Ví dụ ứng với bài toán sắp xếp dãy số theo thứ tự tăng dần thì kết quả cần đạt được là dãy số được sắp xếp tăng, và những phương pháp ta có thể sử dụng cho bài toán này là thuật giải sắp xếp nổi bọt, thuật giải sắp xếp chèn, thuật giải sắp xếp nhanh … Hoặc với bài toán tìm kiếm một phần tử trong mảng, ta có thể sử dụng thuật giải tìm kiếm tuần tự hoặc thuật giải tìm kiếm nhị phân để xác định kết quả của bài toán: tồn tại phần tử đó trong mảng hay không? Với những nhận xét trên, ta định nghĩa các khái niệm , và . Trong đó, biểu diễn một bài toán bất kì, biểu diễn chiến thuật hoặc phương pháp có thể áp dụng để giải bài toán và biểu diễn kết quả trả về của bài toán khi áp dụng thuật giải để giải. Theo nguyên lý phân tán ta đưa ra khung thuật giải tổng quát như hình 3-1: Hình 2-1 Khung thuật giải tổng quát 32 Trong mô hình này, giao diện biểu diễn việc giải với bất kì. Kết quả của lời giải này trả về một . Khi cụ thể hóa với họ thuật giải . Lớp sẽ kế thừa từ và triển khai cụ thể phương thức () ứng với lời giải tổng quát của mình để giải quyết bài toán và trả về kết quả trong . Trong phương thức () này sẽ gọi đến các phương thức ảo nhượng quyền (hook) của lớp biểu diễn chiến lược tổng quát . Ứng với từng thuật giải cụ thể ℎ 1, ℎ 2 … sẽ thi công những hàm ℎ này tương ứng với cách giải quyết vấn đề của mình. Để minh họa việc sử dụng khung thuật giải cho từng họ thuật giải cụ thể, mục 3.2 của luận văn sẽ trình bày cách xây dựng các họ thuật giải kinh điển như chia để trị, quay lui, tham lam … 2.2 Một số khung thuật giải cụ thể 2.2.1 Khung thuật giải chia để trị Chia để trị là một trong những kỹ thuật phổ biến được sử dụng để giải bài toán bằng cách chia bài toán gốc thành một hoặc nhiều bài toán đồng dạng có kích thước nhỏ hơn, rồi giải lần lượt từng bài toán nhỏ một cách độc lập. Lời giải của bài toán gốc chính là sự kết hợp lời giải của những bài toán con. Có rất nhiều các thuật giải kinh điển dựa trên phương pháp này như thuật giải tìm kiếm nhị phân ( ℎ), thuật giải sắp xếp nhanh ( ), thuật giải sắp xếp trộn ( )… Mã giả của thuật giải chia để trị tổng quát được minh họa trong Bảng 3-1. Bảng 2-1 Mã giả thuật giải chia để trị DIVIDE: Chia bài toán p thành các bài toán con CONQUER: Lần lượt giải các bài toán con. Trong trường hợp kích thước bài toán con đủ nhỏ, trả về lời giải trực tiếp COMBINE: Kết hợp lời giải của các bài toán con để đạt lời giải của bài toán gốc 33 Từ mã giả trên, ta nhận thấy có thể cài đặt hàm () biểu diễn một phương thức mẫu chung cho tất cả các thuật giải thuộc họ chia để trị. Bảng 2-2 Thuật giải chia để trị tổng quát DivConqSolution solveByDivConq (DivConqProblem problem) { // kiểm tra tính đơn giản của bài toán if (statregy.isSimple (problem)) // giải bài toán trực tiếp trong trường hợp đơn giản return statregy.simplySolve (problem) // chia nhỏ bài toán thành nhiều bài toán con DivConqProblem subProblems[] = statregy.divide (problem) // giải lần lượt các bài toán con DivConqSolution subSolutions[] = new DivConqSolution[subProblems.length]; for ( i = 0; i < subProblems.length; i++ ) subSolutions[i] = solveByDivConq ( subProblems[i] ) // kết quả của bài toán là sự kết hợp kết quả các bài toán con return statregy.combine ( subSolutions ) } Giải thích chức năng của các hàm nhượng quyền: isSimple(): Kiểm tra tính đơn giản của bài toán simplySolve(): Giải bài toán trong trường hợp đơn giản divide(): Chia nhỏ bài toán thành nhiều bài toán con combine(): Kết hợp lời giải bài toán con về lời giải bài toán gốc Các hàm (), (), () và () chính là các hàm ℎ và sẽ được thi công ở các lớp biểu diễn thuật giải cụ thể trong nhóm. Mỗi thuật giải khác nhau sẽ hiện thực các hàm nhượng quyền này khác nhau. Ví dụ, hàm () được thực hiện khác nhau giữa và , chỉ đơn giản là ghép hai dãy nhỏ hơn và lớn hơn giá trị cầm canh còn phải trộn hai dãy đã sắp xếp. Hoặc hàm () được thực hiện khác nhau giữa 34 ℎ và ℎ khi thu nhỏ không gian tìm kiếm với tỉ lệ [ ; ] và [1; − 1] Tiếp đến, ta xây dựng khung thuật giải chia để trị tổng quát. Theo khung thuật giải tổng quát đã trình bày ở mục 3-1, ta xây dựng lớp là lớp ngữ cảnh trong khung thuật giải chia để trị và là giao diện thể hiện cách giải quyết bài toán bằng phương pháp chia để trị. Các thuật giải cụ thể sử dụng phương pháp này như ℎ, … sẽ được kế thừa và thi công cụ thể 4 hàm ℎ đã được định nghĩa từ . «interface» Strategy +solve(in : Problem) : ISolution «interface» Solver +isSimple(in : Problem) : bool +simplySolve(in : Problem) : ISolution +divide() : IProblem[] +combine(in : Solution[]) : ISolution «interface» DivConqStrategy +isSimple() +simplySolve() +divide() +combine() QuickSort +isSimple() +simplySolve() +divide() +combine() MergeSort +isSimple() +simplySolve() +divide() +combine() BinarySearch +solve() +solveByDivConq() DivConqSolver public DivConqSolution solveByDivConq (DivConqProblem p) { if isSimple (p) return simplySolve (p); subProblems[] = divide (p); for ( i = 0; i < subProblems.length; i++ ) subSolutions[i] = solve( subProblems[i] ); return combine ( subSolutions[i] ); } «interface» Problem «interface» Solution DivConqProblem DivConqSolution Hình 2-2 Khung thuật giải chia để trị Rõ ràng khi tổng quát hóa thuật giải chia để trị theo mô hình này, bản chất và cả độ phức tạp của thuật giải vẫn không hề thay đổi. Ta có thể tính độ phức tạp của thuật giải theo tính chất 2.1 sau: Tính chất 2.1: Giả sử ( ) biểu diễn thời gian chạy của thuật giải chia để trị, và thuật giải này chia bài toán có kích thước thành bài toán con có kích thước / và chi phí chia và ghép này là ( ) thì ta có ( ) = ∗ + ( ) 35 Và ta có được có được độ phức tạp của thuật giải với các trường hợp như sau [29]: · Trường hợp 1: ∃ > 0: ( ) = ( ) ⇒ ( ) = ( ) · Trường hợp 2: ∃ ≥ 0: ( ) = ( ) ⇒ ( ) = ( ) · Trường hợp 3: ∃ ≥ 0: ( ) = Ω( ) ⇒ ( ) = ( ( )) Ví dụ 2.1: Để minh họa việc sử dụng khung thuật giải chia để trị, ta áp dụng với thuật giải ℎ cho việc tìm kiếm phần tử trong dãy như sau: · Đặc tả bài toán: Bài toán tìm kiếm gồm dữ liệu đầu vào là một mảng các phần tử, một khóa kèm theo phép toán so sánh giữa các phần tử trong danh sách. Lời giải của bài toán là phần tử có tồn tại trong mảng hay không? Để đơn giản, ta xây dựng lớp ℎ để cùng mô tả bài toán và lời giải của nó. Trong đó, cấu trúc mảng được biểu diễn bởi [] và hai chỉ số , ℎ . · Xây dựng thuật giải tìm kiếm nhị phân kế thừa từ thuật giải chia để trị tổng quát: Ta xây dựng lớp ℎ kế thừa và cài đặt 4 hàm ℎ của : o (): Bài toán tìm kiếm được gọi là đơn giản khi dãy tìm kiếm chỉ có 1 phần tử (ℎ = ) hoặc phần tử giữa dãy được chọn có giá trị bằng với khóa tìm kiếm ( [ ] = ). o (): Khi bài toán rơi vào trường hợp đơn giản, kết quả trả về chỉ đơn giản là kết quả việc so sánh phần tử giữa và khóa. o (): Khi phần tử [ ] không phải là giá trị cần tìm, ta sẽ chia nhỏ dãy tùy vào kết quả so sánh giữa nó và khóa. Nếu [ ] > : ta đưa về bài toán tìm kiếm trên dãy [ , − 1], ngược lại ta đưa về bài toán tìm kiếm trên dãy [ + 1, ℎ ]. Trong trường hợp này, bài toán được chia nhỏ thành 2 bài toán con nhưng trong đó có một bài toán con chắc chắn không có lời giải. 36 o (): Do ở bước () ta chỉ chia thành 1 bài toán con nên phương thức kết hợp này không cần thực hiện, chỉ trả về lời giải của bài toán con được chia nhỏ. · Độ phức tạp thuật toán: Dễ dàng nhận thấy, thuật giải này tương ứng với = 1 và = 2 , và ( ) = (1) theo tính chất 2.1 ta tìm được = 0 để ( ) thỏa trường hợp 2, do đó ( ) = ( ). Tương tự, ta có thể sử dụng khung thuật giải chia để trị này cho các thuật giải giải bài toán theo phương pháp chia để trị như , … 2.2.2 Khung thuật giải quay lui Thuật giải quay lui (Lehmer, 1950) là một chiến lược tìm kiếm lời giải cho các bài toán thỏa mãn ràng buộc. Về bản chất, tư tưởng chủ đạo của phương pháp quay lui là thử từng khả năng cho đến khi tìm thấy lời giải đúng. Đó là một quá trình tìm kiếm ưu tiên chiều sâu trong một tập hợp các lời giải. Trong quá trình tìm kiếm, nếu ta gặp một hướng lựa chọn không thỏa mãn ràng buộc, ta quay lui về điểm lựa chọn nơi có các hướng khác và thử hướng lựa chọn tiếp theo. Khi đã thử hết các lựa chọn xuất phát từ điểm lựa chọn đó, ta quay lại điểm lựa chọn trước đó và thử hướng lựa chọn tiếp theo tại đó. Quá trình tìm kiếm thất bại khi không còn điểm lựa chọn nào nữa. Không mất tính tổng quát, ta giả sử lời giải của bài toán được biểu diễn bởi véc-tơ (v1, v2, …, vn). Khi thực hiện, thuật giải sẽ bắt đầu bằng một véc-tơ rỗng, và tại mỗi bước ta sẽ chọn giá trị mới vào một thành phần của véc-tơ kết quả v. Tại bước chọn thành phần vi, khi không chọn được bước kế tiếp, thuật giải sẽ quay lui chọn lại bước trước đó. Mã thuật giải giải quay lui tổng quát được trình bày trong bảng 3-3. Bảng 2-3 Mã giả thuật giải quay lui ALGORITHM try(v1,...,vi) IF (v1,...,vi) is a solution THEN RETURN (v1,...,vi) FOR each v DO 37 IF (v1,...,vi,v) is acceptable vector THEN sol = try(v1,...,vi,v) IF sol != () THEN RETURN sol END END RETURN () Từ mã giả trên, ta xây dựng thuật giải quay lui tổng quát (). Bảng 2-4 Thuật giải quay lui tổng quát bool solveByBackTracking BackTrackingProblem problem, int i) { // kiểm tra đã chọn đủ số biến của lời giải hay chưa? if (i > strategy.getNumOfVariables(problem)) { // kiểm tra tập các lựa chọn solution có phải là lời giải? // nếu đúng, thêm solution vào tập các lời giải solutions if (strategy.isAcceptable(solution) { strategy.acceptSolution(solution); return true; } } // nếu chưa, chọn tiếp lựa chọn ở bước thứ i else { // duyệt tất cả các lựa chọn option ở bước thứ i foreach (option in statregy.getOptions(problem,solution,i)) { statregy.setChoice (solution, i, option); // chọn bước i + 1 tiếp theo solveByBackTracking (problem, i + 1); statregy.setChoice (solution, i, null); } } return false; } Giải thích chức năng của các hàm nhượng quyền: getNumOfVariables (): Kích thước không gian lời giải getOptions (): Trả về tất cả các lựa chọn của bài toán ở bước thứ 38 i ứng với các bước đã chọn đến i – 1. isAcceptable(): Kiểm tra các bước lựa chọn có thỏa mãn ràng buộc của bài toán hay không? acceptSolution (): Chấp nhận lời giải setChoice (): Thêm lựa chọn vào lời giải Và đồng thời xây dựng khung thuật giải quay lui tương tự cách xây dựng khung thuật giải chia để trị. T xây dựng lớp kế thừa biểu diễn khung thuật giải quay lui và kế thừa mô tả các hàm ℎ . Các thuật giải cho sử dụng phương pháp quay lui như bài toán quân hậu ( ), bài toán người du lịch ( ) hay bài toán tìm bao lồi của tập điểm ( ) sẽ kế thừa và thi công lại các hàm ℎ này. Hình 2-3 Khung thuật giải quay lui Tính chất 3.2: Độ phức tạp của thuật giải quay lui là ( ) trong trường hợp xấu nhất. Ví dụ 3.2: Để minh họa việc sử dụng khung thuật giải quay lui, ta áp dụng với bài toán 8 Hậu ( ) như sau: 39 · Đặc tả bài toán: Cho bàn cờ 8x8, bài toán đặt ra là tìm các cách đặt 8 quân hậu lên bàn cờ sao không có quân nào có thể ăn được quân nào. · Xây dựng thuật giải: Ta xây dựng lớp kế thừa và cài đặt 5 hàm ℎ của : o (): Hàm này trả về 8 vì cần đặt 8 quân hậu. o (): Hàm này trả về các TRUE vì ta đã đặt được 8 quân hậu vào bàn cờ thỏa điều kiện không quân nào ăn nhau. Trên thực tế, hàm () sẽ là TRUE khi ta đặt được quân cờ thứ 8. o (): Hàm này trả về các vị trí quân hậu mà không bị ăn bởi các quân hậu đã đặt lên trước. o ℎ () : Hàm này gán vị trí đã chọn vào lời giải . o (): Hàm này chỉ đơn giản in lời giải ra màn hình kết quả. · Độ phức tạp thuật toán: Ở cách cài đặt này, do ta đã giới hạn các lựa chọn ở bước thứ i chỉ gồm các vị trí mà không bị các quân từ đặt ở cột 1 đến cột − 1 ăn nên ở bước thứ 2 ta chỉ chọn từ (n-1) ứng viên, bước thứ 3 chọn từ (n-2) ứng viên … Do đó, độ phức tạp chỉ còn ( ) = ! 2.2.3 Khung thuật giải quy hoạch động Quy hoạch động (Richard Bellman, 1953) là phương pháp giải quyết bài toán tổng quát cho các bài toán có những bài toán con chồng nhau và cấu trúc con tối ưu. Quy hoạch động thường dùng một trong hai cách tiếp cận: · Top-down (Từ trên xuống): Bài toán được chia thành các bài toán con, các bài toán con này được giải và lời giải được ghi nhớ để phòng trường hợp cần dùng lại chúng. Đây là đệ quy và lưu trữ được kết hợp với nhau. · Bottom-up (Từ dưới lên): Tất cả các bài toán con có thể cần đến đều được giải trước, sau đó được dùng để xây dựng lời giải cho các bài toán lớn hơn. 40 Cách tiếp cận này hơi tốt hơn về không gian bộ nhớ dùng cho ngăn xếp và số lời gọi hàm. Tuy nhiên, đôi khi việc xác định tất cả các bài toán con cần thiết cho việc giải quyết bài toán cho trước không được trực giác lắm. Trong việc xây dựng khung thuật giải quy hoạch động, chúng tôi sử dụng phương pháp tiếp cận từ dưới lêng xây dựng được thuật giải quy hoạch động tổng quát minh họa trong Bảng 3-5. Bảng 2-5 Thuật giải quy hoạch động tổng quát DynProgSolution solveByDynProg (DynProgProblem p) { // khởi tạo các bài toán và lời giải bộ phận DynProgSolution partSlns[] = strategy.initPartialSolutions(p); while (!strategy.isSolution(problem, partSlns)) { // chọn các bài toán bộ phận để giải int partPbls[] = strategy.getPartialProblems(p, partSlns); // giải bài toán bộ phận DynProgSolution s = strategy.directSolve(p, partPbls, partSlns); // lưu trữ lời giải bộ phận strategy.save(partPbls, partSlns, s); } // trộn các lời giải bộ phận để được lời giải bài toán gốc return strategy.merge(partSlns); } Giải thích chức năng của các hàm hook: initPartialSolutions (): Khởi tạo danh mục bài toán bộ phận getPartialProblems (): Chọn các bài toán bộ phận để giải directSolve (): Giải bài toán bộ phận save (): Lưu trữ lời giải của bài toán bộ phận merge (): Trộn lời giải của tất cả bài toán bộ phận Để xây dựng khung giải thuật quy hoạch động, ta cũng xây dựng lớp kế thừa biểu diễn khung thuật giải quy hoạch động và 41 kế thừa mô tả các hàm ℎ . Các thuật giải cho sử dụng phương pháp quy hoạch động như bài toán nhân ma trận ( ), bài toán sẽ kế thừa và thi công lại các hàm ℎ này. 2.2.4 Khung thuật giải tham lam Thuật giải tham lam là một thuật giải giải quyết một bài toán theo kiểu “metaheuristic” để tìm kiếm lựa chọn tối ưu địa phương ở mỗi bước đi với hy vọng tìm được tối ưu toàn cục. Thuật giải tham lam có năm thành phần: · Một tập hợp các ứng viên (candidate), để từ đó tạo ra lời giải · Một hàm lựa chọn, để theo đó lựa chọn ứng viên tốt nhất để bổ sung vào lời giải · Một hàm khả thi dùng để quyết định nếu một ứng viên có thể được dùng để xây dựng lời giải · Một hàm mục tiêu, ấn định giá trị của lời giải hoặc một lời giải chưa hoàn chỉnh · Một hàm đánh giá, chỉ ra khi nào ta tìm ra một lời giải hoàn chỉnh Có hai thành phần quyết định nhất tới quyết định tham lam: · Tính chất lựa chọn tham lam: Chúng ta có thể lựa chọn giải pháp nào `được cho là tốt nhất ở thời điểm hiện tại và sau đó giải bài toán con nảy sinh từ việc thực hiện lựa chọn vừa rồi. Lựa chọn của thuật giải tham lam có thể phụ thuộc vào các lựa chọn trước đó. Nhưng nó không thể phụ thuộc vào một lựa chọn nào trong tương lai hay phụ thuộc vào lời giải của các bài toán con. Thuật giải tiến triển theo kiểu thực hiện các chọn lựa theo một vòng lặp, cùng lúc đó thu nhỏ bài toán đã cho về một bài toán con nhỏ hơn. Đấy là khác biệt giữa thuật giải này và thuật giải quy hoạch động. Thuật giải quy hoạch động duyệt hết và luôn đảm bảo tìm thấy lời giải. Tại mỗi bước của thuật toán, quy hoạch động đưa ra quyết định dựa trên các quyết định của bước trước, và có thể xét lại đường đi của bước trước hướng tới lời giải. Thuật giải tham lam quyết định sớm và thay 42 đổi đường đi thuật giải theo quyết định đó, và không bao giờ xét lại các quyết định cũ. Đối với một số bài toán, đây có thể là một thuật giải không chính xác. · Cấu trúc con tối ưu: Một bài toán được gọi là "có cấu trúc tối ưu", nếu một lời giải tối ưu của bài toán con chứa lời giải tối ưu của bài toán lớn hơn. Thuật giải tham lam tổng quát được xây dựng ở Bảng 3-6. Bảng 2-6 Thuật giải tham lam tổng quát GreedySolution solveByGreedy(GreedyProblem p) { // khởi tạo danh sách ứng viên List candidates = strategy.initCandidates(); List choices = new LinkedList(); // lặp khi danh sách ứng viên vẫn còn phần tử và các bước lựa // chọn là lời giải bộ phận while (strategy.isPartialSolution(choices) && !candidates.isEmpty()) { // chọn ứng viên theo nguyên tắc tham lam Object option = strategy.greedyChoice(candidates); candidates.remove(option); // thêm ứng viên phù hợp vào danh sách chọn if (strategy.feasible (choices, option)) solution.add(choices); } if (strategy.isSolution(choices)) return GreedySolution.makeSolution(choices); return null; } Giải thích chức năng của các hàm hook: initCandidates (): Khởi tạo danh sách ứng viên isPartialSolution (): Kiểm tra các lựa chọn là lời giải bộ phận? isSolution (): Kiểm tra các lựa chọn là lời giải toàn cục? greedyChoice (): Chọn ứng viên lời giải theo cách tham lam, thường là giá trị lớn nhất theo một phép so sánh cho trước feasible (): Kiểm tra tính phù hợp của một lựa chọn với lời giải 43 Từ thuật giải tổng quát này, tương tự thuật giải chia để trị tổng quát hay thuật giải quay lui tổng quát, ta xây dựng được khung giải thuật tham lam bằng cách thiết lập các lớp cài đặt thuật giải tổng quát và mô tả các hàm nhượng quyền.