Uploaded by Duy Khương

KhuongDuyCTDL&GT

advertisement
Tiêu đề: Thuật toán chia để trị.
1. Giới thiệu:
Chia để trị là 1 phương pháp á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 từ việc giải quyết các bài toán con này. Sau đó lời giải của các bài toán
nhỏ được tổng hợp lại thành lời giải cho bài toán ban đầu.
Hình ảnh minh hoạ cho cách chia bài toán lớn thành nhiều bài toán nhỏ đơn giản hơn rồi sau đó
tổng hợp lại thành lời giải cho bài toán ban đầu
2. Nguyên tắc hoạt động
Nguyên tắc cơ bản của thuật toán chia để trị bao gồm ba bước chính:
Bước 1: Chia\tách nhỏ
Tại bước này thì bài toán ban đầu sẽ được chia thành các bài toán con cho đến khi không thể chia
nhỏ được nữa. Các bài toán con kiểu sẽ trở thành 1 bước nhỏ trong việc giải quyết bài toán lớn.
Bước 2: Trị\giải quyết bài toán con
Tại bước này ta sẽ phải tìm phương án để giải quyết cho bài toán con một cách cụ thể.
Bước 3: Kết hợp lời giải lại để suy ra lời giải
Khi đã giải quyết xong cái bài toán nhỏ, lặp lại các bước giải quyết đó và kết hợp lại những lời
giải đó để suy ra kết quả cần tìm (có thể ở dạng đệ quy).
3. Ví dụ minh hoạ
Bài toán tình kiếm nhị phân
Tìm kiếm nhị phân là một thuật toán dùng để tìm kiếm 1 phần tử trong một danh sách đã được sắp
xếp. Thuật toán hoạt động như sau:
Bước 1(Chia): Danh sách ban đầu sẽ được chia thành 2 nửa
Bước 2 (Trị): Trong mỗi bước, so sánh phần tử cần tìm với phần tử nằm ở chính giữa danh sách.
Nếu hai phần tử bằng nhau thì phép tìm kiếm thành công và thuật toán kết thúc. Nếu chúng không
bằng nhau thì tùy vào phần tử nào lớn hơn, thuật toán lặp lại bước so sánh trên với nửa đầu hoặc
nửa sau của danh sách. Vì số lượng phần tử trong danh sách cần xem xét giảm đi một nửa sau mỗi
bước, nên thời gian thực thi của thuật toán là hàm lôgarit.
Bước 3: Bằng việc lặp lại cách giải quyết như bước 2 ta sẽ tìm được kết quả.
int binarySearch(int array[], int left, int right, int x)
{
// nếu chỉ số left > right dừng lại và return -1 không có kết quả
if (left > right) return -1;
// tìm chỉ số ở giữa của mảng
int mid = (left + right) / 2;
// nếu số cần tìm bằng số ở giữa của mảng thì return
if (x == array[mid])
return mid;
// nếu số cần tìm nhỏ hơn số ở giữa của mảng thì tìm sang nửa bên trái
if (x < array[mid])
return binarySearch(array, left , mid-1, x);
// nếu số cần tìm lớn hơn số ở giữa của mảng thì tìm sang nửa bên phải
if (x > a[mid])
return binarySearch(a, mid+1 , right, x);
}
Bài toán Quicksort
Bước 1(chia): Thuật toán quicksort chia danh sách cần sắp xếp mảng array[1..n] thành hai danh
sách con có kích thước tương đối bằng nhau nhờ chỉ số của phần tử gọi là chốt, ta có thể chọn
chốt là phần tử ở giữa, ở cuối, ở đầu hoặc phần tử ngẫu nhiên nào trong mảng.
Bước 2(trị): Sau khi đã chia thành 2 mảng dựa vào phần tử chốt nhiệm vụ của bước này là phải
sắp xếp sao cho: 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(Trường hợp sắp xếp tăng dần). 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
Bước 3: Bằng việc lặp các bước giải quyết các bài toán con trên ta sẽ thu được kết quả là mảng sẽ
được sắp xếp.
Dưới đây là hình ảnh minh họa việc thực hiện thuật toán quicksort:
// hàm giải quyết việc sắp xếp các phần tử ở hai đầu của mảng
// dựa vào phần tử chốt là phần tử cuối mảng
int partition(int arr[], int low, int high)
{
// chốt được chọn ở đây là phần tử cuối mảng
int pivot = arr[high];
int i = (low-1);
for (int j=low; j<high; j++)
{
// nếu phần tử nhỏ hơn hoặc bằng với chốt
if (arr[j] <= pivot)
{
i++;
// đổi chỗ arr[i] và arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// đổi chỗ arr[i+1] và arr[high] (chốt)
int temp = arr[i+1];
arr[i+1] = arr[high];
arr[high] = temp;
// trả về chỉ số của chốt
return i+1;
}
// Hàm thực hiện quicksort
void sort(int arr[], int low, int high)
{
// nếu chỉ số của đầu mảng nhỏ hơn chỉ số cuối mảng
if (low < high)
{
// tìm chỉ số của chốt sau khi đã thực hiện sắp xếp
int pi = partition(arr, low, high);
// lặp lại các bước với mảng từ phần tử đầu tiên đến chốt - 1
// và từ chốt + 1 đến phần tử cuối cùng của mảng.
sort(arr, low, pi-1);
sort(arr, pi+1, high);
}
}
4. Ưu và nhược điểm
Ưu điểm
Ưu điểm đầu tiên và cũng dễ nhận biết nhất của phương pháp chia để trị là nó giúp ta giải các bài
toán khó. Ví dụ như bài toán tháp Hà Nội là một trò chơi giải đố toán học khó, nhưng với phương
pháp chia để trị, nhờ việc chia bài toán thành các bài toán nhỏ hơn nó đã giải được một cách dễ
dàng.
Một ưu điểm khác của mô hình này, nó góp phần khám phá ra các thuật toán hiệu quả như Quick
sort hay Merge sort. Nó sử dụng bộ nhớ cache một cách hiệu quả do khi bài toán con đủ đơn giản,
chúng có thể được giải bên trong cache mà không phải truy cập bộ nhớ chính. Như trong bài toán
sắp xếp nhanh, thay vì chia mảng thành các mảng con có 1 phần tử, thuật toán có thể kết hợp sắp
xếp chèn cho mảng con có ít phần tử để sử dụng bộ nhớ cache hiệu quả hơn. Ngoài ra, việc tính
toán một số bài toán con có thể thực hiện song song trên các bộ vi xử lý đa nhân, làm giảm thời
gian tính toán một cách đáng kể.
Nhược điểm
Một trong những vấn đề phổ biến của chia để trị là việc sử dụng đệ quy khi cài đặt thuật toán.
Trong một số trường hợp, làm giảm đi các ưu thế của phương pháp này. Đầu tiên việc một hàm tự
gọi chính nó trong đệ quy cần nhiều không gian bộ nhớ và việc cấp phát bộ nhớ làm giảm tốc độ
thuật toán khi đệ quy. Tiếp theo, một bài toán có thể chia thành nhiều bài toán con, và một bài toán
con có thể xuất hiện nhiều lần làm thuật toán kém hiệu quả như trong bài toán tính số Fibonacci
thứ n sử dụng phương pháp chia đệ trị bằng đệ quy.
Một nhược điểm khác của phương pháp này là việc cài đặt thuật toán bằng phương pháp chia để
trị đôi khi phức tạp hơn đáng kể so với phương pháp lặp truyền thống.
5. Bài tập
Bài 1: Cho một dãy gồm n số nguyên và một số nguyên x. Hãy đếm xem trong dãy có bao nhiêu phần tử
có giá trị x.
Ví dụ:

Test mẫu 1:
Input
Output
5
12324
2
2
Với a = [1, 2, 3, 2, 4] và x = 2 thì kết quả mong muốn là 2.

Test mẫu 2:
Input
Output
4
1342
5
0
Với a = [1, 3, 4, 2] và x = 5 thì kết quả mong muốn là 0.
Bài giải:
#include<iostream>
using namespace std;
int a[100001];
int count(int a[], int l, int r, int x){
if (l == r){
if (a[l] == x) return 1;
else return 0;
} else {
int m = (l+r)/2;
return count(a, l, m, x) + count(a, m+1, r, x);
}
}
int main(){
int n, x;
cin >> n;
for (int i = 0; i < n; i++){
cin >> a[i];
}
cin >> x;
cout << count(a, 0, n-1, x);
return 0;
}
Download