1. Trang chủ
  2. » Giáo Dục - Đào Tạo

Cấu trúc dữ liệu và giải thuật (luận văn thạc sỹ luật)

892 12 0

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

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Cấu Trúc Dữ Liệu Và Giải Thuật
Trường học Đại Học Quốc Gia Tp Hồ Chí Minh
Chuyên ngành Cấu Trúc Dữ Liệu Và Giải Thuật
Thể loại Luận Văn Thạc Sỹ
Năm xuất bản 2019
Thành phố Tp Hồ Chí Minh
Định dạng
Số trang 892
Dung lượng 9,16 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Giới thiệu Chương này trình bày phương pháp luận lập trình bao gồm các giai đoạn: đặc tả và phân tích vấn đề, thiết kế giải thuật, xây dựng chiến lược kiểm thử, viết chương trình, thực h

Trang 1

TRƯỜNG ĐẠI HỌC BÁCH KHOA

Trang 2

LỜI NÓI ĐẦU 7

Trang 3

4.4 Danh sách liên kết đơn Header 122 4.5 Danh sách liên kết đơn vòng Header 143

4.7 Danh sách liên kết kép vòng Header 187

Trang 6

Quyển sách Cấu trúc dữ liệu và Giải thuật trình bày cấu trúc dữ liệu

tuyến tính (mảng, danh sách liên kết) và cấu trúc dữ liệu phi tuyến (cây, đồ thị) và các giải thuật của các cấu trúc dữ liệu này

Các cấu trúc dữ liệu và giải thuật được trình bày theo kiểu lập trình có

cấu trúc (structured programming) (minh họa bằng ngôn ngữ lập trình C++)

và theo kiểu lập trình hướng đối tượng (object-oriented programming)

(minh họa bằng ngôn ngữ lập trình C#) Các phần phụ lục là các chương

trình được viết theo kiểu lập trình tổng quát (generic programming)

Quyển sách này bao gồm 12 chương và 9 phụ lục

Chương 1 Phương pháp luận lập trình Giới thiệu phương pháp luận lập

trình, lập trình có cấu trúc, giải thuật và độ phức tạp của giải thuật

Chương 2 Đệ quy Trình bày định nghĩa đệ quy, các loại đệ quy, đệ quy và

vòng lặp

Chương 3 Các cấu trúc dữ liệu cơ bản Trình bày các kiểu dữ liệu cơ bản,

kiểu cấu trúc, kiểu lớp và kiểu con trỏ

Chương 4 Danh sách Trình bày mảng (array), danh sách liên kết đơn

(linked list), danh sách liên kết kép và danh sách liên kết vòng

Chương 5 Chồng và hàng đợi Trình bày chồng (stack) và hàng đợi

(queue) được cài đặt bằng mảng và danh sách liên kết, ứng dụng của chồng

và hàng đợi

Chương 6 Cây Trình bày định nghĩa cây (tree) và các tác vụ của cây, đống

(heap) và các tác vụ của đống

Chương 7 Cây nhị phân tìm kiếm Trình bày cây nhị phân tìm kiếm (BST -

Binary Search Tree) và các tác vụ

Chương 8 Cây cân bằng Trình bày cây cân bằng (AVL) và các tác vụ Chương 9 Cây B Trình bày cây B (B-tree) và các tác vụ

Chương 10 Băm Trình bày định nghĩa băm (hasing), hàm băm, giải quyết

đụng độ bằng phương pháp kết nối và phương pháp địa chỉ mở

Trang 7

Chương 11 Sắp thứ tự Trình bày các giải thuật sắp thứ tự Insertion Sort,

Shell Sort, Selection Sort, Heap Sort, Bubble Sort, Shaker Sort, Quick Sort, Merge Sort, Radix Sort

Chương 12 Đồ thị Trình bày định nghĩa đồ thị (graph), cách biểu diễn đồ

thị, các tác vụ của đồ thị, duyệt đồ thị theo thứ tự DFS và BFS, sắp thứ tự topo,

tìm đường đi ngắn nhất và tìm cây phủ tối thiểu

Phụ lục A đến Phụ lục I là các chương trình của các chương 4 đến

chương 12 được viết theo kiểu lập trình hướng đối tượng tổng quát dùng khuôn

mẫu (template) (minh họa bằng ngôn ngữ lập trình C++)

Quyển sách Cấu trúc dữ liệu và Giải thuật là tài liệu học tập cho sinh

viên chuyên ngành Công nghệ Thông tin, Khoa học Máy tính, Kỹ thuật Máy tính, Công nghệ Phần mềm của hệ đào tạo chính quy bậc đại học và bậc cao

đẳng và lập trình viên

Tác giả trân trọng cảm ơn Ban chủ nhiệm và các giảng viên của Khoa Khoa học và Kỹ thuật Máy tính, Ban Giáo trình của Trường Đại học Bách Khoa – Đại học Quốc gia TP HCM đã tạo điều kiện và giúp đỡ để quyển

sách này được xuất bản

Mặc dù tác giả đã nỗ lực biên soạn, tuy nhiên, quyển sách này không thể tránh khỏi những thiếu sót Tác giả rất mong nhận được những ý kiến đóng góp quý báu của độc giả và xin gửi về:

Khoa Khoa học và Kỹ thuật Máy tính, Trường Đại học Bách Khoa – Đại học Quốc gia TP Hồ Chí Minh, 268 Lý Thường Kiệt, Phường 14, Quận

10, TP Hồ Chí Minh

Email: nttruc@hcmut.edu.vn, truc@cse.hcmut.edu.vn

Tác giả NGUYỄN TRUNG TRỰC

Trang 9

Giới thiệu

Chương này trình bày phương pháp luận lập trình bao gồm các giai đoạn: đặc tả và phân tích vấn đề, thiết kế giải thuật, xây dựng chiến lược kiểm thử, viết chương trình, thực hiện và kiểm tra chương trình, bảo trì chương trình

Chương này cũng giới thiệu lập trình có cấu trúc, là kiểu lập trình nhằm nâng cao tính trong sáng (dễ đọc, dễ hiểu), nâng cao chất lượng và giảm thời gian phát triển chương trình (dễ chẩn đoán lỗi, dễ sửa đổi chương trình)

1.1 Phương pháp luận lập trình

Phương pháp luận lập trình (programming methodology) giải quyết

các vấn đề về phân tích (analysis), thiết kế (design) và thực hiện (implementation) các chương trình

Phương pháp luận lập trình còn được gọi là phát triển chương trình

(program development)

Một phương pháp luận quan trọng là cách tiếp cận từ trên xuống

(top-down approach) để phân tích, thiết kế và thực hiện

Quy trình giải quyết vấn đề (problem-solving process) bằng máy tính

gồm có các giai đoạn (phase) quan trọng sau đây:

1 Đặc tả và phân tích vấn đề (problem analysis and specification)

2 Thiết kế giải thuật (algorithm design)

3 Xây dựng chiến lược kiểm thử (testing strategy development)

4 Viết chương trình (program coding)

5 Thực hiện và kiểm tra chương trình (program execution and testing)

6 Bảo trì chương trình (program maintenance)

Trong thực tế, các giai đoạn này có thể được thực hiện một cách tuần

tự theo mô hình thác nước (waterfall model) hoặc chúng được thực hiện lặp vòng nhiều lần theo mô hình xoắn ốc (spiral model)

Trang 10

1.1.1 Đặc tả và phân tích vấn đề

Đặc tả ban đầu của vấn đề có thể hơi mơ hồ và không chính xác

Do đó, giai đoạn đầu tiên của quy trình giải quyết vấn đề là xem xét lại

(review) vấn đề một cách cẩn thận để xác định phần nhập và phần xuất của vấn

đề Giai đoạn này còn được gọi là hiểu vấn đề (problem understanding) Giai đoạn này có thể là rất khó nhưng nó cũng là giai đoạn hoàn toàn quyết định

Đặc tả vấn đề (problem specification), còn được gọi là định nghĩa vấn đề (problem definition), bao gồm các định nghĩa chính xác của hai

thành phần chính là phần nhập và phần xuất và sự liên hệ giữa chúng

- Phần nhập (input) là thông tin gì được cho và những điều nào là

quan trọng để giải quyết vấn đề Phần nhập được gọi là giả thiết (supposition) của bài toán Phần nhập phải được xác định đầy đủ để

giải quyết vấn đề

- Phần xuất (output) là thông tin gì phải đạt được sau khi giải quyết

vấn đề Phần xuất được gọi là kết luận (conclusion) của bài toán

Việc xác định phần nhập và phần xuất của vấn đề giúp cho ta hiểu

vấn đề một cách rõ ràng và chính xác

Phân tích vấn đề (problem analysis) là một quá trình hiểu vấn đề, các

yêu cầu của người sử dụng và đề xuất các giải pháp (solution) để giải quyết

các yêu cầu này Mục tiêu của phân tích vấn đề là hiểu rõ vấn đề trước khi giải quyết nó

Người sử dụng nêu ra vấn đề và người giải quyết vấn đề (problem solver) phải cùng làm việc chung với nhau để bảo đảm cả hai cùng hiểu rõ

vấn đề

Các bước của phân tích vấn đề gồm có:

1 Đạt được sự chấp thuận (agreement) về định nghĩa vấn đề: Một cách

đơn giản nhất để có được sự chấp thuận là người giải quyết vấn đề phải ghi chép vấn đề rõ ràng và bàn luận với người sử dụng Các nguyên tắc

ghi chép là KISS (Keep It Simple Stupid) và SVDPI

(Subject-Verb-Direct-Preposition-Indirect)

2 Hiểu được các nguyên nhân cơ bản (root cause): Vấn đề ở đằng sau

vấn đề Phân tích các nguyên nhân cơ bản là một cách làm có hệ thống để làm rõ các nguyên nhân và những tiềm ẩn của vấn đề

3 Xác định những người sử dụng (user) và những người có liên quan (stakeholder) Hiểu được các yêu cầu của người sử dụng và người có

Trang 11

liên quan là yếu tố chính để xây dựng giải pháp hiệu quả Người có liên quan là người chịu ảnh hưởng chủ yếu do việc thực hiện giải

quyết vấn đề bằng hệ thống (system) hoặc ứng dụng (application)

4 Xác định phạm vi của hệ thống (system boundary) Phạm vi của hệ thống là sự giao tiếp (interface) với môi trường ngoài hoặc với các hệ

thống khác

5 Xác định các ràng buộc (constraint) áp đặt cho giải pháp Ràng buộc là

sự hạn chế hoặc giới hạn cần phải tuân thủ khi đề xuất giải pháp

Ví dụ: Vấn đề ban đầu là giải phương trình bậc hai ax2 + bx + c = 0 trong trường số thực

Phần nhập: cho ba hệ số a , b , c là các số thực

Phần xuất: tìm nghiệm x là số thực

1.1.2 Thiết kế giải thuật

Giai đoạn thiết kế giải thuật (algorithm design) gồm có hai bước:

- Bước 1: Thiết kế giải pháp

- Bước 2: Tinh chế giải pháp

Trong bước thiết kế giải pháp (solution design), ta xác định giải pháp

sơ bộ Ta chia vấn đề ban đầu thành nhiều vấn đề con nhỏ hơn, dễ giải quyết hơn và cứ như thế cho đến khi các vấn đề con cuối cùng được giải quyết Khi đó, ta đã tìm ra được giải pháp cuối cùng

Các giải pháp từng phần để giải quyết các vấn đề con và giải pháp

cuối cùng để giải quyết vấn đề ban đầu đều được trình bày ở dạng ý tưởng (idea), chưa đề cập đến các yếu tố kỹ thuật cụ thể

Cách tiếp cận này là cách tiếp cận từ trên xuống (top-down approach) với chiến lược chia để trị (devide and conquer)

Các giải pháp cần phải có tính tổng quát Tuy nhiên, ta cũng cần phải lưu ý các khía cạnh riêng biệt (particular aspect) của vấn đề để tìm ra các

giải pháp thích hợp cho chúng

Ví dụ: Vấn đề ban đầu là giải phương trình bậc hai ax2 + bx + c = 0 trong trường số thực Vấn đề này được chia thành hai vấn đề con nhỏ hơn như sau:

Trang 12

- Vấn đề ban đầu: Nếu a = 0 thì giải phương trình bậc nhất bx + c = 0 , ngược lại thì giải phương trình bậc hai đủ ax2+ bx + c = 0

- Vấn đề con 1: Giải phương trình bậc nhất bx + c = 0 , khi a = 0 Giải pháp từng phần là tìm nghiệm x (bậc nhất), nếu có

- Vấn đề con 2: Giải phương trình bậc hai đủ ax2+ bx + c = 0 , khi a ≠

0 Giải pháp từng phần là tìm nghiệm x (bậc hai), nếu có

Trong bước tinh chế giải pháp (solution refinement), một giải pháp

phải được mô tả chi tiết hơn bằng một chuỗi các hành động (action) cụ thể

không phân chia và được thực hiện trên dữ liệu của phần nhập để cho ra kết

quả của phần xuất và được gọi là giải thuật (algorithm)

Quá trình tinh chế (refinement process) thực hiện chi tiết hóa một

giải pháp cho đến khi đạt được mô tả giải thuật tương tự với chương trình

Quá trình này được gọi là tinh chế dần từng bước (stepwise refinement)

Chương trình thực hiện giải thuật phải được viết bằng một ngôn ngữ

mà máy tính hiểu được Ngôn ngữ này được gọi là ngôn ngữ lập trình (programming language)

Do đó, ta thường mô tả giải thuật bằng một ngôn ngữ tương tự với

ngôn ngữ lập trình, ngôn ngữ này được gọi là ngôn ngữ lập trình giả (pseudoprogramming language) và thường được gọi là mã giả (pseudocode)

Ngôn ngữ này là sự pha trộn giữa ngôn ngữ tự nhiên (natuaral language) và

ký hiệu toán học (mathematical notation) Mã giả không phải là một ngôn

ngữ lập trình được dùng cho máy tính

Không giống như ngôn ngữ lập trình mức cao (high-level programming language) như Pascal, C++, C#, Java, …, ta không có một tập các quy tắc nào

để định nghĩa một cách chính xác ngôn ngữ nào là mã giả, ngôn ngữ nào không

là mã giả Mã giả thay đổi từ nhóm lập trình này đến nhóm lập trình khác Sau đây là một số đặc điểm trong các mã giả khác nhau:

- Các ký hiệu máy tính thông thường được sử dụng cho các phép

toán số học (arithmetic operation): + là phép cộng (addition),

phép trừ (subtraction), * là phép nhân (multiplication) và / là phép

chia (division)

- Các tên ký hiệu (symbolic name) hoặc danh hiệu (identifier) được

dùng để biểu diễn các đại lượng được xử lý trong giải thuật

Trang 13

- Các ghi chú (comment) được dùng để giải thích cho các bước, các

thành phần của giải thuật Các ghi chú này thường được ghi trong cặp

(* *) , cặp /* */ hoặc //

- Một số từ của ngôn ngữ tự nhiên được sử dụng làm từ khóa (keyword):

read , enter , input dùng để nhập dữ liệu, write , output ,

display , print dùng để xuất dữ liệu, compute dùng để tính toán

- Thụt đầu dòng (indentation) được dùng để trình bày các cấu trúc điều

khiển cho dễ đọc

Mô tả giải thuật bằng mã giả phải rõ ràng và cho thấy luận lý của

chương trình (program) thực hiện giải thuật này

Giải thuật có cấu trúc (structured algorithm) là một giải thuật được

thiết kế bằng cách sử dụng ba cấu trúc điều khiển sau đây:

- Cấu trúc tuần tự (sequence structure): chuỗi các hành động được

thực hiện một cách tuần tự, mỗi hành động được thực hiện đúng một lần

- Cấu trúc điều kiện (conditional structure): Một hoặc nhiều hành

động khác nhau được chọn và thực hiện

- Cấu trúc lặp (iterational structure): một hoặc nhiều hành động được

thực hiện lặp lại

Ba cấu trúc điều khiển này rất đơn giản Tuy nhiên, trên thực tế, ba cấu trúc điều khiển này được dùng để mô tả bất kỳ giải thuật nào

Cấu trúc của một giải thuật được trình bày bằng một hình vẽ, hình

này được gọi là sơ đồ cấu trúc (structure diagram)

Sơ đồ cấu trúc cho thấy các công việc khác nhau phải được thực hiện

và mối liên hệ giữa chúng Sơ đồ cấu trúc đặc biệt rất hữu ích để mô tả các giải thuật của các vấn đề rất phức tạp

Trang 14

Giải phương trình bậc hai đủ ax2 + bx + c = 0;

- Vấn đề con 1: Giải phương trình bậc nhất bx + c = 0 , khi a = 0 Giải pháp từng phần là tìm nghiệm x (bậc nhất), nếu có

- Vấn đề con 2: Giải phương trình bậc hai đủ ax2+ bx + c = 0 , khi a ≠

0 Giải pháp từng phần là tìm nghiệm x (bậc hai), nếu có

Trang 15

write "Nghiệm kép = ", x;

}

