Tải bản đầy đủ (.doc) (15 trang)

Cấu trúc dữ liệu và giải thuật full

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (150.78 KB, 15 trang )

Cấu trúc dữ liệu (Data Structure) là gì ?
Cấu trúc dữ liệu là cách lưu trữ, tổ chức dữ liệu có thứ tự, có hệ thống để dữ
liệu có thể được sử dụng một cách hiệu quả.
Dưới đây là hai khái niệm nền tảng hình thành nên một cấu trúc dữ liệu:


Interface: Mỗi cấu trúc dữ liệu có một Interface. Interface biểu diễn một tập hợp các phép
tính mà một cấu trúc dữ liệu hỗ trợ. Một Interface chỉ cung cấp danh sách các phép tính được hỗ
trợ, các loại tham số mà chúng có thể chấp nhận và kiểu trả về của các phép tính này.



Implementation (có thể hiểu là sự triển khai): Cung cấp sự biểu diễn nội bộ của một cấu
trúc dữ liệu. Implementation cũng cung cấp phần định nghĩa của giải thuật được sử dụng trong các
phép tính của cấu trúc dữ liệu.

Đặc điểm của một Cấu trúc dữ liệu


Chính xác: Sự triển khai của Cấu trúc dữ liệu nên triển khai Interface của nó một cách chính
xác.



Độ phức tạp về thời gian (Time Complexity): Thời gian chạy hoặc thời gian thực thi của
các phép tính của cấu trúc dữ liệu phải là nhỏ nhất có thể.



Độ phức tạp về bộ nhớ (Space Complexity): Sự sử dụng bộ nhớ của mỗi phép tính của
cấu trúc dữ liệu nên là nhỏ nhất có thể.



Tại sao Cấu trúc dữ liệu là cần thiết ?
Ngày nay, các ứng dụng ngày càng phức tạp và lượng dữ liệu ngày càng lớn với nhiều kiểu đa
dạng. Việc này làm xuất hiện 3 vấn đề lớn mà mỗi lập trình viên phải đối mặt:


Tìm kiếm dữ liệu: Giả sử có 1 triệu hàng hóa được lưu giữ vào trong kho hàng hóa. Và giả
sử có một ứng dụng cần để tìm kiếm một hàng hóa. Thì mỗi khi thực hiện tìm kiếm, ứng dụng này
sẽ phải tìm kiếm 1 hàng hóa trong 1 triệu hàng hóa. Khi dữ liệu tăng lên thì việc tìm kiếm sẽ càng
trở lên chậm và tốn kém hơn.



Tốc độ bộ vi xử lý: Mặc dù bộ vi xử lý có tốc độ rất cao, tuy nhiên nó cũng có giới hạn và
khi lượng dữ liệu lên tới hàng tỉ bản ghi thì tốc độ xử lý cũng sẽ không còn được nhanh nữa.




Đa yêu cầu: Khi hàng nghìn người dùng cùng thực hiện một phép tính tìm kiếm trên một
Web Server thì cho dù Web Server đó có nhanh đến mấy thì việc phải xử lý hàng nghìn phép tính
cùng một lúc là thực sự rất khó.
Để xử lý các vấn đề trên, các cấu trúc dữ liệu là một giải pháp tuyệt vời. Dữ liệu có thể được tổ
chức trong cấu trúc dữ liệu theo một cách để khi thực hiện tìm kiếm một phần tử nào đó thì dữ liệu
yêu cầu sẽ được tìm thấy ngay lập tức.

Độ phức tạp thời gian thực thi trong cấu trúc dữ liệu và
giải thuật
Có 3 trường hợp thường được sử dụng để so sánh thời gian thực thi của các cấu trúc dữ liệu khác
nhau:



Trường hợp xấu nhất (Worst Case): là tình huống mà một phép tính của cấu trúc dữ liệu
nào đó tốn thời gian tối đa (thời gian dài nhất). Ví dụ với ba số 1, 2, 3 thì nếu sắp xếp theo thứ tự
giảm dần thì thời gian thực thi sẽ là dài nhất (và đây là trường hợp xấu nhất); còn nếu sắp xếp theo
thứ tự tăng dần thì thời gian thực thi sẽ là ngắn nhất (và đây là trường hợp tốt nhất).



Trường hợp trung bình (Average Case): miêu tả thời gian thực thi trung bình một phép
tính của một cấu trúc dữ liệu.



Trường hợp tốt nhất (Best Case): là tình huống mà thời gian thực thi một phép tính của
một cấu trúc dữ liệu là ít nhất. Ví dụ như trên.

Thuật ngữ cơ bản trong Cấu trúc dữ liệu


Dữ liệu: Dữ liệu là các giá trị hoặc là tập hợp các giá trị.



Phần tử dữ liệu: Phần tử dữ liệu là một đơn vị đơn lẻ của giá trị.



Các phần tử nhóm: Phần tử dữ liệu mà được chia thành các phần tử con thì được gọi là
các phần tử nhóm.




Các phần tử cơ bản: Phần tử dữ liệu mà không thể bị chia nhỏ thành các phần tử con thì
gọi là các phần tử cơ bản.



Thuộc tính và Thực thể: Một thực thể là cái mà chứa một vài thuộc tính nào đó, và các
thuộc tính này có thể được gán các giá trị.




Tập hợp thực thể: Các thực thể mà có các thuộc tính tương tự nhau thì cấu thành một tập
hợp thực thể.



Trường: Trường là một đơn vị thông tin cơ bản biểu diễn một thuộc tính của một thực thể.



Bản ghi: Bản ghi là một tập hợp các giá trị trường của một thực thể đã cho.



File: Là một tập hợp các bản ghi của các thực thể trong một tập hợp thực thể đã cho.

Cài đặt IDE để biên dịch và thực thi C

Có một số IDE có sẵn và miễn phí để biên dịch và thực thi các chương trình C. Bạn có thể
chọn Dev-C++, Code:: Blocks, hoặc Turbo C. Tuy nhiên, lựa chọn phổ biến nhất và hay được sử
dụng nhất là Dev-C++ và các chương trình C trong loạt bài này cũng được biên dịch và thực thi
trong Dev-C++.
Bạn truy cập theo link sau để tải Dev-C++: Tải Dev-C++. Trên trang này cũng bao gồm cả Code::
Blocks. Sau khi bạn tải xong, để cài đặt IDE này, bạn chỉ cần vào Google và gõ "cài đặt dev-c++" là
có rất nhiều video hướng dẫn chi tiết, cho nên mình không cần trình bày thêm nữa.
Sau khi đã cài đặt xong, để biên dịch và thực thi một chương trình C, bạn: (a) vào File -> New ->
Project -> Console Application -> C project, sau đó nhập tên vào hoặc (b) File -> New -> Source
File. Cuối cùng, sao chép và dán chương trình C vào file bạn vừa tạo. Để biên dịch và thực thi,
chọn Execute -> Compile & Run.

Cài đặt để chạy trên Command Prompt
Nếu bạn muốn cài đặt để biên dịch và chạy trên Command Prompt, thì bạn nên đọc phần sau đây.
Nếu bạn đang muốn cài đặt chương trình C, bạn cần phải sử dụng 2 phần mềm trên máy tính của
bạn: (a) Chương trình soạn văn bản - Text Editor và (b) Bộ biên dịch C.

Text Editor
Được sử dụng để soạn thảo các chương trình. Ví dụ về một vài trình editor như Window Notepad,
Notepad ++, vim hay vi…
Tên và các phiên bản của các trình editor có thể thay đổi theo các hệ điều hành. Ví dụ, Notepad
được sử dụng trên Windows, hoặc vim hay vi được sử dụng trên Linux hoặc UNIX.


