Tài liệu rất hay về Cấu trúc dữ liệu - phân tích và thiết kế giải thuật. Phù hợp với sinh viên học tập.
GIớI THIệU MÔN HọC Trong ngôn ngữ lập trình, liệu bao gồm hai kiểu : - Kiểu liệu đơn giản : char, int, long, float, enumeration, subrange - Kiểu liệu có cấu trúc : struct, array, file (kiểu liệu có kích thớc không đổi) Giáo trình tập trung vào việc nghiên cứu kiểu liệu có cấu trúc có kích thớc không đổi thay đổi ngôn ngữ lập trình, mô tả thông qua ngôn ngữ C Ngoài giới thiệu giải thuật chung quanh cấu trúc liệu nh cách tổ chức, thực phép toán tìm kiếm, thứ tự nội, thứ tự ngoại Điều kiện để tìm hiểu rõ ràng môn học học viên biết khái niệm kỹ thuật lập trình ngôn ngữ C Trong phần mở đầu, giảng giới thiệu cách thức phân tích & thiết kế giải thuật trớc tìm hiểu cấu trúc liệu cụ thể Vào cuối khóa học, sinh viên có thể: - Phân tích độ phức tạp chơng trình có kích thớc nhỏ trung bình - Nhận thức đợc cần thiết việc thiết kế cấu trúc liệu - Làm quen với khái niệm stacks, queues, danh sách đặc, danh sách liên kết, nhị phân, nhị phân tìm kiếm, - Hiểu đợc nguyên lý việc xây dựng chơng trình máy tính - Có thể chọn lựa việc tổ chức liệu phù hợp giải thuật xử lý liệu có hiệu xây dựng chơng trình Sinh viên cần lu ý rằng, tùy vào công việc cụ thể mà ta nên chọn cấu trúc liệu thích hợp theo hớng tối u thời gian thực hay tối u nhớ Chơng I PHÂN TíCH & THIếT Kế GIảI THUậT I mở đầu Hầu hết toán có nhiều giải thuật khác để giải chúng Vậy làm chọn đợc giải thuật tốt ? Việc chọn lựa phụ thuộc vào nhiều yếu tố nh : Độ phức tạp tính toán giải thuật, chiếm dung lợng nhớ, tần suất sử dụng, tính đơn giản, tốc độ thực Thông thờng mục tiêu chọn lựa : Giải thuật rõ ràng, dễ hiểu, dễ mã hóa hiệu chỉnh Giải thuật sử dụng có hiệu tài nguyên máy tính đặc biệt chạy nhanh tốt Do viết chơng trình để chạy lần chạy mục tiêu quan trọng Ngợc lại viết chơng trình để chạy nhiều lần phí tổn chạy chơng trình vợt phí tổn lập chơng trình, phải nhập nhiều số liệu Nói chung, ngời lập trình phải biết chọn lựa, viết, đánh giá giải thuật để có đợc giải thuật tối u cho toán II đánh giá thời gian chạy chơng trình Thời gian chạy chong trình phụ thuộc vào : Input cho chơng trình Chất lợng mã sinh chơng trình dịch Trạng thái tốc độ lệnh chạy máy Độ phức tạp thời gian giải thuật Điều chức nhập Kích thớc input (ví dụ n) ta thờng ký hiệu T(n) đại lợng thời gian cần thiết để giải toán kích thớc n Điều 2, thờng đánh giá khó khăn phụ thuộc vào phần mềm chơng trình dịch phần cứng máy Điều điều mà ngời lập trình cần khảo sát để làm tăng tốc độ chơng trình III ký hiệu o(n o(n) (n) : Ta đánh giá tỷ lệ phát triển hàm T(n) qua ký hiệu O(n) Ta nói thời gian chạy T(n) chơng trình O(n2) có nghĩa : c > n0 cho n n0 ta có T(n) c.n2 Ví dụ : Giả sử T(0) = 1, T(1) = 4, v v Tổng quát T(n) = (n +1)2 ta nói T(n) O(n2) đặt c1 = 4, n0 = 1, n ta có (n +1)2 4n2 Nhng lấy n0 = T(0) = không nhỏ c.02 = 0,c; giả thiết n T(n) Ta nói T(n) O(f(n)) const c n0 cho T(n) c.f(n), n n0 Chơng trình chạy với thời gian O(f(n)) ta nói phát triển tỷ lệ với f(n) Khi nói T(n) O(f(n)) f(n) chặn T(n) Để nói chặn dới T(n) ta dùng ký hiệu Ta nói T(n) (g(n)) const c, n0 cho T(n) c.g(n), n n0 Ví dụ : Để kiểm tra T(n) = n3 + 2n2 (n3) ta đặt c = T(n) c.n3, n = 0, 1, (no= 0) * Sự trái ngợc tỷ lệ phát triển : Ta giả sử chơng trình đánh giá cách so sánh hàm thời gian chúng với tỷ lệ không đáng kể Khi ta nói chơng trình có thời gian chạy O(n2) Nếu chơng trình chạy 100.n2 thời gian (mili giây) chơng trình chạy 5.n3 thời gian, ta có tỷ số thời gian chơng trình 5.n3/100.n2 = n/20, nghĩa n = 20 thời gian chạy chơng trình nhau, n < 20 chơng trình chạy nhanh chơng trình Do n > 20 nên dùng chơng trình Ví dụ : Có chơng trình có độ phức tạp khác đợc biểu diễn bảng dới Thời gian chạy T(n) 100.n 5.n2 n3/2 2n Kích thớc toán tối đa cho 103s 10 14 12 10 Kích thớc toán tối đa cho 104s 100 45 27 13 Tỷ lệ tăng kích thớc 10.0 lần 3.2 lần 2.3 lần 1.3 lần Giả sử 103s chơng trình giải toán có kích thớc tối đa cột Nếu có máy tốt tốc độ tăng lên 10 lần kích thớc tối đa tơng ứng chơng trình trình bày cột Tỉ lệ hai cột 1,2 ghi cột Nh đầu t tốc độ 10 lần thu lợi có 30% kích thớc toán dùng chơng trình có độ phức tạp O(2n) IV cách tính thời gian chạy chơng trình : Qui tắc tổng: Giả sử T1(n) T2(n) thời gian chạy chơng trình P1 P2 tơng ứng đợc đánh giá O(f(n)) O(g(n)) Khi T1(n) + T2(n) O(max(f(n),g(n))) (chạy xong chơng trình P1 chạy P2) Chứng minh: Theo định nghĩa O(f(n)) O(g(n)) c1, n1, c2, n2 cho T1(n) c1.f(n) n n1 ; T2(n) c2.g(n) n n2 Đặt n0 = max(n1, n2) Nếu n no T1(n) + T2(n) (c1 + c2).max(f(n),g(n)) Qui tắc tích: T1(n) T2(n) O(f(n).g(n)) Chứng minh : tơng tự nh tổng Ví dụ : Có chơng trình có thời gian chạy tơng ứng O(n2), O(n3), O(n.logn) Thế thời gian chạy chơng trình đồng thời O(max(n2, n3, nlogn)) O(n3) Nói chung thời gian chạy dãy cố định bớc thời gian chạy lớn bớc dãy Cũng có trờng hợp có hay nhiều bớc có thời gian chạy không tơng xứng (không lớn mà không nhỏ hơn) Khi qui tắc tính tổng phải đợc tính trờng hợp Ví dụ : f(n) = g(n) = { { n4 n chẵn n2 n2 n lẻ n chẵn n3 n lẽ Thời gian chạy O(max(f(n),g(n))) n4 n chẵn n3 n lẻ Nếu g(n) f(n), n no, no const O(f(n)+g(n)) O(f(n)) Ví dụ : O(n2 + n) = O(n2) Trớc đa qui tắc chung để phân tích thời gian chạy chơng trình ta xét ví dụ đơn giản sau Ví dụ : Xét chơng trình Bubble dùng dãy số nguyên theo chiều tăng Procedure Bubble (var A: array [1 n] of integer); Var i, j, temp : integer ; Begin For i := to n For j := n downto i If A[j-1] > A[j] then Begin temp := A[j-1] ; A[j-1] := A[j] ; A[j] := temp ; End ; End ; Phân tích : - N số phần tử - kích thớc toán Mỗi lệnh gán từ dòng - > dòng đơn vị thời gian, theo qui tắc tính tổng O(max(1,1,1) = O(1) - Vòng If For lồng nhau, ta phải xét từ Đối với điều kiện sau If phải kiểm tra O(1) thời gian Ta không thân lệnh If từ - có thực hay không Vì xét trờng hợp xấu nên ta giả thuyết lệnh từ - có thực Vậy nhóm If từ lệnh -6 làm O(1) thời gian - Ta xét vòng lặp từ - Nguyên tắc chung vòng lặp: thời gian vòng lặp tổng thời gian lần lặp thân vòng lập O(1) cho lần lặp số tăng Số lần lặp từ - n - i +1 Vậy theo qui tắc tích : O((n - i +1), 1) O(n -i +1) - Ta xét vòng chứa lệnh chơng trình Lệnh làm n-1 lần, tốn n-1 đơn vị thời gian Vậy tổng thời gian chạy chơng trình bị chặn dới thời gian cố định : n (n i + 1) = n * (n 1) / tức O(n2) i =2 Tuy nhiên qui tắc đầy đủ để phân tích chơng trình Nói chung thời gian chạy lệnh nhóm lệnh hàm kích thớc input hay nhiều biến Nhng có n - kích thớc toán thông số cho phép thời gian chạy chơng trình Qui tắc tính thời gian chạy a) Thời gian chạy lệnh gán, read, write có giả thiết O(1) b) Thời gian chạy dãy lệnh xác định theo qui tắc tổng; nghĩa thời gian chạy dãy thời gian lớn lệnh dãy lệnh c) Thời gian chạy lệnh If thời gian thực lệnh điều kiện cộng với thời gian kiểm tra điều kiện Thời gian thực lệnh If có cấu trúc If then eles thời gian kiểm tra điều kiện cộng với thời gian lớn lệnh rẽ nhánh true false d) Thời gian thực vòng lặp tổng thời gian thực thân vòng lặp thời gian kiểm tra kết thúc vòng lặp e) Gọi thủ tục:Nếu chơng trình có thủ tục thủ tục đệ qui ta tính thời gian chạy lúc, thủ tục không gọi đến thủ tục khác Tất nhiên phải có thủ tục nh trờng hợp này, không phải có thủ tục đệ qui Sau ta đánh giá thời gian chạy thủ tục có gọi, đến thủ tục không chứa lời gọi đợc đánh giá Cứ nh ta lại đánh giá thời gian chạy thủ tục có lời gọi đến thủ tục đánh giá, nghĩa thủ tục đợc đánh giá sau đánh giá hết thủ tục mà đợc gọi Nếu có thủ tục đệ qui tìm đợc thứ tự tất thủ tục cho thủ tục gọi đến thủ tục đánh giá Khi ta phải lập liên hệ thủ tục đệ qui với hàm thời gian cha biết T(n) n kích thớc đối số thủ tục Lúc ta nhận đợc truy hồi T(n), nghĩa phơng trình diễn tả T(n) qua T(k) với giá trị k khác Ví dụ : Xét chơng trình đệ qui tính n giai thừa (n!), n kích thớc hàm nêu Function Fact (n:integer) : LongInt ; Begin If n n Giải phơng trình : Giả sử n > 2, ta khai triển T(n-1) công thức : T(n) = 2.c + T(n-2) n > Sau ta lại thay T(n-2) = c + T(n-3) ta đợc T(n) = 3.c + T(n-3) n > T(n) = i.c + T(n-i) n > i Cuối ta thay i = n - 1, ta đợc T(n) = c(n-1) + T(1) = c(n-1) + d Kết luận T(n) O(n) V phân lớp thuật toán : Nh đợc ý trên, hầu hết thuật toán có tham số N, Thông thờng số lợng phần tử liệu đợc xử lý mà ảnh hởng nhiều tới thời gian chạy Tham số N bậc đa thức, kích thớc tập tin đợc xếp hay tìm kiếm, số nút đồ thị Hầu hết tất thuật toán giảng có thời gian chạy tiệm cận tới hàm sau : Hầu hết tất thị chơng trình đợc thực lần hay nhiều vài lần Nếu tất thị chơng trình có tính chất nói thời gian chạy số Điều hiển nhiên mục tiêu phấn đấu để đạt đợc việc thiết kế thuật toán logN Khi thời gian chạy chơng trình logarit, tức thời gian chạy chơng trình tiến chậm N lớn dần Thời gian chạy loại xuất chơng trình mà giải toán lớn cách chuyển thành toán nhỏ hơn, cách cắt bỏ kích thớc bớt số Với mục đích chúng ta, thời gian chạy có đợc xem nh nhỏ số "lớn" Cơ số logarit làm thay đổi số nhng không nhiều: Khi n 1000 logN số 10; 10 số ; N 1000000, logN đợc nhân gấp đôi Bất N đợc nhân gấp đôi, logN đợc tăng lên thêm số, nhng logN không đợc nhân gấp đôi tới N tăng tới N2 N Khi thời gian chạy chơng trình tuyến tính, nói chung trờng hợp mà số lợng nhỏ xử lý đợc làm cho phần tử liệu nhập Khi N 1.000.000 thời gian chạy cỡ nh Khi N đợc nhân gấp đôi thời gian chạy đợc nhân gấp đôi Đây tình tối u cho thuật toán mà phải xử lý N liệu nhập (hay sản sinh N liệu xuất) NlogN Đây thời gian chạy tăng dần lên cho thuật toán mà giải toán cách tách thành toán nhỏ hơn, giải chúng cách độc lập sau tổ hợp lời giải Bởi thiếu tính từ tốt (có lẽ "tuyến tính logarit" ?), nói thời gian chạy thuật toán nh "NlogN" Khi N 1000000, NlogN có lẽ khoảng triệu Khi N đợc nhân gấp đôi, thời gian chạy bị nhân lên nhiều gấp đôi (nhng không nhiều lắm) N2 Khi thời gian chạy thuật toán bậc hai, trờng hợp có ý nghĩa thực tế cho toán tơng đối nhỏ Thời gian bình phơng thờng tăng lên thuật toán mà xử lý tất cặp phần tử liệu (có thể vòng lặp lồng nhau) Khi N 1000 thời gian chạy 1000000 Khi N đợc nhân đôi thời gian chạy tăng lên gấp lần N3 Tơng tự, thuật toán mà xử lý phần tử liệu (có lẽ vòng lặp lồng nhau) có thời gian chạy bậc có ý nghĩa thực tế toán nhỏ Khi N 100 thời gian chạy 1.000.000 Khi N đợc nhân đôi thời gian chạy tăng lên gấp lần 2n Một số thuật toán có thời gian chạy lũy thừa lại thích hợp số trờng hợp thực tế, thuật toán nh "sự ép buộc thô bạo" để giải toán Khi N 20 thời gian chạy xấp xỉ 1.000.000 Khi N gấp thời gian chạy đợc nâng lên lũy thừa Thời gian chạy chơng trình cụ thể số nhân với số hạng nói cộng thêm số hạng nhỏ Các giá trị số số hạng phụ thuộc vào kết phân tích chi tiết cài đặt Hệ số số liên quan tới số thị bên vòng lặp : tầng tùy ý thiết kế thuật toán phải cẩn thận giới hạn số thị nh Với N lớn số đóng vai trò chủ chốt, với N nhỏ số hạng đóng góp vào so sánh thuật toán khó khăn Ngoài hàm vừa nói có số hàm khác, ví dụ nh thuật toán với N2 phần tử liệu nhập mà có thời gian chạy bậc theo N đợc phân lớp nh thuật toán N3/2 Một số thuật toán có giai đoạn phân tách thành toán có thời gian chạy xấp xỉ với Nlog2N VI công thức truy hồi sở : Phần lớn thuật toán dựa việc phân rã đệ qui toán lớn thành toán nhỏ hơn, dùng lời giải toán nhỏ để giải toán ban đầu Thời gian chạy thuật toán nh đợc xác định kích thớc số lợng toán giá phải trả phân rã Trong phần ta quan sát phơng pháp sở để phân tích thuật toán nh trình bày vài công thức chuẩn thờng đợc áp dụng việc phân tích nhiều thuật toán Tính chất tự nhiên chơng trình đệ qui thời gian chạy cho liệu nhập có kích thớc N phụ thuộc vào thời gian chạy cho liệu nhập có kích thớc nhỏ : điều đợc diễn dịch thành công thức toán học gọi quan hệ truy hồi Các công thức nh mô tả xác tính thuật toán tơng ứng, để có đợc thời gian chạy phải giải toán truy hồi Bây ý vào công thức thuật toán Công thức : Công thức thờng dùng cho chơng trình đệ qui mà có vòng lặp duyệt qua liệu nhập để bỏ bớt phần tử Cn = Cn-1 + n, với n >= C1 = Chứng minh : Cn khoảng n2/2 Để giải công thức truy hồi nh trên, lần lợt áp dụng công thức nh sau : Cn = Cn-1 + n = Cn-2 + (n-1) + n = = C1 + + + (n-2) + (n-1) + n = + + + n = n(n+1)/2 Công thức : Công thức dùng cho chơng trình đệ qui mà chia liệu nhập thành phần bớc Cn = Cn/2 + 1, với n >= C1 = Chứng minh : Cn khoảng logn Phơng trình vô nghĩa n chẵn hay giả sử n/2 phép chia nguyên : giả sử n = 2m công thức luôn có nghĩa Chúng ta viết nh sau : C 2m = C 2m + = C 2m + = C 2m + = = C 2m m + m = m = log n Công thức xác cho n tổng quát phụ thuộc vào biểu diễn nhị phân n, nói chung Cn khoảng logn với n Công thức : Công thức dùng cho chơng trình đệ qui mà chia đôi liệu nhập nhng kiểm tra phần tử liệu nhập Cn = Cn/2 + n, với n >= C1 = Chứng minh : Cn khoảng 2n Tơng tự trên, công thức tổng n + n/2 + n/4 + (dĩ nhiên điều xác n lũy thừa 2) Nếu dãy vô hạn, chuỗi hình học đơn giản mà đợc ớc lợng xác 2n Trong trờng hợp tổng quát lời giải xác phụ thuộc vào biểu diễn nhị phân n Công thức : Công thức dùng cho chơng trình đệ qui mà duyệt tuyến tính xuyên qua liệu nhập, trớc, trong, hay sau liệu nhập đợc chia đôi Cn = 2Cn/2 + n, với n >= C1 = Chứng minh : Cn khoảng nlogn Công thức áp dụng cho nhiều thuật toán theo phơng pháp "chia để trị" C 2m = C 2m1 + m 10 P4 = P5 = 0 0 0 0 1 1 0 0 0 0 0 0 0 1 1 1 1 Bao đóng truyền ứng đồ thị G : 0 0 0 0 0 1 0 * Chơng trình: trình #include #include const MAX = 4; int G[MAX][MAX]= { {0,0,1,0}, {0,0,1,0}, {0,0,1,1}, {0,1,0,0} }; void Xuat(int P[][MAX]) Xuat { int i,j; for( i=0; i