Thiết kế thuật toán cơ bản

5 662 4
Thiết kế thuật toán cơ bản

Đang tải... (xem toàn văn)

Thông tin tài liệu

Thiết kế thuật toán cơ bản

Các chiến lược thiết kế thuật toán cơ bảnNguyễn Thị ChinhA. Tổng quan Để giải được một bài toán trên máy tính điện tử, điều trước tiên là chúng ta phải thuật toán. Một câu hỏi mới được đặt ra là "Làm thế nào để tìm ra thuật toán cho bài toán đặt ra trong khi lớp các bài toán từ các ngành khoa học kĩ thuật là hết sức đa dạng và phong phú?". Đứng trước một bài toán bất kỳ, vấn đề đặt ra cho chúng ta thường là "Ta sẽ bắt đầu từ đâu để đi đến lời giải cho các yêu cầu của bài toán?" và khi tìm được lời giải rồi thì "Đâu là cách giải hay nhất cho bài toán đó?" (theo nghĩa là cách giải nhanh nhất mà máy tính thể thực hiện để cho ta kết quả của bài toán). Trên thực tế, học sinh giỏi khối phổ thông đã bắt đầu được làm quen với cách tư duy để hình thành thuật toán ứng với từng dạng bài toán khác nhau. Tuy nhiên, để học sinh tìm được lời giải thích hợp cho từng bài cụ thể một cách linh hoạt, nhanh nhạy đòi hỏi phải làm thật nhiều các dạng bài tập, mỗi dạng bài tập lại phải biết bắt đầu từ công việc phân tích yêu cầu đầu bài, thiết kế thuật toán phù hợp rồi mới đến công việc cài đặt. Làm như thế, chúng ta không những hướng cho học sinh giải quyết được một bài toán đơn thuần mà còn thể rút ra được tư tưởng của thuật toán để áp dụng vào bài toán đó như thế nào. Như vậy, khi gặp một bài toán mà khi phân tích ta thể đưa về một dạng bài quen thuộc đã gặp từ trước thì công việc đi tìm lời giải cho bài toán trở nên dễ dàng hơn, tiết kiệm thời gian hơn nhiều so với việc ta phải phân tích lại từ đầu. Chính vì vậy, rèn luyện cho học sinh nắm được tư tưởng chủ đạo của các thuật toán đã được học, cách áp dụng chúng để giải các bài toán trở nên quan trọng và cần thiết hơn cả. Ta biết rằng, ứng với mỗi dạng bài toán lại thể áp dụng những thuật toán khác nhau. Hơn nữa, không một phương pháp nào được coi là vạn năng nào để thể giải quyết được tất các các dạng bài toán khác nhau. Tuy nhiên, các nhà nghiên cứu đã tìm ra một số phương pháp thể áp dụng để giải được một phạm vi khá rộng các bài toán (gọi chung là các chiến lược bản để thiết kế thuật toán). Việc nắm chắc được các phương pháp này để áp dụng cho từng bài toán cụ thể là rất cần thiết. Thế nhưng, đó không phải là một việc làm đơn giản. Nó đòi hỏi sự hiểu biết khá rộng và sâu của chúng ta về tư tưởng thuật toán và cách nhận dạng bài toán để thể tìm ra một phương pháp hợp lí cho từng bài toán cụ thể. Dưới đây, ta xem xét ý tưởng của một số thuật toán bản và thường dùng. B. Một số chiến lược bản 1. Phương pháp chia − để − trị (Divide and Conquer) Phương pháp: Tư tưởng chung của phương pháp này như sau: Để giải một bài toán bằng phương pháp này, ta chia bài toán ban đầu thành các bài toán nhỏ hơn rồi đi giải nó. Để giải các bài toán nhỏ hơn này ta lại chia chúng thành các bài toán nhỏ hơn nữa rồi lại giải đi giải các bài toán con này bất kể chúng đã đươc giải hay chưa. Cứ tiếp tục kết hợp như vậy ta được kết quả của bài toán ban đầu. Thông thường khi chia bài toán ban đầu thành các bài toán con ta nhận thấy dạng của bài toán con này dạng giống như dạng của bài toán ban đầu nhưng kích thước nhỏ hơn (theo nghĩa là số biến ít hơn chẳng hạn!!!). Và ta thấy rằng theo cách biểu diễn này thuật toán tìm được rất dễ cài đặt bằng một thủ tục đệ quy. Ta thể biểu diễn phương pháp này một cách tổng quát như sau: Giả sử ta muốn tìm nghiệm x của bài toán PFunction Divide_Conquer (P,x) Begin If (P đủ nhỏ) then Solve(P) Else Begin Chia P thành các bài toán P1, P2 … Pn; {theo một cách nào đó } For i := 1 to n do Divide_Conquer (Pi,xi ); Kết hợp nghiệm của các bài toán con P1, P2 …Pn để được nghiệm của bài toán P. End; Return x; {trả lại nghiệm x cho bài toán P} End;Trong đó, thủ tục Solve() là thuật toán để giải được các bài toán P1, P2,…Pn khi kích thước (cỡ) của chúng đủ nhỏ. Ta sẽ xét một vài ví dụ đơn giản sau đây để hiểu rõ hơn về thuật toán này. Ví dụ: Bài toán tính số Fibonaci Phát biểu bài toán: cho trước một số tự nhiên n (nhập từ bàn phím), hãy tính số Fibonaci thứ n. Lời giải: Phân tích: Ta đã biết rằng số Fibonaci của n được tính theo công thức như sau: Fn = Fn − 1 + F n− 2. F1 = F2 = 1 Như vậy, dễ thấy muốn tính được số Fibonaci của n là Fn ta phải tính được số Fibonaci của n − 1 và n − 2 là Fn − 1 và Fưn − 2. Muốn tính được Fn − 1 ta lại phải tính được F n − 2 ư và Fn − 3…Cứ như vậy ta đưa được từ bài toán Fn về các bài toán nhỏ hơn …và trước hết ta phải tính được giá trị của bài toán con này. áp dụng tư tưởng thuật toán chia − để − trị ta viết được thuật toán cho bài toán này như sau:Procedure Fibonaci(n); Begin If (n = 1) or (n = 2) then Fn = 1 Else Fn ← Fibonaci (n − 1) + Fibonaci(n − 2) Return Fn; End Nhận xét: Nhận thấy, thuật toán này được cài đặt ngắn gọn và dễ hiểu. Với n = 5 ta thể biểu diễn F(5) sẽ được tính bằng mô hình sau: Tuy nhiên, nếu ta gọi T(n) là thời gian thực hiện để tính được Fibo(n) thì T(n) = c (const) với n ≤ 2 và T(n) = T(n − 1) + T(n − 2) với n > 2, quan hệ này giúp ta thể tính được T(n) = O(2n) tức là thời gian thực hiện thuật toán này là một hàm mũ, nó chỉ hiệu quả đối với n nhỏ. Như ví dụ trên, để tính F(5) ta phải tính F(1) hai lần, F(2) ba lần, F(3) hai lần và F(4) một lần. Vậy khi n lớn thì T(n) sẽ tăng rất nhanh, thuật toán này không còn ý nghĩa thực tiễn nữa, ta phải tìm một thuât toán khác hiệu quả hơn. Tuy nhiên, không thể phủ nhận được hiệu quả của phương pháp này mang lại. nhiều bài toán mà việc thiết kế giải thuật đệ quy đơn giản hơn nhiều so với lời giải khác, giải thuật cho bài toán tháp Hà Nội hay thuật toán sắp xếp Quick Sort là những ví dụ cụ thể (chúng ta thể tham khảo chương VIII, sách Cấu trúc dữ liệu và thuật toán − TS Đinh Mạnh Tường để hiểu rõ hơn về vấn đề này). Đối với các bài toán nêu trên, việc thiết kế thuật toán đệ quy tương ứng rất thuận lợi vì nó thuộc dạng tính giá trị hàm mà định nghĩa dạng quy nạp toán học của nó xác định được một cách dễ dàng. Tuy nhiên, vấn đề đặt ra là "khi nào thì dùng được thuật toán chia để trị?" và "những vấn đề gì cần chú ý khi dùng thuật toán này?" Để trả lời được câu hỏi đó ta cần phải trả lời những câu hỏi sau: thể đưa bài toán về dạng phối hợp của các bài toán con cùng dạng nhưng kích thước nhỏ hơn hay không? Nhỏ hơn ở đây ta hiểu theo nghĩa thể là số biến ít hơn, thể là số các phép toán thực hiện ít hơn…Bài toán những trường hợp đặc biệt nào thể tính ngay được giá trị của chúng? Cách áp dụng những trường hợp này để giải các bài toán cỡ lớn dần? Phương pháp chia để trị là một công cụ mạnh để thiết kế thuật toán. Tuy nhiên, trong trường hợp thuật toán này tỏ ra kém hiệu quả, ta sẽ tìm cách khử đệ quy cho thuật toán nó. Tuỳ từng bài toán cụ thể mà ta những cách khử khác nhau và một thuật toán được đánh giá là hiệu quả đối với các bài toán bản chất đệ quy là thuật toán Quy hoạch động với kĩ thuật giải bằng công thức truy hồi mà ta sẽ được nghiên cứu kĩ hơn trong chương II. Sau đây, ta tiếp tục làm quen với các thuật toán khác.2. Phương pháp tham lam (Greedy Method) Phương pháp: Đây là chiến lược thường được áp dụng để tìm nghiệm của bài toán tối ưu (nói một cách khác đây là bài toán đi tìm nghiệm tốt nhất thỏa mãn một số điều kiện ban đầu của bài toán tìm giá trị lớn nhất (Max) hoặc tìm giá trị nhỏ nhất (Min) cho một hàm mục tiêu − bài toán này được giới thiệu rõ hơn trong chương II). Bài toán phát biểu như sau: Cho trước một tập các đối tượng A nào đó. Yêu cầu đặt ra là tìm trong A một tập con các đối tượng thoả mãn một số điều kiện ràng buộc của bài toán, ta gọi là tập S. Như vậy, ứng với mỗi tập S A thoả mãn các ràng buộc thì ta gọi là một nghiệm của bài toán. ứng với mỗi nghiệm S tìm được, hàm mục tiêu sẽ nhận một giá trị nào đó. Trong tất cả các nghiệm đó, nghiệm S mà tại đó hàm mục tiêu nhận giá trị lớn nhất (hoặc nhỏ nhất) thì ta gọi đó là nghiệm tối ưu của bài toán đã cho. thể phát biểu tư tưởng của thuật toán này như sau: Ta cần đi xây dựng tập nghiệm S dần dần từng bước một. Bắt đầu, tập nghiệm là rỗng. Tại mỗi bước, ta sẽ chọn một đối tượng tốt nhất còn lại trong A để đưa vào S (việc quyết định chọn phần tử nào trong A là tốt nhất yêu cầu ta phải xây dựng một hàm xác định đối tượng tốt nhất và xác định nghiệm đang xây dựng đã trở thành nghiệm thực sự chưa?), phần tử được chọn sẽ bị loại khỏi A. Khi đó, ta xét rằng nếu thêm phần tử mới vào mà S vẫn còn thoả mãn điều kiện ràng buộc ban đầu thì S sẽ được mở rộng bằng cách thêm phần tử đó vào trong S.Ta thể mô tả lại thuật toán này như sau: Function Greedy_Method(A,S); Begin S ← ; While A <> do Begin x ← Select(A); A ← A − {x}; If (S + {x} còn thoả mãn điều kiện ràng buộc) then S ← S + {x}; End; Return S; {Nghiệm của bài toán} End; Trong đó, hàm Select(A) sẽ chọn ra một phần tử tốt nhất trong A (theo một nghĩa nào đó) mà thể đưa vào tập nghiệm S. Ta xét ví dụ sau: Ví dụ: Bài toán đổi tiền Phát biểu bài toán: n loại đồng xu, mỗi đồng giá trị tương ứng là d1, d2, …dn. Muốn đổi một lượng tiền tương đương giá trị là N xu ra tiền xu sao cho số đồng xu là ít nhất. Với giả thiết rằng, số đồng xu nhiều tuỳ ý. Lời giải: Phân tích bài toán: Ta cần phải đi tìm một nghiệm x = (x1, x2,…,xm) với xi là số đồng xu thứ i giá trị di sao cho: N = x i di với i = 1 m Yêu cầu của bài toán là tìm cách đổi sao cho tổng số đồng tiền cần đổi là ít nhất, vậy ta sẽ bắt đầu đổi từ đồng xu giá trị lớn nhất và cứ giảm dần cho đến khi số tiền N đã được đổi hết thì ta thông báo là tìm được nghiệm, ngược lại thì thông báo không đổi được. áp dụng tư tưởng của thuật toán tham lam vào bài toán này ta thể viết như sau:Function Doitien_Thamlam(N); Const D = [d1,d2,…d n ] {mảng lưu giá trị của từng loại đồng xu} Var x, sum,i; {x là nghiệm của bài toán} Begin Sắp xếp mảng D theo thứ tự giảm dần; x ← {}; sum ← 0; i ← 1; While (sum <> N) do {trong khi chưa đổi hết tiền} Begin xi ← (N − sum) div di);If (sum + xi *di ≤ N) then begin x = x + {xi}; sum = sum + xi * di; {số tiền đã đổi được tính đến đồng xu i} end; i = i + 1; End; If sum = N then Return x; {trả lại nghiệm của bài toán} Else {thông báo không đổi được} End;Như vậy, trong chương trình chính gọi hàm này thì kết quả trả ra thể nghiệm x hoặc là thông báo không đổi được. Nhận xét: Sở dĩ, phương pháp này được gọi là phương pháp tham lam bởi vì, tại mỗi bước ta luôn chọn phần tử tốt nhất − "miếng ăn ngon nhất" − để chọn trước mà không để ý đến hậu quả sau này. Vậy nên, với một số trường hợp thuật toán cho ta được nghiệm tối ưu của bài toán nhưng trong nhiều bài toán nghiệm tìm được không phải là nghiệm tối ưu mà chỉ gần đúng với nghiệm tối ưu thậm chí là còn không tính được nghiệm đúng. Ví dụ, bài toán đổi tiền, nếu khoản cần đổi là N = 13 và ta số đồng tiền là d1 = 3, d2 = 4, d3 = 6, ta cần tìm cách đổi 13 xu sao cho số tiền đổi là ít nhất. Theo phương pháp tham lam, ta sẽ đổi từ đồng giá trị lớn nhất đến nhỏ nhất. áp dụng vào bài toán này, ta sẽ dùng đồng 6 xu trước vậy là ta được 2 đồng 6 xu dư 1 xu. Như vậy số tiền này sẽ không đổi được nếu như áp dụng cách giải này mặc dù trên thực tế, dễ dàng ta thể đổi được với 1 đồng d3 ,1 đồng d2 và 1 đồng d1 (6 + 4 + 3 = 13 xu). . lược thiết kế thuật toán cơ bảnNguyễn Thị ChinhA. Tổng quan Để giải được một bài toán trên máy tính điện tử, điều trước tiên là chúng ta phải có thuật toán. . rộng các bài toán (gọi chung là các chiến lược cơ bản để thiết kế thuật toán) . Việc nắm chắc được các phương pháp này để áp dụng cho từng bài toán cụ thể

Ngày đăng: 11/09/2012, 14:32

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan