24/05/2018, 17:14

Mở đầu về thiết kế, đánh giá thuật toán và kiến thức bổ trợ

Khái niệm về thuật toán Thuật toán (algorithm) là một trong những khái niệm quan trọng trong lĩnh vực tin học. Thuật ngữ thuật toán được xuất phát từ nhà toán học Arập Abu Ja’far Mohammedibn Musa al ...

Khái niệm về thuật toán

Thuật toán (algorithm) là một trong những khái niệm quan trọng trong lĩnh vực tin học. Thuật ngữ thuật toán được xuất phát từ nhà toán học Arập Abu Ja’far Mohammedibn Musa al Khowarizmi (khoảng năm 825).

Tuy nhiên lúc bấy giờ và trong nhiều thế kỷ sau, nó không mang nội dung như ngày nay chúng ta quan niệm. Thuật toán nổi tiếng nhất có từ thời cổ Hy lạp là thuật toán Euclid, thuật toán tìm ước chung lớn nhất của hai số nguyên. Có thể mô tả thuật toán đó như sau:

ThuậttoánEuclid.

Input: m, n nguyên dương

Output: g (ước chung lớn nhất của m và n)

Phương pháp:

Bước 1: Tìm r, phần dư của m cho n

Bước 2: Nếu r = 0, thì g:=n (gán giá trị của n cho g),và dừng lại. Trong trường

hợp ngược lại (r≠0), thì m:=n; n:=r và quay lại bước 1.

Chúng ta có thể quan niệm các bước cần thực hiện để làm một món ăn, được mô tả trong các sách dạy chế biến món ăn, là một thuật toán. Cũng có thể xem các bước cần tiến hành để gấp đồ chơi bằng giấy ,được trình bày trong sách dạy gấp đồ chơi bằng giấy là một thuật toán. Phương pháp cộng nhân các số nguuyên, chúng ta đã được học

ở cấp I cũng là các thuật toán.

Vì vậy ta có định nghĩa không hình thức về thuật toán như sau:

Thuật toán là một dãy hữu hạn các bước, mỗi bước mô tả chính xác các phép toán,

hoặc hành động cần thực hiện ... để cho ta lời giải của bài toán.

Các yêu cầu về thuật toán

Định nghĩa trên về thuật toán tất nhiên còn chứa nhiều điều chưa rõ ràng. Để hiểu đầy đủ ý nghĩa của khái niệm thuật toán, chúng ta đưa ra 5 đặc trưng sau đây của thuật toán.

Input

Mỗi thuật toán đều có một số (có thể bằng không) các dữ liệu vào (input). Đó là các giá trị cần đưa vào khi thuật toán bắt đầu làm việc. Các dữ liệu này cần được lấy từ các tập hợp giá trị cụ thể nào đó. Chẳng hạn, trong thuật toán Euclid ở trên, các số m và n là các dữ liệu lấy từ tập các số nguyên dương.

Output

Mỗi thuật toán cần có một hoặc nhiều dữ liệu ra (output). Đó là các giá trị có quan hệ hoàn toàn xác định với các dữ liệu vào, và là kết quả của sự thực hiện thuật toán. Trong thuật toán Euclid, có một dữ liệu ra đó là ƯSCLN g, khi thuật toán dừng lại (trường hợp r=0) thì giá trị của g là ước chung lớn nhất của m và n.

Tính xác định

Ở mỗi bước, các bước thao tác phải hết sức rõ ràng, không gây nên sự nhập nhằng. Nói rõ hơn là trong cùng một điều kiện hai bộ xử lý cùng thực hiện một thuật toán phải cho cùng một kết quả như nhau. Nếu biểu diễn thuật toán bằng phương pháp thông thường không có gì đảm bảo được người đọc hiểu đúng ý của người viết thuật toán. Để đảm bảo đòi hỏi này, thuật toán cần được mô tả trong các ngôn ngữ lập trình (ngôn ngữ máy, hợp ngữ hoặc ngôn ngữ bậc cao như Pascal...). Trong các ngôn ngữ này các mệnh đề được tạo theo các qui tắc cú pháp nghiêm ngặt và chỉ có một nghĩa duy nhất.

Tính khả thi/đa năng