else

{

compute x1 = (-b – sqrt(delta)) / (2 * a);

compute x2 = (-b + sqrt(delta)) / (2 * a);

write "Nghiệm x1 = ", x1;

write "Nghiệm x2 = ", x2;

}

1.1.3 Xây dựng chiến lược kiểm thử

Giải thuật được dùng để viết chương trình cho máy tính thực hiện Ta cần phải chạy thử chương trình này với nhiều tổ hợp dữ liệu nhập khác nhau

để bảo đảm chương trình luôn luôn cho ra các kết quả đúng trong mọi trường hợp

Một tổ hợp dữ liệu nhập để chạy kiểm tra một chương trình được gọi

là trường hợp kiểm thử (test-case)

Để kiểm thử một chương trình một cách hoàn toàn kỹ lưỡng, ta phải phân loại các trường hợp kiểm thử sao cho chúng bao quát các giá trị nhập

thông thường (normal input value) và các giá trị nhập ở ngưỡng (extreme input value) để kiểm tra các giới hạn của chương trình Các tổ hợp đặc biệt

của dữ liệu nhập được dùng để kiểm tra chương trình thực hiện các khía cạnh riêng biệt của vấn đề

Lưu ý rằng đối với tất cả các trường hợp kiểm thử, các kết quả mong

đợi phải được xác định trước khi viết chương trình Việc hoàn thành các

trường hợp kiểm thử sẽ giúp cho việc kiểm tra các giải thuật và các chương trình được hiệu quả

Ví dụ: Các trường hợp kiểm thử để kiểm tra chương trình giải phương trình

bậc hai ax2+ bx + c = 0 như sau:

Bảng 1.1 Các trường hợp kiểm thử

Test-case a b c Kết quả mong đợi

TC1 0 0 0 Phương trình có vô số nghiệm

TC2 0 0 1 Phương trình bậc nhất vô nghiệm

TC3 0 1 2 Nghiệm bậc nhất x = -2

TC4 1 1 2 Phương trình bậc hai vô nghiệm

TC5 1 2 1 Nghiệm kép x = -1

TC6 1 -3 2 Hai nghiệm x1 = 1 và x2 = 2

Trang 16

1.1.4 Viết chương trình

Các giải thuật bằng mã giả không thể được thực hiện trực tiếp trên máy tính

Quá trình chuyển giải thuật bằng mã giả thành chương trình bằng ngôn

ngữ lập trình được gọi là viết chương trình hoặc lập trình (programming,

coding)

Nhiều ngôn ngữ lập trình đều có các giới hạn khi thực hiện các chương trình trên máy tính Do đó, việc lập trình sẽ gặp khó khăn khi các

giới hạn này xảy ra Ví dụ, trong toán học thì số nguyên (integer number) có

thể rất lớn, nhưng nhiều phần mềm ngôn ngữ lập trình chứa số nguyên trong

4 byte và có giá trị từ 0 đến 4.294.967.295 (số nguyên không dấu)

Ta phải chọn một phần mềm ngôn ngữ lập trình thích hợp với giải

