Đang tải... (xem toàn văn)
Đề cương chủ yếu về 4 thuật toán: Tham Lam, Chia Để Trị, Quay Lui và Quy Hoạch Động. Ngoài lý thuyết đơn giản ra, đề cương có 31 bài tập, không có đề bài, chỉ bao gồm ý tưởng giải, ví dụ đơn giản và code lập trình bằng C++ đầy đủ.
Trang 2LÝ THUYẾT Định Lý Thợ
Dạng 2: d(n) có dạng log (ko phải hàm nhân) Giả thiết n = bk => k = logbn
=> Nghiệm thuần nhất n^(logba)
Tính nghiệm riêng = ∑𝑘−1𝑗=0 𝑎𝑗𝑑(𝑏𝑘−𝑗)
Tổng dãy số cách đều = (số đầu+số cuối) * số số hạng /2
Xét nghiệm riêng và nghiệm thuần nhất, T(n) = giá trị nghiệm cao nhất
Chia để trị
Áp dụng cho các bài toán có thể giải quyết bằng cách: Chia nhỏ ra thành các bài toán con
Giải quyết các bài toán con
Lời giải của các bài toán con được tổng hợp lại thành lời giải cho bài toán ban đầu Các bài toán con sẽ được chia thành các bài toán nhỏ hơn nữa
Dừng lại khi kích thước bài toán đủ nhỏ mà ta có thể giải dễ dàng bài toán cơ sở
Quy hoạch động
Một số thuật ngữ
◦ Bài toán giải theo phương pháp quy hoạch động được gọi là bài toán quy hoa ̣ch động
Trang 3◦ Công thức phối hợp nghiệm của các bài toán con để có nghiệm của bài toán lớn gọi là công thức truy hồi của quy hoạch động
◦ Tập các bài toán nhỏ nhất có ngay lời giải để từ đó giải quyết các bài toán lớn hơn gọi là cơ sở của quy hoa ̣ch động
◦ Không gian lưu trữ lời giải các bài toán con để tìm cách phối hợp chúng gọi là bảng quy hoạch hay bảng phương án của quy hoạch động
◦ Tính chất thứ nhất và thứ hai là điều kiện cần của một bài toán quy hoạch động
◦ Tính chất thứ ba nêu lên đặc điểm của một bài toán mà cách giải bằng phương pháp quy hoạch động hiệu quả hơn hẳn so với phương pháp giải đệ quy thông thường
◦ Khác với thuật toán đệ quy, phương pháp quy hoạch động thêm vào cơ chế lưu trữ nghiệm hay một phần nghiệm của mỗi bài toán khi giải xong nhằm mục đích sử dụng lại, hạn chế những thao tác thừa trong quá trình tính toán
Mục đích: Cải tiến thuật toán chia để trị hoặc quay lui vét cạn để giảm thời gian thực hiện Ý tưởng: Lưu trữ các kết quả của các bài toán con trong bảng quy hoạch Đổi bộ nhớ lấy
Thử các lựa chọn còn lại tại bước này
Phù hợp với bài toán liệt kê cấu hình dạng X[1, , n]
Trang 5Tham Lam
1 Cái ba lô
Trang 72 Lập lịch
Trang 83 Sắp xếp môn học
Đoạn code trên giải quyết bài toán lựa chọn các khóa học sao cho số lượng khóa học được chọn là lớn nhất, mà không có hai khóa học nào trùng thời gian Ý tưởng chính của phương pháp Greedy trong đoạn code này là sắp xếp các khóa học theo thời gian kết thúc tăng dần, sau đó lựa chọn lần lượt các khóa học không trùng thời gian với nhau
Giả sử có 5 khóa học như sau:
Khóa học 1 (id=1): bắt đầu lúc 6, kết thúc lúc 7 Khóa học 2 (id=2): bắt đầu lúc 7, kết thúc lúc 9 Khóa học 3 (id=3): bắt đầu lúc 8, kết thúc lúc 14 Khóa học 4 (id=4): bắt đầu lúc 10, kết thúc lúc 20 Khóa học 5 (id=5): bắt đầu lúc 9, kết thúc lúc 12
Sử dụng phương pháp Greedy, ta sẽ sắp xếp các khóa học theo thời gian kết thúc tăng dần
Trang 9Sau khi sắp xếp, ta lựa chọn lần lượt các khóa học không trùng thời gian với nhau Trong ví dụ này, ta sẽ lựa chọn các khóa học sau: 1, 2, 5, 4 Do đó, số lượng khóa học được chọn là 4 và đây là lịch trình tối ưu
4 Đóng thùng
Ý tưởng chính của thuật toán là sắp xếp các đồ vật theo thứ tự giảm dần của kích thước, sau đó lần lượt đặt từng đồ vật vào thùng có thể chứa được nó Nếu không thể đặt vào thùng hiện tại, thì tìm thùng trống khác có thể chứa được và đặt đồ vật vào đó
Trang 10Giả sử có các đồ vật có kích thước là {3, 2, 5, 1, 4, 3} và kích thước của thùng là 6 Chương trình sẽ thực hiện như sau:
Sau khi sắp xếp: {5, 4, 3, 3, 2, 1} Lần lượt đặt các đồ vật vào thùng: Thùng 1: {5}, còn lại kích thước 1 Thùng 2: {4, 1}, còn lại kích thước 1 Thùng 3: {3, 3}, còn lại kích thước 0 Thùng 4: {2}, còn lại kích thước 4
Trang 115 Rút tiền
Sắp xếp các mệnh giá tiền giảm dần: Trước tiên, danh sách các mệnh giá tiền được sắp xếp theo thứ tự giảm dần, từ mệnh giá lớn nhất đến nhỏ nhất Điều này giúp chương trình rút tiền bằng các tờ tiền có mệnh giá lớn trước, giảm thiểu số lượng tờ tiền cần rút
Rút tiền từ mệnh giá lớn nhất đến nhỏ nhất: Thuật toán lặp qua từng mệnh giá tiền từ lớn nhất đến nhỏ nhất Với mỗi mệnh giá, nó tính số tờ tiền cần rút bằng cách chia số tiền cần rút cho mệnh giá đó và lấy phần nguyên của kết quả Sau đó, cập nhật số tiền cần rút cho lần lặp tiếp theo
Giả sử danh sách các mệnh giá tiền có sẵn là {100,000, 50,000, 40,000, 20,000, 10,000}, và người dùng muốn rút 180,000 VND từ máy ATM
Trang 12Bắt đầu với mệnh giá 100,000 VND:
Số tờ tiền cần rút = 180,000 / 100,000 = 1 (phần nguyên) Cập nhật số tiền cần rút: 180,000 - 1 * 100,000 = 80,000 VND Tiếp tục với mệnh giá 50,000 VND:
Số tờ tiền cần rút = 80,000 / 50,000 = 1 (phần nguyên)
Cập nhật số tiền cần rút: 80,000 - 1 * 50,000 = 30,000 VND …
Trang 136 Trồng hoa
Trang 15 Bước 3: Kiểm tra xem vị trí đó có phù hợp để đặt quân hậu không bằng cách kiểm tra
các quân hậu đã đặt trước đó Nếu vị trí đó hợp lệ, tiến hành đặt quân hậu và gọi đệ quy để thử vị trí tiếp theo
Bước 4: Nếu đã đặt quân hậu vào vị trí cuối cùng trên bàn cờ (i == n), thì in ra cách xếp
hậu đó
Bước 5: Lặp lại các bước trên cho tất cả các quân hậu cho đến khi tìm được tất cả các
cách xếp hậu đúng trên bàn cờ hoặc không còn cách nào thỏa mãn
Trang 172 Dãy nhị phân
Thuật toán quay lui được sử dụng để sinh ra tất cả các tổ hợp nhị phân có độ dài n Ý tưởng chính của thuật toán là sử dụng một mảng để lưu trữ các giá trị 0 và 1, mỗi giá trị tại một vị trí của mảng biểu diễn cho một bit trong tổ hợp nhị phân Thuật toán sẽ thử tất cả các kết hợp có thể của các bit này, từ bit đầu tiên đến bit cuối cùng, và đệ quy tiến hành thử các giá trị có thể của bit tiếp theo cho đến khi đạt tới bit cuối cùng Khi đạt đến bit cuối cùng, thuật toán sẽ in ra kết quả và quay lui để thử các giá trị khác cho các bit trước đó
Dưới đây là các bước cụ thể của thuật toán:
- Khởi tạo một mảng có độ dài n để lưu trữ các giá trị của tổ hợp nhị phân
- Gọi hàm quay lui với các tham số là mảng đã khởi tạo, chỉ số của bit hiện tại (bắt đầu từ 1), và độ dài của tổ hợp nhị phân
- Trong hàm quay lui:
- Lặp qua các giá trị có thể của bit hiện tại (0 hoặc 1) - Gán giá trị cho bit hiện tại
- Nếu đã đạt tới bit cuối cùng, in ra tổ hợp nhị phân và kết thúc
- Nếu chưa đạt tới bit cuối cùng, gọi đệ quy để thử các giá trị cho bit tiếp theo
- Khi đã thử hết tất cả các giá trị cho bit hiện tại, quay lui để thử các giá trị khác cho bit trước đó
Trang 18Các bước giải: a Khởi tạo mảng x[] và b[] với giá trị ban đầu b Tạo hàm output() để in ra
một hoán vị c Tạo hàm đệ quy hoanvi() để sinh ra tất cả các hoán vị:
- Với mỗi vị trí i từ 1 đến n, thử tất cả các số chưa được chọn để điền vào vị trí i - Đánh dấu số đã chọn và gọi đệ quy để điền số tiếp theo
- Khi đã điền đủ n số, in ra hoán vị đó
- Hủy đánh dấu số đã chọn để thử các số khác d Gọi hàm hoanvi() từ main() để bắt đầu
sinh và in ra tất cả các hoán vị của các số từ 1 đến n
Trang 194 Mã đi tuần
Trang 205 Mã đi tuần theo chu trình đóng Ý tưởng:
- Sử dụng một ma trận A[][] để lưu vị trí các bước di chuyển trên bàn cờ - Sử dụng hai mảng X[] và Y[] để định nghĩa các hướng di chuyển từ mỗi ô - Sử dụng biến dem để đếm số bước đã đi
- Sử dụng biến x_first và y_first để lưu vị trí ban đầu
- Sử dụng hàm xuat() để in ra bàn cờ sau mỗi bước di chuyển
- Sử dụng hàm diChuyen() để thực hiện việc di chuyển từ vị trí hiện tại đến các vị trí khác trên bàn cờ, đồng thời kiểm tra xem mã đã đi hết các ô chưa và đã trở về vị trí ban đầu chưa
Trang 216 Tổng các tập con
Trang 227 Sudoku
- Kiểm tra tính hợp lệ của giá trị trong ô: kiểm tra xem có thể đặt giá trị k vào vị trí (x,
y) trên bảng Sudoku hay không Kiểm tra xem giá trị k đã tồn tại trong hàng, cột hoặc ô 3x3 chứa vị trí (x, y) chưa Nếu không hợp lệ, trả về 0; ngược lại trả về 1
- Giải bài toán Sudoku bằng đệ quy: Sử dụng phương pháp đệ quy để thử tất cả các giá
trị từ 1 đến 9 cho ô hiện tại và tiếp tục đệ quy để thử giá trị tiếp theo cho ô kế tiếp Nếu không tìm thấy giải pháp, quay lui và thử các giá trị khác cho ô hiện tại
- Tìm kiếm giải pháp: kết thúc cột hiện tại, kiểm tra xem đã đến cuối bảng chưa Nếu đã
đến cuối bảng, in ra giải pháp và kết thúc chương trình Nếu chưa, sẽ tiếp tục đệ quy với hàng tiếp theo Nếu ô hiện tại đã có giá trị, nó sẽ tiếp tục đệ quy với ô kế tiếp Nếu ô hiện
tại chưa có giá trị, nó sẽ thử tất cả các giá trị hợp lệ cho ô đó
Trang 238 Tổ hợp chập k của n
Trang 24Quy hoạch động
Ý tưởng chính là sử dụng một bảng hoặc một mảng để lưu trữ kết quả của các bài toán con nhỏ
Ta bắt đầu với các trường hợp cơ bản: C(n, 0) = C(n, n) = 1
Sau đó, ta sử dụng công thức tổ hợp: C(n, k) = C(n-1, k-1) + C(n-1, k) để tính toán các giá trị còn lại
Bằng cách tính toán từ trên xuống dưới và từ trái sang phải, ta có thể xây dựng bảng hoặc mảng chứa kết quả của tất cả các bài toán con nhỏ
Kết quả cuối cùng sẽ nằm ở ô góc phải dưới cùng của bảng hoặc mảng
Trang 252 Dãy Fibonacci
Khởi tạo mảng F và giá trị ban đầu của dãy Fibonacci: Mảng F được khai báo với kích thước n và được sử dụng để lưu trữ các số trong dãy Fibonacci Ban đầu, hai phần tử đầu tiên của dãy Fibonacci là 0 và 1 được gán trực tiếp vào F[0] và F[1]
Duyệt qua từng phần tử của dãy Fibonacci từ 2 đến n: Sử dụng một vòng lặp, với mỗi giá trị i từ 2 đến n, tính giá trị của F[i] bằng cách cộng hai giá trị trước đó của dãy Fibonacci, tức là F[i-1] và F[i-2]
In ra dãy Fibonacci: Cuối cùng, dãy Fibonacci được in ra màn hình từ F[0] đến F[n]
3 Tổng tập con
Trang 26Sử dụng bảng hoặc mảng: Bước đầu tiên là tạo một bảng hoặc mảng 1 chiều để lưu trữ kết quả của các bài toán con nhỏ Mỗi phần tử trong mảng này thường là một giá trị boolean, chỉ ra xem có thể tạo thành tổng mục tiêu từ một tập con của dãy ban đầu hay không
Trường hợp cơ bản: Khởi tạo các giá trị cơ bản của bảng hoặc mảng Ví dụ, nếu mục tiêu là 0, thì tất cả các giá trị trong mảng này đều là True vì có thể tạo ra tổng 0 từ bất kỳ tập con nào (bằng cách không chọn phần tử nào)
Tính toán các giá trị còn lại: Sử dụng phương pháp quy hoạch động, tính toán các giá trị còn lại của bảng hoặc mảng dựa trên giá trị của các bài toán con nhỏ hơn Mỗi ô của bảng hoặc mảng thường được tính toán dựa trên hai trường hợp: (a) nếu không bao gồm phần tử hiện tại, (b) nếu bao gồm phần tử hiện tại
Trả về kết quả: Kết quả cuối cùng thường được lấy từ ô cuối cùng của bảng hoặc mảng Nếu giá trị tại ô này là True, có nghĩa là có thể tạo thành tổng mục tiêu từ một tập con của dãy ban đầu
Trang 274 Cái ba lô
Ý tưởng giải bài toán:
Xây dựng bảng: Tạo một bảng 2 chiều có kích thước n x W, trong đó n là số lượng đồ vật và W là trọng lượng tối đa của cái ba lô
Khởi tạo giá trị ban đầu: Đặt tất cả các giá trị trong hàng đầu tiên và cột đầu tiên của bảng là 0
Tính toán giá trị tối ưu: Sử dụng công thức tối ưu L[i][t] = max(L[i - 1][t], L[i][t-g[i]] + v[i]), trong đó v [i] là giá trị của đồ vật thứ i, g[i] là trọng lượng của đồ vật thứ i, và L[i][t] là giá trị tối ưu có thể đạt được với i đồ vật và trọng lượng t
Trả về giá trị tối ưu: Giá trị tối ưu cuối cùng sẽ nằm ở ô góc dưới cùng bên phải của bảng
Trang 285 Dãy con tăng dài nhất
Ý tưởng giải bài toán:
Khởi tạo mảng L để lưu độ dài của dãy con tăng dài nhất kết thúc tại mỗi vị trí của dãy Bổ sung phần tử ảo INT_MAX vào cuối dãy A
Duyệt qua từng phần tử của dãy, cập nhật giá trị của L[i] bằng cách so sánh với các phần tử
Trang 29Trả về độ dài của dãy con tăng dài nhất và in ra dãy con đó Lập bảng quy hoạch
| 3 4 2 8 10 5 1 ∞ - 1 | 1 1 1 1 1 1 1 1 1 2 2 3 4 3 2 1
Trang 306 Đổi tiền
Khởi tạo mảng P và L: Mảng P được khởi tạo với giá trị INT_MAX, đại diện cho số lượng đồng tiền ít nhất cần để đổi đến số tiền tương ứng Mảng L cũng được khởi tạo tương tự nhưng có thêm phần tử 0 ở đầu
Duyệt qua từng loại đồng tiền và mỗi mức số tiền từ 1 đến M: Tại mỗi bước lặp, ta cập nhật mảng L dựa trên mảng P, sử dụng các loại đồng tiền hiện có
Cập nhật mảng L: Tại mỗi mức số tiền t, ta cập nhật L[t] bằng giá trị nhỏ nhất giữa P[t] (số lượng đồng tiền ít nhất cho số tiền t mà không sử dụng đồng tiền hiện tại) và L[t-a[i]] + 1 (số lượng đồng tiền ít nhất cho số tiền t - a[i] cộng thêm một đồng tiền hiện tại)
Trả về kết quả: Trả về giá trị cuối cùng của L[M], tức là số lượng đồng tiền ít nhất cần thiết để đổi số tiền M
Lập bảng quy hoạch
| 0 1 2 3 4 5 6 7 8 9 10 - 0 | 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ 1 | 0 1 2 3 4 5 6 7 8 9 10 2 | 0 1 1 2 2 3 3 4 4 5 5 3 | 0 1 1 1 2 2 2 3 3 3 4 5 | 0 1 1 1 2 1 2 2 3 2 3 10 | 0 1 1 1 2 1 2 2 3 2 1
Trang 317 Độ dài xâu con dài nhất
Duyệt qua từng ký tự trong xâu X và Y:
Nếu X[i] bằng Y[j], tức là ta tìm thấy một ký tự mới trong xâu con chung Ta cập nhật giá trị của L[j] bằng giá trị của P[j-1]
Ngược lại, ta cập nhật giá trị của L[j] bằng giá trị lớn nhất giữa P[j] và L[j-1]
Sau khi duyệt qua tất cả các ký tự trong X và Y, ta cập nhật mảng P bằng mảng L và tiếp tục duyệt
Kết quả cuối cùng là giá trị của L[ny], tức là độ dài của xâu con chung dài nhất Lập bảng quy hoạch
| C o n g C h i e n - 0 | 0 0 0 0 0 0 0 0 0 C 0 | 0 1 1 1 1 1 1 1 1 h 0 | 0 1 1 1 1 2 2 2 2 i 0 | 0 1 1 1 1 2 3 3 3
Trang 32e 0 | 0 1 1 1 1 2 3 3 4 n 0 | 0 1 1 1 1 2 3 4 4 C 0 | 0 1 2 2 2 2 2 2 2 o 0 | 0 1 2 2 3 3 3 3 3 n 0 | 0 1 2 3 3 3 3 3 3 g 0 | 0 1 2 3 4 4 4 4 4
2 Kiểm tra phần tử ở giữa:
So sánh phần tử ở giữa mảng với giá trị cần tìm Nếu phần tử này trùng khớp với giá trị cần tìm, chúng ta đã tìm thấy nó và trả về chỉ số của phần tử đó
3 Chọn phần mảng để tiếp tục tìm kiếm:
Nếu phần tử ở giữa mảng lớn hơn giá trị cần tìm, chúng ta chỉ cần tìm kiếm trong nửa mảng bên trái
Trang 33Nếu phần tử ở giữa mảng nhỏ hơn giá trị cần tìm, chúng ta chỉ cần tìm kiếm trong nửa mảng bên phải
4 Lặp lại quá trình cho đến khi tìm thấy hoặc không thể tìm thấy:
Lặp lại quá trình trên cho đến khi tìm thấy phần tử cần tìm hoặc phần tử này không tồn tại trong mảng Nếu phần tử không tồn tại, chúng ta sẽ trả về một giá trị đặc biệt để biểu thị việc không tìm thấy
5 Trả về kết quả:
Nếu tìm thấy phần tử, trả về chỉ số của nó trong mảng
Nếu không tìm thấy, trả về một giá trị đặc biệt để biểu thị việc không tìm thấy
Trang 342 Dãy con liên tục có tổng lớn nhất
1 Khởi tạo các biến:
max_so_far: lưu trữ tổng lớn nhất của dãy con liên tục đã tìm thấy curr_max: lưu trữ tổng tạm thời của dãy con liên tục hiện tại begin và end: lưu trữ chỉ số của dãy con liên tục có tổng lớn nhất 2 Duyệt qua mảng:
Bắt đầu từ phần tử thứ 2 của mảng, vì chúng ta đã xử lý phần tử đầu tiên ở bước khởi tạo Duyệt qua từng phần tử của mảng:
So sánh a[i] với curr_max + a[i] Nếu a[i] lớn hơn tổng tạm thời hiện tại (curr_max + a[i]), tức là bắt đầu một dãy con mới từ a[i]
Cập nhật curr_max thành giá trị lớn hơn giữa a[i] và curr_max + a[i] So sánh max_so_far với curr_max để cập nhật giá trị max_so_far nếu cần 3 Xác định dãy con liên tục có tổng lớn nhất:
Sau khi duyệt qua toàn bộ mảng, max_so_far sẽ chứa tổng lớn nhất của dãy con liên tục Sử dụng begin và end để in ra dãy con liên tục có tổng lớn nhất
4 Trả về kết quả:
Trả về giá trị max_so_far, tức là tổng lớn nhất của dãy con liên tục