Chuyển tới nội dung

Bài toán xâu con chung dài nhất C/C++| Longest common subsequence

truy vet xau con dai nhat

Bài viết chia sẻ cách giải quyết bài toán xâu con chung dài nhất (LCS – Longest common subsequence) bằng ngôn ngữ C/C++. Độ dài xâu con chung lớn nhất sử dụng thuật toán quy hoạch động. Cách truy vết, in ra dãy con tăng liên tiếp theo. . .

1. Giới thiệu bài toán LCS

Bài toán xâu con chung dài nhất (lớn nhất) là một bài toán kinh điển cho việc ứng dụng thuật toán quy hoạch động để giải quyết. Bạn là người đang tìm hiểu về Dynamic Programming, đừng bỏ lỡ bài toán này. Có thể nói, đây là một bài toán rất hay giúp bạn hiểu cả về QHD, cả về cấu trúc ma trận mảng hai chiều.

Bài toán được phát biểu như sau:

Viết chương trình nhập vào hai xâu kí tự, in ra độ dài xâu con chung lớn nhất và xâu chung đó của hai xâu vừa nhập.
Ví dụ:  hai xâu “BACDB” và “BDCB”, xâu con chung lớn nhất có độ dài là 3 và là “BCB”.

Như vậy có hai điểm cần chú ý trong bài toán này đó là:

  • Tìm độ dài của xâu con chung dài nhất
  • In xâu con tìm được ra màn hình

2. Ý tưởng giải quyết bài toán

Như đã nói ở phần đầu, bài toán này mình sẽ sử dụng thuật toán quy hoạch động. Nếu bạn chưa hiểu rõ về thuật toán QHD, có thể tham khảo thêm tại đây. Ngoài ra để lưu lời giải của các bài toán con sử dụng đến cấu trúc mảng hai chiều C/C++, chính vì thế hãy làm chủ nó trước nhé!

2.1 Tìm độ dài xâu con lớn nhất

Bài toán nhỏ nhất ở đây chính là việc so sánh các phần tử nằm trong xâu, nếu chúng bằng nhau thì ta ghi nhật kết quả.

Công thức quy hoạch động bài toán xâu con chung lớn nhất:

  • Nếu A[i] == B[j] thì L[i][j] = L[i-1][j-1] + 1
  • Ngược lại thì L[i][j] = max (L[i-1][j], L[i][j-1])

Trong đó:

  • A, B là hai xâu cần xét
  • i, j tương ứng là chỉ số hàng và cột của mảng kết quả L
  • Hàm max là hàm để tìm ra số lớn hơn trong hai số, trường hợp hai số bằng nhau thì lấy số đầu tiên – L[i-1][j-1]

Để dễ hiểu hơn bạn xem hình minh họa dưới đây:

xau con chung dai nhat c

Mảng lưu kết quả là một mảng hai chiều có kích thước L[n+1][m+1] trong đó n, m chính là độ dài của hai xâu a và b.
Sở dĩ phải tăng thêm một đơn vị (n+1, m+1) để lưu thêm một hàng, cột có giá trị = 0. Tức là khi chưa có phần tử nào thì số con chung sẽ là 0.

Giá trị L[n][m] chính là độ dài của xâu con chung lớn nhất ta cần tìm.

2.2 Truy vết xâu con chung

Truy vết để tìm và in ra xâu con ra màn hình sẽ dựa vào bảng kết quả mà ta tìm được ở phần trên. Bắt đầu từ vị trí L[n][m] và dừng lại khi L[i][j] == 0.

  • Bắt đầu duyệt từ i = n , j = m. Phần tử thứ i của xâu a sẽ là a[i-1], thứ j của xâu b sẽ là b[j-1]
  • Nếu a[i-1] == b[j-1] ta sẽ lưu lại con chung a[i-1] và giảm i và j đi 1 đơn vị
  • Nếu a[i-1] != b[j-1] có 2 trường hợp:
    – Nếu L[i-1][j] >= L[i][j-1] thì giảm i 1 đơn vị
    – Ngược lại giảm j đi 1 đơn vị