Tất cả các phép toán có mặt trong thuật toán phải đủ đơn giản . Điều đó có nghĩa là, các phép toán phải sao cho, ít nhất về nguyên tắc có thể thực hiện bởi con người chỉ bằng giấy trắng và bút chì trong một khoảng thời gian hữu hạn. Chẳng hạn, trong thuật toán Euclid ta chỉ cần thực hiện các phép chia các số nguyên, các phép gán và các phép so sánh r=0 hay r ≠ 0. Điều quan trọng nữa là thuật toán phải có tính đa năng làm việc được với tất cả các tập hợp dữ liệu có thể của đầu vào.

Tính dừng

Với mọi bộ dữ liệu vào thoả mãn các điều kiện của dữ liệu vào (tức là được lấy ra từ các tập của dữ liệu vào), thuật toán phải dừng lại sau một số hữu hạn bước thực hiện.

Thuật toán Euclid thoả mãn điều kiện này. Bởi vì giá trị của r luôn nhỏ hơn n (khi thực hiện bước 1), nếu r <>0 thì giá trị của n ở bước i (ký hiệu là ni) sẽ là giá trị của ri-1 ở bước i-1, ta phải có bất đẳng thức n>r =n1>r1 = n2 > r2. Dãy số nguyên dương này giảm dần và cần phải kết thúc ở 0, do đó sau một số hữu hạn bước nào đó giá trị của r phải = 0 và thuật toán phải dừng lại.

Với một vấn đề đặt ra, có thể có một hoặc nhiều thuật toán giải. Một vấn đề có thuật toán giải gọi là vấn đề giải được (bằng thuật toán). Chẳng hạn, tìm nghiệm của hệ phương trình tuyến tính là vấn đề giải được. Một vấn đề không tồn tại thuật toán gọi là vấn đề không giải được (bằng thuật toán). Một trong những thành tựu suất xắc nhất của toán học thế kỷ 20 là đã tìm ra những vấn đề không giải được bằng thuật toán. Chẳng hạn thuật toán chắc thắng cho người thứ hai của cờ ca rô hoặc thuật toán xác định xem một máy Turing có dừng lại sau n bước không, đềulà những vấn đề không tồn tại thuật toán giải được.

Để giải một bài toán trên máy tính điện tử (MTĐT), điều trước tiên là chúng ta phải có thuật toán. Một câu hỏi đặt ra là làm thế nào để tìm ra được thuật toán cho một bài toán đã đặt ra- Lớp các bài toán được đặt ra từ các ngành khoa học kỹ thuật, từ các lĩnh vực hoạt động của con người là hết sức phong phú và đa dạng. Các thuật toán giải các lớp bài toán khác nhau cũng rất khác nhau. Tuy nhiên, có một số kỹ thuật thiết kế thuật toán chung như: Chia để trị (divide-and-conque), phương pháp tham ăn (greedy method), qui hoạch động (dynamic programming)... Việc nắm được các chiến lược thiết kế thuật toán này là hết sức quan trọng và cần thiết vì nó giúp cho ta dễ tìm ra các thuật toán mới cho các bài toán mới được đưa ra.

Khi một thuật toán được làm ra, ta cần phải chứng minh rằng, thuật toán khi được thực hiện sẽ cho ta kết quả đúng với mọi dữ liệu vào hợp lệ. Điều này gọi là chứng minh tính đúng đắn của thuật toán. Việc chứng minh tính đúng đắn của thuật toán là một công việc không dễ dàng. Trong nhiều trường hợp, nó đòi hỏi ta phải có trình độ và khả năng tư duy toán học tốt.

Sau đây ta sẽ chỉ ra rằng, khi thực hiện thuật toán Euclid, g sẽ là ước chung lớn nhất của hai số nguyên dương bất kỳ m, n. Thật vậy, khi thực hiện bước 1, ta có m = qn + r, trong đó q là số nguyên nào đó. Nếu r = 0 thì n là ước của m và hiển nhiên n (do đó g) là ước chung lớn nhất của m và n. Nếu r 0 thì một ước chung bất kỳ của m và n cũng là ước chung của n và r (vì r=m-qn). Ngược lại một ước chung bất kỳ của n và r cũng là ước chung của m và n (vì m = qn + r). Do đó ước chung lớn nhất của n và r cũng là ước chung lớn nhất của ma và n. Vì vậy, khi thực hiện lặp lại bước 1, với sự thay đổi giá trị của m bởi n, và sự thay đổi giá trị của n bởi r, cho tới khi r=0 ta nhận được giá trị của g là ước chung lớn nhất của các giá trị m và n ban đầu.

Giả sử, với một số bài toán nào đó chúng ta có một số thuật toán giải. Một câu hỏi mới xuất hiện là, chúng ta cần chọn thuật toán nào trong số các thuật toán đó để áp dụng. Việc phân tích thuật toán, đánh giá độ phức tạp của thuật toán là nội dung của phần dưới đây sẽ giải quyết vấn đề này.

Khi giải một vấn đề, chúng ta cần chọn trong số các thuật toán, một thuật toán mà chúng ta cho là “tốt” nhất .Vậy ta cần lựa chọn thuật toán dựa trên cơ sở nào- Thông thường ta dựa trên hai tiêu chuẩn sau đây:

  • Thuật toán đơn giản, dễ hiểu, dễ cài đặt (dễ viết chương trình)
  • Thuật toán sử dụng tiết kiệm nhất các nguồn tài nguyên của máy tính, và đặc biệt chạy nhanh nhất có thể được.

Khi ta viết một chương trình chỉ để sử dụng một số ít lần, và cái giá của thời gian viết chương trình vượt xa cái giá của chạy chương trình thì tiêu chuẩn (1) là quan trọng nhất. Nhưng có trường hợp ta cần viết các chương trình (hoặc thủ tục, hàm) để sử dụng nhiều lần, cho nhiều người sử dụng, khi đó giá của thời gian chạy chương trình sẽ vượt xa giá viết nó. Chẳng hạn, các thủ tục sắp xếp, tìm kiếm được sử dụng rất nhiều lần, bởi rất nhiều người trong các bài toán khác nhau. Trong trường hợp này ta cần dựa trên tiêu chuẩn 2. Ta sẽ cài đặt thuật táon có thể sẽ rất phức tạp, miễn là chương trình nhận được chạy nhanh hơn so với các chương trình khác.

Tiêu chuẩn 2 được xem là tínhhiệuquảcủa thuật toán. Tính hiệu quả của thuật toán bao gồm hai nhân tố cơ bản:

Dung lượng không gian nhớ cần thiết để lưu giữ các giữ liệu vào, các kết quả tính toán trung gian và các kết quả của thuật toán.

Thời gian cần thiết để thực hiện thuật toán (ta gọi là thời gian chạy). Chúng ta chỉ quan tâm đến thời gian thực hiện thuậ toán, có nghĩa là ta nói đến đánh giá thời gian thực hiện. Một thuật toán có hiệu quả được xem là thuật toán có thời gian chạy ít hơn so với các thuật toán khác.

Có nhiều phương pháp biểu diễn thuật toán .Có thể biểu diễn thuật toán bằng danh sách các bước, các bước được diễn đạt bằng ngôn ngữ thông thường và các ký hiệu toán học. Có thể biểu diễn thuật toán bằng sơ đồ khối. Tuy nhiên, để đảm bảo tính xác định của thuật toán như đã trình bày trên, thuật toán cần được viết trên các ngôn ngữ lập trình. Một chương trình là sự biểu diễn của một thuật toán trong ngôn ngữ lập trình đã chọn. Thông thường ta dùng ngôn ngữ lập trình Pascal, một ngôn ngữ thường được chọn để trình bày các thuật toán trong sách báo.

Ngôn ngữ thuật toán là ngôn ngữ dùng để miêu tả thuật toán .Thông thường ngôn ngữ thuật toán bao gồm ba loại:

+ Ngôn ngữ liệt kê từng bước;

+ Sơ đồ khối;

+ Ngôn ngữ lập trình;

Phương pháp liệt kê từng bước

Ngôn ngữ liệt kê từng bước nội dung như sau:

Thuật toán: Tên thuật toán và chức năng.

Vào: Dữ liệu vào với tên kiểu.

Ra: Các dữ liệu ra với tên kiểu.

Biến phụ (nếu có) gồm tên kiểu.

Hành động là các thao tác với các lệnh có nhãn là các số tự nhiên.

Để giải phương trình bậc hai ax2 + bx +c = 0, ta có thể mô tả thuật toán bằng

ngôn ngữ liệt kê như sau:

Bước 1: Xác định các hệ số a, b, c.

Bước 2: Kiểm tra xem các hệ số a, b, c có khác 0 hay không- Nếu a=0 quay lại thực hiện bước 1.

Bước 3: Tính biểu thức Δ= b2 – 4*a*c.

Bước 4: Nếu Δ <0 thông báo phương trình vô nghiệm và chuyển sang bước 8. Bước 5: Nếu Δ=0, tính x1=x2= −b2∗a size 12{ { { - b} over {2*a} } } {} và chuyển sang bước 7.

Bước 6: Tính x1= −b+Δ2a size 12{ { { - b+ sqrt {Δ} } over {2a} } } {} , x2= −b−Δ2a size 12{ { { - b - sqrt {Δ} } over {2a} } } {} và chuyển sang bước 7. Bước 7: Thông báo các nghiệm x1, x2.

Bước 8: Kết thúc thuật toán.

Phương pháp sơ đồ

Phương pháp dùng sơ đồ khối mô tả thuật toán là dùng mô tả theo sơ đồ trên mặt

phẳng các bước của thuật toán. Sơ đồ khối có ưu điểm là rất trực giác dễ bao quát.

Để mô tả thuật toán bằng sơ đồ khối ta cần dựa vào các nút sau đây:

Nútthaotác:Biểu diễn bằng hình chữ nhật,

Nútđiềukhiển:Được biểu diễn bằng hình thoi, trong đó ghi điều kiện cần kiểm tra trong quá trình tính toán.

Nútkhởiđầu,kếtthúc:Thường được biểu diễn bằng hình tròn thể hiện sự bắt đầu hay kết thúc quá trình.

Cung:Đoạnnối từ nút này đến nút khác và có mũi tên chỉ hướng.

Danh sách

Danh sách là một tập sắp thứ tự các phần tử cùng một kiểu. Đối với danh sách, người ta có một số thao tác: Tìm một phần tử trong danh sách, chèn một phần tử vào danh sách, xoá một phần tử khỏi danh sách, sắp xếp lại các phần tử trong danh sách theo một trật tự nào đó v.v...

Các phương pháp biểu diễn danh sách trong máy tính:

- Mảng một chiều

- Danh sách nối đơn

- Danh sách nối kép

- Danh sách nối vòng một hướng

- Danh sách nối vòng hai hướng

Các phép toán cơ bản trên danh sách

Để thiết lập kiểu dữ liệu trừu tượng danh sách (hay ngắn gọn là danh sách) ta phải định nghĩa các phép toán trên danh sách. Và như chúng ta sẽ thấy trong toàn bộ giáo trình, khôngc ó một tập hợp các phép toán nào thích hợp cho mọi ứng dụng (application). Vì vậy ở đây ta sẽ định nghĩa một số phép toán cơ bản nhất trên danh sách. Để thuận tiện cho việc định nghĩa ta giả sử rằng danh sách gồm các phần tử có kiểu là kiểu phần tử (elementType); vị trí của các phần tử trong danh sách có kiểu là kiểu vị trí và vị trí sau phần tử cuối cùng trong danh sách L là ENDLIST(L). Cần nhấn mạnh rằng khái niệm vị trí (position) là do ta định nghĩa, nó không phải là giá trị của các phần tử trong danh sách. Vị trí có thể là đồng nhất với vị trí lưu trữ phần tử hoặc không.

Các phép toán được định nghĩa trên danh sách là:

INSERT_LIST(x,p,L):xen phần tử x ( kiểu ElementType ) tại vị trí p (kiểu

position) trong danh sách L. Tức là nếu danh sách là a1, a2, . , ap-1, ap,.. , an thì sau khi xen ta có kết quả a1, a2. . . ap-1, x, ap, . . . , an. Nếu vị trí p không tồn tại trong danh sách thì phép toán không được xác định.

LOCATE(x,L):thực hiện việc định vị phần tử có nội dung x đầu tiên trong danh sách

L. Locate trả kết quả là vị trí (kiểu position) của phần tử x trong danh sách. Nếu x không có trong danh sách thì vị trí sau phần tử cuối cùng của danh sách được trả về, tức là ENDLIST(L).

