- Khi cần giải quyết một bài toán trong thực tế với sự trợ giúp của máy tắnh ựiện tử ta thường phải biết dữ liệu vào của bài toán (Input) là gì? và bài toán yêu cầu dữ liệu ra (Output) là gì?. Bước tiếp theo ta phải thiết lập ựược các bước thao tác cụ thểựể từ Input ta có ựược Output. Công việc ựó trong tin học ựược gọi là xây dựng giải thuật.
- Giải thuật của 1 bài toán là một dãy các câu lệnh (Statements) chặt chẽ và rõ ràng xác ựịnh một trình tự các thao tác trên một số ựối tượng nào ựó sao cho sau một số bước hữu hạn thực hiện ta thu ựược kết quả mong muốn.
- Với ựịnh nghĩa như vậy ta thấy rằng ựối với một bài toán cụ thể có thể có nhiều giải thuật khác nhau nhưng tất nhiên là các giải thuật ựó phải cho cùng một kết quả theo ựúng yêu cầu của bài toán.
- Khi nghiên cứu về giải thuật thường ta phải biết ựược giải thuật ựó tác ựộng lên dữ
liệu nào. Việc lựa chọn cấu trúc dữ liệu (Data structures) phù hợp và việc thiết lập ựược các giải thuật ựúng ựắn có cấu trúc tốt và hiệu quả là những vấn ựề mấu chốt của công việc thiết lập phần mềm. Chắnh vì vậy mà Niklaus Wirth người sáng lập ra ngôn ngữ lập trình Pascal ựã tổng kết: Giải thuật+Cấu trúc dữ liệu= Chương trình
Vắ dụ: Xây dựng giải thuật tìm UCLN của 2 số nguyên dương a và b, ký hiệu (a,b) + đối với bài toán này ta có
Input: 2 số nguyên dương a, b Output: (a,b)
+ Giải thuật ựược xây dựng dựa trên tắnh chất Nếu a=b thì (a,b)=a
Nếu a>b thì (a,b)=(a-b,b) Nếu a<b thì (a,b)=(a,b-a)
+ Cụ thể giải thuật của bài toán như sau:
Bước 1: So sánh a và b, Nếu a=b thì dừng giải thuật và thông báo (a,b)=a. Nếu a≠b thì chuyển sang bước 2
Bước 2: Nếu a>b thì thay thế a bởi a-b, nếu a<b thì thay thế b bởi b-a. Quay lại thực hiện bước 1
Minh hoạ: a=20, b=32
Bước thực hiện a b Kiểm tra ựiều kiện a=b
Bước 1 20 32 Sai Bước 2 20 12 Bước 1 20 12 Sai Bước 2 8 12 Bước 1 8 12 Sai Bước 2 8 4 Bước 1 8 4 Sai Bước 2 4 4 Bước 1 4 4 đúng Kết quả là: (20,32)=4
2. Các yêu cầu với giải thuật
Giải thuật của bài toán phải thoả mãn 3 yêu cầu sau:
Yêu cầu 1: Tắnh dừng
Giải thuật phải dừng sau một số hữu hạn các thao tác, ựây là yêu cầu hết sức quan trọng với một giải thuật
Yêu cầu 2: Tắnh ựúng ựắn
Ta phải ựặt câu hỏi "Liệu giải thuật có thể hiện ựúng lời giải của bài toán không?" . Thông thường chúng ta cài ựặt giải thuật dưới dạng chương trình và cho thực hiện trên máy tắnh với một số bộ dữ liệu nào ựó, sau ựó so sánh với những kết quả mà ta ựã biết. Nhưng cách thử này chỉ khẳng ựịnh ựược tắnh sai chứ chưa thể khẳng ựịnh ựược tắnh ựúng ựắn của giải thuật. Bằng cách sử dụng các công cụ toán học ta có thể khẳng ựịnh ựược tắnh ựúng ựắn của 1 giải thuật nhưng thường thì ựây là một công việc phức tạp.
Yêu cầu 3: Tắnh ựơn giản và hiệu quả
Ta thường mong muốn xây dựng ựược một giải thuật ựơn giản, dễ hiểu, dễ lập trình. Nhưng ựôi khi thuật giải ựơn giản lại gây ra sự lãng phắ thời gian và bộ nhớ. Do ựó mục tiêu là phải xây dựng ựược các giải thuật có thời gian thực hiện nhanh, hạn chế tối ựa dung lượng bộ nhớ dành cho việc lưu trữ những kết quả trung gian.
3. Các cách diễn tả giải thuật 3.1. Cách 1: Liệt kê từng bước
Vắ dụ: Có 31 que diêm, người và máy thay nhau bốc. Mỗi lần bốc từ 1 ựến 4 que. Ai phải bốc sau cùng là thua. Hãy xây dựng thuật giải sao cho máy bốc trước bao giờ cũng thua.
Bước 1: Máy bốc ngẫu nhiên x que diêm (1≤x≤4)
Bước 2: Người bốc (5- x) que, tổng số que diêm giảm ựi 5 que. Nếu số que diêm còn lại là 1 que thì chuyển sang bước 3, nếu không thì quay lại thực hiện bước 1
Bước 3: Tuyên bố người thắng cuộc
3.2. Cách 2: Sử dụng lưu ựồ
Sử dụng phương tiện hình học cũng là một cách tốt ựể minh hoạ giải thuật của 1 bài toán. Trong lưu ựồ người ta sử dụng các hình sau, với kắ hiệu + là ựúng, - là sai.
- +
Kh i i u ki n Kh i l nh
Kh i b t u và k t thúc
gt1
K
a. Cấu trúc rẽ nhánh
- Rẽ nhánh dạng khuyết: if <ựk thoả mãn> then <thực hiện lệnh> (hình 1) - Rẽ nhánh dạng ựủ: if <ựk thoả mãn> then <thực hiện lệnh 1> else <thực hiện lệnh 2> (hình 2) - Cấu lệnh lựa chọn: case <biểu thức nhận> Giá trị 1: Thực hiện lệnh 1 Giá trị 2: Thực hiện lệnh 2 ... Giá trị n: Thực hiện lệnh n else Thực hiện lệnh (n+1) end - - - + + + Hình 3 b. Cấu thúc lặp + Lặp một số lần ựịnh trước: Dạng 1: for i:=m to n do <lệnh>
Thực hiện lệnh với i nhận các giá trị nguyên tăng từ m tới n với bước nhảy bằng 1
Dạng 2: for i:=n downto m do <lệnh>
Tương tự như dạng 1 nhưng bước nhảy giảm bằng 1 + Lặp với ựiều kiện trước: while <ựiều kiện > do < lệnh>
Khi ựiều kiện còn ựúng thì còn thực hiện lệnh, quá trình lặp kết thúc khi ựiều kiện sai (hình 1)
+ Lặp với ựiều kiện sau: repeat <lệnh> until <ựiều kiện>
Khi ựiều kiện còn sai thì còn thực hiện lệnh, quá trình lặp kết thúc khi ựiều kiện ựúng (hình 2) - + - + L nh 1 gt2 gtn L nh n L nh (n+1) L nh 1 L nh K L nh L nh L nh L nh 1 L nh K K L nh 2 Hình 1 Hình 2 + - + - +
3.3 Cách 3: Sử dụng giả ngôn ngữ có cấu trúc tựa ngôn ngữ lập trình bậc cao
Là phương pháp diễn tả giải thuật dựa vào các cấu trúc ựiều khiển, cùng với các từ khoá của một ngôn ngữ lập trình bậc cao nào ựó. Trong giáo trình này ta sẽ sử dụng ngôn ngữ tựa Pascal ựể diễn tả giải thuật. Cách diễn ựạt này ựã tiếp cận gần hơn với ngôn ngữ lập trình.
Vắ dụ: Với thuật giải tìm UCLN ở trên ta có thể diễn ựạt như sau
while a≠b
begin
if a>b then thay a bởi a-b
else thay b bởi b-a
end
writeước chung lớn nhất là a
4. Thiết kế giải thuật
4.1. Mô-ựun hoá và việc giải quyết bài toán
Những bài toán ta gặp trong thực tế thường là phức tạp, trong trường hợp ựó người ta thường chia bài toán thành những bài toán nhỏ và dễ giải quyết hơn. Nghĩa là coi bài toán ban
ựầu là Mô- ựun chắnh, ta chia nó thành các Mô- ựun con, và mỗi Mô- ựun con này có thể lại
ựược chia thành các Mô- ựun nhỏ hơn... Cách giải quyết bài toán như vậy người ta thường gọi là chiến thuật "Chia ựể trị"(divide and conquer).
Trong khi lập trình việc chia chương trình chắnh thành các chương trình con thể hiện tắnh có cấu trúc của ngôn ngữ lập trình về mặt chương trình
4.2. Tinh chỉnh từng bước giải thuật
Tinh chỉnh từng bước là phương pháp thiết kế giải thuật gắn liền với lập trình. Bước ựầu giải thuật ựược minh hoạ bằng ngôn ngữ tự nhiên, càng ở các bước sau ngôn ngữ tự nhiên
ựược thay thế bởi ngôn ngữ tự nhiên pha lẫn ngôn ngữ lập trình mà ta gọi là giả ngôn ngữ. Ta có sơựồ sau: Ngôn ngữ tự nhiên→ Giả ngôn ngữ→ Ngôn ngữ lập trình
4.3. Phân tắch thuật giải
Phân tắch giải thuật phải căn cứ vào 3 tiêu chuẩn ựối với một giải thuật: Tắnh dừng, tắnh
ựúng ựắn, tắnh ựơn giản và hiệu quả.
Việc kiểm tra giải thuật là một phần hết sức quan trọng, lý tưởng là khi có thể khẳng
ựịnh một cách hình thức tắnh ựúng ựắn của giải thuật. Tuy nhiên trong thực tế thời gian và công sức ựể viết ra một cách cẩn thận và ựầy ựủ tất cả những chi tiết chứng minh tắnh ựúng
ựắn của một giải thuật phức tạp thường là không cho phép. Người lập trình thường áp dụng các biện pháp sau:
-Chứng minh một cách suy diễn rằng những bước trong giải thuật là ựúng ựắn. Nghĩa là giải thuật bắt ựầu bằng một khẳng ựịnh (giả thiết) về dữ liệu vào và dùng phương pháp suy luận lôgic ựể chỉ ra rằng việc thực hiện giải thuật sẽ cho một khẳng ựịnh (kết luận) vềựầu ra.
- Thể hịên giải thuật bằng một ngôn ngữ lập trình và thử thực hiện chương trình với các bộ dữ liệu vào mà kết quả ta ựã biết trước. Thường thì các lỗi về cú pháp và lỗi lúc thực hiện chương trình thường dễ tìm và sửa chữa còn các lỗi lôgic thường khó phát hiện hơn nhiều.
-Có ựôi lúc viêc kiểm tra chương trình phải thực hiện thủ công, kiểm tra từng bước, từng thủ tục của chương trình chắnh. Kỹ thuật này ựược gọi là Ộựi bộ qua chương trìnhỢ(Walking through the program)
-Quan tâm ựặc biệt tới thời gian thực hiện chương trình, thời gian thực hiện phụ thuộc rất nhiều vào việc tổ chức dữ liệu ựưa vào (kắch thước dữ liệu).
5. Giải thuật sắp xếp (Sorting)
Sắp xếp là một trong số yêu cầu thường xuyên xuất hiện trong quá trình xử lý số liệu. Bản chất của thuật giải sắp xếp là bố trắ lại vị trắ của số liệu theo thứ tự tăng dần hoặc giảm dần. Có nhiều giải thuật sắp xếp trong tin học, trong giáo trình này chúng ta sẽựề cập ựến một số thuật giải ựơn giản ựó là sắp xếp lựa chọn (selection sort), sắp xếp chèn (insertion sort) và sắp xếp nổi bọt (bubble sort). đểựơn giản giả sử yêu cầu của bài toán là: Sắp xếp một dãy số
cho trước a1, a2, ..., an theo thứ tự tăng dần.
5.1 Sắp xếp lựa chọn (selection sort)
Thuật giải chọn ựược diễn tả như sau: Tìm phần tử nhỏ nhất trong dãy số và hoán vị nó với phần tửựầu tiên, tìm phần tử nhỏ nhất kế tiếp và hoán vị nó với phần tử thứ hai. Tiếp tục quá trình này ựến khi toàn bộ dãy sốựược sắp xếp.
procedure Selection_Sort;
begin
for i:=1 to n-1 do begin
m:=i
for j:=i+1 to n do if aj<amthen m:=j if m≠i thenựổi chỗ ai và am end end Vắ dụ Dãy số ban ựầu : 3 6 -2 7 5 i=1 -2 6 3 7 5 i=2 -2 3 6 7 5 i=3 -2 3 5 7 6 i=4 -2 3 5 6 7 5.2 Sắp xếp chèn (insertion sort)
Thuật giải chèn ựược diễn tả như sau: Xét lần lượt từng phần tử và chèn vào vị trắ thắch hợp của phần tử ựó trong số các phần tử ựã xét trước ựó. Cụ thể giả sử ựã có (i-1) phần tử ựược sắp xếp ựúng vị trắ, ựể chèn phần tử thư i vào ựúng vị trắ ta so sánh lần lượt với các phần tử thứ (i-1), (i-2),... khi tìm ựược vị trắ ựúng thì chèn phần tử thứ i ựó vào.
procedure Insertion_Sort begin for i:=2 to n do begin k:=ai j:=i
while aj-1>k do begin aj:=aj-1, j:=j-1 end
aj:=k end end Vắ dụ Dãy số ban ựầu : 3 6 -2 7 5 i=2 3 6 -2 7 5 i=3 -2 3 6 7 5 i=4 -2 3 6 7 5 i=5 -2 3 5 6 7 5.3 Sắp xếp nổi bọt (bubble sort)
Thuật giải này còn có tên gọi khác là sắp xếp bằng cách ựổi chỗ trực tiếp (exchange sort), thuật giải nổi bọt ựược diễn tả như sau: Duyệt dãy số theo thứ tự từ phải sang trái nếu hai phần tử kề cận ngược thứ tự thì ựổi chỗ cho nhau. Như vậy sau lượt duyệt ựầu tiên phần tử ựầu tiên sẽ là phần tử nhỏ nhất, sau lượt thứ hai phần tử nhỏ thứ hai ựược chuyển lên vị trắ thứ
hai...cứ như vậy dãy số sẽựược sắp xếp tăng dần.
procedure Bubble_Sort
begin
for i:=1 to n-1 do
for j:=n downto i+1 do
if aj<aj-1thenựổi chỗ aj và aj-1 end Vắ dụ Dãy số ban ựầu : 3 6 -2 7 5 i=1 -2 3 6 5 7 i=2 -2 3 5 6 7 i=3 -2 3 5 6 7 i=4 -2 3 5 6 7 6. Giải thuật tìm kiếm (Searching)
Cùng với các thuật giải sắp xếp, các thuật giải tìm kiếm cũng ựóng một vai trò quan trọng trong khi xủ lắ số liệu. Bài toán tìm kiếm ựặt ra như sau: Giả sủ ta có một dãy số a1, a2,...., an, ta phải tìm vị trắ của phần tử có giá trị bằng giá trị X cho trước. Chúng ta sẽ xét hai thuật giải tìm kiếm ựó là tìm kiếm tuần tự (sequential searching) và tìm kiếm nhị phân (binary searching).
6.1 Tìm kiếm tuần tự (sequential searching)
đây là thuật giải tìm kiếm ựơn giản nhất, ta sẽ duyệt tuần tự dãy số, thuật giải sẽ kết thúc khi tìm thấy phần tử bằng giá trị X hoặc khi duyệt hết dãy số nhưng không có phần tử
nào có giá trị là X
procedure Sequential_Searching
begin
i:=1
while ai≠ X do i:=i+1
if i=n+1 then không có phần tử cần tìm else vị trắ phần tử cần tìm là i
end
6.2 Tìm kiếm nhị phân (binary searching)
Giả sử dãy số ựã ựược sắp xếp tăng dần a1≤a2≤...≤an (trường hợp sắp xếp giảm dần thì tương tự), thuật giải nhị phân gần giống như khi ta tìm một từ trong từ ựiển. để tìm phần tử
bằng X trước tiên ta so sánh nó với phần tửở vị trắ giữa của dãy số nếu X nhỏ hơn thì X chỉ
có thểở trong một nửa trước của dãy nếu ngược lại thì X chỉ có thểở trong nủa sau của dãy. Lặp lại quá trình tìm kiếm ựó ựến khi tìm thấy hoặc dãy số trở nên rỗng (không tìm thấy).
procedure Binary_Searching
begin
left:=1 right:=n
repeat
mid:=[(left+right)/2] (*Kắ hiệu [a] nghĩa là lấy phần nguyên của số thực a*) if X<amid then right:=mid-1
else left:=mid+1
until (X=amid) or(left>right)
if X=amidthen vị trắ cần tìm là mid else không có phần tử cần tìm
end
Vắ dụ: Tìm phần tử 28 trong dãy số sau
[4 15 28 33 67 99 103]
Lặp lần 1 [4 15 28]
Lặp lần 2 [28]
7. Giải thuật ựệ quy 7.1. Khái niệm ựệ qui
Một ựối tượng ựược gọi là ựệ qui nếu nó bao gồm một phần của chắnh nó hay ựược ựịnh nghĩa bởi chắnh nó.
Trong khi thiết kế giải thuật ta thường thiết kế dưới dạng các mô- ựun. Khi giải thuật
ựược cài ựặt thành chương trình thắ các mô- ựun sẽ tương ứng với các chương trình con (hàm- function và thủ tục- procedure),
Chương trình con ựược gọi là ựệ qui nếu trong thân của nó có lời gọi trực tiếp hoặc gián tiếp ựến chắnh bản thân nó.
Vắ dụ 1: định nghĩa số tự nhiên + 0 là số tự nhiên
+ Số tiếp theo của một số tự nhiên là một số tự nhiên
Vắ dụ 2: định nghĩa n! + 0!=1
+ n!=n*(n-1)! nếu n>0
- định nghĩa một phép ựệ qui gồm có 2 phần
+ Trường hợp suy biến: Giúp cho quá trình ựệ qui kết thúc
+ Phần ựệ qui (hay phần qui nạp): Trong ựó tác ựộng cần ựược thực hiện cho giá trị hiện thời của các tham số ựược ựịnh nghĩa bằng các tác ựộng hay giá trị ựược ựịnh nghĩa trước ựây
Trong vắ dụ ựịnh nghĩa n! thì trường hợp suy biến ựịnh nghĩa 0!, phần qui nạp ựịnh nghĩa n! qua các giá trị của n và giá trị của (n-1)!
Dễ nhận xét, nếu (n-1)! ựã tắnh ựược thì n! sẽ dễ dàng tắnh ựược. Với cách suy diễn tương tự, (n-1)! sẽ tắnh ựược nếu như (n-2)! ựã tắnh ựược... cuối cùng 1! sẽ tắnh ựược nếu 0!
ựã tắnh ựược. Ta thấy rằng 0! ựã cho trong ựịnh nghĩa. Do vậy ựi ngược từ cuối, vì 0! ựã tắnh
ựược nên 1! cũng tắnh ựược,....,sau khi (n-1)! ựã có ta sẽ nhận ựược n! Minh hoạ: Tắnh 3!
3!=3*2!=3*2=6
2!=2*1!=2*1=2 1!=1*0!=1*1=1
Giải thuật ựược viết dưới dạng thủ tục hàm (tựa Pascal) như sau:
function giaithua(n)
begin
if n=0 then giaithua:=1 (* trường hợp suy biến*)
else giaithua:=n*giaithua(n-1) (* phần ựệ qui*)
end
Chú ý: Không phải lúc nào tắnh ựệ qui trong cách giải bài toán cũng thể hiện rõ nét và dễ phát hiện như vắ dụ trên. Do ựó muốn biết giải thuật của một bài toán có thể thiết kế dưới dạng giải thuật ựệ qui ựược hay không? Có thể thấy câu trả lời qua việc trả lời các câu hỏi sau : + Có thểựịnh nghĩa ựược bài toán dưới dạng một bài toán cùng loại nhưng ỘnhỏỢ hơn