Nếu thấy khó hiểu, bạn xem hình phía trên. Màu green là trường hợp a[i-1] == b[j-1], màu blue là trường hợp a[i-1] != b[j-1]

3. Code xâu con chung dài nhất C/C++

Phần này mình sẽ trình bày code theo đúng ý tưởng đã nếu ở phần trên. Mời bạn tham khảo

/* COde by https://duongdinh24.com/ 
   Github: https://github.com/duongdinh24/
*/
#include<iostream>
#include<string>  // Thư viện xử lý xâu
using namespace std;

void longest_Common(string a, string b){  // Hàm tìm xâu con lớn nhất và in ra màn hình
	int n = a.size();  // n chiều dài xâu a, m chiều dài xâu b
	int m = b.size();
	int max_Size;     // Biến lưu độ dài con chung lớn nhất
	string subsequence = "";  // Biến lưu con chung dùng khi truy vết
	int L[n+1][m+1];   // Khai báo mảng lưu kết quả:  n+1 hàng, m+1 cột
	
	for(int i=0; i<=n; i++)   // Gán cột đầu tiên bằng 0
		L[i][0] = 0;
	for(int j=0; j<=m; j++)   // Gán hàng đầu tiên = 0
		L[0][j] = 0;
		
	for(int i = 1; i<=n; i++){
		for(int j = 1; j<=m; j++){
			if(a[i-1] == b[j-1]){  // Nếu có phần tử bằng nhau
				L[i][j] = L[i-1][j-1] + 1;   // Áp dụng công thức
			}
			else{   // Trường hợp a[i-1] khác b[j-1]
				if(L[i-1][j] >= L[i][j-1])     // Tìm max giữa L[i-1][j] và L[i][j-1]
					L[i][j] = L[i-1][j];
				else
					L[i][j] = L[i][j-1];
			}
		}
	}
	
	max_Size = L[n][m];  // Tìm được độ dài con lớn nhất
	int i = n; 
	int j = m;
	while(L[i][j] != 0){ // Điều kiện dừng
		if(a[i-1] == b[j-1]){  // Nếu bằng nhau
			subsequence += a[i-1];   // Cộng a[i-1] vào xâu con
			i--;
			j--;
		}
		else{  // Nếu khác nhau
			if(L[i-1][j] >= L[i][j-1]) // So sánh
				i--;
			else
				j--;
		}
	}
	
	cout<<"\nDo dai xau lon nhat: "<<max_Size;  // In ra độ dài con lớn nhất
	cout<<"\nXau con: ";
	for(int t = max_Size-1 ; t>=0; t--)  // In ngược từ cuối về đầu để xâu con đúng thứ tự
		cout<<subsequence[t];
}
int main(){
	string a, b;
	cout<<"Nhap xau a: "; cin>>a;
	cout<<"Nhap xau b: "; cin>>b;
	longest_Common(a,b);
	return 0;	
}

Và đây là kết quả chạy chương trình trên:

Bài viết của mình đến đây là hết, nếu bạn có bất kì thắc mắc nào, đừng ngại để lại comment xuống phía dưới nhé! Cảm ơn bạn đã quan tâm bài viết này.

Xem thêm các bài viết về ứng dụng thuật toán khác tại đây.

9 bình luận trong “Bài toán xâu con chung dài nhất C/C++| Longest common subsequence”

    1. Không sao đâu bạn à, mới đầu mình cũng không hiểu gì vì đây là bài toán không hề đơn giản. Bạn thử cố gắng nhìn vào hình minh họa xem có hiểu k nhé. Nếu k hiểu cũng đừng buồn, đó là chuyện bình thường của lập trình

    1. Chào Vinh, câu hỏi của bạn rất hay. Thực ra cái công thức đó dựa vào các trường hợp có thể xảy ra khi so sánh 2 xâu. Bạn có thể theo dõi cái bảng ví dụ của mình để dễ hình dung. Thực sự giải thích chi tiết hơi khó ^^.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *