1. Trang chủ
  2. » Công Nghệ Thông Tin

[Giáo trình] Phân tích thiết kế thuật toán và đánh giá độ phức tạp của giải thuật - ĐH Sư phạm Hà Nội

96 2,8K 28
Tài liệu đã được kiểm tra trùng lặp

Đ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

Định dạng
Số trang 96
Dung lượng 1,82 MB

Nội dung

Một thuật toán là một danh sách từng bước các chỉ dẫn để giải quyết cho một bài toán cụ thể.Ở góc độ lập trình, thuật toán còn được gọi là thuật giải hay giải thuật, là một danh sách các thao tác (câu lệnh) theo đó máy tính thực hiện để sau một số hữu hạn bước, từ input là dữ liệu vào của bài toán, sẽ thu được output là dữ liệu ra cần tìm của bài toán.

Trang 1

NGUYỄN CHÍ TRUNG NGUYỄN THỊ THU THỦY

PHÂN TÍCH THIẾT KẾ THUẬT TOÁN VÀ ĐÁNH GIÁ ĐỘ PHỨC TẠP GIẢI THUẬT

Trang 2

MỤC LỤC

TÀI LIỆU THAM KHẢO 4

Chương 1 CÁC KHÁI NIỆM CƠ BẢN 5

1 Thuật toán (giải thuật, thuật giải) 5

1.1 Định nghĩa 5

1.2 Các đặc trưng của thuật toán 5

2 Phân tích thuật toán 5

2.1 Tại sao phải phân tích thuật toán 10

2.2 Thời gian thực hiện thuật toán 11

2.3 Khái niệm độ ph1độ phức tạp thuật toán 15

3.1 Qui tắc hằng số 15

3.2 Qui tắc cộng 16

3.3 Qui tắc lấy max 16

3.4 Qui tắc nhân 17

3 Các kỹ thuật đánh giá độ phức tạp thuật toán 17

3.1 Câu lệnh đơn 17

3.2 Câu lệnh hợp thành 17

3.3 Câu lệnh lặp với số lần lặp biết trước for-do 18

3.4 Câu lệnh rẽ nhánh if 19

3.5 Câu lệnh lặp với số lần lặp chưa biết trước while, repeat 19

4 Một số ví dụ minh họa thiết kế thuật toán và đánh giá độ phức tạp 21

Bài toán 1.1 Tính giá trị gần đúng của exp(x) theo khai triển Taylor 21

Bài toán 1.2 Thuật toán tìm kiếm tuần tự 22

Bài toán 1.3 Thuật toán tìm kiếm nhị phân 22

Bài toán 1.4 Thuật toán sắp xếp chọn lựa 23

5 Phân tích chương trình (con) đệ qui 24

5.1 Khái niệm về đệ qui 24

5.2 Chương trình (con) đệ qui 25

5.3 Xây dựng phương trình (công thức) đệ qui 25

5.4 Giải phương trình đệ qui và Định lí Thợ 26

BÀI TẬP CHƯƠNG 1 30

Chương 2 CHIA ĐỂ TRỊ 33

1 Sơ đồ chung của thuật toán chia để trị 33

1.1 Thuật toán β 33

1.2 Thuật toán γ 34

1.3 Thuật toán γ tổng quát 35

2 Một số ví dụ minh họa Chia để trị 35

2.1 Thuật toán sắp xếp trộn (Merge Sort) 35

2.2 Thuật toán sắp xếp nhanh (QuickSort) 37

2.3 Nhân số nguyên lớn 39

2.4 Mảng con trọng số lớn nhất 40

BÀI TẬP CHƯƠNG 2 43

Chương 3 QUY HOẠCH ĐỘNG 45

1 Giới thiệu phương pháp qui hoạch động 45

2 Phương pháp chung của qui hoạch động 45

3 Một số ví dụ minh họa 46

3.1 Dãy con tăng dần dài nhất 46

Trang 3

3.2 Trở lại bài toán mảng con trọng số lớn nhất 51

3.3 Xâu con chung dài nhất 52

3.4 Bài toán cái túi 55

3.5 Nhân ma trận 57

BÀI TẬP CHƯƠNG 3 62

Chương 4 THUẬT TOÁN THAM LAM 64

1 Giới thiệu thuật toán tham lam 64

1.1 Đặc điểm của thuật toán tham lam 64

1.2 Sơ đồ chung của thuật toán tham lam 65

1.3 Chứng minh thuật toán đúng 65

2 Một số ví dụ minh họa 66

2.1 Bài toán tập các đoạn thẳng không giao nhau 66

2.2 Tìm hiểu các thuật toán tham lam đối với bài toán cái túi 69

2.3 Bài toán người du lịch (TSP - Travelling Salesman Problem) 70

2.4 Bài toán mã hóa Huffman 71

BÀI TẬP CHƯƠNG 4 75

Chương 5 CÁC THUẬT TOÁN ĐỒ THỊ CƠ BẢN 77

1 Các khái niệm cơ bản 77

1.1 Đồ thị 77

1.2 Các khái niệm 77

2 Các phương pháp biểu diễn đồ thị 78

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

1.2 Biểu diễn đồ thị bằng danh sách cạnh 78

1.3 Biểu diễn đồ thị bằng danh sách kề 79

1.4 Biểu diễn đồ thị bằng danh sách liên thuộc 81

3 Thuật toán tìm kiếm theo chiều rộng 81

3.1 Nguyên tắc tô màu 81

2.2 Breadth – First Tree 81

3.3 Mô tả thuật toán 82

4 Thuật toán tìm kiếm theo chiều sâu 84

4.1 Giới thiệu thuật toán 84

4.2 Thủ tục tìm kiếm theo chiều sâu 85

4.3 Đánh giá độ phức tạp thuật toán DFS và DFS-Visit 86

5 Bài toán tìm đường đi ngắn nhất 87

5.1 Một số khái niệm cơ bản 87

5.2 Thuật toán Dijkstra 88

6 Bài toán về cây khung nhỏ nhất 90

6.1 Các khái niệm cơ bản 90

6.2 Thuật toán Kruskal 91

6.3 Thuật toán Prim 92

BÀI TẬP CHƯƠNG 5 94

CÁC CHUYÊN ĐỀ MÔN HỌC 96

Trang 4

TÀI LIỆU THAM KHẢO

1 Vũ Đình Hòa, “Giải thuật và đánh giá độ phức tạp giải thuật”, Gói giáo trình môn học theo

chuẩn SCORM, Trường ĐHSP HN

2 Hồ Sỹ Đàm (chủ biên), Đỗ Đức Đông, Lê Minh Hoàng, Nguyễn Thanh Hùng, “Tài liệu giáo

khoa Chuyên Tin” Quyển 1 và 2, Nhà xuất bản giáo dục, 2009

3 Nguyễn Đức Nghĩa, Nguyễn Tô Thành, “Toán rời rạc”, Nhà xuất bản giáo dục, tài bản 2005

4 Larry Nyhoff, “Lập trình nâng cao bằng Pascal với các cấu trúc dữ liệu”, Dịch giả Lê Minh

Trung, Công ty liên doanh tư vấn và dịch vụ khoa học kỹ thuật SCITEC, 1991

5 Nguyễn Chí Trung, “Giáo trình Thuật toán và kĩ thuật lập trình Pascal”, Nhà xuất bản Hà

Nội, 2005

Trang 5

Chương 1 CÁC KHÁI NIỆM CƠ BẢN

1 Thuật toán (giải thuật, thuật giải)

1.1 Định nghĩa

Một thuật toán là một danh sách từng bước các chỉ dẫn để giải quyết cho một bài toán cụ thể 1

Ở góc độ lập trình, thuật toán còn được gọi là thuật giải hay giải thuật, là một danh sách các thao tác (câu lệnh) theo đó máy tính thực hiện để sau một số hữu hạn bước, từ input là dữ liệu vào của bài toán, sẽ thu được output là dữ liệu ra cần tìm của bài toán

1.2 Các tính chất cơ bản của thuật toán

1.2.1 Tính dừng

Thuật toán phải kết thúc sau một số hữu hạn lần thực hiện các thao tác

Ví dụ: thuật toán sau đây vi phạm tính dừng

Bước 1: S Å 0; i Å 0;

Bước 2: i Å i + 1;

Bước 3: S Å S + i*i;

Bước 4: Quay về bước 2;

Bước 5: Đưa ra S và kết thúc thuật toán

Thuật toán được sửa lại để nó có tính dừng (trở thành thuật toán tính tổng các bình phương của n

số tự nhiên đầu tiên) như sau:

Bước 6: Quay về bước 3;

Bước 7: Đưa ra S và kết thúc thuật toán

1 Từ “thuật toán” (algorithm) xuất phát từ tên của quốc gia châu Á trung tâm cổ xưa là Khorezm, về sau là các nước cộng hòa xã hội chủ nghĩa Kazakh, Turkmen, and Uzbek Vào khoảng năm 825 sau công nguyên, nghiên cứu chính

về đại số và hệ thống khái niệm số học Ấn Độ được viết bởi Mohammed, là con trai của Musa (Khorez); tiếng

Lattinh nghĩa là bởi “Mohamed ibn Musa al-Khowarizmi.” Vào năm 857, đoạn văn bản tiếng này được dịch sang

tiếng Anh là "Algoritmi” Từ đây, xuất phát từ cụm từ al-Khowarizmi, Hisab al-jabrw'sal-muqabalah

(Mathematics-al-jabrw'sal muqabalah) mà chúng ta có tù algebra (đại số)

Trang 6

Bước 1: Nhập a, b, nhập chọn lựa choice;

//Qui ước choice = 1 là tính diện tích hình chữ nhật, ngược lại, tính thể tích hình nón

Bước 2: Nếu choice = 1 thì S Å a * b và thực hiện bước 4;

Bước 3: S Å (1/3)π.a.b2

Bước 4: Đưa ra S và kết thúc thuật toán;

Ví dụ khác: thuật toán ”Tìm số hạng Fibonacci thứ N” dưới đây vi phạm tính xác định

Bước 1: Nhập số dương N

Bước 2: Nếu N ≤ 2 thì c Å 1, kết thúc thuật toán

Bước 3: a Å 1; b Å 1; k Å 2;

Bước 4: Nếu k = N thì đưa ra c và kết thúc thuật toán,

Bước 5: k Å k + 1; Thực hiện bước 6 hoặc bước 7 sau đây:

Bước 6: c Å a + b; a Å b; b Å c; Quay về bước 4;

Trang 7

Ta định nghĩa một bộ dữ liệu vào đầy đủ là nó bao phủ hết (cover all the cases) tất cả các trường

Bước 3: Đưa ra m và kết thúc thuật toán;

Rõ ràng thuật toán trên sai tại một số bộ dữ liệu, ví dụ nếu bộ dữ liệu vào là (a, b, c) = (1, 2, 3) thì thuật toán cho kết quả m = 2, không đúng yêu cầu của đề bài; nếu bộ dữ liệu vào là (a, b, c) = (2, 1, 3) thì không có chỉ thị nào trong thuật toán tác động vào m, do đó m không xác định và không tính được m như yêu cầu đề bài

Có thể sửa lại thuật toán như sau:

Bước 1: Nhập a, b, c;

Bước 2: m Å a;

Bước 3: Nếu m < b thì m Å b;

Bước 4: Nếu m < c thi m Å c;

Bước 5: Đưa ra m và kết thúc thuật toán;

1.2.4 Tính phổ dụng

Thuật toán phải đảm bảo giải được một lớp bài toán

Ví dụ thay vì xây dựng thuật toán và viết chương trình giải các phương trình:

1) 5x2 + 12x - 1 = 0

2) 2x2 -6x +2 = 0

3) 7x + 100 = 0

4) -50x2 +112x - 11 = 0

Trang 8

Người ta tiến hành xây dựng thuật toán và viết chương trình giải phương trình:

ax2 + bx + c = 0 với mọi số thực a, b, c cho trước

1.3 Các tính quan trọng của thuật toán

Các tính chất này liên quan đến việc nhấn mạnh ưu điểm của "thuật toán tin học" là có thể giao cho máy tính thực hiện Một "thuật toán toán học" thuần túy có thể “rất đẹp” nhưng chưa chắc

đã cài đặt dễ dàng trên máy tính, và nếu cài đặt được thì thuật toán đó chưa chắc ổn định và khả thi Nói ở góc độ tương tự, hai tính chất sau đây thể hiện sự khác biệt giữa toán lí thuyết và toán tính

- Toán lí thuyết quan tâm đến các vấn đề định tính của bài toán: tồn tại, duy nhất, tính chất nghiệm của các bài toán

- Toán tính quan tâm đến xây dựng phương pháp, thuật toán để để tìm nghiệm bài toán trên máy tính

Thuật toán được xây dựng phải thỏa mãn yêu cầu về tính khả thi và tính ổn định

1.3.1 Tính khả thi

Một thuật toán là khả thi nếu nó thực hiện được trên máy tính trong một thời gian chấp nhận được Thòi gian ở đây không tính đến kiểu CPU và chưa tính đến dung lượng bộ nhớ cần cấp phát

Ví dụ (tính khả thi) Cho hệ phương trình đại số tuyến tính

b

trong đó A là ma trận vuông cấp n với định thức khác 0

Về lý thuyết, có thể giải hệ trên bằng thuật toán mà ý tưởng của nó dựa vào công thức Cramer:

= i i

trong đó , còn là định thức của ma trận A sau khi thay cột i bởi cột tự do b Nhưng việc tính toán ra nghiệm bằng số cụ thể lại là một việc không đơn giản Theo công thức (2) cần phải tính n +1 định thức cấp n Mỗi định thức là tổng của n! số hạng, mỗi số hạng là tích của n thừa số Do vậy, để tính mỗi số hạng cần thực hiện n – 1 phép nhân Như vậy, tất cả số phép tính nhân cần thực hiện trong (2) là Q = n!(n+1)(n-1)

2010

*7073.9

Q

910

*2.6965 3.0782*105

Ở trên ta mới chỉ xét việc giải một hệ cỡ 20, mà thực tế khoa học và công nghệ đòi hỏi phải giải các hệ phương trình đại số tuyến tính cỡ hàng vạn, hàng triệu hoặc hơn thế nữa Vì thế, cần phải

Trang 9

nghiên cứu đề xuất các phương pháp hiệu quả để có thể giải được các hệ thống phương trình cỡ lớn

0

1 1

1)1

1 1

0

