5. Discussion question
Tại sao vét cạn luôn là phương pháp tiếp cận đầu tiên để giải bài toán?
Tăng tốc độ vét cạn bao gồm những chiến thuật nào?
Theo bạn, giới hạn của việc áp dụng vét cạn cho các bài toán tổ hợp là bao nhiêu và vì sao?
Lấy ví dụ trong thực tế (cuộc sống, công việc) bạn đã áp dụng thuật toán vét cạn. Cách bạn tăng tốc các xử lý.
6. Homework
2.1.0 Homework
Dựa theo 2.0 Các khái niệm cơ bản, các bài tập nên dựa theo chiến lược sau cho việc áp dụng thuật toán vét cạn:
Cách liệt kê các cấu hình và cách xác thực một cấu hình (thuật toán vét cạn)
Không gian tìm kiếm (phân tích thời gian giải thuật)
Mã giả
Tinh chế từng bước mã giả → code
Kiểm thử (xây dựng các bộ test + chạy thử và tìm lỗi + submit lên hệ thống)
Các nhận xét để tăng tốc vét cạn (giảm không gian tìm kiếm hoặc sắp xếp lại không gian tìm kiếm)
Áp dụng cách thức tăng tốc → code (có thể áp dụng tinh chế từng bước)
Kiểm thử (chạy thử và tìm lỗi + submit lên hệ thống)
Lời giải cùng giải thích sẽ được cung cấp trước buổi học 2.2 Binary Search & Divide and Conquer (Thuật toán tìm kiếm nhị phân và chia để trị)
Bài 1: Tìm một số trong mảng đã sắp xếp và dịch phải - easy level
https://leetcode.com/problems/search-in-rotated-sorted-array-ii/
Cho một mảng sắp xếp tăng dần đã bị dịch phải n bước, tìm xem liệu một số cho trước có nằm trong mảng này không
Ví dụ: cho mảng [1 2 3 4 5 6]
Dịch 1 bước ta có: [6 1 2 3 4 5]
Dịch 4 bước ta có: [3 4 5 6 1 2]
Dịch 6 bước ta có mảng ban đầu
Điều kiện:
Độ dài mảng: 1 <= n <= 5000
Phần tử của mảng: -10^4 <= A[i] <= 10 ^ 4
Bài 2: Tìm chuỗi con palindrome dài nhất - easy level
https://leetcode.com/problems/longest-palindromic-substring/
Chuỗi palindrome là chuỗi mà viết xuôi hay ngược đều giống nhau, ví dụ: “aba” , “tnt”
Cho một chuỗi kí tự s, tìm chuỗi con dài nhất của s sao cho chuỗi này là palindrome
Độ dài của chuỗi s nằm trong khoảng từ 1 đến 1000
Bài 3: REPROAD - easy level
https://www.spoj.com/problems/REPROAD/
Tóm tắt đề bài:
Cho một con đường dài N (5 <= N <= 10000) gồm các đoạn 0 (bị hỏng, không di chuyển được) và 1 (di chuyển được); và số K (0<=K<=N).
Bài toán cho phép sửa một đoạn đường bị hỏng (sửa 0 thành 1). Có tối đa K lần sửa.
In ra chiều dài lớn nhất của quãng đường di chuyển được (số các số 1 liên tiếp) với K lần sửa.
Input
Tổng số lượng phép thử là T (1 <= T <= 20) được cho trên dòng đầu tiên.
Mỗi phép thử được cho trên 2 dòng, dòng đầu tiên của mỗi phép thử là chiều dài N (5 <= N <= 10000) của con đường và số lần sửa tối đa K (0 <= K <= N), cách nhau bởi 1 dấu cách trắng.
Dòng tiếp theo mô tả trạng thái của từng đoạn đường (0 hoặc 1), 2 số cạnh nhau được ngăn cách bởi 1 dấu cách trắng.
Output
Hãy in đáp án của mỗi phép thử trên 1 dòng.
Bài 4: SOLIT - hard level
https://www.spoj.com/problems/SOLIT/
Tóm tắt đề bài:
Cho một bàn cờ 8x8 ô, đánh số từ 1 đến 8 (từ trên xuống dưới, từ trái sang phải).
Trên bàn cờ có 4 quân cờ khác nhau. Mỗi quân cờ có thể di chuyển:
- Di chuyển theo hướng Trên/Dưới/Trái/Phải sang ô còn trống ngay bên cạnh.
- Nếu ô bên cạnh đã có một quân cờ, nhảy qua ô này đến ô còn trống tiếp theo (trên cùng hướng di chuyển) nếu ô đó còn trống.
Bàn cờ A có 4 quân cờ.
Bàn cờ B có 4 quân cờ.
Xác định xem với tối đa 8 lần di chuyển các quân cờ trên bàn cờ A, có thể làm bàn cờ A và bàn cờ B có các quân cờ ở vị trí giống nhau hay không?
Input
Tổng số lượng phép thử là T được cho trên dòng đầu tiên.
Mỗi phép thử được cho trên 2 dòng, dòng đầu tiên của mỗi phép thử là tọa độ các quân cờ (a[2j-1] và a[2j] (1 <= j <= 4) lần lượt là hàng và cột của quân cờ thứ j) của bàn cờ A, cách nhau bởi dấu cách trắng.
Dòng tiếp theo là tọa độ các quân cờ của bàn cờ B, cách nhau bởi dấu cách trắng.
Output
Hãy in đáp án của mỗi phép thử trên 1 dòng dưới dạng:
Case #i: K
Bài 5: BURGLARY - medium level
https://www.spoj.com/problems/BURGLARY/
Tóm tắt đề bài:
Cho 1 mảng N (1<=N<=30) các số tự nhiên (mỗi số nằm trong khoảng từ 0 đến 1e9) và một số D (0<=D<=3*1e10).
Chọn K số bất kì trong mảng N sao cho tổng của các số này bằng D.
- Nếu chỉ có 1 cách chọn duy nhất, in ra số k
- Nếu có nhiều cách chọn khác nhau (nhiều số k khác nhau thỏa mãn đề bài), in "AMBIGIOUS"
- Nếu không có cách chọn nào, in "IMPOSSIBLE"
Input
Tổng số lượng phép thử là T (1 <= T <= 20) được cho trên dòng đầu tiên.
Mỗi phép thử được cho trên 2 dòng, dòng đầu tiên của mỗi phép thử là N và D, cách nhau bởi 1 dấu cách trắng.
Dòng tiếp theo mô tả các giá trị trong mảng, 2 số cạnh nhau được ngăn cách bởi 1 dấu cách trắng.
Output
Hãy in đáp án của mỗi phép thử trên 1 dòng dưới dạng:
Case #i: K
7. Xem thêm:
2.1.2 Thuật toán quay lui (backtracking)
1. Thuật toán quay lui
Thuật toán quay lui dùng để giải bài toán liệt kê các cấu hình. Mỗi cấu hình được xây dựng bằng cách xây dựng từng phần tử, mỗi phần tử được chọn bằng cách thử tất cả các khả năng.
GIả sử cấu hình cần liệt kê có dạng (x1, x2, ..., x(n)). Khi đó thuật toán quay lui thực hiện qua các bước sau:
1> Xét tất cả cá giá trị x1 có thể nhận, thử cho x1 lần lượt nhận các giá trị đó. Với mỗi giá trị thử gán cho x1 ta sẽ tiến hành bước 2.
2> Xét tất cả cá giá trị x2 có thể nhận, thử cho x2 lần lượt nhận các giá trị đó. Với mỗi giá trị thử gán cho x2 ta sẽ tiến hành các khả năng chọn cho x3... cứ tiếp tục như vậy cho đến bước n
.........
n> Xét tất cả cá giá trị x(n) có thể nhận, thử cho x(n) lần lượt nhận các giá trị đó. Với mỗi giá trị thử gán cho x(n) ta sẽ thông báo cấu hình tìm được (x1, x2, ..., x(n)).
Trên phương diện quy nạp, ta có thể nói rằng thuật toán quay lui liệt kê các cấu hình n phần tử dạng (x1, x2, ..., x(n)) bằng cách thử cho x1 nhận lần lượt các giá trị có thể. Với mỗi giá trị gán cho x1 lại liệt kê cấu hình n-1 phần tử (x2, x3, ..., x)n)).
Mô hình của thuật toán quay lui có thể mô tả như sau:
void try(int i) {
for (v: {tập giá trị V mà có thể gán cho x[i]}) {
x[i] = v;
if (i == n) {
thông báo cấu hình tìm được;
}
else {
mark[v] = true; //Ghi nhận việc x[i] nhận giá trị v nếu cần
try(i + 1);
mark[v] = false; //Bỏ việc ghi nhận x[i] nhận giá trị v nếu cần
}
}
}
Thuật toán quay lui sẽ bắt đầu bằng lời gọi try(1);
2. Liệt kê các dãy nhị phân độ dài n (bài toán 4)
Dãy nhị phân đội dài n được biểu diễn dưới dạng (x1, x2, ..., x(n)). Ta sẽ liệt kê dãy này bằng cách dùng các giá trị {0 , 1} gán cho x(i). Với mỗi giá trị thử gán cho x(i) ta thử lại các giá trị có thể gán cho x(i+1).
Ta có chương trình liệt kê các dãy nhị phân độ dài n bằng thuật toán quay lui như sau:
void try(int i) {
for (int v = 0; v <= 1; ++v) {
x[i] = j;
if (i == n)
print(x);
else
try(i + 1);
}
}
Khi n =3, cây tìm kiếm quay lui sẽ như sau:
(star) Với dãy tam phân, tứ phân, ... n phân thì việc áp dụng cũng chỉ đơn giản là thay tập giá trị V. Với nhị phân V = {0 , 1}, tam phân V = {0 , 1, 2}, .... n phân V = {0 , 1, ..., n}
3. Liệt kê các tập con k phần tử (bài toán 2)
Để liệt kê tập con k phần tử của tập {1, 2, ..., n}, ta có thể đưa về liệt kê các cấu hình (x1, x2, ...., x(k)) với x1 < x2 < .... < x(k).
Ta có nhận xét:
x(k) ≤ n
x(k-1) ≤x(k) - 1 ≤ n -1
....
x(i) ≤ n - k + i
....
x1 ≤ n -k + 1
Từ đó suy ra x(i-1) + 1 ≤ x(i) ≤ n - k + i (1 ≤ i ≤ k), ở đây giả thiết chúng ta có thêm 1 số x0 khi xét i = 1.
Như vậy ta sẽ xét tất cả các cách chọn x1 từ 1 (x0 + 1) đến n - k + 1, với mỗi giá trị đó xét tất cả các lựa chọn x2 từ x1+1 đến n - k + 2, ...., cứ như vậy đến x(k) thì ta có cấu hình cần liệt kê.
Ta có chương trình liệt kê các tập con k phần tử bằng thuật toán quay lui như sau:
void try(int i) {
for (int v = x[i - 1] + 1; v <= n - k + i; ++v) {
x[i] = v;
if (i == k)
print(x);
else
try(i+1);
}
}
4. Liệt kê các chỉnh hợp không lặp chập k (bài toán số 3)
Để liệt kê các chính hợp không lặp chập k của tập {1, 2, ..., n} ta có thể đưa về liệt kê các cấu hình (x1, x2, ...., x(k)) với các x(i) khác nhau từng đôi một.
Như vậy hàm try(i) xét tất cả các khả năng chọn từ 1 đến n mà các giá trị này chưa bị các phần tử đứng trước chọn. Muốn xem phần tử nào chưa được chọn thì chúng ta sử dụng mảng đánh dấu.
Khởi tạo một mảng c1, c2, ..., c(n) kiểu boolean. C(i) cho biết giá trị i có còn tự do hay đã bị chọn rồi.
Ban đầu, tất cả giá trị trong mảng c là TRUE, có nghĩa là đang tự do.
Tại bước chọn các giá trị có thể của x(i), ta chỉ xét những giá trị đang tự do.
Trước khi gọi đệ quy tìm x(i+1) <try(i + 1)>, ta cần đặt giá trị v vừa gán cho x(i) là đã bị chọn, có nghĩa là đặt c(v) = FALSE để các bước tiếp theo try(i+1), try(i+1),... không chọn phải giá trị đó nữa.
Sau khi gọi đệ quy tìm x(i+1) <try(i + 1)>, nghĩa là chúng ta chuẩn bị gán một giá trị khác cho x(i) thì chúng ta đặt lại giá trị v vừa thử thành tự do, có nghĩa là c(v) = TRUE.
Tại bước i = k, ta không cần đánh dấu vì sau đó không còn bước nào nữa cả, việc của chúng ta là in ra cấu hình.
Ta có chương trình liệt kê các dãy nhị phân độ dài n bằng thuật toán quay lui như sau:
void try(int i) {
for (int v = 1; v <= n; ++v) {
if (c[v]) { //giá trị v đang tự do
x[i] = v;
if (i == k) // Nếu đã chọn đến bước thứ k thì chỉ cần in ra
print(x);
else {
c[v] = false; // đánh dấu giá trị v đã được lựa chọn
try(i+1);
c[v] = true; // bỏ đánh dấu, giá trị v trở thành tự do vì x[i] sắp được thử một các chọn khác.
}
}
}
}
5. Liệt kê các hoán vị (bài toán số 1)
Bài toán liệt kê các hoán vị chính là bài toán liệt kê các chỉnh hợp lặp chập k với k = n. Do đó, việc thực hiện không khác gì phần 4. Do k = n nên ta thay điều kiện i == k bằng i ==n là được.
void try(int i) {
for (int v = 1; v <= n; ++v) {
if (c[v]) { //giá trị v đang tự do
x[i] = v;
if (i == n) // Nếu đã chọn đến bước thứ n thì chỉ cần in ra
print(x);
else {
c[v] = false; // đánh dấu giá trị v đã được lựa chọn
try(i+1);
c[v] = true; // bỏ đánh dấu, giá trị v trở thành tự do vì x[i] sắp được thử một các chọn khác.
}
}
}
}
6. Discussion question
Khi nào thì cần ghi nhận việc phần tử thứ i được gán giá trị v, khi nào không?
So sánh thứ tự liệt kê của backtracking và tuần tự (nằm trong 2.1 Exhaustive search (Thuật toán vét cạn)) cho bài toán liệt kê các chỉnh hợp không lặp chập k.
Sự khác nhau cơ bản trong cách sử dụng của cách liệt kê bằng backtracking và tuần tự là gì?
Thay đổi cách cài đặt các chương trình trên để thuật toán dừng khi liệt kê đến một cấu hình định trước.
2.1.3 Kỹ thuật nhánh cận
1. Cơ sở lý thuyết
Bài toán tối ưu
Một trong những bài toán đặt ra trong thực tế là tìm ra một nghiệm thỏa mãn một số điều kiện nào đó, và nghiệm đó là tốt nhất theo mội số chỉ tiêu cụ thể (ví dụ bài tìm min, max). Việc nghiên cứu lời giải các bài toán tối ưu thuộc về lĩnh vực quy hoạch toán học. Tuy nhiên có nhiều trường hợp chúng ta chưa thể xây dựng được một thuật toán nào thực sự hữu hiệu để giải bài toán. Mà tới nay chúng ta vẫn phải dùng mô hình liệt kê toàn bộ các cấu hình để đánh giá và tìm ra cấu hình tốt nhất.
Sự bùng nổ tổ hợp
Mô hình thuật toán quay lui là tìm kiếm trên một cây phân cấp. Giả sử rằng mỗi nút sẽ có k lựa chọn, thì cây n cấp sẽ có n^m lá. Do vậy nếu ta có thao tác thừa trong việc chọn giá trị cho nút thì sẽ phải trả giá rất lớn về chi phí thực thi. Khi đó, một vấn đề đặt ra là trong quá trình liệt kê lời giải chúng ta cần tận dụng những thông tin đã tìm được để loại bỏ sớm những phương án không tối ưu. Kỹ thuật đó gọi là kỹ thuật đánh giá nhánh cận trong tiến trình quay lui.
2. Mô hình kỹ thuật nhánh cận
Dựa trên mô hình thuật toán quay lui, ta xây dựng mô hình như sau:
void init() {
Khởi tạo một cầu hình bất kỳ BEST_CONFIG;
}
void try(int i) {
for (v : {tập giá trị có thể cho x[i]) {
x[i] = v;
if (việc thử còn hy vọng để tìm ra cấu hình tốt hơn BEST_CONFIG) {
if (i == k) { //Nếu i là phần tử cuối cùng trong cấu hình
cập nhật BEST_CONFIG;
}
else {
mark[v] = true; //Ghi nhận việc x[i] nhận giá trị V nếu cần
try(i+1);
mark[v] = false; //Bỏ ghi nhận việc thử cho x[i] nếu cần
}
}
}
}
Kỹ thuật nhánh cận thêm vào cho thuật toán quay lui khả năng đánh giá từng bước. Nếu tại bước thứ i mà việc thử gán giá trị cho x(i) không có hi vọng tìm thấy cấu hình tốt hơn BEST_CONFIG thì thử ngay giá trị khác mà không cần gọi đệ quy bước tiếp hay ghi nhận kết quả. Nghiệm của bài toán sẽ được làm tốt dần, bởi vì khi ta tìm ra một cấu hình mới tốt hơn BEST_CONFIG, ta sẽ không in ra kết quả ngay mà cập nhập BEST_CONFIG bằng cấu hình mới vừa tìm được.
3. Discussion question
Dùng kỹ thuật nhánh cận để giải bài toán người giao hàng. https://www.spoj.com/PTIT/problems/BCTSP/
Dùng kỹ thuật nhánh cận để giải bài toán Knapsack. https://www.spoj.com/problems/KNAPSACK/
Video: https://drive.google.com/drive/folders/1-92wNzSETMXSIDQgaPsei2OrKGBeaUDx?usp=sharing
Highlights
Lastest Articles
Thứ Tư, 6 tháng 4, 2022
Algorithm 2: Exhaustive search (Thuật toán vét cạn) phần 2
Algorithm 3: Binary Search & Divide and Conquer (Thuật toán tìm kiếm nhị phân và chia để trị) phần 2
3.3 Thuật toán heap sort
4. Các bài toán chia để trị
4.1 Tìm tập con liên tục có tổng lớn nhất
https://practice.geeksforgeeks.org/problems/kadanes-algorithm-1587115620/1
Cho một mảng gồm N số nguyên. Tìm mảng con liền nhau có tổng lớn nhất.
Bài toán dùng brute-fore ta có độ phức tạp sẽ là O(n^3), bằng cách xét tổng của tất cả các mảng con liền nhau:
int maxSubarraySum(int arr[], int n){
int maxsum = arr[0];
for (int i = 0; i < n; i ++) {
for (int j = i; j < n; j ++) {
int sum = 0;
for (int k =i; k <= j; k++) sum+= arr[k];
if (sum > maxsum)
maxsum = sum;
}
}
return maxsum;
}
Áp dụng chia để trị ta có thể giải bài toán này theo cách sau:
Chia mảng ban đầu thành hai mảng
Trả ra tổng lớn nhất của:
Tổng lớn nhất của mảng bên trái (bằng cách gọi đệ quy)
Tổng lớn nhất của mảng bên phải (bằng cách gọi đệ quy)
Tổng lớn nhất của mảng gồm phần tử cả phần tử ở giữa
int maxCrossingSum(int arr[], int l, int m, int h)
{
int sum = 0;
int left_sum = INT_MIN;
for (int i = m; i >= l; i--) {
sum = sum + arr[i];
if (sum > left_sum) {
left_sum = sum;
}
}
sum = 0;
int right_sum = INT_MIN;
for (int i = m + 1; i <= h; i++) {
sum = sum + arr[i];
if (sum > right_sum) {
right_sum = sum;
}
}
return max(left_sum + right_sum, left_sum, right_sum);
}
int maxSubArraySum(int arr[], int l, int h)
{
if (l == h)
return arr[l];
int m = (l + h) / 2;
return max(maxSubArraySum(arr, l, m),
maxSubArraySum(arr, m + 1, h),
maxCrossingSum(arr, l, m, h));
}
4.2 Tìm cặp điểm gần nhất
https://www.spoj.com/problems/CLOPPAIR/
Cho N điểm trên một mặt phẳng và nhiệm vụ của bạn là tìm một cặp điểm có khoảng cách giữa chúng là nhỏ nhất. Tất cả các điểm sẽ là duy nhất và chỉ có một cặp có khoảng cách nhỏ nhất.
Áp dụng brute-fore: bằng cách tìm khoảng cách của tất cả các cặp điểm để tìm ra đáp án. Độ phức tạp sẽ là O(n^2).
struct Point
{
int x, y;
};
float bruteForce(Point P[], int n) {
float min = FLT_MAX;
for (int i = 0; i < n; ++i)
for (int j = i+1; j < n; ++j)
if (dist(P[i], P[j]) < min)
min = dist(P[i], P[j]);
return min;
}
double dist(Point p1, Point p2) {
return sqrt( (p1.x - p2.x)*(p1.x - p2.x) +
(p1.y - p2.y)*(p1.y - p2.y)
);
}
Áp dụng chia để trị, chúng ta có thể giải bài toán này như sau:
Sắp xếp tất cả các điểm theo tọa độ X
Chia các điểm ra làm hai phần
Đệ quy để tìm khoảng cách nhỏ nhất của hai dãy d1 và d2.
Lấy khoảng cách nhỏ nhất từ hai dãy con (được gọi là d)
Tạo ra một mảng strip[] chứa toàn bộ các điểm mà cách điểm chia (ở bước 2) là <=d.
Tìm khoảng cách nhỏ nhất ở trong strip[] (được gọi là k)
Trả ra kết quả nhỏ nhất trong hai số k và d.
Code minh họa:
class Point
{
public:
int x, y;
};
int compareX(const void* a, const void* b)
{
Point *p1 = (Point *)a, *p2 = (Point *)b;
return (p1->x - p2->x);
}
int compareY(const void* a, const void* b)
{
Point *p1 = (Point *)a, *p2 = (Point *)b;
return (p1->y - p2->y);
}
float dist(Point p1, Point p2)
{
return sqrt( (p1.x - p2.x)*(p1.x - p2.x) +
(p1.y - p2.y)*(p1.y - p2.y)
);
}
float bruteForce(Point P[], int n)
{
float min = FLT_MAX;
for (int i = 0; i < n; ++i)
for (int j = i+1; j < n; ++j)
if (dist(P[i], P[j]) < min)
min = dist(P[i], P[j]);
return min;
}
float min(float x, float y)
{
return (x < y)? x : y;
}
float stripClosest(Point strip[], int size, float d)
{
float min = d; // Initialize the minimum distance as d
qsort(strip, size, sizeof(Point), compareY);
for (int i = 0; i < size; ++i)
for (int j = i+1; j < size && (strip[j].y - strip[i].y) < min; ++j)
if (dist(strip[i],strip[j]) < min)
min = dist(strip[i], strip[j]);
return min;
}
float closestUtil(Point P[], int n)
{
if (n <= 3)
return bruteForce(P, n);
int mid = n/2;
Point midPoint = P[mid];
float dl = closestUtil(P, mid);
float dr = closestUtil(P + mid, n - mid);
float d = min(dl, dr);
Point strip[n];
int j = 0;
for (int i = 0; i < n; i++)
if (abs(P[i].x - midPoint.x) < d)
strip[j] = P[i], j++;
return min(d, stripClosest(strip, j, d) );
}
float closest(Point P[], int n)
{
qsort(P, n, sizeof(Point), compareX);
return closestUtil(P, n);
}
5. Discussion questions
Lấy ví dụ về worst case và best case của quick sort.
Giải thích vì sao độ phức tạp best case của thuật toán quick sort là O(nlogn) và trung bình cũng là O(nlogn).
Vì sao độ phức tạp best case của quick sort là O(nlogn) và độ phức tạp của merge sort, heap sort luôn luôn là O(nlogn) mà quicksort lại có thể nhanh hơn?
Theo bạn, thuật toán sắp xếp nào được dùng khi sử dụng các hàm sắp xếp của thư viện? Và vì sao?
Lấy ví dụ thực tế bạn đã áp dụng chia để trị. Giải thích bạn đã chia, trị và combine như thế nào? more.
Algorithm 3: Binary Search & Divide and Conquer (Thuật toán tìm kiếm nhị phân và chia để trị)
1. Bài toán tìm kiếm
Tìm kiếm là một đòi hỏi rất thường xuyên trong các ứng dụng. Bài toán có thể phát biểu như sau:
Cho một dãy gồm n bản ghi r1,r2,....,r(n). Mỗi bản ghi r(i) (1 ≤ i ≤ n) tương ứng với một khóa k(i). Hãy tìm bản ghi có giá trị khóa bằng X cho trước.
X được gọi là khóa tìm kiếm hay đối trị tìm kiếm (argument).
Công việc tìm kiếm sẽ hoàn thành nếu như có một trong hai tính huống sau xảy ra:
Tìm được bản ghi có khóa tương ứng bằng X, lúc đó phép tìm kiếm thành công.
Không tìm được bản ghi nào có khóa bằng X cả, phép tìm kiếm thất bại.
1.1 Tìm kiếm tuần tự (sequential search)
Tìm kiếm tuần tự là một kỹ thuật tìm kiếm đơn giản. Nội dung của nó như sau:
Bắt đầu từ bản ghi đầu tiên, lần lượt so sánh khóa tìm kiếm với khóa tương ứng của các bản ghi trong danh sách, cho tới khi tìm thấy bản ghi mong muốn hoặc đã duyệt hết danh sách.
int sequentialSearch(Type k[], Type X) { //tìm kiếm tuần tự trên dãy khóa k1,k2,...,kn. hàm này tìm xem trong dãy có khóa nào = X không, nó thấy thì trả về chỉ số của khóa ấy, nếu không trả về 0, sử dụng khóa phụ k[n+1] để tăng tốc chương trình.
k[n+1] = X; //Sử dụng khóa phụ để đảm bảo lúc nào cũng tìm thấy kết quả.
int i = 1;
while (k[i] != X) ++i; //Sử dụng khóa phụ làm cho vòng lặp không cần kiểm tra điều kiện -> tăng tốc chương trình.
if (i == n + 1)
return 0;
else
return i;
}
Độ phức tạp của thuật toán tím kiếm tuần tự trong trường hợp tốt nhất là O(1), xấu nhất là O(n) và trung bình là O(n).
1.2 Tìm kiếm nhị phân
Phép tìm kiếm nhị phân có thể áp dụng trên dãy khóa đã có thứ tự: k1 ≤ k2≤ ... ≤ k(n).
Giả sử ta cần tìm trong đoạn k(inf), k(inf +1), ... , k(sup) với khóa tìm kiếm là X, trước hết ta xét khóa nằm giữa dãy k(median) với median = (inf + sup) / 2;
Nếu k(median) < X thì có nghịa đoạn từ k(inf) tới k(median) chỉ chứa toàn khóa < X, ta tiến hành tìm kiếm tiếp đoạn từ k(median+1) tới k(sup).
Nếu k(median) > X thì có nghĩa đoạn từ k(median) tới k(sup) chỉ chứa toàn khóa > X, ta tiến hành tìm kiếp tới đoạn từ k(inf) tới k(median-1).
Nếu k(median) = X thì việc tìm kiếm thành công (kết thúc quá trình tìm kiếm).
Quá trình tìm kiếm thất bại nếu đến một bước nào đó, đoạn tìm kiếm là rỗng (inf > sup).
int binarySearch(Type k[], Type X) {
int inf = 1, sup = n, median;
while (inf <= sup) {
median = (inf + sup) / 2;
if (k[median] == X)
return median;
if (k[median] < X)
inf = median + 1;
else
sup = median -1;
}
return 0;
}
Độ phức tạp của thuật toán tìm kiếm nhị phân trong trường hợp tốt nhất là O(1), xấu nhất là O(log2(n)) và trung bình cũng là O(log2(n)).
1.3 Cây nhị phân tìm kiếm
2. Thuật toán chia để trị
Chia để trị là một mô hình thiết kế thuật toán dựa trên đệ quy với nhiều phân nhánh.
Thuật toán chia để trị (divide and conquer) là chia một cách đệ quy một vấn đề thành hai hoặc nhiều vấn đề con cùng loại hoặc có liên quan, cho đến khi chúng trở nên đủ đơn giản để giải quyết trực tiếp. Các giải pháp của các vấn đề con đó sau đó được kết hợp lại để đưa ra giải pháp cho vấn đề ban đầu.
Kỹ thuật chia để trị là cơ sở của các thuật toán sắp xếp hiệu quả cho nhiều vấn đề, ví dụ: sắp xếp (quick sort, merge sort), nhân các số lớn (thuật toán Karatsuba), tìm cặp điểm gần nhất (finding the closest pair of points), phân tích cú pháp và các tính toán biến đổi Fourier rời rạc.
Việt thiết kế thuật toán chia để trị hiệu quả có thể khó khăn. Tưng tụ như quy nạp toán học, thông thường cần phải tổng quát hoát vấn đề để biến nó thành giải pháp đệ quy. Tính đúng đắn của thuật toán chia để trị thường được chứng minh bằng quy nạp toán học và chi phí tính toán của nó thường được xác định bằng cách giải các quan hệ lặp đi lặp lại.
Thuật toán chia để trị có thể chia thành ba phần:
Divide (chia): Chia bài toán thành các bài toán nhỏ hơn. Bước này thường thực hiện một cách đệ quy để chia bài toán nhỏ cho đến khi không chia nhỏ được nữa.
Conquer (trị): Giải quyết bài toán con đã được chia nhỏ đến mức đơn giản có thể giải quyết trực tiếp.
Combine (kết hợp): Khi các bài toán nhỏ hơn được giải quyết, kết hợp chúng một cách đệ quy cho đến khi chúng là lời giải cho bài toán ban đầu.
3. Bài toán sắp xếp
Sắp xếp là việc đặt các phần tử của một danh sách theo một thứ tự nhất định. Các thứ tự được sử dụng thường xuyên nhất là thứ tự các số và thứ tự từ vựng. Đầu ra của bài toán sắp xếp phải thoản mã hai điều kiện:
Đầu ra theo thứ tự không giảm.
Đâu ra là một hoán vị.
Việc sắp xếp có thể dựa vào so sánh các khóa và đổi chỗ các phần tử cho nhau hoặc không cần sự so sánh (các kỹ thuật đánh dấu).
Một số thuật toán sắp xếp so sánh:
Sắp xếp nổi bọt (bubble sort)
Sắp xếp chèn (insertion sort)
Sắp xếp chọn (selection sort)
Sắp xếp trộn (merge sort)
Sắp xếp vun đống (heap sort)
Sắp xếp nhanh (quick sort)
....
3.1 Thuật toán quick sort
Quick sort là thuật toán sắp xếp tại chỗ - in place - (sắp xếp dựa trên danh sách có sẵn mà không phát sinh thêm bộ nhớ). Nó là một thuật toán thường được sử dụng khi sắp xếp. Khi được triển khai tốt, nó có thể nhanh hơn merge sort và hai hoặc ba lần so với heapsort.
Quick sort là một thuật toán chia để trị. Nó hoạt động bằng cách chọn một phần tử "pivot" từ mảng và chia các phần tử khác thanh hai mảng con, tùy theo việc chúng nhỏ hơn hay lớn hơn pivot. Các mảng con sau đó được sắp xếp một cách đệ quy. Điều này có thể thực hiện tại chỗ (in-place), chỉ yêu cầu một lượng nhỏ bộ nhớ bổ xung để thực hiện việc phân loại. Các bước của thuật toán là:
Kiểm tra kích thước: nếu số lượng phần tử ít hơn hai (<2), trả về ngay lập tức vì không phải làm gì (dãy được sắp xếp).
Chọn pivot: chọn một phần tử gọi là pivot trong dãy cần sắp xếp.
Phân vùng: Sắp xếp lại thứ tụ của các phần tử của nó, đồng thời xác định điểm phân chia (vị trí đứng của pivot), sao cho tất cả phần tử nhỏ hơn pivot đứng trước điểm phân chia và tất cả các phần tử lớn hơn nó đứng sau điểm phân chia (các phần tử bằng nó có thể đứng trước hay đứng sau đều được).
Gọi đệ quy cho hai dãy con vừa được phân vùng, dãy con từ phần tử đầu điến điểm phân chia, và dãy con từ điểm phân chia đến phần tử cuối.
Thể hiện của thuật toán quick sort như sau:
void quicksort(Type list, int lo, int hi) {
if (low < hi) {
int p = partition(list, lo, hi);
quicksort(list, lo, p - 1);
quicksort(list, p+1, hi);
}
}
void partition(Type list, int lo, int hi) {
int pivot = list[lo];
int left = lo +1, right = hi;
while (left < right) {
while (list[left] <= pivot && left <= right) ++left;
while (list[right] >= pivot && right >= left) --right;
if (left < right) {
swap(list[left], list[right]);
}
}
swap(list[lo], list[right]);
}
Việc sắp xếp toàn bộ mảng được thực hiện bằng: quicksort(list, 0, list.length() - 1);
Độ phức tạp của thuật toán quicksort trong best case là O(nlogn), độ phức tạp trung bình O(nlogn) và worst case là O(n^2).
3.2 Thuật toán merge sort
Thuật toán merge sort cũng là một thuật toán thường được sử dụng khi sắp xếp. Hầu hết các triển khai của nó tạo một stable sort, có nghĩa là thứ tự các phần tử giống nhau trong cả đầu vào đầu ra.
Merge sort cũng là một thuật toán chia để trị. Nó hoạt động bằng cách chia danh sách chưa được sắp xếp thành n danh sách con, mỗi danh sách chưa một phần tử. Sau đó liên tục hợp nhất cá danh sách con đã được sắp xếp cho đến khi còn lại một danh sách con.
Độ phức tạp của thuật toán merge sort trong best case là O(nlogn), độ phức tạp trung bình O(nlogn) và worst case là O(nlogn).
Các bước mô tả về khái niệm như sau:
Chia danh sách chưa được sắp xếp thành n danh sách con, mỗi danh sách chưa 1 phần tử (danh sách một phần tử được coi là đã sắp xếp).
Liên tục hợp nhất các danh sách con để tạo ra các danh sách con được sắp xếp mới cho đến khi chỉ còn lại một danh sách con. Đây sẽ là danh sách được sắp xếp.
Có hai cách triển khai thuật toán merge sort, đó là top-down hoặc bottom-up.
Top-down implementation
Do có việc hợp nhất các danh sách con nên ta cần thêm một không gian trung gian để chứa các danh sách con đã được sắp xếp này. Ta tạo một danh sách trung gian đó là B.
void topdownMergeSort(Type A[], Type B[], int n) {
copyArray(A, 0, n, B);
topdownSplitMerge(B, 0, n, A); //sort data from B[] to A[]
}
void topdownSplitMerge(Type B[], int iBegin, int iEnd, Type A[]) {
if (iEnd - iBegin <= 1) return; // nếu size là 1 thì dãy đã được sắp xếp
int iMiddle = (iEnd + iBegin) / 2; //chia dãy ra làm hai
topdownSplitMerge(A, iBegin, iMiddle, B); // xắp xếp dãy bên trái của dãy B, do dó dãy A biến thành dãy trung gian
topdownSplitMerge(A, iMiddle, iEnd, B); // xắp xếp dãy bên phải của dãy B, do dó dãy A biến thành dãy trung gian
topdownMerge(B, iBegin, iMiddle, iEnd, A); //hợp nhất hai dãy đã được sắp xếp nằm trên B thành một dãy nằm trên A
}
void topdownMerge(Type A[], int iBegin, int iMiddle, int iEnd, Type B[]) {
int i = iBegin, j = iMiddle;
for (int k = iBegin; k < iEnd; k++) {
if (i < iMiddle && (j >= iEnd || A[i] < A[j])) { //nếu dãy bên trái vẫn còn phần tử kết hợp với dãy bên phải hết phần tử hoặc cả hai dãy vẫn còn phần tử nhưng phần tử bên trái nhỏ hơn phần tử bên phải.
B[k] = A[i];
++i;
} else {
B[k] = A[j];
++j;
}
}
}
void copyArray(Type A[], int iBegin, int iEnd, Type B[]) {
for (int i = iBegin; i < iEnd; ++i) {
B[i] = A[i];
}
}
Việc sắp xếp toàn bộ mảng sẽ được thực hiện bằng: topdownMergeSort(A, B, A.length());
Bottom-up implementation
void bottomupMergeSort(Type A[], Type B[], n) {
for (int width = 1; width < n; width = 2 * width) {
for (int i = 0; i < n; i = i + 2 * width) {
bottomupMerge(A, i, min(i+width, n), min(i+2*midth, n), B);
}
copyArray(B, 0, n, A);
}
}
void bottomupMerge(Type A[], int iLeft, int iRight, int iEnd, Type B[]) {
int i = iLeft, j = iRight;
for (k = iLeft; k < iEnd; k++) {
if (i < iRight && (j >= iEnd || A[i] <= A[j])) {
B[k] = A[i];
++i;
} else {
B[k] = A[j];
++j;
}
}
}
void copyArray(Type A[], int iBegin, int iEnd, Type B[]) {
for (int i = iBegin; i < iEnd; ++i) {
B[i] = A[i];
}
}
Việc sắp xếp toàn bộ mảng sẽ được thực hiện bằng: bottomupMergeSort(A, B, A.length());
more.
Thứ Sáu, 25 tháng 3, 2022
Algorithm 2: Exhaustive search (Thuật toán vét cạn)
1. Định nghĩa
Trong khoa học máy tính, vét cạn (brute-force search hoặc exhaustive search), còn được gọi là tạo và kiểm tra, là một kỹ thuật giải quyết vấn đề rất chung và mô hình thuật toán bao gồm liệt kê một cách có hệ thống tất cả các ứng viên có thể có cho giải pháp và kiểm tra xem mỗi ứng viên có đáp ứng yêu cầu của bài toán hay không.
Mặc dù vét cạn rất dễ thực hiện và luôn tìm ra giải pháp nếu nó tồn tại, nhưng chi phí thực hiện tỷ lệ thuận với số lương giải pháp. Các vấn đề trong thực tế có xu hướng tăng rất nhanh số lượng giải pháp khi quy mô của vấn đề tăng lên. Do đó, vét cạn thường được sử dụng khi kích thước hạn chế hoặc các kinh nghiệm về vấn đề có thể làm giảm số lượng giải pháp xuống mức chấp nhận được. Phương pháp này cũng được sử dụng khi tính đơn giản của việc thực hiện quan trọng hơn tốc độ.
Thuật toán
Xác định candidate solution c đầu tiên cho P
Kiểm tra xem c có phải là "null candidate" hay không
Kiểm tra xem c có phải là giải pháp hợp lệ của P hay không
In ra giải pháp hợp lệ c của P
Sinh ra candidate solution c tiếp theo.
Ví dụ: tìm số lớn nhất trong dãy số nguyên cho trước (-5, 2, -1, 0, 3, 1)
Các bước chạy:
Click here to expand...
2.Tăng tốc độ vét cạn
2.1 Giảm không gian tìm kiếm
Một cách để tăng tốc thuật toán vét cạn là giảm không gian tìm kiếm, tức là tập hợp các giải pháp ứng viên, bằng cách sử dụng phương pháp kinh nghiệm. Việc một chút phân tích thường sẽ dẫn đến việc giảm đáng kể số lượng các khả năng và có thể biến một vấn đề khó thành một vấn đề tầm thường.
Ví dụ: trong bài toán tám quân hậu, bài toán là đặt tám quân hậu trên một bàn cờ tiêu chuẩn sao cho không có quân hậu nào tấn công con nào khác. Vì mỗi quân hậu có thể được đặt vào bất kỳ ô nào trong số 64 ô vuông, về nguyên tắc có 64^8 = 281.474.976.710.656 khả năng cần xem xét. Tuy nhiên, bởi vì tất cả các quân hậu đều giống nhau, và không có hai quân hậu nào có thể được xếp vào cùng một hình vuông, nên tất cả các ứng cử viên đều có thể chọn một bộ 8 ô vuông từ tập tất cả 64 ô vuông; có nghĩa là 64 chọn 8 = 64! / (56! * 8!) = 4.426.165.368 khả năng - khoảng 1 / 60.000 so với ước tính trước đó. Hơn nữa, không có sự sắp xếp nào với hai quân hậu trên cùng một hàng hoặc cùng một cột có thể là một giải pháp. Do đó, chúng ta có thể hạn chế hơn nữa nhóm khả năng đối với những sắp xếp đó.
Trong một số trường hợp, phân tích có thể giảm các khả năng xuống thành tập hợp tất cả các khả năng hợp lệ; nghĩa là, có thể có một thuật toán liệt kê trực tiếp tất cả các giải pháp hợp lệ, mà không lãng phí thời gian với phần kiểm tra và tạo ra các khả năng không hợp lệ.
Ví dụ: đối với bài toán "tìm tất cả các số nguyên từ 1 đến 1.000.000 chia hết cho 417", một giải pháp vét cạn sẽ tạo ra tất cả các số nguyên trong phạm vi, kiểm tra tính chia hết của từng số đó. Tuy nhiên, vấn đề đó có thể được giải quyết hiệu quả hơn nhiều bằng cách bắt đầu với 417 và liên tục thêm 417 cho đến khi con số vượt quá 1.000.000 - chỉ mất 2398 (= 1.000.000 ÷ 417) bước và không cần kiểm tra.
2.2 Sắp xếp lại không gian tìm kiếm
Trong các bài toán chỉ yêu cầu một giải pháp, thay vì tất cả các giải pháp, thời gian chạy dự kiến của vét cạn thường sẽ phụ thuộc vào thứ tự mà các khả năng được kiểm tra. Theo nguyên tắc chung, người ta nên kiểm tra những khả năng có triển vọng nhất trước. Và thường thì xác suất của một khả năng hợp lệ thường bị ảnh hưởng bởi các khả năng thất bại trước đó.
Ví dụ: khi tìm kiếm ước riêng của một số ngẫu nhiên n, tốt hơn nên liệt kê các ước ứng viên theo thứ tự tăng dần, từ 2 đến n - 1, hơn là ngược lại - bởi vì xác suất n chia hết cho c là 1 / c.
3. Sự bùng nổ của các bài toán tổ hợp
Nhược điểm chính của phương pháp vét cạn là đối với nhiều bài toán trong thế giới thực, số lượng khả năng là rất lớn. Hãy xem xét các bài toán sau:
Bài toán 1: Cho một danh sách n thành phố và khoảng cách giữa từng cặp thành phố, tìm con đường ngắn nhất có thể đến thăm mỗi thành phố chính xác một lần và quay trở lại thành phố ban đầu?
https://www.spoj.com/PTIT/problems/BCTSP/
Bài toán 2: Cho một danh sách n thành phố và khoảng cách giữa từng cặp thành phố, tìm con đường ngắn nhất có thể đến thăm chính xác m thành phố (m <= n), mỗi thành phố chính xác một lần và quay trở lại thành phố ban đầu?
Bài toán 3: Cho một danh sách n thành phố và khoảng cách giữa từng cặp thành phố, tìm con đường ngắn nhất có thể đến thăm chính xác m thành phố (m <= n), mỗi thành phố chính xác một lần và không quay trở lại thành phố ban đầu?
Bài toán 4: Cho một danh sách n thành phố và chi phí đi đến từng thành phố và giá trị văn hóa của từng thành phố, tìm cách đi sao cho giá trị văn hóa thu được là lớn nhất với một chi phí c cho trước?
https://www.spoj.com/problems/KNAPSACK/
Bài toán 1 số lượng khả năng là hoán vị của n, bài toán 2 số lượng khả năng là tổ hợp chập m của n (hay còn gọi là các tập hợp con m phần tử), bài toán 3 là số lượng khả năng là chỉnh hợp không lặp chập m của n (hay còn gọi là tập hợp các tập con m phần tử có vị trí phần tử khác nhau), bài toán 4 số lượng khả năng là hai mũ n.
Với bài toán 1,2,3, số lượng khả năng máy tính thông thường chỉ xử lý được với kích cỡ 12 hoặc 13.
5 6 7 8 9 10 11 12 13 14 15 30 100
120 720 5040 40320 362880 4E+06 4E+07 5E+08 6E+09 9E+10 1E+12 3E+32 9E+157
Với bài toán số 4, số lượng khả năng máy tính thông thường chỉ xử lý được với kích cỡ 29 hoặc 30.
1 5 10 20 30 40 50 100 1000
2 32 1024 1048576 1.07E+09 1.1E+12 1.13E+15 1.27E+30 1.1E+301
4.Phương pháp sinh (generation)
Việc giải quyết bằng phương pháp vét can đòi hỏi cần có phương pháp sinh ra các khả năng. Phương pháp sinh có thể mô tả như sau:
<Xây dựng cấu hình đầu tiên>;
repeat
<Đưa ra cấu hình đang có>;
<Từ cấu hình đang có đưa ra cấu kình kế tiếp nếu còn>;
until <hết cầu hình>;
Việc từ cấu hình đang có đưa ra cấu hình kế tiếp là giải thuật chính của phương pháp sinh. Chúng ta xem xét việc đưa ra cấu hình tiếp như thế nào.
Với cơ sở lý thuyết tất cả các cấu hình (candidate) là độc nhất. Do đó tồn tại một thứ tự cho việc sắp xếp các cấu hình này và được gọi là thứ tự từ điển.
Thứ tự từ điển đối với các tập hợp có cùng độ dài:
Ví dụ: các tập con có ba phần tử có tính vị trí của tập hợp (1, 2, 3) theo thứ tự từ điển là:
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
Hai phần tử thứ i = (i1, i2, ..., in) và j = (j1, j2,...,jn), phần tử i nhỏ hơn phần tử j (i < j) nếu tồn tại một số nguyên dương k: 1≤ k < n để:
i1 = j1
i2 = j2
...
i(k-1) = j(k-1)
i(k) < j(k)
Thứ tự từ điển đối với các tập hợp không có cùng độ dài:
Ví dụ: các tập con của tập hợp (1, 2, 3) theo thứ tự từ điển là:
(Ø)
(1)
(1, 2)
(1, 2, 3)
(2)
(2, 3)
(3)
Bằng cách thêm các phần tử rỗng (Ø) dể độ dài của hai phần tử bằng nhau, ta xác định được thứ tự từ điển của các phần tử có độ dài không bằng nhau.
Dưới đây là các phương pháp sinh:
4.1Liệt kê các hoán vị (bài toán 1):
Ta sẽ lập chương trình liệt kê các hoán vị của {1,2,..,n} theo thứ tự từ điển.
Ví dụ với n = 4, ta phải liệt kê đủ 24 hoán vị:
1>1234 2>1243 3>1324 4>1342 5>1423 6>1432
7>2134 8>2143 9>2314 10>2341 11>2413 12>2431
13>3124 14>3142 15>3214 16>3241 17>3412 18>3421
19>4123 20>4132 21>4213 22>4231 23>4312 24>4321
Như vậy hoán vị đầu tiên sẽ là (1,2,..,n) và hoán vị cuối cùng là (n,...,2,1).
Hoán vị sẽ sinh phải lớn hơn hoán vị hiện tại, hơn thế nữa phải là hoán vị vừa đủ lớn hơn hoán vị hiện tại nghĩa là không thể có một hoán vị nào chen giữa chúng khi săp sếp thứ tự.
Giả sử hoán vị hiện tại là x = (3, 2, 6, 5, 4, 1), xét 4 phần tử cuối cùng, ta thấy chúng được sắp xếp giảm dần, điều đó có nghĩa là cho dù chúng ta có hoán vị 4 phần tử này thế nào, ta cũng được một hoán vị bé hơn hoán vị hiện tại.
Như vậy ta phải xét đến đến x2 = 2, thay nó bằng một giá trị khác. Vậy giá trị nào sẽ thay thế x2?
Giá trị thay thế x2 không thể là 1 vì nếu vậy sẽ được hoán vị nhỏ hơn.
Giá trị thay thế x2 không thể là 3 vì x1 đang có giá trị là 3 (phần tử sau không được chọn vào những giá trị mà phần tử trước đã chọn).
Các giá trị còn lại để thay thế x2 là 4,5,6. Vì cần một hoán vị vừa đủ lớn hơn hiện tại nên ta giá trị thay thế cho x2 là 4. Còn các giá trị (x3,x4,x5,x6) sẽ lây trong tập {2, 6, 5 ,1}.
Cũng vì tính vừa đủ nên ta sẽ tìm cách biểu diễn nhỏ nhất của bốn số này gán cho x3,x4,x5,x6 tức là (1,2,5,6).
Vậy hoán vị mới sẽ là (3, 4, 1, 2, 5 ,6).
(3, 2, 6, 5, 4, 1) → (3, 4, 1, 2, 5 ,6)
Qua ví dụ này ta nhận xét:
Đoạn cuối của hoán vị được sắp xếp giảm dần.
Số x5 = 4 là số nhỏ nhất trong đoạn cuối giảm dần thỏa mãn điều kiện lớn hơn x2 = 2. Nếu ta đổi chỗ x5 cho x2 thì ta vẫn được đoạn cuối sắp xếp giảm dần.
Sau khi đổi chỗ x5 với x2, ta muốn biểu diễn giá trị nhỏ nhất của đoạn cuối thì chỉ cần đảo ngược đoạn cuối.
Trong trường hợp hoán vị hiện tại là (2, 1, 3, 4) thì hoán vị kế tiếp là (2, 1, 4, 3):
Đoạn cuối giảm dần là (4).
Giá trị nhỏ nhất lớn hơn x3 là 4 → đổi chỗ x3 và x4 ta được (2, 1, 4, 3) và có đoạn cuối giảm dần là (3).
Đạo ngược đoạn cuối (3) → (3), cuối cùng ta có hoán vị kế tiếp là (2, 1, 4, 3).
Vậy kỹ thuật sinh hoán vị kế tiếp từ hoán vị hiện tại có thể xây dựng như sau:
Xác định đoạn cuối giảm dần dài nhất, tìm chỉ số i của phần tử x(i) đứng liền trước đoạn đó. Điều đó đồng nghĩa với việc tìm từ sát vị trí cuối dãy lên đầu, gặp chỉ số i đầu tiên thỏa mãn x(i) < x(i+1). Nếu toàn dãy là giảm dần → đó là cấu hình cuối.
i = n - 1;
while ((i > 0) && (x[i] > x[i+1])) --i;
Trong đoạn cuối giảm dần, tìm phẩn tử x(k) sao cho thỏa mãn điều kiện x(k) > x(i). Do đoạn cuối giảm dần, ta cũng có thể tìm chỉ số k bằng cách tìm cuối dãy lên đầu và dừng khi gặp chỉ số k đầu tiên thỏa mãn x(k) > x(i) hoặc tìm kiếm nhị phân.
k = n;
while (x[k] < x[i]) --k;
Đổi chỗ x(k) và x(i), lật ngược thứ tự đoạn cuối giảm dần trở thành tăng dần.
swap(x[i], x[k]);
a = i + 1;
b = n;
while (a < b) {
swap(x[a], x[b]);
++a;
--b;
}
4.2 Liệt kê các tập con k phần tử (bài toán 2):
Ta sẽ lập chương trình liệt kê các tập con k phần tử của tập {1, 2, ...., n} theo thứ tự từ điển.
Ví dụ: với n = 5, k = 3, ta phải liệt kê đầy đủ 10 tập con:
1> {1, 2, 3} 2> {1, 2, 4} 3> {1, 2, 5} 4> {1, 3, 4} 5> {1, 3, 5}
6> {1, 4, 5} 7> {2, 3, 4} 8> {2, 3, 5} 9> {2, 4, 5} 10> {3, 4, 5}
Như vậy tập con đầu tiên (cấu hinh khởi tạo) là {1, 2, ..., k}.
Cấu hình kết thúc là {n - k + 1, n - k + 2, ..., n}.
Nhận xét:
Các tập con có phần tử được sắp xếp theo thứ tự tăng dần → giới hạn trên của các phần tử thứ i sẽ là x(i) = n - k + i;
Các tập con có phần tử được sắp xếp theo thứ tự tăng dần → giới hạn dưới của các phần tử thứ i sẽ là x(i) = x(i-1) + 1;
Giới hạn trên của các phần tử thứ i sẽ là x(i) = n - k + i → tất cả phần tử đạt giới hạn thì quá trình sinh kết thúc.
Dãy mới sinh ra phải tăng dần thỏa mãn vừa đủ lớn hơn dãy cũ.
Ví dụ: n = 9m k = 6. Cấu hình đang có x = {1, 2, 6, 7, 8, 9}.
Các phần tử x3 đến x6 đã đạt giới hạn trên nên ta không thể sinh bằng cách tăng một phần tử trong các số x6, x5, x4, x3 lên được → ta phải tăng x2 lên một đơn vị x2 = 3, cấu hình mới sẽ là {1, 3, 6, 7, 8, 9}.
Cấu hình {1, 3, 6, 7, 8, 9} đã thỏa mãn lớn hơn cầu hình hiện tại nhưng chưa thỏa mãn vừa đủ lớn, nên ta phải thay x3, x4, x5, x6 bằng các giới hạn dưới của nó, tức là:
x3 = x2 + 1 = 4
x4 = x3 + 1 = 5
x5 = x4 + 1 = 6
x6 = x5 + 1 = 7
Ta được cấu hình mới x = {1, 3, 4, 5, 6, 7} là cấu hình kế tiếp.
Nếu muốn tìm tiếp, ta thấy x6 = 7 chưa đạt giới hạn trên, như vậy chỉ cần tăng x6 lên một đơn vị là được x = {1, 3, 4, 5, 6, 8}.
Vậy kỹ thuật sinh tập con có thể xây dựng như sau:
Tìm từ cuối lên đầu dãy cho đến khi gặp phần tử x(i) chưa đạn giới hạn trên n - k + i.
i = n;
while ((i > 0) && (x[i] == n - k + i)) --i;
if (i == 0) continue;// lùi đến 0 nên cấu hình hiện tại là cấu hình kết thúc.
Tăng x(i) lên một đơn vị.
++x[i];
Đặt lại tất cả các phần tử phía sau bằng giới hạn dưới.
for (j = i + 1; j <= k; ++j) x[j] = x[j-1] + 1;
4.3 Liệt kê các tập con k phần tử có vị trí phần tử khác nhau (bài toán 3):
Nhận xét rằng việc liệt kê này bao gồm bài toán 2 và bài toán 1.
Do vậy kỹ thuật sinh là sử dụng kỹ thuật sinh các tập con (bài toán 2) và liệt kê các hoán vị trong tập con đó (bài toán 1) → Bài tập về nhà.
4.4 Liệt kê các dãy nhị phân (bài toán 4):
Một dãy nhị phân độ dài n là một dãy x =x1x2...x(n) trong đó x(i) € {0 , 1} với i: 1 ≤ i ≤ n.
Dễ thấy một dãy nhị phân độ dài n là biểu diễn của một giá trị nguyên p(x) nào đó nằm trong [0, 2^n). Do đó ta sẽ lập chương trình liệt kê các dãy nhị phân theo thứ tự từ điển của các số nguyên có thứ tự 0, 1, ..., (2^n )- 1.
Ví dụ: khi n = 3, các dãy nhị phân độ dài 3 được liệt kê như sau:
p(x) 0 1 2 3 4 5 6 7
x 000 001 010 011 100 101 110 111
Như vậy dãy đầu tiên sẽ là 00...0 và dãy cuối cùng sẽ là 11...1.
Nhận xét thấy rằng nếu dãy x = (x1, x2, ..., xn) không phải là dãy cuối cùng thì dãy kế tiếp sẽ được sinh bằng cách cộng thêm 1 (theo cơ số 2 có nhớ) vào dãy hiện tại.
Ví dụ: khi n = 8:
Dãy đang có: 10010000 Dãy đang có: 10010111
cộng thêm 1: 1 cộng thêm 1: 1
Dãy mới: 10010001 Dãy mới: 10011000
Như vậy kỹ thuật sinh: Xét từ cuối dãy về đầu, gặp số 0 đầu tiên thì thay nó bằng số 1 và đặt tất cả các phần tử phía sau vị trí đó bằng 0.
i = n;
while ((i > 0) && (x[i] == 1)) --i;
if (i > 0) {
x[i] = 1;
for (j = i + 1; j <=n; ++j) x[j] = 0;
}
Video: https://drive.google.com/drive/folders/1-92wNzSETMXSIDQgaPsei2OrKGBeaUDx?usp=sharing
Thứ Tư, 23 tháng 3, 2022
Algorithm 1: Các khái niệm cơ bản
1. Thuật toán
Thuật toán là một hệ thống chặt chẽ và rõ ràng các quy tắc nhắm xác định một dãy thao tác trên cấu trúc dữ liệu sao cho: với một dữ liệu đầu vào, sau một số hữu hạn bước thực hiện các tao tác đã chỉ ra, ta đạt được mục tiêu nhất định.
Các đặc trung của thuật toán
1.1 Tính đơn định
Ở mỗi bước của thuật toán, các thao tác phải hết sức rõ ràng, không nên gây sự nhập nhằng, lộn xộn, tùy tiện, đa nghĩa. Thực hiện đúng các bước của thuật toán với một dữ liệu đầu vào thì chỉ ra duy nhất một kết quả.
1.2 Tính dừng
Thuật toán không được rơi vào vô hạn, phải dừng lại và cho kết quả sau một số hữu hạn bước.
1.3 Tính đúng
Sau khi thực hiện tất cả các bước của thuật toán theo đúng trình tự đã định, ta phải được kết quả mong muốn với mọi bộ dữ liệu đầu vào. Kết quả đó được kiểm chứng bằng yêu cầu bài toán.
1.4 Tính phổ dụng
Thuật toán phải dễ sửa đổi để thích ứng với bất kỳ bài toàn nào trong một lớp các bài toán và có thể làm việc trên các dữ liệu khác nhau.
1.5 Tính khả thi
Kích thước phải đủ nhỏ: Một thuật toán sẽ có tính hiệu quả bằng 0 nếu số lượng bộ nhớ mà nó yêu cầu vượt quá khả năng lưu trữ của hệ thống máy tính.
Thuật toán phải được máy tính thực hiện trong thời gian cho phép.
Phải dễ hiểu và dễ cài đặt.
2. Lập trình
Sau khi có thuật toán, ta phải tiến hành lập trình để thể hiện thuật toán đó. Muốn lập trình đạt hiệu quả cao, cần phải có kỹ thuật lập trình tốt. Kỹ thuật lập trình tốt thể hiện ở kỹ năng viết chương trình, khả năng gỡ rối và thao tác nhanh. Lập trình tốt không phải chỉ cần nắm vững ngôn ngữ lập trình là đủ, phải biết cách viết chương trình uyển chuyển, khôn khéo và phát triển dần các ý tưởng thành chương trình hoàn chỉnh. Kinh nghiệm cho thấy một thuật toán hay nhưng do cài đặt vụng về nên khi chạy lại cho kết quả sai hoặc tốc độ chậm.
Thông thường, ta không nên cụ thể hóa ngay toàn bộ chương trình mà nên tiến hành theo phương pháp tinh chế từng bước một:
Ban đầu, chương trình được thể hiện bằng ngôn ngữ tự nhiên, thể hiện thuật toán với các bước tổng thể, mỗi bước nêu lên một công việc phải được thực hiện.
Nếu một công việc đơn giản hoặc là một đoạn chương trình đã được học thuộc thi ta tiến hành viết mã ngay.
Nếu một công việc phức tạp thì ta lại chia công việc ra thành những công việc nhỏ hơn để tiếp tục với những công việc nhỏ hơn đó. Trong quá trình tinh chế từng bước, ta phải đưa ra những biểu diễn kiểu dữ liệu.
Như vậy cùng với sự tinh chế các công việc, dữ liệu cũng dần được tinh chế, có cấu trúc hơn, thể hiện rõ hơn mối liên hệ giữa những dữ liệu.
Phương pháp tinh chế từng bước là một thể hiện của tư duy giải quyết vấn đề từ trên xuống, giúp cho người lập trình có một định hướng thể hện trong phong cách viết chương trình. Tránh việc mò mẫm, xóa đi viết lại nhiều lần, biến chương trình thành tờ giấy nháp.
3. Kiểm thử
3.1 Chạy thử và tìm lỗi
Chương trình là do con người viết ra, mà đã là con người thì ai cũng có thể nhầm lẫn. Một khi chương trình viết xong thì chưa chắc đã chạy trên máy tính cho kết quả như kỳ vọng. Kỹ năng tìm lỗi, sửa lỗi, điều chỉnh lại chương trình cũng là một kỹ năng quan trọng của người lập trình. Kỹ năng này chỉ có được bằng kinh nghiệm tìm và sửa lỗi của chính mình. Có ba loại lỗi:
Lỗi cú pháp: Lỗi này hay gặp và dễ sửa nhất. Chỉ cần nắm vững ngôn ngữ lập trình là đủ. Một người được coi là không biết lập trình nếu không biết sửa lỗi cú pháp.
Lỗi cài đặt: Việc cài đặt không đúng thuật toán đã định, đối với lỗi này phải xem lại tổng thể chương trình, kết hợp với các chức năng gỡ rối để sửa lại cho đúng.
Lỗi thuật toán: Lỗi này ít gặp nhưng nguy hiểm nhất, nếu nhẹ thì phải điều chỉnh lại thuật toán. Nếu nặng thì có khi phải loại bỏ hoàn toàn thuật toán và làm lại từ đầu.
3.2 Xây dựng các bộ test
Có nhiều chương trình rất khó kiểm tra tính đúng đắn. Nhất là khi ta không biết kết quả đúng là như thế nào. Vì vậy nếu chương trình vẫn chạy ra kế quả mà không biết đúng hay sai thì việc tìm lỗi rất khó khăn. Khi đó chúng ta nên tạo ra các bộ test cho chương trình của mình.
Các bộ test nên đặt trong file văn bản, vì việc tạo một file văn bản rất nhanh và mỗi lần chạy thử chỉ cần thay đổi tên file dữ liệu là xong. Kinh nghiệm với các bộ test là:
Bắt đầu với các bộ test nhỏ, đơn giản, làm bằng tay cũng có thể có được đáp án để so sánh với kết quả chương trình.
Tiếp theo vẫn là các bộ test nhỏ nhưng chưa các giá trị đặc biệt hoặc bất thường. Kinh nghiệm cho thấy đây là những bộ test dễ sai nhất.
Các bộ test phải da dạng, tránh sự lặp đi lặp lại.
Có một vài test lớn để kiểm tra tính chịu đựng của chương trình, kết quả đúng hay không chúng ta rất khó kiểm chứng được.
Lưu ý rằng việc chạy qua được hết các bộ test không có nghĩa là chương trình đã đúng. Vì vậy nếu có thể, chúng ta nên tìm cách chứng minh tính đúng đắn của thuật toán và chương trình khi thực hành. Tuyệt đối không nên làm điều này lúc thi vì điều này thường rất khó (tương đương với việc mất rất nhiều thời gian).
4. Tối ưu chương trình
Một chương trình chạy đúng không có nghĩa là việc lập trình đã xong, chúng ta phải sửa đổi lại vài chi tiết để chương trình có thể chạy nhanh hơn, hiệu quả hơn. Thông thường, trước khi kiểm thử thì ta nên đặt mục tiêu viết chương trình sao cho đơn giản, miễn chạy đúng là được, sau đó tối ưu chương trình. Xem lại chỗ nào viết chưa tốt thì tối ưu lại. Không nên viết tới đâu, tối ưu tới đó. Vì chương trình có mã tối ưu thường phức tạp và khó kiểm soát.
Việc tối ưu chương trình dựa vào các tiêu chuẩn sau:
4.1 Tính tin cậy
Chương trình phải chạy đúng như dự định, mô tả đúng một giải thuật đúng. Thông thương, tại mỗi bước ta nên kiểm tra lại tính đúng đắn của bước đó.
4.2 Tính uyển chuyển
Chương trình phải dễ sửa đổi. Bởi ít chương trình nào viết ra đã hoàn hảo ngay được mà vẫn cần sửa đổi. Chương trình viết dễ sửa đổi sẽ làm giảm bớt công sức của lập trình viên khi phát triển chương trình.
4.3 Tính trong sáng
Chương trình viết ra phải dễ đọc, để sau một thời gian dài, khi đọc lại chúng ta hiểu mình đã làm cái gì. Để nếu có điều kiện thì còn có thể sửa sai (nếu phát hiện ra lỗi), cải tiến hay biến đổi chương trình (để giải quyết bài toán khác). Tính trong sáng phụ thuộc rất nhiều vào công cụng lập trình và phong cách lập trình.
4.4 Tính hữu hiệu
Chương trình phải chạy nhanh và tốn ít bộ nhớ, tức là tiết kiệm được về cả không gian và thời gian. Để có một chương trình hữu hiệu, cần phải có giải thuật tốt và những kỹ thuật cài đặt (tiểu xảo). Tuy nhiên nếu áp dụng quá nhiều tiểu xảo có thể khiến chương trình trở nên rối rắm, khó hiểu. Tiêu chuẩn hữu hiệu nên dừng ở mức chấp nhận được, không quan trọng bằng ba tiêu chuẩn trên.
Từ những phân tích ở trên, chúng ta nhận thấy rằng việc làm ra một chương trình đòi hỏi rất nhiều công đoạn và tiêu tốn khá nhiều công sức. Chỉ một công đoạn không hợp lý sẽ làm tăng chi phí viết chương trình. Việc hiểu điều này cho chúng ta bài học kinh nghiệm:
Đừng bao giờ viết chương trình mà chưa xuy xét về giải thuật và những dữ liệu cần thao tác.
5. Phân tích thời gian thực hiện giải thuật
https://en.wikipedia.org/wiki/Analysis_of_algorithms
**: Với bản thân mình việc áp dụng thời gian giải thuật 1s = 1 tỷ phép tính.
Thứ Bảy, 12 tháng 3, 2022
Tôi có thể cải thiện kỹ năng lập trình của mình bằng cách nào?
Tốt! Lập trình rất thú vị khi bạn hiểu nó và phần tốt là một lập trình viên giỏi sẽ nhận được một khoản tiền lương khổng lồ từ Top Tech Giants Like ( Google, Amazon, Microsoft, Facebook & Apple ).
Phần tốt nhất là Lập trình cũng cải thiện khả năng tư duy phản biện của bạn. Lập trình là viết mã chất lượng và tối ưu hóa.
Dưới đây là một số kỹ năng lập trình của một lập trình viên giỏi.
Bạn cần phải nỗ lực rất nhiều vào tất cả những điểm này và sau đó bạn trở thành một lập trình viên giỏi.
Điểm 1 và Điểm 3 giống nhau, Để trở thành một lập trình viên giỏi, bạn nên hiểu tất cả các chủ đề về cấu trúc dữ liệu và thuật toán , Khi bạn đã biết các thuật toán thì việc viết mã tối ưu rất dễ dàng. Chúng tôi học các thuật toán khác nhau để viết vì vậy chúng tôi có thể viết tối ưu hóa.
Cuối cùng thì tất cả những Gã khổng lồ về công nghệ lớn đều trong cuộc phỏng vấn tìm kiếm những người có khả năng viết mã tối ưu hóa tốt.
Điểm 2 không quá khó chỉ cần bạn có một lệnh tốt trong bất kỳ ngôn ngữ lập trình nào như JAVA hoặc Python hoặc C ++, v.v.
Điểm 4 đến từ thực tiễn. Khi bạn hiểu các thuật toán thì bạn cần thực hành nhiều vấn đề và viết mọi vấn đề với mã cấp sản xuất
Có nhiều tài liệu Trực tuyến ở đó mà bạn có thể nhận trợ giúp.
Để cải thiện điểm 1 và điểm 2,
Bạn cần học tất cả các chủ đề về cấu trúc dữ liệu và thuật toán theo trình tự bên dưới
Dành một lượng thời gian đáng kể cho tất cả các chủ đề và thực hành
Một số tài liệu Trực tuyến cũng có thể giúp quá trình chuẩn bị trở nên dễ dàng
GeeksforGeeks: Trang web này là kinh thánh của Thuật toán. Ở đây bạn sẽ tìm thấy hàng triệu vấn đề cho Thực hành. Họ đã sắp xếp tất cả các chủ đề trên một cách riêng biệt. Tất cả các Kỹ sư phần mềm đều phải xem qua trang web này ít nhất một lần. Bởi vì bất cứ khi nào chúng ta cần chuyển đổi tổ chức, chúng ta cần phải kiểm tra trang web này
Logicmojo Đây là Khóa học Trực tuyến để Học Lập trình. Nó dạy tất cả các kỹ thuật viết mã tối ưu hóa bằng cách sử dụng các thuật toán và cấu trúc dữ liệu. Mặc dù đó là một khóa học trả phí nhưng là một khoản đầu tư tốt. Hướng dẫn tuyệt vời để tìm hiểu tất cả các khái niệm và thực hành các kỹ thuật đó chỉ trong trình soạn thảo mã hóa của họ. Cùng với đó, họ cũng cung cấp các bài tập cho các thuật toán thực hành.
Đối với người mới bắt đầu, họ cung cấp các công cụ để phân tích mã để việc thực thi từng bước của mọi chương trình cũng có thể được kiểm tra, cùng với đó, họ cung cấp các bài kiểm tra mã hóa trực tuyến. Khi họ cung cấp đăng ký trọn đời, cuối cùng sẽ giúp mọi lúc trong quá trình chuyển đổi tổ chức
visualgo Điều này đặc biệt dành cho người mới bắt đầu vì có rất nhiều thuật toán sắp xếp và tìm kiếm ở đó. Họ cung cấp các sơ đồ và công cụ hoạt ảnh để bạn có thể xem dòng mã. Các hộp, bảng khác nhau và tất cả những thứ đó để học lập trình.
Bây giờ khi bạn đã hiểu các khái niệm thì bạn cần phải làm việc trên Điểm 4, đó là nó viết mã không có lỗi ở cấp độ sản xuất. Như tôi đã nói nó chỉ đi kèm với thực hành.
Bạn có thể theo dõi các nguồn dưới đây để thực hành mã
Leetcode : Leetcode có một bộ sưu tập rất lớn các vấn đề khác nhau, từ Dễ, Trung bình và Khó. Nếu bạn chỉ mở phần thực hành ở đây, một danh sách khổng lồ các chương trình có sẵn để thực hành. Vì vậy, bạn bắt đầu thực hành chương trình, bắt đầu từ dễ sau đó dần dần chuyển sang trung bình và sau đó là Khó.
Topcoder : Cái này dành cho các lập trình viên nâng cao. Sau khi dựa và thực hành từ các tài nguyên trên, sau đó cuối cùng cũng nếm thử topcoder. Các lập trình viên hàng đầu thế giới đến với nền tảng này để thực hành viết mã. Nếu bạn cảm thấy rằng bạn trở thành một chuyên gia trong lĩnh vực này thì hãy thử Topcode cũng được.
more.Thứ Năm, 6 tháng 4, 2017
365 mẫu câu dùng trong giao tiếp hàng ngày (Crazy English)
Ngày thứ nhất
1.Absolutely. (Dùng để trả lời) Đúng thế, vậy đó, đương nhiên rồi, chắc là vậy rồi.
2.Absolutely impossible! Không thể nào! Tuyệt đối không có khả năng đó.
3.All I have to do is learn English. Tất cả những gì tôi cần làm là học tiếng Anh.
4.Are you free tomorrow? Ngày mai cậu rảnh không?
5.Are you married? Ông đã lập gia đình chưa?
6.Are you used to the food here? Cậu ăn có quen đồ ăn ở đây không?
7.Be careful. Cẩn thận/ chú ý
8.Be my guest. Cứ tự nhiên/ đừng khách sáo!
9.Better late than never. Đến muộn còn tốt hơn là không.
10.Better luck next time. Chúc cậu may mắn lần sau.
11.Better safe than sorry. Cẩn thận sẽ kô xảy ra sai sót.
12.Can I have a day off? Tôi có thể xin nghỉ một ngày được không?
Ngày thứ 2
14.Can I take a message? Có cần tôi chuyển lời không?
15.Can I take a rain check? Cậu có thể mời mình bữa khác được không?
16.Can I take your order? Ông muốn chọn món không?
17.Can you give me a wake-up call? Cậu có thể gọi điện đánh thức mình dậy không?
18.Can you give me some feedback? Anh có thể nêu một vài đề nghị cho tôi được không?
19.Can you make it? Cậu có thể tới được không?
20.Can I have a word with you? Tôi có thể nói chuyện với anh một lát được không?
21.Cath me later. Lát nữa đến tìm tôi nhé!
22.Cheer up! Vui vẻ lên nào/ Phấn khởi lên nào!
23.Come in and make yourself at home. Xin mời vào, đừng khách sáo!
24.Could I have the bill,please? Xin cho xem hóa đơn tính tiền?
25.Could you drop me off at the airport? Cậu có thể chở mình đến sân bay được không?
26.Could you speak slower? Anh nói chậm lại một chút được không?
27.Could you take a picture for me? Có thể chụp hình giúp tôi không?
28.Did you enjoy your flight? Chuyến bay của ông vui chứ?
29.Did you have a good day today?
30.Did you have a nice holiday? Kì nghỉ vui vẻ chứ?
31.Did you have fun? Cậu chơi vui vẻ chứ?
32.Dinner is on me. Bữa tối tôi mời.
33.Do you have a room available? Chỗ các ông còn phòng trống không?
34.Do you have any hobbies? Anh có sở thích gì không?
35.Do you have some change? Cậu có tiền lẻ không?
36. Do you mind my smoking? Tôi hút thuốc có phiền gì không ạ?
37.Do you often work out? Anh thường xuyên rèn luyện thân thể chứ?
38.Do you speak English? Cậu biết nói tiếng Anh không?
39.Don't be so modest. Đừng khiêm tốn thế.
Ngày thứ 3
40.Don't bother. Đừng có phiền phức nữa.
41.Don't get me wrong. Đừng hiểu lầm tôi
42.Don't give up. Đừng từ bỏ.
43.Don't jump to conclusions. Đừng đưa ra kết luận quá vội vàng.
44.Don't let me down. Đừng làm tôi thất vọng đấy.
45.Don't make any mistakes. Đừng có mắc sai lầm đấy.
46.Don't mention it. Không cần khách sáo!
47.Don't miss the boat. Đừng bỏ lỡ cơ hội.
48.Don't take any chances. Đừng trông chờ vào may mắn.
49.Don't take it for granted. Đừng coi đó là điều đương nhiên.
50.Don't worry about it. Đừng lo lắng về điều đó.
51.Easy come,easy go. Nhanh đến, nhanh đi.
52.Enjoy your meal. Ăn tự nhiên nhé!
53.Easier said than done. Nói thường dễ hơn làm.
54.First come,first served. Nhanh chân thì được.
55.For here or to go? Ăn ở đây hay là mang về.
56.Forget it. Quên đi! Thôi đi! Bỏ qua đi!
57.Forgive me. Xin lượng thứ cho tôi.
58.Give me a call. Gọi điện thoại cho tôi nhé!
59.Give my best to your family. Gửi lời hỏi thăm của tôi tới toàn thể gia đình cậu nhé!
60.Have him return my call. Bảo nó gọi lại cho tôi nhé!
61.Have you ever been to Japan? Anh đã từng đến Nhật bao giờ chưa?
62.Have you finished yet? Cậu đã làm xong chưa?
Ngày thứ 4
63.Have you got anything larger? Có cái nào lớn hơn chút nữa không?
64.Have you got that? Cậu hiểu ý tôi chứ?
65.Have you heard from Mary? Cậu có tin tức gì về Mary không?
66.He is in conference. Anh ấy đang họp.
67.Help yourself,please. Tự phục vụ nhé!
68.Hold your horses. Kiên nhẫn một chút nghe!
69.How can I get in touch with you? Tôi liên lạc vơi cậu bằng cách nào được?
70.How do I look? Nhìn tôi thế nào?
71.How is it going? Tình hình thế nào?
72.How late are you open? Các anh mở cửa đến mấy giờ?
73.How long did it last? Đã kéo dài bao lâu rồi?
74.How long will it take me to get there?
75.How much is it? Bao nhiêu tiền?
76.How often do you eat out?
77.I apologize. Tôi xin lỗi
78.I appreciate your invitation. Cám ơn lời mời của anh.
Ngày thứ 5
79.I assure you. Tôi đảm bảo với anh đấy!
80.I bet you can. Tôi tin chắc rằng anh có thể làm được.
81.I can manage. Tôi có thể tự mình ứng phó được.
82.I can't afford it. Tôi mua không nổi.
83.I can't believe it. Quả thật tôi không dám tin.
84.I can't resist the temptation. Tôi không tài nào cưỡng lại được sự cám dỗ.
85.I can't stand it. Tôi không thể chịu đựng nổi nữa.
86.I can't tell. Tôi cũng không dám chắc.
87.I couldn't agree more. Tôi hoàn toàn đồng ý.
88.I couldn't get through. Tôi không gọi được.
89.I couldn't help it. Tôi cũng hết cách.
90.I didn't mean to. Tôi không cố ý
91.I don't know for sure. Tôi không dám khẳng định.
92.I enjoy your company. Tôi thích làm việc với anh.
93.I enjoyed it very much. Tôi rất thích.
94.I envy you. Tôi rất ngưỡng mộ anh.
95.I feel like having some dumplings. Tôi rất muốn ăn xủi cảo.
96.I feel terrible about it. Tôi rất lấy làm tiếc. Tôi xin lỗi.
97.I feel the same way. Tôi cũng có cùng cảm giác vậy.
98.I have a complaint. Tôi cần phải kiện.
99.I have nothing to do with it. Điều đó chẳng có liên quan gì đến tôi cả.
100.I haven't the slightest idea. Nó chẳng biết cái quái gì cả.
101.I hope you'll forgive me. Tôi hi vọng cậu sẽ tha thứ cho tôi.
102.I know the feeling. Tôi rất hiểu cảm giác đó.
Ngày thứ 6
103.I mean what I say. Tôi biết những gì mình nói.
104.I owe you one. Tôi nợ anh.
105.I really regret it. Quả thật tôi rất lấy làm tiếc.
106.I suppose so. Tôi nghĩ là như vậy.
107.I thought so, too. Tôi cũng cho là như vậy
108.I understand completely. Tôi hoàn toàn hiểu được.
109.I want to report a theft. Tôi muốn báo công an về vụ án ăn trộm.
110.I want to reserve a room. Tôi muốn đặt một phòng.
111.I was just about to call you. Tôi đang chuẩn bị gọi cho anh.
112.I was moved.= I was touched. Tôi rất cảm động.
113.I wasn't aware of that. Tôi không ý thức được điều đó.
114.I wasn't born yesterday. Tôi không phải là trẻ lên ba.
115.I wish I could. Ước gì tôi có thể.
116.I wouldn't worry about it, if I were you. Nếu tôi là anh, tôi sẽ chẳng có gì phải lo lắng vì nó cả.
117.I'd like a refund. Tôi muốn được trả lại tiền.
118.I'd like to deposit some money. Tôi muốn gửi ít tiền.
119.I'd like to make a reservation. Tôi muốn đặt vé.
120.I'll be right with you. Tôi tới ngay đây.
121.I'll check it. Để tôi đi kiểm tra lại.
122.I'll do my best. Tôi sẽ cố gắng hết sức.
123.I'll get it. Để tôi đi nghe điện thoại.
124.I'll give you a hand. Tôi sẽ giúp cậu một tay.
125.I'll have to see about that. Về việc này tôi phải nghĩ một chút rồi mới quyết định.
Ngày thứ 7
126.I'll keep my eyes open. Tôi sẽ lưu ý đến điều đó.
127. I'll keep that in mind. Tôi sẽ ghi nhớ.
128.I'll pick up the tab. Để tôi tính tiền.
129.I'll play it by ear. Tôi sẽ làm tùy theo hứng.
130.I'll see what I can do. Để tôi xem liệu tôi có thể làm được gì.
131.I'll show you. Tôi sẽ chỉ cho cậu thấy.
132.I'll take care of it. Để tôi làm việc đó.
133.I'll take it. Tôi đã lấy rồi.
134.I'll take your advice. Tôi ghi nhận lời khuyên của anh.
135.I'll think it over. Tôi sẽ suy nghĩ kĩ một chút.
136.I'll treat you to diner. Tôi muốn mời anh đi ăn tối.
137.I'll walk you to the door. Để tôi tiễn anh ra cửa.
138.I'm broke. Tôi cạn túi rồi./ Viêm màng túi rồi./ Hết nhăn tiền rồi.
139.I'm crazy about English. Tôi rất thích tiếng Anh.
140.I'm easy to please. Tôi rất dễ chịu.
141.I'm glad to hear that. Nghe được tin này tôi rất vui.
142.I'm glad you enjoyed it. Em thích là tôi vui rồi.
143.I'm good at it. Tôi làm cái này rất rành.
144.I'm in a good mood. Tâm trạng tôi lúc này rất tốt.
145.I'm in good shape. Tình trạng sức khỏe của tôi rất tốt.
146.I'm just having a look. Tôi chẳng qua nhân tiện xem qua thôi.
147.I'm looking for a part-time job.
148.I'm looking forward to it. Tôi đang mong ngóng điều đó.
Ngày thứ 8
149.I'm lost. Tôi bị làm cho hồ đồ rồi.
150.I'm not feeling well. Tôi cảm thấy không được khỏe.
151.I'm not myself today. Hôm nay tôi bị làm sao ấy.
152.I'm not really sure. Tôi thực sự không rõ lắm.
153.I'm on a diet. Tôi đang ăn kiêng.
154.I'm on my way. Tôi đi bây giờ đây.
155.I'm pressed for time. Tôi đang vội.
156.I'm sorry I'm late. Xin lỗi, tôi đến muộn.
157.I'm sorry to hear that. Tôi lấy làm tiếc khi nghe tin đó.
158.I'm under a lot of pressure. Tôi chịu áp lực rất lớn.
159.I'm working on it. Tôi đang cố gắng đây!
160.I've changed my mind. Tôi đã thay đổi ý định rồi.
161.I've got a headache. Tôi đau đầu quá!
162.I've got my hands full. Tôi đang dở tay.
163.I've got news for you. Tôi có tin tức tốt lành nói cho anh đây.
164.I've got no idea. Tôi không biết.
165.I've had enough. Tôi ăn no rồi.
166.If I were in your shoes. Nếu tôi đứng vào vị trí của anh./ Nếu như tôi đứng trên lập trường của anh.
167.Is that OK? Như thế được không?
Ngày thứ 9
168.Is this seat taken? Chỗ này có người ngồi không?
169.It all depends. Còn tùy vào tình hình.
170.It can happen to anyone. Điều này có thể xảy ra đối với bất cứ ai.
171.It doesn't make any difference. Đều giống nhau cả thôi./ Đều thế cả thôi.
172.It doesn't matter to me. Đối với tôi mà nói thì đó chẳng là vấn đề gì cả.
173.It doesn't work. Nó hư rồi.
174.It drives me crazy. Nó làm tôi phát điên lên được.
175.It isn't much. Nó chẳng thấm tháp gì.
176.It really comes in handy. Có cái này thật là tiện biết mấy.
177.It slipped my mind. Không chú ý nên tôi quên mất rồi.
178.It takes time. Vấn đề này cần có thời gian.
179.It will come to me. Tôi sẽ nhớ ra.
180.It will do you good. Điều này có ích cho bạn đấy.
181.It won't happen again. Điều đó sẽ không xảy ra nữa.
182.It won't take much time. Vấn đề đó không mất nhiều thời gian đâu.
183.It won't work. Không được đâu.
184.It's nice meeting you. Rất vui được biết anh.
Ngày thứ 10
185.It's a deal. Nhất định thế nhé!
186.It's a long story. Một lời thật khó mà nói hết!
187.It's a nice day today. Hôm nay thời tiết rất đẹp.
188.It's a once in a lifetime chance. Đây là một cơ hội hiếm có trong đời.
189.It's a pain in the neck. Thật là khổ hết chỗ nói.
190.It's a piece of cake. Điều này rất dễ dàng.
191.It's a small world. Thế giới thật là nhỏ.
192.It's a waste of time. Thật là lãng phí thời gian.
193.It's about time. Gần hết thời gian rồi./ cũng đến lúc rồi đấy.
194.It's all my fault. Tất cả đều là lỗi của tôi.
Ngày thứ 11
195.It's awesome. Tuyệt qúa! Cừ quá!
196.It's awful. Thật khủng khiếp.
197.It's been a long time. Lâu rồi không gặp.
198.It's better than nothing. Vẫn còn tốt hơn là không có.
199.It's essential. Điều đó thật cần thiết.
200.It's hard to say. Thật khó nói
201.It's incredible. Thật không thể tin được
202.It's just what I had in mind. Đó chỉ là những gì tôi nghĩ
203.It's my pleasure. Thật vinh hạnh cho tôi
204.It's no big deal. Chẳng có chuyện gì to lớn cả
205.It's not your fault. Không phải lỗi của bạn
206.It's nothing. Chẳng là gì hết
207.It's only a matter of time. Chỉ là vấn đề thời gian
Ngày thứ 12
208.It's out of the question. Điều đó không thể được/ Không bàn đến nó nửa
209.It's time for dinner. Đến giờ ăn tối rồi
210.It's up in the air. Không chắc chắn/ đang lưỡng lự
211.It's up to date. Hợp thời/ đang cập nhật
212.It's up to you. Tùy bạn
213.It's very popular. Nó rất phổ biến
214.It's worth seeing. Đáng để xem
215.Just let it be. Cứ để nó như thế/ mặc kệ nó đi
216.Just to be on the safe side. Để cho chắc/cho an toàn
217.Keep the change. Không cần thối tiền
218.Keep up the good work. Tiếp tục phấn đấu nhé
219.Keep your fingers crossed. Cứ hy vọng kết quả khả quan/mong là vận may đến với mình
220.Kill two birds with one stone. Một công đôi việc
221.Let me get back to you. Tôi sẽ trả lời bạn sau
222.Let me guess. Để tôi đoán
223.Let me put it this way. Để tôi nói theo cách này
224.Let me see. Để tôi xem nào
225.Let's call it a day. Hôm nay đến đây thôi/Bây giờ nghỉ nhé
226.Let's celebrate! Mở tiệc thôi
227.Let's find out. Để chúng tôi tìm ra/ để chúng tôi khám phá
228.Let's get to the point. Vào thẳng vấn đề
Ngày thứ 13
229.Let's get together sometime. Khi nào đi chơi nhé
230.Let's hope for the best. Hy vọng mọi chuyện sẽ ổn
231.Let's keep in touch. Giữ liên lạc nhé
232.Let's make up. Làm lành nhé
233.Let's go visit them. Đi thăm họ thôi
234.Let's talk over dinner. Đi ăn tối rồi mình bàn
235.Long time no see. Lâu lăm không gặp ha
236.Look before you leap. Suy nghĩ trước khi quyết định
237.May I ask you a question?
238.May I have a receipt? Cho tôi xin hóa đơn
239.May I have your name,please?
240.May I pay by credit card? Tôi thanh toán bằng thẻ tín dụng được không?
241.May I try it on? Tôi mặc thử được không
242.Maybe it will work. Có lẽ nó sẽ hiệu quả
243.Maybe some other time. Có lẽ khi khác/ để khi khác nhe
244.My mouth is watering. Tôi đang them chảy nước miếng nè ( nhất là khi mấy bạn nữ ăn me)
245.My phone was out of order. Điện thoại của tôi hư rồi
Ngày thứ 14
246.No pain,no gain. Không vấp ngã thì không trưởng thành được/ không khó khăn thì không thành công
247.No problem. Không có chi
248.Nothing is impossible to a willing heart. Có chí là làm gì cũng được/ quyết tâm thì không có gì khó hết
249.Pain past is pleasure. Sau cơn mưa trời lại sáng
250.Please accept my apology. Xin lỗi nhe (trang trọng hơn I’m sorry)
251. Please don't blame yourself. Đừng tự trách mình
252. Please leave me alone. Để tôi yên / Xin để tôi 1 mình
253. Please let me know. Báo cho mình biết nhe
254. Please make yourself at home. Xin tự nhiên như ở nhà
255. Please show me the menu.
256. Probably. có lẽ vậy
257. So far, so good. Tạm thời vẫn ổn
258.Something must be done about it. Vấn đề này phải được giải quyết
259.Something's come up. Có chuyện (ngoài dự kiến )xảy ra
260.Storms make trees take deeper roots. Thử thách mới khiến người ta mạnh mẽ
261.Suit yourself. Thích thì cứ làm/tùy bạn thôi
262.Take care. Bảo trọng
Ngày thứ 15
263.Take it or leave it. Chịu hay không?
264.Take my word for it. Dạy nó theo cách của tôi
265.Take your time. Cứ thư thả/ vẫn còn thời gian
266.Thank you all the same. Cảm ơn bạn rất nhiều (=thank you very much)
267.Thank you for everything. Cảm ơn vì tất cả
268.Thanks a million. Triệu lần cảm ơn bạn
269.Thanks for the warning. Cảm ơn vò cảnh báo trước
270.Thanks for your cooperation.
271.That couldn't be better. Không thể tốt hơn được
272.That depends. Tùy tình hình thôi
273.That makes sense. Cũng có lý nhỉ
274.That reminds me. Việc đó gợi nhắc cho tôi
275.That rings a bell. Cái đó có cảm giác quen thuộc
276.That sounds like a good idea. Nghe có vẻ hay đấy
277.That's all right. Ổn rồi/ được rồi
278.That's disgusting. Thật kinh tởm
279.That's fair. Thật công bằng
280.That's for sure. Chắc chắn là vậy
281.That's good to know. Biết được thì tốt quá
Ngày thứ 16
282.That's just what I was thinking.
283.That's life. Cuộc đời là vậy đó
284.That's more like it. Phải như vậy chứ/ tớ đã nói mà lị
285.That's not a problem. Đó không phải là vấn đề
286.That's not true. Điều đó không đúng
287.That's OK. Cũng được
288.That's ridiculous. Thật là lố bịch
289.That's the way I look at it,too. Mình cũng thấy như vậy
290.That's the way it is. Bản chất nó là vậy đó
291.That's worthwhile. Thật đáng để bỏ công
292.The same to you. Bạn cũng vậy nha
293.The shortest answer is doing. Đơn giản là phải làm thôi
294.The sooner,the better. Càng sớm càng tốt
Ngày thứ 17
295.There is a call for you. Cậu có điện thoại nè
296.There is no doubt about it. Không nghi ngờ gì nửa
297.There is nothing I can do. Tôi không thể làm gì được nửa
298.There's a possibility. Hoàn toàn có thể
299.These things happen all the time. mấy chuyện này gặp hoài thôi
300.This soup tastes great. Món súp thật tuyệt
301.Time is money. Thời gian là vàng bạc
302.Tomorrow never comes. Đừng có chần chừ/ đừng có đợi
303.Two heads are better than one. Nhiều người thì mọi việc dễ dàng hơn
304.We are in the same boat. Cùng hội cùng thuyền
305.We can get by. Chúng ta có thể xoay sở
306.We can work it out. Chúng ta có thể làm được
307.We have a lot in common. Chúng mình có nhiều điểm chung nhỉ (kua gái)
308.We'll see. Chúng ta sẽ thấy thôi
309.What a coincidence! Thật là ngẫu nhiên/thật là trùng hợp
310.What a shame! Thật đáng xấu hổ
311.What are you up to? Bạn đang làm gì thế (up to=doing now)
312.What are you talking about? Bạn đang nói gì thế
313.What are your plans for the weekend? Có kế hoạch gì cho cuối tuần chưa?
Ngày thứ 18
314.What can I do for you? Tôi có thể làm gì cho bạn
315.What do you do for relaxation? Bạn làm gì để giải trí?
316.What do you recommend? Bạn giới thiệu gì nè?
317.What do you think of my new car? Bạn nghĩ gì về chiếc xe tôi mới mua
318.What do you think of it? Bạn nghĩ sao về nó
319.What is it about? Nó là gì?
320.What is it like there? Thời tiết ở đó như thế nào?
321.What makes you say so? Chuyện gì khiến bạn nói như vậy?
322.What's going on? Chuyện gì đang diễn ra vậy?
323.What's on your mind? Bạn đang nghĩ gì vậy?
324.What's the deadline? Hạn cuối là khi nào?
325.What's the matter with you?
326.What's the purpose of your visit? bạn tới đây có chuyện gì không?(muốn mượn tiền hả?)
327.What's the weather like? Thời tiết như thế nào?
328.What's your favorite food? Bạn thích ăn món gì?
Ngày thứ 19
329.What's your job? Bạn làm nghề gì?
330.Whatever you think is fine with me. Chuyện gì bạn nghĩ cũng tốt cho tôi hểt
331.When is the most convenient time for you? Thời gian thích hợp nhất cho bạn là lúc nào?
332.When will it be ready? Khi nào thì được?
333.Where are you going? Bạn đang đi đâu đó?
334.Where can I check in? tôi có thể đăng kí ở chổ nào vậy?
335.Where can I go for help? Tôi có thể được giúp đỡ ở đâu vậy?
336.Where do you live? Bạn sống ở đâu?
337.Where have you been? Bạn đã ở đâu?
338.Where is the rest room,please? Nhà vế sinh ở chổ nào vậy?
339.Where were we? Chúng ta đã ở đâu?
340.Who is in charge here? Ai phụ trách ở đây?
341.Would you care for a drink? Muốn uống gì không?
Ngày thứ 20
342.Would you do me a favor? Bạn có thể giúp tôi được không?
343.You are just saying that. Bạn chỉ biết nói thôi/ giỏi cái miệng à
344.You are kidding. Bạn đang đùa hả?
345.You are so considerate. Bạn thật chu đáo
346.You can count on me. Bạn có thể trông cậy vào mình
347.You can say that again. Bạn có thể nhắc lại được không?
348.You can't complain. Đừng phàn nàn/ bạn không thể kêu ca như vậy
349.You deserve it. Bạn xứng đáng với nó (khen)/ đáng đời (chê)
350.You did a good job. Làm tốt lắm
351.You get what you pay for. Làm bao nhiêu thì ăn bấy nhiêu/tiền nào của đó thôi
352.You got a good deal. Bạn có một hợp đồng tốt đó
353.You need a vacation. Bạn cần nghỉ ngơi
354.You never know. Bạn không bao giờ biết đâu
355.You said it. Bạn nói đó nha / bạn đã nói như vậy mà
356.You should give it a try. Bạn nên thử xem
Ngày thứ 21
357.You should take advantage of it. Bạn nên nhìn nhận cái tốt của nó/bạn nên nắm bắt cơ hooij
358.You will be better off. Bạn sẽ tốt hơn thôi(sau sự cố gì đó, thất tình chẳng hạn)
359.You will have to wait and see. Hãy đợi đấy/ chờ rồi xem
360.You'll get used to it. Bạn sẽ quen với nó thôi
361.You've dialed the wrong number. Bạn gọi lộn số rồi
362.You've got a point there. Bạn có lý về điều đó( tranh luận)
363.You've got it. Bạn hiểu nó rồi đó/ bạn đã đạt được rồi
364.You've made a good choice. Bạn đã lựa chọn đúng
365.Your satisfaction is guaranteed. Đảm bảo ban sẽ hài lòng more.