thuật đã thiết kế để viết chương trình

Hiện tại có nhiều ngôn ngữ lập trình mức cao (high-level programming language) như Pascal, C++, C#, Java, … Mỗi ngôn ngữ lập trình đều có các

đặc điểm riêng biệt

Sau khi ta chọn ngôn ngữ lập trình, việc viết chương trình phải bảo

đảm chương trình chạy đúng (correct), dễ đọc (readable) và dễ hiểu

(understandable)

Các nguyên tắc viết chương trình là:

- Chương trình nên có cấu trúc tốt (well-structured)

- Mỗi chương trình nên được lập tài liệu (program documentation)

- Chương trình nên được định dạng (formatted)

a Chương trình có cấu trúc tốt

- Sử dụng cách tiếp cận từ trên xuống (top-down approach) khi phát

triển chương trình để giải quyết một vấn đề phức tạp Phân chia vấn

đề ban đầu thành nhiều vấn đề con ngày càng đơn giản hơn Sau đó,

ta viết các đơn thể chương trình riêng biệt để giải quyết các vấn đề

con này Cách làm này được gọi là lập trình đơn thể (modular

programming)

- Cố gắng đạt được tính đơn giản (simplicity) và tính trong sáng

(clarity) Nên tránh kiểu lập trình thông minh chỉ nhằm mục đích

chứng minh sự khéo léo của người lập trình hoặc viết đoạn mã lệnh

để chạy nhanh hơn một chút

Trang 17

b Lập tài liệu cho đơn vị chương trình

- Mỗi đơn vị chương trình (program unit) nên có tài liệu mở (opening

documentation) Nên có các ghi chú (comment) ở đầu mỗi chương trình (thủ tục, hàm) để giải thích chương trình làm gì (what), làm như thế nào (how), các giải thuật (algorithm) được sử dụng, thông tin của

người lập trình, ngày viết và ngày cập nhật chương trình, các tài liệu tham khảo để bổ sung thông tin về chương trình, giải thích các biến được sử dụng trong chương trình

- Các đoạn chương trình (program segment) quan trọng nên có các ghi

chú Tuy nhiên, quá nhiều các ghi chú chi tiết và không cần thiết sẽ làm cho chương trình khó đọc và khó hiểu

- Nên sử dụng các danh hiệu có nghĩa (meaningful identifier) Ví dụ:

các danh hiệu có nghĩa found , firstName , …

c Định dạng chương trình

Chương trình nên được định dạng theo một kiểu mẫu (style) để làm

tăng tính dễ đọc (readability) Điều này được gọi là phong cách lập trình

(programming style)

- Sử dụng các khoảng trống (space) giữa các thành phần trong một phát biểu để dễ đọc Ví dụ: nên có khoảng trống trước và sau mỗi toán tử

- Thêm hàng trống (blank line) giữa các phần (section) của chương

trình và ở bất cứ chỗ nào thích hợp trong chuỗi các phát biểu để tạo thành các nhóm phát biểu

- Tuân theo một cách nghiêm ngặt các hướng dẫn về sắp thẳng hàng

(alignment) và thụt đầu dòng (indentation) để làm nổi bật mối liên hệ

giữa các phần khác nhau của chương trình

Ví dụ: Chương trình sau giải phương trình bậc hai ax2+ bx + c = 0

Trang 18

void GiaiPTBac2(double a, double b, double c)

double x1 = (-b - sqrt(delta)) / (2 * a);

double x2 = (-b + sqrt(delta)) / (2 * a);

cout << "Nghiem x1 = " << x1 << endl;

cout << "Nghiem x2 = " << x2 << endl;

}

}

// - // Giải phương trình bậc hai ax2 + bx + c = 0

Trang 19

1.1.5 Thực hiện và kiểm thử chương trình

Sau khi viết xong chương trình, ta phải chạy chương trình này với các

trường hợp kiểm thử (test-case) đã được xác định trong giai đoạn xây dựng

chiến lược kiểm thử

a Các lỗi của chương trình

Khi chạy chương trình, ta có thể gặp các lỗi sau đây:

- Lỗi cú pháp (syntax error), hoặc lỗi thời gian biên dịch

(compile-time error), là một lỗi trong cú pháp của một chuỗi hoặc các token

của một ngôn ngữ lập trình cụ thể

Đối với trình biên dịch (compiler), lỗi cú pháp xảy ra trong thời gian

biên dịch (compile-time) Trình biên dịch sẽ thông báo các lỗi cú

pháp và người lập trình phải sửa các lỗi này Một chương trình sẽ được biên dịch xong khi tất cả các lỗi cú pháp đều được sửa

Đối với trình thông dịch (interpreter), không phải tất cả các lỗi cú

pháp đều được phát hiện cho đến khi chạy chương trình

Ví dụ: Trong C++, một lỗi cú pháp là cout Hello; viết đúng là

cout << "Hello";

- Lỗi thời gian chạy (run-time error) là một lỗi xảy ra trong thời gian

chạy chương trình Khi xảy ra lỗi thời gian chạy, chương trình bị dừng thực hiện (kết thúc bất thường) và trình biên dịch sẽ thông báo lỗi Để sửa lỗi này, người lập trình phải tìm nguyên nhân gây ra lỗi bằng cách lần ngược từ phát biểu bị dừng thực hiện

Ví dụ: Đoạn mã C++ sau đây:

- Lỗi luận lý (logic error, logical error, semantic error) là một lỗi

(bug) trong chương trình làm cho chương trình tạo ra kết quả không

mong đợi hoặc hành vi khác Thông thường, ta khó tìm ra lỗi luận lý

Trang 20

Ví dụ: Một lỗi luận lý là delta = b * b + 4 * a * c; viết đúng là

delta = b * b - 4 * a * c;

Lỗi luận lý có thể xảy ra trong các trường hợp: thiết kế giải thuật bị sai, thiết kế chương trình chưa hoàn thiện, viết chương trình bị sai, tính toán các kết quả mong đợi của các trường hợp kiểm thử bị sai Quá trình viết chương trình và kiểm thử chương trình được gọi là

thực hiện chương trình (program implementation)

b Các cách thức chạy chương trình

Có hai cách thức chạy chương trình là:

- Cách thức tương tác (interactive mode): Người sử dụng tương tác

với chương trình trong quá trình chạy chương trình bằng cách người

sử dụng nhập vào dữ liệu và chương trình sẽ cho ra kết quả

- Xử lý theo bó (batch processing): Tập tin dữ liệu (data file) chứa các

dữ liệu Trong quá trình chạy, chương trình sẽ đọc dữ liệu từ các tập tin dữ liệu và cho ra kết quả mà không có sự tương tác của người sử

dụng (user interaction)

1.1.6 Bảo trì chương trình