- RETRIEVE(p,L):lấy giá trị của phần tử ở vị trí p (kiểu position) của danh sách L; nếu vị trí p không có trong danh sách thì kết quả không xác định (có thể thông báo lỗi).

- DELETE_LIST(p,L):chương trình con thực hiện việc xoá phần tử ở vị trí p (kiểu position) của danh sách. Nếu vị trí p không có trong danh sách thì phép toán không được định nghĩa và danh sách L sẽ không thay đổi

- NEXT(p,L):cho kết quả là vị trí của phần tử (kiểu position) đi sau phần tử p; nếu p là phần tử cuối cùng trong danh sách L thì NEXT(p,L) cho kết quả là

ENDLIST(L):Next không xác định nếu p không phải là vị trí của một phần tử trong danh sách.

- PREVIOUS(p,L):cho kết quả là vị trí của phần tử đứng trước phần tử p trong danh sách. Nếu p là phần tử đầu tiên trong danh sách thì Previous(p,L) không xác định. Previous cũng không xác định trong trường hợp p không phải là vị trí của phần tử nào trong danh sách.

- FIRST(L):cho kết quả là vị trí của phần tử đầu tiên trong danh sách. Nếu danh sách rỗng thì ENDLIST(L) được trả về.

- EMPTY_LIST(L):cho kết quả TRUE nếu danh sách có rỗng, ngược lại nó cho giá trị FALSE.

- MAKENULL_LIST(L):khởi tạo một danh sách L rỗng.

- Trong thiết kế các giải thuật sau này chúng ta dùng các phép toán trừu tượng đã được định nghĩa ở đây như là các phép toán nguyên thủy.

Đồ thị

Các định nghĩa

Một đồ thị G bao gồm một tập hợp V các đỉnh và một tập hợp E các cung, ký hiệu G=(V,E). Các đỉnh còn được gọi là nút (node) hay điểm (point). Các cung nối giữa hai đỉnh, hai đỉnh này có thể trùng nhau. Hai đỉnh có cung nối nhau gọi là hai đỉnh kề (adjacency). Một cung nối giữa hai đỉnh v, w có thể coi như là một cặp điểm (v,w). Nếu cặp này có thứ tự thì ta có cung có thứ tự, ngược lại thì cung không có thứ tự. Nếu các cung trong đồ thị G có thứ tự thì G gọi là đồ thị có hướng (directed graph). Nếu các cung trong đồ thị G không có thứ tự thì đồ thị G là đồ thị vô hướng (undirected graph).

Biểu diễn đồ thị

- Biểu diễn đồ thị bằng ma trận kề

- Biểu diễn đồ thị bằng danh sách các đỉnh kề:

Các phép duyệt đồ thị

- Duyệt theo chiều sâu (depth-first search)

- Duyệt theo chiều rộng (breadth-first search)

Cây

Các thuật ngữ cơ bản trên cây

Cây là một tập hợp các phần tử gọi là nút (nodes) trong đó có một nút được phân biệt gọi là nút gốc (root). Trên tập hợp các nút này có một quan hệ, gọi là mối quan hệ

cha-con(parenthood), để xác định hệ thống cấu trúc trên các nút. Mỗi nút, trừ nút gốc, có duy nhất một nút cha. Một nút có thể có nhiều nút con hoặc không có nút con nào. Mỗi nút biểu diễn một phần tử trong tập hợp đang xét và nó có thể có một kiểu nào đó bất kỳ, thường ta biểu diễn nút bằng một kí tự, một chuỗi hoặc một số ghi trong vòng tròn. Mối quanhệchaconđược biểu diễn theo qui ước nútchadòng trênnútcondòngdưivàđưcnốibimtđoạnthẳng. Một cách hình thức ta có thể định nghĩa cây một cách đệ qui như sau:

Định nghĩa

Một nút đơn độc là một cây. Nút này cũng chính là nút gốc của cây.

Giả sử ta có n là một nút đơn độc và k cây T1,.., Tk với các nút gốc tương ứng là n1,.., nk thì có thể xây dựng một cây mới bằng cách cho nút n là cha của các nút n1,.., nk. Cây mới này có nút gốc là nút n và các cây T1,.., Tk được gọi là các cây con. Tập rỗng cũng được coi là một cây và gọi là cây rỗng kí hiệu.

Xét mục lục của một quyển sách.

0