Các file bạn tạo trong trình editor được gọi là source file (file nguồn) và chứa các chương trình
code. Các file trong chương trình C thường được đặt tên với phần mở rộng ".c".
Trước khi bắt đầu chương trình của bạn, hãy chắc chắn bạn có một trình editor trên máy tính và
bạn có đủ kinh nghiệm để viết các chương trình máy tính, lưu trữ trong file và thực thi nó.

Bộ biên dịch C

Mã nguồn được viết trong file nguồn dưới dạng có thể đọc được. Nó sẽ được biên dịch thành mã
máy, để cho CPU có thể thực hiện các chương trình này dựa trên các lệnh được viết.
Bộ biên dịch được sử dụng để biên dịch mã nguồn (source code) của bạn đến chương trình có thể
thực thi. Tôi giả sử bạn có kiến thức cơ bản về một bộ biên dịch ngôn ngữ lập trình.
Bộ biên dịch thông dụng nhất là bộ biên dịch GNU C/C++, mặt khác bạn có thể có các bộ biên dịch
khác như HP hoặc Solaris với Hệ điều hành tương ứng.
Dưới đây là phần hướng dẫn giúp bạn cách cài đặt bộ biên dich GNU C/C++ trên các hệ điều hành
khác nhau. Tôi đang đề cập đến C/C++ bởi vì bộ biên dịch GNU gcc hoạt động cho cả ngôn ngữ C
và C++.

Cài đặt trên môi trường UNIX/Linux
Nếu bạn đang sử dụng Linux hoặc UNIX, bạn có thể kiểm tra bộ GCC đã được cài đặt trên môi
trường của bạn chưa bằng lệnh sau đây:
$ gcc -v

Nếu bạn có bộ cài đặt GNU trên máy tính của bạn, sau đó nó sẽ phản hồi một thông báo sau:
Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix=/usr .......
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)

Nếu bộ GCC chưa được cài đặt, bạn có thể cài đặt nó với hướng dẫn trên đường link dưới
đây: />Bài hướng dẫn này được viết dựa trên Linux và tất cả các ví dụ dược biên dịch trên Cent OS của
hệ thống Linux.


Cài đặt trên môi trường Mac OS
Nếu bạn sử dụng hệ điều hành Mac OS X, cách đơn giản nhất để có GCC là download môi trường
phát triển Xcode, bạn có thể sử dụng bộ biên dịch GNU cho C/C++.

Xcode được sẵn dưới link sau: developer.apple.com/technologies/tools/.

Cài đặt trên Windows
Để cài đặt GCC trên Windows bạn cần phải cài đặt MinGW. Để cài đặt MinGW, bạn truy cập
vào www.mingw.org, và theo hướng dẫn trên trang download này. Download phiên bản mới nhất
cho chương trình MinGW, dưới tên MinGW-<version>.exe.
Khi cài đặt MinWG, ít nhất bạn phải cài đặt gcc-core, gcc-g++, binutils và MinGW runtime, nhưng
bạn có thể cài đặt nhiều hơn.
Thêm thư mục con bin trong nơi cài đặt MinGW vào biến môi trường PATH của bạn, bạn có thể sử
dụng trực tiếp các công cụ dưới dạng command line một các dễ dàng.
Khi quá trình cài đặt hoàn tất, bạn có thể chạy gcc, g++, ar, ranlib, dlltool và các công cụ GNU khác
trên Windows command line.