Để bảo trì chương trình, ta cần phải lập tài liệu chương trình

Lập tài liệu chương trình (program documentation) bắt đầu từ giai

đoạn đầu tiên và tiếp tục trong suốt quá trình phát triển chương trình

Tài liệu cuối cùng của chương trình, được gọi là danh sách chương trình (program listing), gồm có các nội dung sau đây:

- Giải thích tất cả các giai đoạn của quá trình phát triển chương trình

- Các thiết kế chương trình

- Mô tả giải pháp giải quyết vấn đề

- Giải thích các chương trình phức tạp sao cho dễ hiểu

- Bản in mã nguồn của các chương trình

- Các vấn đề gặp phải trong quá trình viết và kiểm thử chương trình

Tài liệu này là tài liệu kỹ thuật (technical report) và được trình bày sao

cho dễ đọc, dễ hiểu và giúp ích cho việc bảo trì và phát triển chương trình

Trang 21

Ngoài ra, tài liệu chương trình bao gồm tài liệu cung cấp cho người

sử dụng (user instruction), đây là tài liệu hướng dẫn người sử dụng không

quen với máy tính có thể sử dụng được các chương trình Sổ tay người sử dụng (user's manual) hướng dẫn sử dụng chương trình, chuẩn bị dữ liệu

nhập như thế nào, giải thích các kết quả Ta không nên sử dụng các thuật ngữ kỹ thuật trong tài liệu này

Sau một thời gian sử dụng, chương trình có thể bị thay đổi vì các lý

do sau đây, nhất là đối với các hệ thống phức tạp:

- Phát hiện thêm các lỗi (bug) mà chúng chưa được phát hiện trong giai

đoạn kiểm thử chương trình

- Thay đổi các yêu cầu nghiệp vụ (business requirement)

- Thay đổi các yêu cầu hệ thống (system requirement)

- Thêm các yêu cầu nghiệp vụ mới

Bảo trì chương trình (program maintenance) bao gồm tất cả các hoạt

động (activity) xảy ra sau khi vận hành chương trình, nghĩa là chương trình

đã được đưa vào sử dụng trong thực tế Người lập trình phải sửa lại các chương trình hiện tại và/hoặc viết thêm các chương trình mới

Các hoạt động của bảo trì chương trình gồm có:

- Loại bỏ các lỗi đã được phát hiện

- Sửa các chương trình hiện tại để đáp ứng các yêu cầu thay đổi về nghiệp vụ và về hệ thống

- Viết thêm các chương trình để thêm các yêu cầu nghiệp vụ mới

- Chỉnh sửa tài liệu chương trình

Tài liệu bảo trì (maintenance documentation) sẽ giúp ích cho những

người bảo trì (maintainer) hoàn thành các công việc của họ Tài liệu này

gồm có các nội dung:

- Tài liệu thiết kế

- Mã nguồn chương trình

- Thông tin về kiểm thử

Trong thực tế, chi phí (cost) bảo trì thường lớn hơn chi phí xây dụng

chương trình ban đầu

Trang 22

Lưu ý

Chi phí bảo trì chương trình có thể giảm đáng kể Điều này phụ thuộc vào việc thiết kế và lập trình ban đầu sao cho dễ đọc, dễ hiểu, có cấu trúc tốt, dễ sửa đổi và được lập tài liệu

1.2 Lập trình goto và lập trình có cấu trúc

1.2.1 Lập trình goto

Phát biểu goto cho phép chuyển điều khiển đến một phát biểu bất kỳ nào đó trong một chương trình Điều này có thể dẫn đến chương trình hoàn

toàn không có cấu trúc và ta khó theo dõi dòng điều khiển (control flow) của

chương trình, khó bảo trì và khó phát triển chương trình

Kiểu lập trình này được gọi là lập trình goto (goto programming), là

kiểu lập trình không có cấu trúc

"Spaghetti code" (mã rối) là một thuật ngữ có nghĩa xấu dùng cho mã

nguồn có cấu trúc điều khiển phức tạp và lộn xộn, đặc biệt là sử dụng nhiều phát biểu goto (là phát biểu rẽ nhánh không điều kiện)

Có nhiều nguyên nhân gây ra mã rối, bao gồm:

- Những người lập trình chưa có kinh nghiệm, thiết kế giải thuật chưa tốt, chưa biết sử dụng các cấu trúc điều khiển một cách hợp lý để viết chương trình

- Nhiều người lập trình cùng hiệu chỉnh một chương trình trong một khoảng thời gian dài

- …

Lưu ý

Lập trình goto cho phép một chương trình có nhiều ngõ vào (entry point) và nhiều ngõ ra (exit point) và có thể gây ra mã rối

Lập trình có cấu trúc làm giảm đáng kể tầm ảnh hưởng của mã rối

Ví dụ: Chương trình sau đây minh họa mã rối dùng để hiển thị tổng của các

số bình phương của các số từ 1 đến 5

C++

int main()

Trang 23

Lập trình goto cho phép người lập trình không chuyên viết chương

trình một cách tùy tiện, miễn sao chương trình cho ra kết quả đúng

Điều này giúp cho người lập trình tốn ít thời gian viết chương trình và chương trình chạy nhanh Tuy nhiên, điều này cũng làm cho ta khó theo dõi

dòng điều khiển của chương trình, khó hiểu luận lý của chương trình (program logic) tức là giải thuật (algorithm), khó bảo trì và khó phát triển

chương trình

Trang 24

Người lập trình chuyên nghiệp không sử dụng lập trình goto mà sử

dụng lập trình có cấu trúc (structured programming) và lập trình hướng đối tượng (object-oriented programming).

1.2.2 Lập trình có cấu trúc

Lập trình có cấu trúc (structured programming) là một kiểu lập trình nhằm mục đích nâng cao tính trong sáng (dễ đọc, dễ hiểu), nâng cao chất lượng và giảm thời gian phát triển chương trình (dễ chẩn đoán lỗi, dễ sửa

đổi chương trình) bằng cách sử dụng các chương trình con, cấu trúc tuần tự,

cấu trúc điều kiện, cấu trúc lặp nhằm để loại bỏ kiểu lập trình kiểm tra và rẽ nhánh không điều kiện (thường được gọi là kiểu lập trình goto ) mà nó dẫn đến mã rối khó theo dõi dòng điều khiển của chương trình, khó bảo trì

và khó phát triển chương trình

Chương trình có cấu trúc (structured program) là một chương trình

chỉ bao gồm các cấu trúc tuần tự, cấu trúc điều kiện và cấu trúc lặp

Trong lập trình có cấu trúc, phương pháp để viết một chương trình là

sử dụng:

- Phân tích từ trên xuống (top-down analysis) để giải quyết vấn đề:

phân tích một vấn đề (công việc) phức tạp thành nhiều vấn đề con

(công việc con) đơn giản hơn Đây là chiến lược chia để trị (divide

and conquer) Sau đó, mỗi vấn đề con có thể được giải quyết một

cách độc lập, có thể tiếp tục sử dụng chiến lược chia để trị để chia

chúng thành các vấn đề con đơn giản hơn nữa Quá trình tinh chế

(refinement process) này tiếp tục cho đến khi đạt được các vấn đề

con đủ đơn giản mà ta có thể dễ dàng giải quyết chúng

- Đơn thể hóa (modularization) và lập trình đơn thể (modular

programming): tổ chức và cấu trúc của chương trình bao gồm các

đơn thể (module) và viết chương trình cho các đơn thể riêng biệt

- Cấu trúc hóa (structuralization): sử dụng các cấu trúc tuần tự, cấu

trúc điều kiện và cấu trúc lặp để viết chương trình cho các đơn thể Các quy tắc của lập trình có cấu trúc gồm:

Quy tắc 1: Phân tích từ trên xuống – Sử dụng cấu trúc tuần tự

Phân rã một công việc phức tạp thành nhiều công việc đơn giản hơn

(phân tích từ trên xuống) và chúng được thực hiện một cách tuần tự (sử dụng cấu trúc tuần tự)

Trang 25

Thực hiện công việc phức tạp bằng cách thực hiện tuần tự các công

việc sau đây (xem Hình 1.2):

Công việc đơn giản 1

Công việc đơn giản 2

Công việc đơn giản n

Quy tắc 2: Đơn thể hóa – Lập trình đơn thể

Một công việc đơn giản có thể được thực hiện bởi một đơn thể

Trong lập trình đơn thể, đơn thể (module) là một chương trình con (subroutine, subprogram) có thể là một hàm (function) hoặc một thủ tục

(procedure) bao gồm một số ít phát biểu nhằm để thực hiện một công việc

Trang 26

Quy tắc 3: Cấu trúc hóa – Lập trình có cấu trúc

Một công việc đơn giản có thể được thực hiện bởi:

- Một phát biểu thực thi: phát biểu gán, phát biểu nhập / xuất, phát

biểu gọi hàm (function call), … nhưng không là phát biểu rẽ nhánh

không điều kiện nhằm để loại bỏ goto ( goto elimination) Phát biểu thực thi thực hiện một hành động không phân chia được (indivisible action)

- Một cấu trúc điều kiện: cấu trúc if , cấu trúc switch ,

- Một cấu trúc lặp: cấu trúc for , cấu trúc while , cấu trúc do while

và thân của các cấu trúc này thực hiện tuần tự các công việc đơn giản hơn nữa

Hai cấu trúc điều khiển không bao giờ giao nhau (cắt nhau) và hai cấu trúc điều khiển có thể được kết hợp theo hai cách như sau:

- Tuần tự (sequential): cấu trúc điều khiển đứng trước được thực hiện

trước, cấu trúc điều khiển đứng sau được thực hiện sau

Ví dụ: Cấu trúc điều kiện if đứng trước cấu trúc lặp while

Ngõ ra

Trang 27

- Lồng nhau (nested): thân của cấu trúc điều khiển này (cấu trúc nằm

ngoài) có chứa cấu trúc điều khiển khác (cấu trúc nằm trong)

Ví dụ: Cấu trúc lặp while chứa cấu trúc lặp for

break , continue ở trong thân của các cấu trúc lặp for , while ,

do while , các phát biểu return ở trong thân của một đơn thể để trở về đơn thể gọi.

Hình 1.4 Hai cấu trúc được thực hiện tuần tự

Trang 28

Lưu ý rằng các phát biểu rẽ nhánh không điều kiện thường được sử dụng chung với các phát biểu if

Một ngoại lệ của lập trình có cấu trúc là cho phép sử dụng các phát biểu break trong cấu trúc điều kiện switch vì lý do ngữ nghĩa của cấu trúc này

Lập trình có cấu trúc chỉ cho phép một loại rẽ nhánh không điều kiện

là gọi đơn thể (module call) để thực hiện một đơn thể khác, tức là thực hiện

một công việc khác

Điều này thể hiện mục đích của lập trình có cấu trúc là loại bỏ goto

( goto elimination): mỗi cấu trúc chỉ có một ngõ vào (bắt đầu) và một ngõ

ra (thoát)

Ví dụ: Giải quyết bài toán tính diện tích của hình chữ nhật có chiều dài và

chiều rộng được nhập từ bàn phím: nếu chiều dài và chiều rộng là số dương thì hiển thị diện tích, ngược lại thì hiển thị lỗi sai do nhập dữ liệu

- Áp dụng Quy tắc 1 – Phân tích từ trên xuống, bài toán này được phân

rã thành các công việc được thực hiện một cách tuần tự (cấu trúc tuần tự) như sau:

CV1: Nhập chiều dài a từ bàn phím;

CV2: Nhập chiều rộng b từ bàn phím;

CV3: Kiểm tra dữ liệu hợp lệ và tính toán, xuất kết quả;

- Áp dụng Quy tắc 3 – Cấu trúc hóa, CV1 và CV2 được thực hiện bởi các phát biểu nhập và CV3 được thực hiện bởi một cấu trúc điều kiện

CV6: Hiển thị lỗi sai do nhập dữ liệu;

- Áp dụng Quy tắc 2 – Đơn thể hóa, CV4 được thực hiện bởi một phát biểu gọi hàm tính diện tích TinhDienTichCN(a, b) và gán cho biến dienTich :

Trang 29

CV6: Hiển thị lỗi sai do nhập dữ liệu;

Hàm tính diện tích TinhDienTichCN(a, b) là một đơn thể và được viết bằng một cấu trúc tuần tự như sau:

CV6: cout << "Du lieu nhap bi sai" << endl;

- Áp dụng Quy tắc 3 – Cấu trúc hóa, CV7 được thực hiện bởi phát biểu gán và CV8 được thực hiện bởi phát biểu return :

Trang 30

Sự lệch hướng phổ biến ở trong nhiều ngôn ngữ lập trình là sử dụng

các phát biểu rẽ nhánh không điều kiện:

- Phát biểu return được dùng để kết thúc sớm việc thực hiện một chương trình con

- Phát biểu break được dùng để kết thúc sớm việc thực hiện một cấu trúc

lặp for , whiledo while

Điều này dẫn đến một chương trình con hoặc một cấu trúc lặp có thể

có nhiều ngõ ra (exit point) và cho phép việc lập trình được dễ dàng hơn so

với lập trình có cấu trúc

Tuy nhiên, người lập trình phải thật cẩn thận khi sử dụng các phát biểu returnbreak

Trang 31

Nên nhớ rằng việc sử dụng quá đáng nhiều phát biểu break trong một cấu trúc lặp và nhiều phát biểu return trong một chương trình con sẽ dẫn đến " spagetti-code " (mã xấu): cấu trúc lặp có quá nhiều điều kiện thoát, chương trình con có quá nhiều điểm trở về Điều này sẽ làm cho ta khó đọc hiểu, khó chẩn đoán các lỗi và khó phát triển chương trình

Các phát biểu returnbreak thường ở trong các phát biểu if Kiểu lập trình if returnif break cũng là kiểu lập trình kiểm tra

và rẽ nhánh không điều kiện.

Ví dụ: Chương trình C++ sau đây cho biết phần tử của lần xuất hiện đầu tiên

có giá trị là 5 trong ma trận vuông a (mảng hai chiều)

Ta sử dụng hai cấu trúc lặp for lồng nhau Khi phát hiện phần tử

a[i][j] bằng 5, ta dùng phát biểu break để thoát khỏi vòng lặp for(j)

bên trong Tuy nhiên, điều khiển chương trình vẫn ở trong vòng lặp for(i)

bên ngoài Để điều khiển chương trình tiếp tục thoát khỏi vòng lặp for(i)

bằng phát biểu break , ta phải sử dụng thêm biến cờ found , nó được gán giá trị true trong vòng lặp for(j) và nó được kiểm tra trong vòng lặp

Trang 32

// Cờ found để thoát vòng lặp while(i)

while ((i < n) && ! found)

{

j = 0;

// Cờ found để thoát vòng lặp while(j)

while ((j < n) && ! found)

Trang 33

{

if (a[i][j] == 5)

// Biến cờ found dùng để thoát cùng lúc

// hai vòng lặp while(j) và while(i)

Trong trường hợp này, việc thoát khỏi cùng lúc hai cấu trúc lặp

while lồng nhau bằng biến cờ found một cách tự nhiên hơn so với việc thoát khỏi cùng lúc hai cấu trúc lặp for lồng nhau bằng phát biểu break và biến cờ found

Các ưu điểm của lập trình có cấu trúc

Nói chung, lập trình có cấu trúc tập trung vào việc giải quyết vấn đề

về độ phức tạp (complexity) trong lập trình

Lập trình có cấu trúc dựa vào các nguyên tắc:

- Phân tích từ trên xuống để giải quyết vấn đề

- Đơn thể hóa để thiết kế chương trình

- Cấu trúc hóa để lập trình

Do đó, lập trình có cấu trúc có các ưu điểm sau đây:

1 Viết các chương trình dễ dàng hơn (more easily) và nhanh hơn

(more quickly) Một công việc phức tạp phải được phân chia thành

các công việc con, sao cho viết chương trình một cách dễ dàng cho các công việc con này, mỗi chương trình là một đơn vị riêng biệt

Trang 34

2 Các chương trình có tính tin cậy cao hơn (greater reliability) Ít xảy

ra các lỗi luận lý trong các giai đoạn đầu tiên của quá trính phát triển

và viết chương trình

3 Tốn ít thời gian chẩn đoán lỗi (debug) và kiểm thử (test) các chương

trình, bởi vì ít lỗi xảy ra trong quá trình viết chương trình Các lỗi chỉ xảy ra trong các chương trình đơn giản và ta dễ dàng sửa các lỗi này

4 Dễ dàng bảo trì (maintain) các chương trình Khi các chương trình

được đưa vào sử dụng, các yêu cầu về thay đổi chương trình hoặc thêm các tính năng mới cho chương trình ngày càng gia tăng

Khảo sát thực tế cho thấy việc viết chương trình, chẩn đoán lỗi và kiểm thử chương trình tốn dưới 50% toàn bộ công sức lập trình Hơn 50% công sức lập trình dành cho việc bảo trì chương trình

Đối với lập trình có cấu trúc, công việc bảo trì trở nên dễ dàng hơn, đặc biệt đối với những người lập trình mới tham gia vào dự án, bởi vì chương trình dễ đọc hiểu, luận lý chương trình được thiết kế tốt, các đơn thể chương trình đơn giản

Khi có yêu cầu thay đổi, ta dễ dàng tìm ra các đơn thể chương trình nào cần phải thay đổi và thay đổi như thế nào Bởi vì các đơn thể là tách biệt, là các đơn vị chương trình độc lập, nên ta dễ dàng thay thế một đơn thể cũ bởi một đơn thể mới và không dẫn đến việc thay đổi lớn trong toàn bộ chương trình

Khi có yêu cầu thêm các tính năng mới, ta dễ dàng thêm các đơn thể chương trình mới vào các chương trình đang có sẵn

Các ưu điểm này làm tăng hiệu suất lập trình (programming

productivity) của những người lập trình

1.2.3 Tinh chế dần từng bước

Tinh chế dần từng bước (stepwise refinement) là một phương pháp

để phát triển một chương trình bằng cách trước tiên mô tả các chức năng tổng quát, sau đó mỗi chức năng sẽ được mô tả ở mức chi tiết hơn trong các bước tiếp theo cho đến khi toàn bộ chương trình đã được xác định Cách tiếp

cận này còn được gọi là thiết kế từ trên xuống (top-down design)

Tinh chế dần từng bước cho phép ta tập trung giải quyết các vấn đề

khó khăn nhất trong giai đoạn phát triển, nghĩa là tập trung vào các khía cạnh quan trọng nhất và bỏ qua các chi tiết cụ thể

Trang 35

Để mô tả các chức năng trong quá trình tinh chế dần từng bước, ta sử dụng mã giả

Mã giả (pseudocode) là sự mô tả quen thuộc ở mức cao về nguyên lý

hoạt động của chương trình hoặc giải thuật

Mã giả được xây dựng từ các quy ước ngôn ngữ lập trình được kết hợp với ngôn ngữ tự nhiên và các ký hiệu toán học

Mục tiêu của việc sử dụng mã giả là để cho con người dễ đọc hiểu giải thuật, bỏ qua các chi tiết không cần thiết mà mã nguồn của ngôn ngữ lập trình quy định; ví dụ, khai báo các biến, các mã đặc trưng của hệ thống, các chương trình con của thư viện chương trình

Không có cú pháp chuẩn cho mã giả và chương trình được viết bằng

mã giả không phải là một chương trình thực thi (chương trình máy tính) Một số từ ngữ và các cấu trúc điều khiển được sử dụng trong mã giả như sau:

set reset increment compute process calculate add sum multiply generate

print display input output edit test

call call with(parameters) return return( )

if else switch for while do while

Quy trình lập trình mã giả (PPP – Pseudocode Programming

Process) là một phương pháp để phát triển chương trình bằng cách viết mã

giả thành các câu ghi chú trong chương trình, sau đó viết mã thật ở phía dưới các câu ghi chú này

Bước đầu tiên của quá trình tinh chế dần từng bước là phân chia một công việc phức tạp ban đầu thành nhiều công việc đơn giản hơn được thực hiện một cách tuần tự

Nhiều chương trình có thể được chia thành ba giai đoạn mà chúng được thực hiện tuần tự như sau:

Giai đoạn bắt đầu (initialization phase): Gán giá trị ban đầu cho các biến Giai đoạn xử lý (processing phase): Nhập dữ liệu và điều chỉnh các biến Giai đoạn kết thúc (termination phase): Tính toán và xuất kết quả

Các bước kế tiếp của quá trình tinh chế dần từng bước là mô tả chi tiết các công việc đơn giản của các giai đoạn trên bằng các cấu trúc điều

Trang 36

khiển gồm có cấu trúc tuần tự, cấu trúc điều kiện và cấu trúc lặp, cho đến khi toàn bộ các công việc đã được xác định

Bước cuối cùng của quá trình tinh chế dần từng bước là thay thế mã giả thành mã thật của một ngôn ngữ lập trình cụ thể

Ví dụ: Tính giá trị trung bình của các số được nhập từ bàn phím, kết thúc

việc nhập khi nhập vào số 0

Bước 1: Phân chia công việc phức tạp

// CV1 (Initiation phase): set 0 to tongCong, dem

// CV2 (Processing phase): input x and compute tongCong, dem // CV3 (Termination phase): compute and output gtTrungBinh

Bước 2: Mô tả chi tiết cho công việc CV2

// CV1 (Initiation phase): set 0 to tongCong, dem

// CV2 (Processing phase): input x and compute tongCong, dem

// CV3 (Termination phase): compute and output gtTrungBinh

Bước 3: Mô tả chi tiết cho công việc CV3

// CV1 (Initiation phase): set 0 to tongCong, dem

// CV2 (Processing phase): input x and compute tongCong, dem

output "Khong tinh duoc gia tri trung binh"

Bước 4: Thay thế mã giả thành mã thật, ví dụ ngôn ngữ C++

C++

int main()

Trang 37

double gtTrungBinh = tongCong / dem;

cout << "Gia tri trung binh = " << gtTrungBinh << endl; }

Ví dụ: Viết hàm Find(a, n, x) để tìm tuần tự x trong mảng a có n phần

tử Nếu tìm thấy x thì hàm trả về true ; ngược lại thì hàm trả về false

Cách 1: Sử dụng phát biểu return để thoát bất thường vòng lặp for và trở

Trang 38

Cách viết này cho thấy:

- Vòng lặp for có hai ngõ ra là i ≥ n và return true

- Hàm Found() có hai ngõ ra là return truereturn false

Cách 2: Áp dụng quy trình lập trình mã giả PPP Ta có thể diễn giải bình thường cách thức tìm kiếm x trong mảng a như sau:

// (1) Ban đầu chưa tìm thấy x trong mảng a

// (2) Bắt đầu từ phần tử đầu tiên

// (3) Trong khi xét phần tử hiện tại và chưa tìm thấy

// (4) Nếu phần tử hiện tại là x thì tìm thấy rồi

// (5) Ngược lại, ta xét phần tử kế tiếp

// (6) Trả về kết quả tìm kiếm

(1) Chưa tìm thấy x trong mảng a : ta gán false cho biến found

(2) Bắt đầu từ phần tử đầu tiên: ta gán 0 cho biến i

(3) Trong khi xét phần tử hiện tại và chưa tìm thấy: ta sử dụng vòng lặp

while với điều kiện i < n và not found

(4) Nếu phần tử hiện tại là x thì tìm thấy rồi: ta gán true cho biến found

(5) Ngược lại, ta xét phần tử kế tiếp: ta cho i tăng thêm 1

(6) Trả về kết quả tìm kiếm: trả về found

Ta có đoạn mã giả sau:

// (1) Ban đầu chưa tìm thấy x trong mảng a

found = true;

// (2) Bắt đầu từ phần tử đầu tiên

i = 0;

// (3) Trong khi xét phần tử hiện tại và chưa tìm thấy

while (i < n and not found)

// (4) Nếu phần tử hiện tại là x thì tìm thấy rồi

Trang 39

Cách viết này cho thấy:

- Vòng lặp while chỉ có một ngõ ra khi điều kiện:

(i < n) && (! found)) là false

- Hàm Found() chỉ có một ngõ ra là return found và phát biểu này luôn luôn là phát biểu cuối cùng của hàm

1.3 Giải thuật

Giải thuật là một phương pháp để giải quyết một vấn đề

Giải thuật xác định một chuỗi các hành động chi tiết và rõ ràng để giải quyết một vấn đề cụ thể hoặc thực hiện một công việc cụ thể

Trong khoa học máy tính (computer science), giải thuật (algorithm)

là một chuỗi các hành động (action) không phân chia được, còn được gọi là quá trình (process), được thực hiện trên một tập dữ liệu nhập (input) và tạo

ra kết quả (output) trong một khoảng thời gian hữu hạn

Ta có thể mô tả một giải thuật bằng nhiều cách khác nhau:

- Ngôn ngữ tự nhiên (natural language)

- Mã giả (pseudocode)

- Sơ đồ tiến trình (flowchart) …

Quá trình(Xử lý)

Hình 1.6 Mô hình của giải thuật

Thời gian thực hiện

Trang 40

1.3.1 Mã giả

Mã giả (pseudocode) là sự mô tả quen thuộc ở mức cao về nguyên lý

hoạt động của chương trình máy tính hoặc giải thuật

Mã giả được xây dựng từ các quy ước ngôn ngữ lập trình được kết hợp với ngôn ngữ tự nhiên và các ký hiệu toán học

Mục tiêu của việc sử dụng mã giả là để cho con người dễ đọc hiểu giải thuật, bằng cách bỏ qua các chi tiết không cần thiết mà mã nguồn của ngôn ngữ lập trình quy định, ví dụ như khai báo các biến, các mã đặc trưng của hệ thống, các chương trình con của thư viện chương trình

Ta không có cú pháp chuẩn cho mã giả và chương trình được viết bằng

mã giả không phải là một chương trình thực thi (chương trình máy tính)

Ví dụ: Tìm ước số chung lớn nhất (GCD – Greatest Common Divisor) của hai

số nguyên dương a và b bằng giải thuật Euclid (Euclid's algorithm) Ta có thể

mô tả giải thuật này bằng ngôn ngữ tự nhiên và bằng mã giả sau đây:

Ngôn ngữ tự nhiên:

Bước 1: Nhập a và b

Bước 2: Nếu b = 0 thì a là ước số chung lớn nhất và kết thúc giải thuật

Bước 3: Nếu a > b thì a = a – b , ngược lại b = b – a Sau đó, đến Bước 2

Ngày đăng: 12/07/2021, 11:03

TỪ KHÓA LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w