,2,1

Kết quả giảm dần từ 0.3679 (khi n = 1) đến 0.0555 (khi n=16)

Kết quả sau đó kết quả thay đổi thất thường và giá trị tuyệt đối tăng rất nhanh

Điều này hoàn toàn không phù hợp với lý thuyết vì theo lý thuyết thì I n →0 khi n→∞ do đó

.1

10

Trang 10

Hiện tượng kết quả tính toán nêu trên là sự không ổn định của thuật toán: sai số ban đầu khi tính

n

I

3679.0

)21()(21

1 1

n I

~ |~I nI n|=n!δ δ có bé thì khi n đủ lớn, sai số vẫn

đủ lớn và ta không thể nhận được giá trị chấp nhận được là gần đúng cho I n

2 Phân tích thuật toán

2.1 Tại sao phải phân tích thuật toán

Xét một thuật toán nhân 2 số phức

z1 = a + bi; z2 = c + di

z = z1 * z2 = (ac – bd) + (ad + bc)i

Khi tiến hành thuật toán: máy tính thực hiện 4 phép nhân và 3 phép cộng (ở đây là phép cộng đại

số, nghĩa là phép trừ được xem là cộng với số âm)

Giả sử phép nhân thực hiện mất 1 giây, phép cộng thực hiện mất 0.01 giây, phép gán thực hiện mất 0.005 giây Khi đó phép nhân hai số phức trên thực hiện mất 4*1 + 3*0.01 + 0.005 = 4.035 giây Để giảm thời gian tính toán, ta có thể giảm phép nhân nhờ các tính toán sau đây:

ac - bd và ad + bc = (a + b)*(c + d) - ac - bd

Do đó nếu đặt p := ac; q := bd; Thì z := (p - q) + ((a +b)*(c+d) - p - q)i

Khi đó việc tính z gồm 3 phép nhân, 6 phép cộng và 3 phép gán; mất khoảng thời gian là 3*1 + 6*0.01 + 3*0.005 = 3.075 giây, giảm được 4.04 - 3.09 = 0.96 giây

Ví dụ trên cho thấy một bài toán có thể tồn tại nhiều thuật toán để giải, do đó cần lựa chọn thuật toán tốt nhất Điều này cũng dẫn đến việc phân tích thuật toán Ngoài ra, một bài toán được cài đặt bằng một thuật toán đúng, nhưng chưa chắc cho kết quả mong muốn Vì các lí do sau:

• Thời gian thực hiện quá lâu

• Tốn nhiều bộ nhớ

Điều này cũng dẫn đến cần phân tích thuật toán Khi phân tích thuật toán, ta thường xem xét về thời gian và bộ nhớ chi phí cho thuật toán, trong đó chủ yếu phân tích về mặt thời gian

Trang 11

2.2 Thời gian thực hiện thuật toán

Thời gian thực hiện thuật toán phụ thuộc vào các yếu tố sau:

1 Kích thước dữ liệu đầu vào (ở đây ta sẽ kí hiệu là n)

Định nghĩa 1.1 Ta gọi T(n) là hàm thời gian phụ thuộc vào kích thước dữ liệu đầu vào n

Định nghĩa 1.2. Đơn vị tính của hàm T(n) không phải là đơn vị thời gian thực mà là số lần thực hiện các phép tính cơ bản Các phép tính cơ bản là các phép toán có thời gian thực hiện bị chặn bởi một hàm số

Các phép tính cơ bản bao gồm:

1 Lời gọi thủ tục như read, write, và lời gọi hàm như sqr, sqrt,

2 Câu lệnh gán

3 Phép tính số học (+, -, *, /)

4 Phép toán logic và phép toán so sánh

Chú ý: Ở đây ta không xem xét thời gian thực hiện đối với các câu lệnh điều khiển (rẽ nhánh then, case-of, lặp for-do, while-do, và repeat-until) vì chúng không được xem là các phép tính cơ bản Việc bỏ qua các câu lệnh điều khiển mặc dù không cho kết quả chính xác về thời gian tính (khác nhau một cơ số lần giá trị của n, với n là kích thước dữ liệu vào), nhưng thường không ảnh hưởng đến độ phức tạp cần đánh giá Vài trường hợp, câu lệnh rẽ nhánh khi kiểm tra điều kiện được quan tâm và thời gian của việc kiểm tra điều kiện này được tính là một hằng số nào đó Một cách tổng quát, nếu mục đích là tính thời gian thực hiện thuật toán thì nên xem xét đầy đủ cả các câu lệnh điều khiển, nếu mục đích là đánh giá độ phức tạp thuật toán thì có thể bỏ qua các câu lệnh điều khiển

Trang 12

Phân tích và đánh giá: Các lệnh 1, 2, 3 và 7 được thực hiện một lần Thân vòng lặp gồm các lệnh

4, 5, 6 được thực hiện n lần Vậy T(n) = 3n + 4

Định nghĩa 1.3. Có ba loại thời gian tính:

• Thời gian tính tốt nhất: Là thời gian thực hiện nhanh nhất của thuật toán với một bộ dữ

liệu vào nào đó

• Thời gian tính tồi nhất: Là thời gian thực hiện chậm nhất của thuật toán với một bộ dữ

liệu vào nào đó

• Thời gian tính trung bình: Là trung bình cộng của các thời gian thực hiện thuật toán đối

với tất cả các trường hợp thực hiện thuật toán (ứng với một bộ dữ liệu vào đầy đủ)

Phân tích và đánh giá: Mỗi câu lệnh 1, 2 luôn thực hiện 1 lần Một trong hai lệnh 5 hoặc 6 thực

hiện một lần Vậy thời gian thực hiện thuật toán luôn có dạng T(n) = 3 + k, trong đó k là số lần thực hiện các câu lệnh 3 và 4 Khi đó ta có thể tạm thời không cần xem xét các câu lệnh 1, 2, 5, 6

Trang 13

Thời gian tính tốt nhất khi x = a1: Câu lệnh 3 thực hiện một lần, câu lệnh 4 thực hiện không lần,

do đó k = 1 và :

T(n) = 3 + 1 Thời gian tính tồi nhất xảy ra khi không có x trong dãy (không tìm thấy) Câu lệnh 3 thực hiện không lần, câu lệnh 4 thực hiện n lần Do đó k = n và

T(n) = 3 + n Thời gian tính trung bình được tính như sau:

Nếu không thấy: T(n) = 3 + n (lệnh 3 không lần, lệnh 4 thực hiện n lần)

Suy ra thời gian tính trung bình là :

)1(2

691

32

)1(3)(

2+

++

=+

++

++

=

n

n n n

n n

n n n T

Phân tích thuật toán theo nghĩa hẹp ở đây là xác định T(n) trong trường hợp xấu nhất Phân tích thuật toán theo nghĩa rộng là việc lựa chọn thuật toán tốt: tốn ít bộ nhớ, và có thời gian tính trong trường hợp xấu nhất là chấp nhận được (tức là thỏa mãn tính khả thi)

Một số vấn đề đặt ra: Khi phân tích thuật toán, người ta ít khi quan tâm đến tính chính xác của hàm thời gian tính mà thường quan tâm đến độ tăng của hàm này

Ví dụ 1.3 Đánh giá hàm thời gian khi n tăng

Xét hàm thời gian T(n) = 60n2 + 9n + 19 Khi n tăng rất lớn thì T(n) ≈ 60n2

Giả sử T(n) được tính bằng giây, khi đó hàm T(n) trên đây tính bằng phút có dạng:

T = n2 + 0,15n + 0,316

Khi n tăng rất lớn thì T(n) ≈ n2

Khi đó ta nói rằng T(n) có thời gian tính tương đương với hàm n2 , hay T(n) là VCL (vô cùng lớn) cùng bậc với n2, và ta viết T(n) = O(n2) Kí hiệu O đọc là kí hiệu big-O Ở dưới đây ta có cách gọi khác, đó là T(n) có bậc không quá n2

Vậy trong quá trình phân tích thuật toán, ta cần tính T(n) theo kí hiệu Big-O

Trang 14

2.3 Khái niệm độ phức tạp của thuật toán, kí hiệu big-O

Định nghĩa 1.4. Cho f và g là hai hàm đối số nguyên dương

• Ta viết f(n) = O(g(n)) và nói f(n) có bậc không quá g(n) nếu tồn tại hằng số dương C 1 và

số nguyên N 1 sao cho

f(n) ≤ C 1 g(n) với n ≥ N 1 Theo cách viết giới hạn, điều này nghĩa là:

n f

1)(

)(lim

• Ta viết f(n) = (g(n)) và nói f(n) có bậc ít nhất là g(n) nếu tồn tại hằng số dương C 2 và

số nguyên dương N 2 sao cho

n g

n f n

• Ta viết f(n) = θ(g(n)) và nói f(n) có bậc là g(n) nếu f(n) = O(g(n)) và f(n) = (g(n)) Theo cách viết giới hạn, điều này nghĩa là: 0

)(

)(

g n

n f n

Theo định nghĩa trên, đánh giá thời gian tồi nhất của thuật toán chính là việc tính O(.), đánh giá thời gian tốt nhất của thuật toán là việc tính Ω(.)

Định nghĩa 1.5 Khi hàm thời gian tính T(n) của thuật toán được biểu diễn qua kí hiệu big-O thì

T(n) được gọi là độ phức tạp thuật toán (Complexity of Algorithms)

Ví dụ 1.4 Biểu diễn hàm thời gian theo các kí pháp big-O, omega, theta

Trang 15

STT Hàm Tên gọi: độ phức tạp Đánh giá

Ví dụ 1.5 Dùng kí hiệu θ đánh giá tốc độ tăng của hàm

a)