Phân tích tiệm cận là gì ?
Phân tích tiệm cận của một giải thuật là khái niệm giúp chúng ta ước lượng được thời gian chạy
(Running Time) của một giải thuật. Sử dụng phân tích tiệm cận, chúng ta có thể đưa ra kết luận tốt
nhất về các tình huống trường hợp tốt nhất, trường hợp trung bình, trường hợp xấu nhất của một
giải thuật. Để tham khảo về các trường hợp này, bạn có thể tìm hiểu chương Cấu trúc dữ liệu là gì
?.
Phân tích tiệm cận tức là tiệm cận dữ liệu đầu vào (Input), tức là nếu giải thuật không có Input thì
kết luận cuỗi cùng sẽ là giải thuật sẽ chạy trong một lượng thời gian cụ thể và là hằng số. Ngoài
nhân tố Input, các nhân tố khác được xem như là không đổi.
Phân tích tiệm cận nói đến việc ước lượng thời gian chạy của bất kỳ phép tính nào trong các bước
tính toán. Ví dụ, thời gian chạy của một phép tính nào đó được ước lượng là một hàm f(n) và với
một phép tính khác là hàm g(n2). Điều này có nghĩa là thời gian chạy của phép tính đầu tiên sẽ tăng
tuyến tính với sự tăng lên của n và thời gian chạy của phép tính thứ hai sẽ tăng theo hàm mũ khi n
tăng lên. Tương tự, khi n là khá nhỏ thì thời gian chạy của hai phép tính là gần như nhau.
Thường thì thời gian cần thiết bởi một giải thuật được chia thành 3 loại:





Trường hợp tốt nhất: là thời gian nhỏ nhất cần thiết để thực thi chương trình.



Trường hợp trung bình: là thời gian trung bình cần thiết để thực thi chương trình.



Trường hợp xấu nhất: là thời gian tối đa cần thiết để thực thi chương trình.

Asymptotic Notation trong Cấu trúc dữ liệu và giải thuật
Dưới đây là các Asymptotic Notation được sử dụng phổ biến trong việc ước lượng độ phức tạp thời
gian chạy của một giải thuật:


Ο Notation



Ω Notation



θ Notation

Big Oh Notation, Ο trong Cấu trúc dữ liệu và giải thuật
Ο(n) là một cách để biểu diễn tiệm cận trên của thời gian chạy của một thuật toán. Nó ước lượng
độ phức tạp thời gian trường hợp xấu nhất hay chính là lượng thời gian dài nhất cần thiết bởi một

giải thuật (thực thi từ bắt đầu cho đến khi kết thúc). Đồ thị biểu diễn như sau:

Ví dụ, gọi f(n) và g(n) là các hàm không giảm định nghĩa trên các số nguyên dương (tất cả các hàm
thời gian đều thỏa mãn các điều kiện này):
Ο(f(n)) = { g(n) : nếu tồn tại c > 0 và n0 sao cho g(n) ≤ c.f(n) với mọi n > n0. }


Omega Notation, Ω trong Cấu trúc dữ liệu và giải thuật
The Ω(n) là một cách để biểu diễn tiệm cận dưới của thời gian chạy của một giải thuật. Nó ước
lượng độ phức tạp thời gian trường hợp tốt nhất hay chính là lượng thời gian ngắn nhất cần thiết
bởi một giải thuật. Đồ thị biểu diễn như sau:

Ví dụ, với một hàm f(n):
Ω(f(n)) ≥ { g(n) : nếu tồn tại c > 0 và n0 sao cho g(n) ≤ c.f(n) với mọi n > n0. }

Theta Notation, θ trong Cấu trúc dữ liệu và giải thuật
The θ(n) là cách để biểu diễn cả tiệm cận trên và tiệm cận dưới của thời gian chạy của một giải
thuật. Bạn nhìn vào đồ thì sau:

θ(f(n)) = { g(n) nếu và chỉ nếu g(n) = Ο(f(n)) và g(n) = Ω(f(n)) với mọi n > n0. }

Một số Asymptotic Notation phổ biến trong cấu trúc dữ
liệu và giải thuật
hằng số



Ο(1)



logarit



Ο(log n)

Tuyến tính (Linear)



Ο(n)

n log n



Ο(n log n)

Bậc hai (Quadratic)



Ο(n2)

Bậc 3 (cubic)



Ο(n3)


Đa thức (polynomial)



nΟ(1)

