bài báo cáo môn phân tích thuật toán chia để trị
Trang 1Nhóm thực hiện Trần Đình Anh Huy 0811062
Nguyễn Hoàng Quốc 0811300 GV: TS Trần Nam Dũng
Đại Học Khoa Học Tự Nhiên Hồ Chí Minh
Khoa Toán – Tin Học
Bài Báo Cáo Môn Phân Tích Thuật Toán
Trang 2Tóm tắt nội dung
Chia để trị là một mô hình thiết kế thuật toán rất quan trọng trong ngành khoa học máy tính Mô hình sử dụng chủ yếu giải thuật đệ quy, được sử dụng phổ biến để giải quyết các vấn đề , bài toán phức tạp nhằm mục đích giảm chi phí của bài toán đến mức tối ưu có thể Tư tưởng của phương pháp chia để trị hình thành rất sớm (khoảng 200 năm trước công nguyên1) từ một bài toán sắp xếp các mặt hàng một cách đơn giản của người Babylon Trong khuôn khổ bài báo cáo môn học, nhóm thực hiện chỉ nêu lên những vấn đề cơ bản của phương pháp này
1 theo http:// en.wikipedia
Trang 3Mục lục
1.1 Tại Sao Phải Chia 3
1.1.1 Ưu Điểm 3
1.1.2 Nhược Điểm 3
1.2 Các Bước Thực Hiện 4
1.3 Các Vấn Đề Cài Đặt 5
1.3.1 Chia Ra Nhiều Sẽ Dễ Trị? 5
1.3.2 Mối Quan Hệ Với Đệ Quy 6
1.3.3 Các Vấn Đề Về Cài Đặt 6
2 Những Bài Toán Sử Dụng Phương Pháp Chia Để Trị 7 2.1 Thuật Toán Tìm Kiếm 7
2.1.1 Quick Sort 7
2.1.2 Merge Sort 8
2.2 Nhân 2 Số Nhị Phân N Bit 10
2.3 Đọc Ảnh Vệ Tinh 11
2.3.1 Vấn Đề 11
2.3.2 Ý tưởng 11
2.3.3 Thực Hiện 11
Trang 4Danh sách hình vẽ
1.1 Các bước của mô hình chia để trị 4
2.1 một minh họa cho thuật toán Merge Sort 9
2.2 Mô hình cây nhị nhân 2 chiều 12
2.3 Minh họa các bước tìm kiếm 13
2.4 Minh họa các bước tìm kiếm, màu xanh là các vùng bị cách ly nhanh chóng 14
Trang 5Chương 1
Tư Tưởng Chia Để Trị
Tư tưởng chính mô hình chia để trị là chia một bài toán (một vấn đề) thành hai hay nhiều bài toán (vấn đề) nhỏ hơn cùng loại hoặc liên quan với nhau Cho đến khi kết quả bài toán (vấn đề) đó có được từ cách tổng hợp kết quả của những bài toán (vấn đề) cơ sở hoặc đơn giản có thể giải quyết một cách trực tiếp dễ dàng Nguyên lý mô hình Chia Để Trị giải xử
lý vấn đề từ trên xuống Ta cũng có một mô hình khác giải quyết theo cách ngược lại đó là mô hình Quy Hoạch Động xử lý vấn đề từ dưới lên
1.1.1 Ưu Điểm
Đối với tin học mô hình chia để trị ngày này được sử dụng ngày càng mạnh mẻ trong tin học dựa trên sự phát triển của công nghệ và sự ra đời các bộ xử lý đa luồng, các mô hình tính toán song song Giúp các vấn đề nhỏ được xử lý gần như một lúc, giúp giảm thiểu thời gian và chi phí thực thi đi gấp nhiều lần, đây là một trong những ưu điểm chính của mô hình chia để trị Hơn thế nữa, mô hình thuật toán còn giúp tận dụng bộ nhớ đêm (cache) một cách hiểu quả, đó là kết quả của việc chia nhỏ vấn đề mà bản thân các vấn để đó (đủ nhỏ đến mức cần thiết) có thể giải quyết được trên
bộ nhớ cache, không cần gửi thông tin đến bộ nhớ truy cập
1.1.2 Nhược Điểm
Chia để trị có một nhược điểm khá lớn đó là chia để trị không thể lưu lại kết quả của những vấn đề đã giải quyết cho lần yêu cầu tiếp theo, vì vậy
ta phải xem xét lại vấn đề bài toán có nên sử dụng chia để trị hay không
Trang 6Hình 1.1: Các bước của mô hình chia để trị
Một bài toán áp dụng được chia để trị tốt nhất là một bài toán có thể chia
nhỏ thành nhiều vấn đề nhỏ khác cùng loại và trong quá trình giải quyết
vấn đề số lần giải quyết lại cùng một vấn để đã giải quyết là cực tiểu
Các bước thiết kế thuật toán:
1 Chia vấn đề thành các vấn đề con
2 Giải quyết vấn đề con một cách đệ quy, nếu vấn đề con có kích thước
đủ nhỏ thì giải quyết một cách trực tiếp
3 Tổng hợp các kết quả của vấn đề con là kết quả của vấn đề cần tìm
Ví dụ: cho mảng A gồm các số thực được sắp xếp tăng dần: A = [a1, a2, a3, , an] Tìm một phần tử của mảng có giá trị là x
Giải quyết cách đơn giản nhất là vét cạn, duyệt hết các vị trí trong mảng
từ đầu mảng Với cách làm này thì độ phức tạp thuật toán sẽ là O(n) Trong
thực tế có những mảng dữ liệu lên tới hàng tỷ phần tử , điều đó có nghĩa
là chi phí của thuật toán sẽ rất lớn, ta sẽ phải tìm một thuật toán khác có
độ phức tạp thấp hơn để giảm chi phí một cách đáng kể Đối với chia để trị
ta sẽ sử dụng thuật toán BinarySearch để minh họa và so sánh
Đặt F (i) là kết quả của việc xem xét phần tử giá trị x có nằm trong
mảng dữ liệu đang xét hay không và xuất ra vị trí Với BinarySearch cứ chia
đôi dãy ra, theo đó ta có hệ thức truy hồi:
F (n) = F (n − 1) + F (n − 2) + + F (1)
Trang 7Với F (1) là kết quả của việc xét xem ai có bằng giá trị x hay không Khi đã
có được kết quả của F (1), F (2) ta truy hồi lại kết quả của F (n)
Gọi T (n) là độ phức tạp thuật toán, ta có hệ thức truy hồi: T (n) =
T (n2) + 1và T (1) = 1 Giả sử n = 2k,theo hệ thức truy hồi:
T (n) = T (n
2) + 1 = T (
n
22) + 2 = = T (n
2k) + k
Theo cách đặt đó ta có: n = 2k ,T (1) = 1, T (n) = 1 + k mà k = log2(n) ⇒
T (n) = log2(n) + 1 ⇒ T (n) = O(log2(n))
1.3.1 Chia Ra Nhiều Sẽ Dễ Trị?
Trong đa số các bài toán chia để trị trên lý thuyết người ta thường chia nhỏ vấn đề đến mức tối đa để dễ dàng giải quyết Chúng ta hãy cùng xem xét 1 câu hỏi: chia càng nhỏ có hẳn là đã tốt không?
Nhắc lại: Thuật toán chia để trị thường có dạng phân rã 1 vấn đề có kích thước n thành a vấn đề kích thước n
b rồi tổ hợp kết quả trong O(nd)với
a, b, d > 0( trong phép nhân thì a = 3, b = 2, d = 1).Thời gian chạy có thể được tính bằng công thức T (n) = aT (n
b) + O(nd) Chiều cao của cây tạo ra
là logbn xét lại bài toán phép nhân khi chưa rút gọn a(a = 4, b = 2, d = 1)
có thời gian chạy là O(n2)bây giờ ta thay b = 4
x = x4+ x3.2n/4+ x2.2n/2 + x1.23n/4
y = y4+ y3.2n/4+ y2.2n/2+ y1.23n/4
xy = (x4+ x3.2n/4+ x2.2n/2+ x1.23n/4)(y4+ y3.2n/4 + y2.2n/2+ y1.23n/4) Nhìn vào biểu thức ta thấy a = 16, b = 4, d = 1, logba = 2 > d nên
T (n) = O(nlog b a) = O(n2) Vậy độ phức tạp bằng với phương pháp chia đôi (b = 2) Tuy nhiên biểu thức tổ hợp của ta lại phức tạp và khó cài đặt hơn khi b = 2 Vậy chia nhỏ chưa hẳn đã tốt hơn
Định lý 1.3.1 (Định Lý Master) Nếu T (n) = aT ([n
b]) + O(nd) với a > 0,
b > 1và d ≥ 0 thì:
T (n) = O(nd) nếu d > logba
T (n) = O(ndlogn) nếu d = logba
T (n) = O(nlogb a) nếu d < logba
Trang 8Vậy theo định lý Master, cách chia tối ưu nhất là chia sao cho d = logab Ngoài ra, để dễ dàng hơn, người ta còn chia theo định nghĩa đệ quy của
nó Có thể lấy ví dụ lại bài toán luỹ thừa, bây giờ ta muốn luỹ thừa xn, với
nkhá lớn Như ta đã biết, luỹ thừa có thể dễ dàng tách ra từ Aa+bthành Aa
và Ab, do đó ta có định nghĩa đệ quu cho nó như sau:
xn =
1 nếu n=0 (xn2)2 nếu n chẵn
xn−1x nếu n lẻ
Dễ thấy khi có biểu thức trên, rõ ràng n đã giảm một nữa, khi n khá lớn, đây là một sự giảm đáng kể
1.3.2 Mối Quan Hệ Với Đệ Quy
Đệ Qui
Để có thể hiểu được mối quan hệ giữa chia để trị với đệ quy thì ta cần xét những điểm mạnh của đệ quy Đệ quy mạnh ở chỗ có thể định nghĩa một tập rất lớn các tác động chỉ bởi số rất ít mệnh đề, rất thích hợp để giải quyết những bài toán có tính chất đệ qui Khi dùng đệ quy, bài toán giải quyết sẽ sáng sủa, dễ hiểu hơn Từ đó có thể nói bản chất của của đệ quy là giải quyết bài toán theo kiểu qui nạp, hạ bậc (ThS.Trần Đức Huyên1), điều này rất có ý nghĩa trong việc chúng ta chia bài toán ra để "trị"
Theo như hiện nay, nhiều thuật toán vẫn chưa có cách giải nào khác nếu không sử dụng đệ quy Nhưng bên cạnh đó, không ít những bài toán
đệ quy bị khử đệ quy bằng nhiều phương pháp khác nhau Lý do để khử đệ quy là tránh cho máy mất quá nhiều tài nguyên hay thực hiện thừa các tác vụ
Mối Quan Hệ
Có thể nói rằng mối quan hệ giữa đệ quy và chia để trị là hết sức khắng khít Với bản chất của đệ quy, chúng ta có thể dùng nó để thiết kế việc chia như thế nào trong thuật toán đặt ra hết sức dễ dàng, sáng sủa Nếu như khẳng định việc sử dụng đệ quy trong việc chia để trị là yếu tố hiển nhiên
là không sai
Tuy nhiên, ta cần chú ý rằng đệ quy không phải là chìa khoá vàng Đệ quy cũng có một số khiếm khuyết như đã đề cập ở trên, cho nên đệ quy
1 NXB Giáo Dục, phương pháp giải các bài toán trong Tin Học
Trang 9không hẳn là co đường duy nhất đi đến thành công Chúng ta có thể sử dụng những phương pháp khử đệ quy khác đã biết như: stack, vòng lặp,
1.3.3 Các Vấn Đề Về Cài Đặt
Sử dụng lưu trữ tùy thuộc vào "Input" mà ta phải chọn một loại hình lưu trữ các bài toán con thích hợp Ví dụ với một mảng cực lớn các số khi chia ra có rất nhiều bài toán con nên ta phải sử dụng một danh sách liên kết hoặc một stack nào đó để lưu trữ
Môi trường cài đặt bất cứ ngôn ngữ lập trình nào có hổ trợ giải thuật đệ quy ta đều có thể cài đặt chia để trị Nhưng ta phải lưu ý đến việc hổ trợ vùng nhớ đệm của ngôn ngữ lập trình Vì đối với những vấn đề lớn ta cần
bộ nhớ rất lớn Ngày này hầu như toàn bộ các ngôn ngữ lập trình đều hổ trợ giải thuật đệ quy
Trang 10Chương 2
Những Bài Toán Sử Dụng Phương Pháp Chia Để Trị
2.1.1 Quick Sort
Sắp xếp nhanh (Quicksort), còn được gọi là sắp xếp kiểu phân chia (part sort) là một thuật toán sắp xếp dựa trên phép phân chia danh sách được sắp thành hai danh sách con Khác với sắp xếp trộn, chia danh sách cần sắp xếp a[1 n] thành hai danh sách con có kích thước tương đối bằng nhau nhờ chỉ số đứng giữa danh sách, sắp xếp nhanh chia nó thành hai danh sách bằng cách so sánh từng phần tử của danh sách với một phần tử được chọn được gọi là phần tử chốt Những phần tử nhỏ hơn hoặc bằng phần tử chốt được đưa về phía trước và nằm trong danh sách con thứ nhất, các phần tử lớn hơn chốt được đưa về phía sau và thuộc danh sách đứng sau Cứ tiếp tục chia như vậy tới khi các danh sách con đều có độ dài bằng 1
Phần tử chốt (pivot) là một phần tử được chọn dùng để đối sánh 2 bên của mảng để hoán vị Kỹ thuật chọn phần tử chốt ảnh hưởng khá nhiều đến khả năng rơi vào các vòng lặp vô hạn đối với các trường hợp đặc biệt Tốt nhất là chọn phần tử chốt là trung vị của danh sách Khi đó sau log2(n)lần phân chia ta sẽ đạt tới kích thước danh sách bằng 1 Tuy nhiên điều đó rất khó Có các cách chọn phần tử chốt như sau:
1 Chọn phần tử đứng đầu hoặc đứng cuối làm phần tử chốt
2 Chọn phần tử đứng giữa danh sách làm phần tử chốt
Trang 113 Chọn phần tử trung vị trong 3 phần tử đứng đầu, đứng giữa và đứng cuối làm phần tử chốt
4 Chọn phần tử ngẫu nhiên làm phần tử chốt (Cách này có thể dẫn đến khả năng rơi vào các trường hợp đặc biệt)
Thuật phân chia sau khi phần tử chốt được chọn giải thuật phân chia nên tiến hành như thế nào? Một giải pháp đơn giản nhất cho vấn đề này là duyệt từ đầu đến cuối lần lượt so sánh các phần tử của danh sách với phần
tử chốt Theo cách này, ta phải tiến hành n phép so sánh, ngoài ra còn phải dành n đơn vị bộ nhớ để lưu giữ các giá trị trung gian
Một giải pháp khác được đề nghị là duyệt theo hai đường Một đường từ đầu danh sách, một đường từ cuối danh sách Theo cách này, ta tìm phần
tử đầu tiên tính từ trái lớn hơn phần tử chốt và phần tử đầu tiên phía phải nhỏ hơn hoặc bằng phần tử chốt rồi đổi chỗ cho nhau Tiếp tục như vậy cho đến khi hai đường gặp nhau
Để có thể gọi đệ quy ta xét bài toán phân chia một danh sách con của a: a[k1, k2]thành hai danh sách Công thức truy hồi và cách tính độ phức tạp giống hoàn toàn với phương pháp Merge Sort Với một mảng n phần tử ta
cần sắp xếp theo Merge Sort Đặt T (n) là độ phức tạp của thuật toán, ta có
được công thức truy hồi theo độ phức tạp thuật toán: T (n) = 2T (n/2) + Cn (với Cn là chi phí thực hiện bài toán ở mức n phần tử), T (n2) = 2T (n4) + Cn2 Như vậy ta có: T (n) = 2T (n
2) + Cn = 4T (n4) + 2Cn = = 2kT (2nk) + Cnk = 2k+ Cnk = n + Cnlog2(n)với n = 2k, T (n
2 k) = T (1) = 1 Vậy thuật toán Quick có độ phức tạp là O(n) = nlog2(n)
2.1.2 Merge Sort
Sắp xếp trộn (merge sort) là một thuật toán sắp xếp để sắp xếp các danh sách (hoặc bất kỳ cấu trúc dữ liệu nào có thể truy cập tuần tự, (v.d: luồng tập tin) theo một trật tự nào đó Thuật toán này là một ví dụ tương đối điển hình của lối thuật toán chia để trị Nó được xếp vào thể loại sắp xếp so sánh
Trộn có hai danh sách đã được sắp xếp a[1 m] và b[1 n.] Ta có thể trộn chúng lại thành một danh sách mới c[1, m + n] được sắp xếp theo cách sau:
So sánh hai phần tử đứng đầu của hai danh sách, lấy phần tử nhỏ hơn cho vào danh sách mới Tiếp tục như vậy cho tới khi một trong hai danh sách là rỗng Khi một trong hai danh sách là rỗng ta lấy phần còn lại của danh sách kia cho vào cuối danh sách mới Ví dụ: Cho hai danh sách a = (1, 3, 7, 9),
b = (2, 6), quá trình hòa nhập diễn ra như sau:
Trang 12Hình 2.1: một minh họa cho thuật toán Merge Sort
Trang 13Danh sách a Danh sách b So sánh Danh sách c
1,3,7,9 2,6 1<2 1
3,7,9 2,6 2<3 1.2
3,7,9 6 3<6 1,2,3
7,9 6 6<7 1,2,3,6
7,9 1,2,3,6,7,9
Trộn tại chỗ: giả sử trong danh sách a[1 n] có 2 danh sách con kề nhau a[k1 k2] và a[k2 + 1 k3] đã được sắp Ta áp dụng cách trộn tương tự như trên để trộn hai danh sách con vào một danh sách tạm T [k1 k3] rồi trả lại các giá trị của danh sách tạm T về danh sách A Làm như vậy gọi là trộn tại chỗ
Trộn từ dưới lên nếu danh sách con chỉ gồm hai phần tử, mối nửa của
nó gồm một phần tử đương nhiên đã được sắp Do đó việc trộn tại chố hai nửa danh sách này cho danh sách con 2 phân tử được sắp Xuất phát từ đầu danh sách a ta trộn a[1] với a[2], a[3] với a[4], Khi đó mọi danh sách con gồm hai phần tử của a đã được sắp Tiếp tục trộn các danh sách con
kế tiếp nhau gồm 2 phần tử thành các danh sách con 4 phần tử Mỗi lần trộn số các danh sách con cần trộn giảm đi một nửa Quá trình dừng lại khi
số danh sách con chỉ còn một Ví dụ: Cho danh sách a = (2, 3, 5, 6, 4, 1, 7)
Công việc Số Danh sách con Kết quả Trộn các phần tử đứng kề nhau 7 2,3-5,6-1,4-7 Trộn các danh sách con 2 phần tử kề nhau 4 2,3,5,6-1,4,7 Trộn các danh sách con 4 phần tử kề nhau 2 1,2,3,4,5,6,7
Độ Phức Tạp: với một mảng n phần tử ta cần sắp xếp theo Merge Sort ,đặt T (n) là độ phức tạp của thuật toán Ta có được công thức truy hồi theo
độ phức tạp thuật toán : T (n) = 2T (n
2) + Cn , với C là một hằng số (với
Cnlà chi phí thực hiện bài toán ở mức n phần tử) T (n
2) = 2T (n4) + Cn2, như vậy ta có : T (n) = 2T (n
2) + Cn = 4T (n
4) + 2Cn = = 2kT (n
2 k) + Cnk =
2k + Cnk = n + Cnlog2(n) với n = 2k, T (2nk) = T (1) = 1 Vậy thuật toán Merge Sort có độ phức tạp là O(n) = nlog2(n)
Nhân 2 số tự nhiên n-bit X và Y thông thường độ phức tạp ở mức O(n2) Bây giờ chúng ta sẽ xét lại bài toán này với kỹ thuật chia để trị Ta phân
Trang 14tách mỗi số X, Y thành 2 phần, mỗi phần n
2 Để đơn giản, ta luôn xét n là luỹ thừa của 2 X, Y sẽ được phân tách như sau:
X = A|B(X = A2n2 + B)
Y = C|D Khi đó XY sẽ có dạng:
XY = AC2n+ (AD + BC)2n2 + BD
Từ công thức kiếm được, ta thấy việc tính XY chỉ còn là tính 4 pháp nhân với các số n
2 là AC, AD, BC và BD, sau đó thực hiện phép cộng 2n bit, cuối cùng là 2 phép chuyển chữ số (2 phép nhân với luỹ thừ của 2) Các phép cộng và phép chuyển chữ số đều được thực hiện với thời gian O(n), do đó
ta thu được công thức tính động phức tạp của pháp toán trên T (n) là:
T (1) = 1
T (n) = 4T (n
2) + Cn
Và do đó T (n) = O(n2)nên không hiệu quả lắm với cách thông thường cho nên chúng ta tiếp tục biến đổi XY như sau:
XY = AC2n+ ((A − B)(D − C) + AC + BD)2n2 + BD
Đến đây ta thấy hiệu quả của phép biến đổi vừa được tạo ra, cụ thể để tính
XY ta tốn 3 phép nhân n
2 bit: AC, BD và (A − B)(D − C), 6 phép tính cộng trừ số n
2, 2 phép chuyển chữ số (nhân với luỹ thừa của 2) Do vậy với cách tính trên ta có độ phức tạp của thuật toán này
T (1) = 1
T (n) = 3T (n
2) + Cn
Ta dễ thấy phương pháp chia đôi này sẽ đệ quy log2n bước và ở bước cuối thì chỉ còn 1 bit Cây đệ quy có chiều cao là log2nvà ở mỗi bước có 3 nhánh vậy ở độ sâu k thì số bài toán con là 3k, mỗi bài có kích thước là (n
2)kbước Với mỗi bài toán con ta cần 1 thời gian tuyến tính để phân rã chúng và gom nhóm nên đến bước k, thời gian chạy sẽ là
T (n) = 3k.O(n
2k) = (3
2)
k
.O(n)
Do đó, độ phức tạp của thuật toán chỉ còn O(nlog 3
) = O(n1.59)