222

)1(

1)

(

2

++

=+

+

=

n

n n

++

n n

Trang 16

Chứng minh: Vì T(n) = O(C.f(n)) nên tồn tại số dương C1 và số nguyên N1 sao cho T(n) ≤ C.C1.f(n) với ∀ n ≥ N1 Khi đó chọn C2 = C.C1 thì T(n) ≤ C2.f(n) với ∀ n ≥ N1, hay T(n) = O(f(n))

3.2 Qui tắc cộng

Giả sử một thuật toán T gồm hai phần liên tiếp T 1 và T 2 Và, giả sử phần T 1 có thời gian thực hiện là T 1 (n) = O(f(n)); phần T 2 có thời gian thực hiện là T 2 (n) = O(g(n)) Khi đó thời gian thực hiện thuật toán sẽ là T(n) = T 1 (n) + T 2 (n) = O(f(n) + g(n))

Chứng minh: Vì T1 = O(f(n)) nên tồn tại hằng số dương C1 và số nguyên N1 sao cho T1(n) ≤

C1.f(n) với ∀ n ≥ N1 Và vì T2 = O(g(n)) nên tồn tại hằng số dương C2 và số nguyên N2 sao cho

T2(n) ≤ C2.g(n) với ∀ n ≥ N2 Chọn C0 = max(C1, C2) và N0 = max(N1, N2) thì với ∀ n ≥ N0 ta có: T(n) = T1(n) + T2(n) ≤ C1.f(n) + C2.g(n) ≤ C0.f(n) + C0.g(n) = C0(f(n)+g(n))

Do đó T(n) = O(f(n) + g(n))

3.3 Qui tắc lấy max

Nếu thuật toán T có thời gian thực hiện T(n) = O(f(n) + g(n)) thì có thể coi thời gian thực hiện thuật toán T có độ phức tạp là T(n) = O(max(f(n), g(n))

Chứng minh: Vì T(n) = O(f(n) + g(n)) nên tồn tại số dương C1 và số nguyên N1 sao cho với ∀ n

≥ N1 thì T(n) ≤ C1.(f(n) + g(n)) = C1.f(n) + C1.g(n) ≤ 2C1.max(f(n), g(n)) Do đó T(n) =

O(max(f(n), g(n)))

Chú ý: Qui tắc max rất hay được sử dụng Với qui tắc này:

• Nếu T(n) là một đa thức thì có thể khẳng định các toán hạng bậc thấp là không quan trọng, có thể bỏ qua khi đánh giá độ phức tạp thuật toán

• Trong một đoạn chương trình, câu lệnh được thực hiện nhiều nhất (được gọi là câu lệnh

đặc trưng) sẽ được sử dụng để đánh giá độ phức tạp thuật toán của đoạn chương trình đó,

mà không cần quan tâm đến các câu lệnh khác (điều này không đúng nếu tính thời gian thực hiện thuật toán cho toàn bộ đoạn chương trình) Câu lệnh đặc trưng thường là câu lệnh đơn nằm trong một vòng lặp ở mức sâu nhất Việc đánh giá độ phức tạp thuật toán

sử dụng câu lệnh đặc trưng sẽ được dùng đến từ phần áp dụng của chương 3, hiện tại không dùng đến để rèn luyện việc phân tích thuật toán

Ví dụ 1.6 Minh họa qui tắc max

a) T(n) = 3n + 4 (Trong Ví dụ 1.1) Ta có T(n) = 3n + 4n0 Æ T(n) = O(n)

Vậy thuật toán tính giá trị trung bình có độ phức tạp tuyến tính

b)

)1(

2

69)

+

++

=

n

n n

n

Trang 17

696

9)

1(

2

69

)

+

++

n

n n

n n n

3.4 Qui tắc nhân

Nếu đoạn thuật toán T có thời gian thực hiện T(n) = O(f(n)) Khi đó nếu thực hiện k(n) lần đoạn thuật toán T với k(n) = O(g(n)) thì độ phức tạp tính toán của quá trình lặp này là: T(n) = O(f(n).g(n))

Chứng minh: Thời gian thực hiện k(n) đoạn thuật toán T sẽ là k(n).T(n) Theo định nghĩa big-O

ta có:

- Tồn tại hằng số dương Ck và số nguyên Nk sao cho k(n) ≤ Ck.g(n) với ∀ n ≥ Nk

- Tồn tại hằng số dương Cr và số nguyên Nr sao cho T(n) ≤ Cr.f(n) với ∀ n ≥ Nr

Vậy nếu đặt N0 = max(Nk, Nr) và C0 = Ck.Cr thì với ∀ n ≥ N0 ta có: k(n).T(n) ≤ C0.f(n).g(n) hay

độ phức tạp tính toán của quá trình lặp là T(n) = O(f(n).g(n))

4 Các kỹ thuật đánh giá độ phức tạp thuật toán

4.1 Câu lệnh đơn

Câu lệnh đơn là câu lệnh thực hiện một thao tác, ví dụ câu lệnh gán đơn giản (không chứa lời gọi hàm trong biểu thức), câu lệnh vào/ra đơn giản, câu lệnh chuyển điều khiển đơn giản như break, goto, continue, return

Thời gian thực hiện một câu lệnh đơn không phụ thuộc vào kích thước dữ liệu nên sẽ là O(1) Nói cách khác, các câu lệnh đơn có thời gian tính bị chặn bởi hàm số O(1) (hay O(c)) Ví dụ mỗi câu lệnh sau đều có thời gian thực hiện là O(1): readln; writeln; readln(x); writeln(k);

4.2 Câu lệnh hợp thành

Thời gian thực hiện một câu lệnh hợp thành sẽ được tính theo qui tắc cộng và qui tắc max

Ví dụ 1.7 Minh họa qui tắc cộng

if n > 1 then begin

Trang 18

Hiển nhiên T(n) = 1 + 1 + 1 = 3 (đúng như qui tắc cộng) Æ T(n) = O(1)

Ví dụ 1.8 Minh họa qui tắc max đối với câu lệnh hợp thành

Đúng như qui tắc max: T(n) = O(max(1, n)) = O(n)

3.3 Câu lệnh lặp với số lần lặp biết trước for-do

for i := 1 to n do P(i);

Trong đó P(i) là một câu lệnh hoặc một khối lệnh (câu lệnh hợp thành) trong thân vòng lặp Có hai trường hợp:

Trường hợp 1: Thời gian thực hiện P(i) là một hằng số và không phụ thuộc vào i, nghĩa là T(P(i)) = t , với t là hằng số Khi đó thời gian thực hiện câu lệnh lặp là n lần thực hiện P(i), tức là:

t n n

T(P(i)) = 3 Do đó T(n) = n.3 Æ T(n) = O(n)

Trang 19

Trường hợp 2: Thời gian thực hiện của P(i) phụ thuộc vào i, nghĩa là T(P(i)) = t(i) Khi đó thời gian thực hiện câu lệnh lặp “for i” với i lần lượt nhận giá trị từ 1 đến n là T(n) = t(1) + t(2) + … + t(n), hay ta có:

=

= n

i i t n T

1)()

(

Ví dụ 1.10 Đánh giá thời gian tính của vòng lặp khi P(i) phụ thuộc i

for i := 1 to n do begin for j:=1 to i do

)(

)

(

2

12

12

)1()

1(

2 1

n n i n i

=+

=+

Thời gian kiểm tra điều kiện thường là hằng số, tức là O(1)

Ví dụ 1.11 Minh họa thời gian tính của câu lệnh rẽ nhánh

3.5 Câu lệnh lặp với số lần lặp chưa biết trước while, repeat

Để đánh giá thời gian thực hiện câu lệnh lặp này ta dựa vào kinh nghiệm:

Ví dụ 1.12 Thời gian tính đối với vòng lặp while đơn giản

Trang 20

T((P(i)) = 4 Æ T(n) = 2 + 4.T(P(i)) = 2 + 4n Æ T(n) = O(n)

Ví dụ 1.13 Độ phức tạp của vòng while mà biến điều khiển thay đổi không liên tục

cơ bản end;

Phân tích, đánh giá: P(i) gồm 4 câu lệnh cơ bản 3, 4, 5, và 6

Trang 21

- Lần 1: i = n/20 Æ thực hiện n + 2 câu lệnh cơ bản

- Lần 2: i = n/21 Æ thực hiện n/2 + 2 câu lệnh cơ bản

- Lần 3 i = n/22 Æ thực hiện n/22 + 2 câu lệnh cơ bản

- …

- Lần k: i = n/2k-1 Æ thực hiện n/2k + 2 câu lệnh cơ bản

Nếu đây là lần thực hiện cuối cùng thì n/2k-1 = 1 Ù n = n/2k-1 Ù k = log2n + 1 Khi đó:

1 2

2

1 2

2 2 log

2

4

2

1 1 2

1 1 2 log

2

2

) 2

1

2

1 2

1 1 ( 2 2

=

− + + +

=

+ + + + + +

n n

n k n

!11

2

n

x x

x e

Trang 22

b) T(n) = 2 + 2*n Æ T(n) = O(n)

Bài toán 1.2 Thuật toán tìm kiếm tuần tự

Cho dãy gồm n phần tử a1, a2, , an Hãy đưa ra vị trí của phần tử đầu tiên bằng phần tử đứng ngay trước đó trong dãy

a) Thiết kế giải thuật

while (i<=n) and not found do

3 if a[i] = a[i-1] then found := true

⎫P(i)

5 if found then write(‘Vi tri can tim: ‘, i)

6 else write(‘khong co phan tu nao nhu vay’);

1 lần

Trong trường hợp xấu nhất, lệnh rẽ nhánh đủ - thân vòng lặp while thực hiện n-1 lần Do đó ta có T(n) = 2 + (n-1) + 1 = n + 2 Æ T(n) = O(n)

Bài toán 1.3 Thuật toán tìm kiếm nhị phân

Cho dãy n số a1, a2, , an đã được sắp xếp tăng Hãy đưa ra vị trí của phần tử trong dãy có giá trị bằng x cho trước

Giải

Trang 23

while (d <= c) and not found do begin

4 k := (d+c) div 2;

5 if x < a[k] then c := k - 1

6 else if x > a[k] then d := k + 1

⎫P(i)

end;

8 if found then write(‘Tim thay o vi tri ‘, k)

9 else write(‘khong co x trong day’);

1 lần

Trong trường hợp xấu nhất, không có x trong dãy, ta cần tính số lần thực hiện khối lệnh P(i) trong thân vòng lặp, gồm 2 lệnh cơ bản Vì mỗi lần đi qua vòng lặp độ dài của dãy giảm đi một nửa, nên sau vòng lặp thứ k độ dài của dãy còn là n/2k Vòng lặp kết thúc tại lần thứ k mà độ dài còn lại của dãy là n/2k= 1 hay k = log2n Khi đó:

T(n) = 4 + 2k = 4 + 2log2n ≤ log2n + 2log2n với ∀ n ≥ N0 = 3 T(n) ≤ 3log2n Æ T(n) = O(log2n)

Bài toán 1.4 Thuật toán sắp xếp chọn lựa

Cho dãy (a) gồm n số a1, a2, , an Hãy sắp xếp dãy (a) theo thứ tự không giảm

Thuật toán sắp xếp chọn trực tiếp kinh điển (chưa tối ưu)

if (k<>i) then begin

Xét thuật toán trong trường hợp tồi nhất: dãy (a) đã được sắp xếp không tăng Ta cần đánh giá được số lần thực hiện hai câu lệnh cơ bản 3 và 4, do đó tính được thời gian P(i) để thực hiện các câu lệnh for j phụ thuộc vào i

Trang 24

- i = 1: hai câu lệnh 3 và 4 thực hiện n - 1 lần

- i = 2: hai câu lệnh 3 và 4 thực hiện n - 2 lần

- …

- i = n-1: hai câu lệnh 3 và 4 thực hiện n- (n-1) = 1 lần

Vậy T(P(i)) = 1 + 2 + … + (n-2) + (n-1) = n(n-1)/2 (ta đặt bằng p)