Hàm mũ (exponential)



2Ο(n)

Giải thuật tham lam (Greedy Algorithm)
Trang trước

Trang sau

Giải thuật tham lam là gì ?
Tham lam (hay tham ăn) là một trong những phương pháp phổ biến nhất để thiết kế giải thuật. Nếu
bạn đã đọc truyện dân gian thì sẽ có câu chuyện như thế này: trên một mâm cỗ có nhiều món ăn,


món nào ngon nhất ta sẽ ăn trước, ăn hết món đó ta sẽ chuyển sang món ngon thứ hai, và chuyển
tiếp sang món thứ ba, …
Rất nhiều giải thuật nổi tiếng được thiết kế dựa trên ý tưởng tham lam, ví dụ như giải thuật cây
khung nhỏ nhất của Dijkstra, giải thuật cây khung nhỏ nhất của Kruskal, …
Giải thuật tham lam (Greedy Algorithm) là giải thuật tối ưu hóa tổ hợp. Giải thuật tìm kiếm, lựa
chọn giải pháp tối ưu địa phương ở mỗi bước với hi vọng tìm được giải pháp tối ưu toàn cục.
Giải thuật tham lam lựa chọn giải pháp nào được cho là tốt nhất ở thời điểm hiện tại và sau đó giải
bài toán con nảy sinh từ việc thực hiện lựa chọn đó. Lựa chọn của giải thuật tham lam có thể phụ

thuộc vào lựa chọn trước đó. Việc quyết định sớm và thay đổi hướng đi của giải thuật cùng với việc
không bao giờ xét lại các quyết định cũ sẽ dẫn đến kết quả là giải thuật này không tối ưu để tìm giải
pháp toàn cục.
Bạn theo dõi một bài toán đơn giản dưới đây để thấy cách thực hiện giải thuật tham lam và vì sao
lại có thể nói rằng giải thuật này là không tối ưu.

Bài toán đếm số đồng tiền
Yêu cầu là hãy lựa chọn số lượng đồng tiền nhỏ nhất có thể sao cho tổng mệnh giá của các đồng
tiền này bằng với một lượng tiền cho trước.
Nếu tiền đồng có các mệnh giá lần lượt là 1, 2, 5, và 10 xu và lượng tiền cho trước là 18 xu thì giải
thuật tham lam thực hiện như sau:


Bước 1: Chọn đồng 10 xu, do đó sẽ còn 18 – 10 = 8 xu.



Bước 2: Chọn đồng 5 xu, do đó sẽ còn là 3 xu.



Bước 3: Chọn đồng 2 xu, còn lại là 1 xu.



Bước 4: Cuối cùng chọn đồng 1 xu và giải xong bài toán.
Bạn thấy rằng cách làm trên là khá ổn, và số lượng đồng tiền cần phải lựa chọn là 4 đồng tiền.
Nhưng nếu chúng ta thay đổi bài toán trên một chút thì cũng hướng tiếp cận như trên có thể sẽ
không đem lại cùng kết quả tối ưu.
Chẳng hạn, một hệ thống tiền tệ khác có các đồng tiền có mệnh giá lần lượt là 1, 7 và 10 xu và

lượng tiền cho trước ở đây thay đổi thành 15 xu thì theo giải thuật tham lam thì số đồng tiền cần


chọn sẽ nhiều hơn 4. Với giải thuật tham lam thì: 10 + 1 + 1 +1 + 1 + 1, vậy tổng cộng là 6 đồng
tiền. Trong khi cùng bài toán như trên có thể được xử lý bằng việc chỉ chọn 3 đồng tiền (7 + 7 +1).
Do đó chúng ta có thể kết luận rằng, giải thuật tham lam tìm kiếm giải pháp tôi ưu ở mỗi bước
nhưng lại có thể thất bại trong việc tìm ra giải pháp tối ưu toàn cục.