Ta thấy khi thay đổi thuật toán, độ phức tạp không thay đổi nhưng thời gian tính toán ít hơn

6 Phân tích chương trình (con) đệ qui

6.1 Khái niệm về đệ qui

Khái niệm về đề qui dẫn đến một loạt các khái niệm như bài toán đệ qui, lời giải đệ qui, thuật toán đệ qui và cuối cùng là chương trình con đệ qui

Ta nói: một đối tượng là đệ qui khi nó bao gồm chính nó như một bộ phận hoặc nó được định nghĩa dưới dạng chính nó

Bài toán T gọi là bài toán đệ qui nếu nó được giải bằng một bài toán T’ có dạng giống như T, nói cách khác T là bài toán được giải bằng một thuật toán đệ qui

Trang 25

Ví dụ về hình ảnh đệ qui: giả sử cần xác định một cái túi: Cần lấy một cái túi mà nó đựng trong một cái túi thứ hai mà cái túi thứ hai này là một cái túi mà nó đựng trong một cái túi thứ ba, cái túi thứ ba là một cái túi mà nó đựng trong cái túi thứ tư, Tuy nhiên quá trình các cái túi chứa trong nhau ấy không thể vô hạn, đến một cái túi thứ n hữu hạn nào đó thì nó không đựng trong một cái túi nào nữa Cái túi thứ n này gọi là cái túi “neo”, các cái túi còn lại gọi là các cái túi được xác định một cách đệ qui

Trong toán học, ta gặp rất nhiều định nghĩa đệ qui mà thường là các công thức để tính giá trị cho một hàm số nào đó có thể tính được bằng qui nạp toán học (hay công thức truy hồi)

1(

01

)

(

n if n

f n

n if n

f

Như vậy bài toán T tính f(n) được giải dựa vào bài toán T’ tính f(n-1) có dạng giống như T Bài toán T’ tính f(n-1) lại được giải dựa vào bài toán T” tính f(n-2) có dạng giống như T’ (hoặc như T), cứ tiếp tục quá trình đệ qui đó và cuối cùng đến phần “neo”, ta nhận được bài toán Tn’ được giải hoàn toàn khác, đó là f(0) = 1

6.2 Chương trình (con) đệ qui

Chương trình con thể hiện một thuật toán đệ qui gọi là chương trình (con) đệ qui Định nghĩa một chương trình con đệ qui phản ánh chính xác định nghĩa công thức đệ qui, nghĩa là gồm hai phần

• Phần neo: Lời gọi hàm hay thủ tục được thực hiện bằng một lời giải đã biết

• Phần đệ qui: Lời gọi chính hàm hay thủ tục đó nhưng có kích thước dữ liệu đầu vào thay đổi theo xu hướng (thường là nhỏ hơn) để quá trình đệ qui dẫn đến phần neo

Ví dụ 1.16 Chương trình (con) đệ qui tính hàm giaithua(n) = n!

function giaithua(n:integer): longint;

begin

if n = 0 then giaithua := 1 else giaithua := n*giaithua(n-1);

end;

6.3 Xây dựng phương trình (công thức) đệ qui

Phương trình đệ qui là phương trình thể hiện mối quan hệ giữa T(n) và T(k) Trong đó T(n) là thời gian thực hiện thuật toán với dữ liệu vào kích thước là n, T(k) là thời gian thực hiện chính thuật toán đó nhưng với dữ liệu kích thước là k

Ví dụ 1.17 Xây dựng phương trình đệ qui tính hàm giaithua(n)

Trang 26

Gọi T(n) là thời gian thực hiện thuật toán tính hàm giaithua(n) Ta có

- n = 0: T(n) = 2 (là thời gian thực hiện việc kiểm tra điều kiện và lệnh gán giaithua := 1)

- n > 0: Hàm gọi tới hàm giaithua(n-1) để tính (n-1)! mất thời gian là T(n-1) Sau khi có kết quả của giaithua(n-1) thì cần thực hiện thêm một phép nhân và một phép gán, mất một thời gian là một hằng số c, do đó T(n) = T(n-1) + c;

Vậy phương trình đệ qui tính hàm giaithua(n) là:

1(

02

)

(

n if c n

T

n if n

T

Ví dụ 1.18 Viết phương trình đệ quy tính số hạng Fibonaci thứ n

Dãy Fibonaci có dạng 1, 1, 2, 3, 5, 8, 13, 21, 35, …

Gọi f(n) là giá trị của số hạng Fibonaci thứ n

a) Định nghĩa qui tính hàm f(n) như sau:

=

2)

2()1(

21

)

(

n if n

f n

f

n if n

f

b) Chương trình con đệ qui tính hàm fibonaci(n):

function fibonaci(n: integer) : longint;

begin

if n <= 2 then fibonaci := 1 else fibonaci := fibonaci(n-1) + fibonaci(n-2);

=

2)

2()1(

22

)

(

n if c n

T n

T

n if n

T

Trong đó hằng số dương c là thời gian thực hiện phép cộng và phép gán

6.4 Giải phương trình đệ qui và Định lí Thợ

Giải phương trình đệ qui thực chất là tiến hành đánh giá độ phức tạp của thuật toán đệ qui

Trang 27

02

)

(

n if c n

T

n if n

Ví dụ 1.20 Độ phức tạp đệ qui khi lời gọi đệ qui giảm 1/2 kích thước dữ liệu đầu vào

Giải phương trình đệ qui

2/(

1)

n if c n T

n if c n

Do đó T(n) = O(log2n)

Ví dụ 1.21 Thuật toán đệ qui có độ phức tạp O(nlog2n)

Giải phương trình đệ qui

)2/(2

1)

n if n c n T

n if c n

Trang 28

T(n) = c1.2k + k.c = c1.2log2(n) + c.n.log2n = c1.n + c.n.log2n

Do đó theo qui tắc cộng và qui tắc max ta có T(n) = O(nlog2n)

b) Sử dụng phương trình đặc trưng

Định nghĩa phương trình đặc trưng

Cho phương trình đệ qui có dạng

trong đó α, β là các số được xác định bởi điều kiện “ neo” và điều kiện đầu

Ví dụ 1.22 Đánh giá độ phức tạp thuật toán đệ qui bằng phương trình đặc trưng

Giải phương trình đệ qui sau

2(6)1(5

16

02

)

(

n if n

T n

T

n if

n if n

Trang 29

=+

=

=+

=

6)

1

(

2)

0

(

1 2

1 1

0 2

0 1

r r T

r r T

βα

βα

=+

632

2

βα

βα

0

βα

Vậy T(n) = 0 2n + 2.3n = 2.3n Æ T(n) = O(3n)

Ví dụ 1.22 Đánh giá độ phức tạp thuật toán đệ qui bằng phương trình đặc trưng

Giải phương trình đệ qui sau:

2(9)1(6

17

01

)

(

n if n

T n

T

n if

n if n

=

=+

=

7.1.)

1

(

1.0.)

0

(

1 2

1 1

0 2

0 1

r r

T

r r

T

βα

βα

=7.1.33

1

βα

1

βα

• Trường hợp 1: Nếu a > bk thì T(n)=θ(n log a b )

• Trường hợp 2: Nếu a = bk thì T(n)=θ(n k.log2n)

• Trường hợp 3: Nếu a < bk thì T(n)=θ(n k)

Ví dụ 1.24 Minh họa vận dụng định lí thợ trường hợp 1

Giải phương trình đệ qui

Trang 30

Ví dụ 1.25 Minh họa vận dụng định lí thợ trường hợp 2

Giải phương trình đệ qui

Ví dụ 1.26 Minh họa vận dụng định lí thợ trường hợp 3

Giải phương trình đệ qui

Trang 32

10 Đưa ra một thuật toán tìm phần tử lớn nhất của một dãy hữu hạn số thực

a) Mô tả thuật toán của bạn bằng các cách khác nhau (mô tả, sơ đồ khối, ngôn ngữ tựa Pascal) b) Xác định số phép tính nhiều nhất phải thực hiện trong thuật toán trên

11 Mô tả thuật toán xếp lại một dãy theo thứ tự tăng dần

a) Mô tả thuật toán của bạn bằng các cách khác nhau (mô tả, sơ đồ khối, ngôn ngữ tựa Pascal) b) Xác định số phép tính nhiều nhất phải thực hiện trong thuật toán trên

12 Mô tả thuật toán tìm một dãy các số liên tiếp nhau có tổng dương trong một dãy số thực cho trước

a) Mô tả thuật toán của bạn bằng các cách khác nhau (mô tả, sơ đồ khối, ngôn ngữ tựa Pascal)

b) Xác định số phép tính nhiều nhất phải thực hiện trong thuật toán trên

Trang 33

Chương 2 CHIA ĐỂ TRỊ

Ý tưởng của phương pháp chia để trị (Divide & Conquer) là giải quyết bài toán thành 3 bước

- Chia: Chia bài toán thành các bài toán con có kích thước nhỏ hơn

- Trị: Giải các bài toán con một cách độc lập

- Tổng hợp: Tổng hợp các kết quả của các bài toán con để thu được lời giải của bài toán

ban đầu

1 Sơ đồ chung của thuật toán chia để trị

1.1 Thuật toán β

Ta xét bài toán tổng quát P với kích thước dữ liệu vào là n

Giả sử có thuật toán α để giải bài toán P với thời gian bị chặn bởi c.n 2

Xét một thuật toán β khác giải chính bài toán P đã cho theo ba bước sau:

- Chia: Chia bài toán thành 3 bài toán con kích thước n/2

- Trị: giải 3 bài toán con theo thuật toán α

- Tổng hợp lời giải các bài toán con

Giả sử thời gian chia và tổng hợp các bài toán con là tuyến tính, tức là có độ phức tạp đa thức

O(n) hay d.n

Khi đó:

Tα = cn2 = (3/4)cn2 + (1/4)cn2 Æ T(n) = O(n2)

Tβ = 3Tα + dn = 3c(n/2)2 + dn = (3/4)cn2 + dn Æ T(n) = O(n2)

(Tức là Tβ bằng tổng của thời gian trị (3/4)cn2 + thời gian tổng hợp dn)

Từ đó, nếu dn < (1/4)cn2 Ù d < cn2/4 Ù n > 4d/c thì thuật toán β nhanh hơn thuật toán α Điều này luôn đúng với n đủ lớn Tuy nhiên ta thấy thuật toán β mới chỉ thay đổi được nhân tử hằng

số chưa thay đổi được bậc nhưng cũng hiệu quả khi n lớn Nói cách khác, độ phức tạp thuật toán không thay đổi, nhưng thời gian thực hiện thuật toán được cải thiện

Thủ tục Beta dưới đây thể hiện thuật toán β

begin

i Chia bài toán thành ba bài toán con kích thước n/2;

iii Tổng hợp lời giải của các bài toán con;

Trang 34

end;

Việc chia để trị rõ ràng có xu hướng làm giảm thời gian tính toán Vì thế các bài toán con nên tiếp tục được chia nhỏ như thế khi còn có lợi, tức là khi còn thỏa mãn điều kiện n > 4dc Nói cách khác, các bài toán con sẽ tiếp tục được chia nhỏ cho đến khi việc chia đó không làm giảm thời gian tính (tức là khi n ≤ 4dc) thì dừng lại Điều này được thể hiện trong thuật toán γ dưới đây

1.2 Thuật toán γ

Thủ tục Gamma dưới đây thể hiện thuật toán γ

begin

if n ≤ n0 then {việc chia không còn lợi nữa thì giải trực tiếp}

else begin

i Chia bài toán thành ba bài toán con kích thước n/2;

iii Tổng hợp lời giải của các bài toán con;

end;

end;

Nhận xét:

- Nếu bài toán P giải bởi thuật toán α thì không có chia để trị

- Nếu bài toán P giải bằng thuật toán β thì được việc chia để trị được thực hiện một lần, trong đó

có 3 bài toán con được chia, và được giải bằng thuật toán α

- Nếu bài toán P được giải bằng thuật toán γ (thay vì thuật toán β) thì quá trình chia để trị được thực hiện nhiều lần nếu thời gian tính vẫn tốt hơn, mỗi bài toán con sử dụng chính thuật toán γ của bài toán mẹ, tức là thực hiện bởi một lời giải đệ qui

Ta có phương trình đệ qui sau:

if dn

n T

c

d n

if

cn n

T

4 )

2 ( 3

4 )