Ví dụ áp dụng giải thuật tham lam
Có khá nhiều giải thuật nổi tiếng được thiết kế dựa trên tư tưởng của giải thuật tham lam. Dưới đây
là một trong số các giải thuật này:


Bài toán hành trình người bán hàng



Giải thuật cây khung nhỏ nhất của Prim



Giải thuật cây khung nhỏ nhất của Kruskal



Giải thuật cây khung nhỏ nhất của Dijkstra



Bài toán xếp lịch công việc




Bài toán xếp ba lô

Giải thuật chia để trị (divide and conquer)
Trang trước

Trang sau

Giải thuật chia để trị (Divide and Conquer)là gì ?
Phương pháp chia để trị (Divide and Conquer) là một phương pháp quan trọng trong việc thiết kế
các giải thuật. Ý tưởng của phương pháp này khá đơn giản và rất dễ hiểu: Khi cần giải quyết một
bài toán, ta sẽ tiến hành chia bài toán đó thành các bài toán con nhỏ hơn. Tiếp tục chia cho đến khi
các bài toán nhỏ này không thể chia thêm nữa, khi đó ta sẽ giải quyết các bài toán nhỏ nhất này và
cuối cùng kết hợp giải pháp của tất cả các bài toán nhỏ để tìm ra giải pháp của bài toán ban đầu.


Nói chung, bạn có thể hiểu giải thuật chia để trị (Divide and Conquer) qua 3 tiến trình sau:

Tiến trình 1: Chia nhỏ (Divide/Break)


Trong bước này, chúng ta chia bài toán ban đầu thành các bài toán con. Mỗi bài toán con
nên là một phần của bài toán ban đầu. Nói chung, bước này sử dụng phương pháp đệ qui để chia
nhỏ các bài toán cho đến khi không thể chia thêm nữa. Khi đó, các bài toán con được gọi là "atomic
– nguyên tử", nhưng chúng vẫn biểu diễn một phần nào đó của bài toán ban đầu.

Tiến trình 2: Giải bài toán con (Conquer/Solve)



Trong bước này, các bài toán con được giải.

Tiến trình 3: Kết hợp lời giải (Merge/Combine)


Sau khi các bài toán con đã được giải, trong bước này chúng ta sẽ kết hợp chúng một cách
đệ qui để tìm ra giải pháp cho bài toán ban đầu.

Hạn chế của giải thuật chia để trị (Devide and Conquer)
Giải thuật chia để trị tồn tại hai hạn chế, đó là:


Làm thế nào để chia tách bài toán một cách hợp lý thành các bài toán con, bởi vì nếu các
bài toán con được giải quyết bằng các thuật toán khác nhau thì sẽ rất phức tạp.



Việc kết hợp lời giải các bài toán con được thực hiện như thế nào.


Ví dụ giải thuật chia để trị
Dưới đây là một số giải thuật được xây dựng dựa trên phương pháp chia để trị (Divide and
Conquer):


Giải thuật sắp xếp trộn (Merge Sort)




Giải thuật sắp xếp nhanh (Quick Sort)



Giải thuật tìm kiếm nhị phân (Binary Search)



Nhân ma trận của Strassen



...

Giải thuật qui hoạch động (Dynamic
Programming)
Trang trước

Trang sau

Giải thuật Qui hoạch động (Dynamic Programming) là
gì ?
Giải thuật Qui hoạch động (Dynamic Programming) giống như giải thuật chia để trị (Divide and
Conquer) trong việc chia nhỏ bài toán thành các bài toán con nhỏ hơn và sau đó thành các bài
toán con nhỏ hơn nữa có thể. Nhưng không giống chia để trị, các bài toán con này không được giải
một cách độc lập. Thay vào đó, kết quả của các bài toán con này được lưu lại và được sử dụng cho
các bài toán con tương tự hoặc các bài toán con gối nhau (Overlapping Sub-problems).
Chúng ta sử dụng Qui hoạch động (Dynamic Programming) khi chúng ta có các bài toán mà có thể
được chia thành các bài toán con tương tự nhau, để mà các kết quả của chúng có thể được tái sử
dụng. Thường thì các giải thuật này được sử dụng cho tối ưu hóa. Trước khi giải bài toán con, giải



thuật Qui hoạch động sẽ cố gắng kiểm tra kết quả của các bài toán con đã được giải trước đó. Các
lời giải của các bài toán con sẽ được kết hợp lại để thu được lời giải tối ưu.
Do đó, chúng ta có thể nói rằng:


Bài toán ban đầu nên có thể được phân chia thành các bài toán con gối nhau nhỏ hơn.



Lời giải tối ưu của bài toán có thể thu được bởi sử dụng lời giải tối ưu của các bài toán con.



Giải thuật Qui hoạch động sử dụng phương pháp lưu trữ (Memoization) – tức là chúng ta
lưu trữ lời giải của các bài toán con đã giải, và nếu sau này chúng ta cần giải lại chính bài toán đó
thì chúng ta có thể lấy và sử dụng kết quả đã được tính toán.

So sánh
Giải thuật tham lam và giải thuật qui hoạch động


Giải thuật tham lam (Greedy Algorithms) là giải thuật tìm kiếm, lựa chọn giải pháp tối ưu địa
phương ở mỗi bước với hi vọng tìm được giải pháp tối ưu toàn cục.



Giải thuật Qui hoạch động tối ưu hóa các bài toán con gối nhau.


Giải thuật chia để trị và giải thuật Qui hoạch động:


Giải thuật chia để trị (Divide and Conquer) là kết hợp lời giải của các bài toán con để tìm ra
lời giải của bài toán ban đầu.



Giải thuật Qui hoạch động sử dụng kết quả của bài toán con và sau đó cố gắng tối ưu bài
toán lớn hơn. Giải thuật Qui hoạch động sử dụng phương pháp lưu trữ (Memoization) để ghi nhớ
kết quả của các bài toán con đã được giải.

Ví dụ giải thuật Qui hoạch động
Dưới đây là một số bài toán có thể được giải bởi sử dụng giải thuật Qui hoạch động:


Dãy Fibonacci



Bài toán tháp Hà Nội (Tower of Hanoi)



Bài toán ba lô



...



Giải thuật Qui hoạch động có thể được sử dụng trong cả hai phương pháp Phân tích (Topdown) và Qui nạp (Bottom-up). Và tất nhiên là nếu dựa vào vòng đời làm việc của CPU thì việc
tham chiếu tới kết quả của lời giải trước đó là ít tốn kém hơn việc giải lại bài toán.

Giải thuật Định lý thợ (Master Theorem)
Trang trước

Trang sau

Giải thuật Định lý thợ (Master Theorem) là gì ?
Chúng ta sử dụng Định lý thợ (Master Theorem) để giải các công thức đệ quy dạng sau một cách
hiệu quả :
T(n) =aT(n/b) + c.n^k trong đó a>=1 , b>1


Bài toán ban đầu được chia thành a bài toán con có kích thước mỗi bài là n/b, chi phí để
tổng hợp các bài toán con là f(n).



Ví dụ : Thuật toán sắp xếp trộn chia thành 2 bài toán con , kích thước n/2. Chi phí tổng hợp
2 bài toán con là O(n).

Định lý thợ
a>=1, b>1, c, k là các hằng số. T(n) định nghĩa đệ quy trên các tham số không âm
T(n) = aT(n/b) + c.n^k + Nếu a> b^k thì T(n) =O(n^ (logab)) + Nếu a= b^k thì T(n)=O(n^k.lgn) + Nếu
a< b^k thì T(n) = O(n^k)
Chú ý: Không phải trường hợp nào cũng áp dụng được định lý thợ



VD : T(n) = 2T(n/2) +nlogn a =2, b =2, nhưng không xác định được số nguyên k



×