(

2

γ γ

Trang 35

Phương trình đệ qui có dạng Tγ(n) = a.Tγ(n/b) + c.nk thỏa mãn điều kiện của định lí thợ với a =

3, b = 2, c = d và k = 1 Mặt khác, vì 3 = a > bk = 2 nên định lí thợ rơi vào trường hợp thứ nhất,

do đó T ( n ) = θ ( nlog a b ) Ù T ( n ) = θ ( nlog23) ≈ θ ( n1 589 )

Thuật toán γ thu được có thời gian tính là tốt hơn cả thuật toán α và thuật toán β Hiệu quả thu được trong thuật toán γ có được là nhờ ta đã khai thác triệt để hiệu quả của việc sử dụng thuật toán β

1.3 Thuật toán γ tổng quát

Để có được một mô tả chi tiết thuật toán chia để trị chúng ta cần phải xác định 4 tham số:

1 n0: giá trị neo, là điểm kết thúc quá trình chia bài toán con

2 k: kích thước của mỗi bài toán con trong cách chia

3 r: số lượng các bài toán con được chia tại mỗi lần thực hiện

4 Thuật toán tổng hợp lời giải của các bài toán con

Chia như thế nào (xác định r và k) là căn cứ vào mục đích thời gian thực hiện thuật toán tổng hợp là nhỏ (thường là tuyến tính)

Ta có thủ tục DivideAndConquer thể hiện thuật toán γ tổng quát như sau:

begin

if n <= n0 then

else begin

i Chia bài toán thành r bài toán con kích thước n/k;

ii for (r bài toán con) do DivideAndConquer(n/k);

iii Tổng hợp lời giải của các bài toán con;

end;

end;

2 Một số ví dụ minh họa Chia để trị

2.1 Thuật toán sắp xếp trộn (Merge Sort)

Bài toán 2.1 Cho dãy (a) gồm n phần tử a1, a2, , an Hãy sắp xếp dãy (a) theo thứ tự không giảm bằng thuật toán sắp xếp kiểu trộn

Thuật toán γ được vận dụng giải bài toán như sau

procedure MergeSort(a, n) ;

Trang 36

begin

if n = 1 then return a;

if U[i]<V[j] then

begin T[k]:=U[i];

i:=i+1;

end else begin T[k]:=V[j];

j:=j+1;

end;

end;

if n < m then <Nối đoạn còn lại của mảng V vào cuối mảng T>

else <Nối đoạn còn lại của mảng U vào cuối mảng T>;

end;

Giải thuật sắp xếp này minh hoạ tất cả các khía cạnh của chia để trị Khi số lượng các phần tử cần sắp là nhỏ thì ta thường sử dụng các giải thuật sắp xếp đơn giản Khi số phần tử đủ lớn thì ta chia mảng ra hai phần, tiếp đến trị từng phần một và cuối cùng là kết hợp các lời giải

Trang 37

Giả sử T(n) là thời gian cần thiết để giải thuật này sắp xếp một mảng n phần tử Việc tách T thành U và V là tuyến tính Ta cũng dễ thấy merge(U,V,T) cũng tuyến tính Dễ viết được phương trình đệ qui:

2(2

1)

(

n if dn

n T

n if c n

T

Phương trình đệ qui có dạng T(n) = a.T(n/b) + c.nk thỏa mãn điều kiện của định lí thợ với a = 2,

b = 2, c = d và k = 1 Mặt khác, vì 2 = a = bk = 2 nên định lí thợ rơi vào trường hợp thứ hai, do

đó T(n)=θ(n k.log2n) Ù T(n)=θ(nlog2n)

Khi xét thuật toán sắp xếp kiẻu vun đống (HeapSort) ta thấy hiệu quả của MergeSort tương tự HeapSort Trong thực tế sắp xếp trộn có thể nhanh hơn vun đống một ít nhưng nó cần nhiều bộ nhớ hơn cho các mảng trung gian U và V Ta nhớ lại HeapSort có thể sắp xếp tại chỗ (in-place),

và cảm giác nó chỉ sử dụng một ít biến phụ Theo lý thuyết, MergeSort cũng có thể làm được như vậy, tuy nhiên chi phí về thời gian sắp xếp có tăng một chút

2.2 Thuật toán sắp xếp nhanh (QuickSort)

Bài toán 2.2 Cho dãy (a) gồm n phần tử a1, a2, , an Hãy sắp xếp dãy (a) theo thứ tự không giảm bằng thuật toán sắp xếp kiểu QuickSort

QuickSort được phát minh bởi Hoare, dựa theo nguyên tắc chia để trị Không giống như MergeSort, QuickSort quan tâm đến việc giải các bài toán con hơn là sự kết hợp giữa các lời giải của chúng

Ý tưởng của thuật toán QuickSort như sau: Nếu đoạn cần sắp xếp chỉ có một phần tử thì đoạn đó

đã được sắp xếp, ngược lại ta chọn một phần tử x trong đoạn làm phần tử “chốt”, mọi phần tử

nhỏ hơn chốt được xếp vào vị trí đứng trước chốt, mọi phần tử lớn hơn chốt được xếp vào vị trí đứng sau chốt Sau phép toán chuyển như vậy thì đoạn được chia thành hai đoạn con mà đoạn trước gồm các phần tử nhỏ hơn chốt, đoạn sau gồm các phần tử lớn hơn chốt Tiếp tục áp dụng thuật toán đã làm (như đối với đoạn đầu tiên) cho hai đoạn con, và cứ tiếp tục một cách đệ qui như thế ta sẽ thu được toàn đoạn được sắp

Ý tưởng cụ thể như sau: Giả sử cần sắp xếp đoạn có chỉ số từ L (Left) đến R (Right):

- Chọn x làm phần tử ngẫu nhiên trong đoạn L R, có thể chọn x là phần tử ở giữa đoạn, tức là phần tử x = a[(L+R) div 2]

- Cho i chạy từ L sang phải; j chạy từ R sang trái, nếu gặp một cặp phải tử sai thứ tự, tức i ≤ j mà a[i] > x và a[j] < x thì tiến hành đổi chỗ hai phần tử đó Quá trình này còn tiếp tục khi i > j thì

dừng

- Tiếp tục làm như thế đối với 2 đoạn từ L đến j và từ i đến R

Thuật toán γ vận dụng cho thuật toán QuickSort được diễn tả trong thủ tục QuickSort sau đây đối

với mảng a từ vị trí L đến R:

Trang 38

procedure Quicksort(L, R); (* Sắp xếp theo thứ tự không giảm *)

begin

i := L; j := R;

x := a[(L + R) div 2];

while i <= j do begin

while (a[i] < x) and (i < R) do i := i + 1;

while (a[j] > x) and (j > L) do j := j -1;

if i <= j then begin

tmp := a[i]; a[i] := a[j]; a[j] := tmp;

Gọi T(n) là thời gian trung bình dùng Quicksort để sắp mảng n phần tử a[1 n] Trường hợp riêng

như thuật toán trên, thời gian để xác định phần tử chốt ở vị trí m = (L+R) div 2 bằng hằng số c; trường hợp tổng quát, phần tử chốt ở vị trí m nằm trong đoạn từ 1 đến n, có xác suất là 1/n, và thời gian để tìm m có thể xác định bởi một hàm tuyến tính g(n) Giả sử thời gian dùng đệ qui để sắp xếp hai mảng con kích thước (m - 1) và (n - m) tương ứng là T(m-1) và T(n-l) Như vậy với

−+

k

n m

n m

k T n n g n m n T m

T n g n n

T

0 1

1

)(

2)(

1))()1()((

1)

k k T n dn n

T

0)(

2)

Trang 39

Công thức như trên khó phân tích độ phức tạp, vì thế ta thừa nhận kết quả sau: Thời gian trung bình để thực hiện thuật toán QuickSort là T(n) = O(nlog2n)

Gọi T(n) là thời gian thực hiện thuật toán Nhan(X, Y, n) thực hiện phép nhân hai số X và Y với

số chữ số n; Thời gian giải mỗi bài con (4 bài toán con) thực hiện phép nhân hai số X và Y với

Trang 40

số chữ số n/2 sẽ là T(n/2); Thời gian tổng hợp kết quả là dn Khi đó ta có phương trình đệ qui tính T(n) là:

2/(4

1)

(

n if dn n

T

n if c n

T

Phương trình đệ qui có dạng T(n) = a.T(n/b) + c.nk thỏa mãn điều kiện của định lí thợ với a = 4,

b = 2, c = d và k = 1 Mặt khác, vì 4 = a > bk = 2 nên định lí thợ rơi vào trường hợp thứ nhất, do

2/(3

1)

(

n if dn n

T

n if c n

T

Phương trình đệ qui có dạng T(n) = a.T(n/b) + c.nk thỏa mãn điều kiện của định lí thợ với a = 3,

b = 2, c = d và k = 1 Mặt khác, vì 3 = a > bk = 2 nên định lí thợ vẫn rơi vào trường hợp thứ nhất,

Cách thứ nhất: Phương pháp liệt kê toàn bộ (duyệt toàn bộ)

procedure maxsub(n, var p, q);

begin

max := -∞;

for i := 1 to n do for j := i to n do begin (*xet mảng con a[i j]*)

s := 0;

Ngày đăng: 24/12/2014, 06:19

TỪ KHÓA LIÊN QUAN

TRÍCH ĐOẠN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w