查找N個字符串(環)的最長公共子序列

N個字符串


理論知識:

二進制模擬串實現暴力破解——暴力枚舉出(最長)公共子序列

代碼實現:

#include <iostream>
#include <string>
#include <ctime>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;

// 判斷字符串subSeq中的每一個字符在字符串str中的出現順序是否爲遞增
bool isSubsequence(const string& str, const string& subSeq) {
	int index = -1; //設爲-1而不是0,因爲後邊還要 +1;
	for (char ch : subSeq) {
		index = str.find_first_of(ch, index + 1); // 起始位置得是下一位,故需要 +1;
		if (index == str.npos) {
			return false;
		}
	}
	return true;
}
// 多串的公共子序列判斷;
bool isComSeq(const vector<string>& vs, const string& subseq) {
	for (string str : vs) {
		if (!isSubsequence(str, subseq)) {
			return false;
		}
	}
	return true;
}

// 2 ^ 64 是上限,故 只能處理母串長度 <64 的情況;
int main() {

	std::ios::sync_with_stdio(false);
	std::cin.tie(0);

	int n; // 帶輸入的字符串個數;
	vector<string> vs; // 待輸入的字符串;
	string s; // 臨時存儲輸入的字符串;
	string s_little; // s輸入的最短的字符串;
	while (cin >> n) {
		while (n--) {
			cin >> s;
			vs.push_back(s); // 存入字符串;
			if (s_little.length() < s.length()) {
				s_little = s;
			}
		}
		clock_t startTime = clock();
		// 確保遍歷的是短字符串的所有子序列,並且是採用剔除元素的方式、自長到短遍歷
		
		int len = s_little.length();
		int cont = 1 << len;
		string subSeq; // 臨時存儲子序列;
		set<string> ss; // 存儲所有公共子序列;
		int longest = 0; // 最長公共子序列長度;
		bool flag = false; // 公共子序列是否存在;
		// i 將會直接影響到選擇子集元素個數的多少(二進制表示);
		for (int i = cont - 1; i >= 0; --i) {
			for (int j = 0; j < len; ++j) {
				if (i & (1 << j)) {
					//cout << s1[j];
					subSeq += s_little[j];
				}
			}
			//cout << "subSeq = " << subSeq << endl;
			// 接着判斷 s_little 的子序列 subSeq 是否也同時是 其他字符串 的子序列;
			
			if (isComSeq(vs,subSeq)) {
				if (longest <= subSeq.size()) { // 是公共子序列不行,還得是最長公共子序列才進行存儲;
					ss.insert(subSeq);
					longest = subSeq.size();
					flag = true; // 不放在 if 語句外邊實爲減少不必要的賦值次數;
				}
			}
			subSeq.clear(); // 記得重置爲空;
			
		}
		// 不存在公共序列則輸出空串;
		//if (ss.empty()) {// 此判別條件存在漏洞,因爲兩個任意字符串至少存在  空串 作爲公共子序列,故需要立flag;
		if (!flag) {
			cout << endl;
		}
		else {
			for (auto item : ss) {
				/*cout << item << endl;*/ // 輸出所有公共子序列;
				if (item.length() == longest) {
					cout << item << endl;
					//break; // 只需要輸出ACSLL最小的最長公共子序列時,break;
				}
			}
		}
		// 重置容器爲空;
		s_little.clear();
		vs.clear();
		// ss.clear(); // ss 是局部聲明,清空沒有必要; 
		// longgest = 0; 同理;
		cout << "總共耗時:" << double(clock() - startTime) / CLOCKS_PER_SEC << "s" << endl;
	}
	return 0;
}

測試樣例:

鼠標置於此處可預覽圖片(Chrome)


拓展:N個字符環,求最長公共子序列?


代碼實現:

#include <iostream>
#include <string>
#include <ctime>
#include <algorithm>
#include <set>
#include <vector>
#include <cassert>
using namespace std;

// 判斷字符串subSeq中的每一個字符在字符串str中的出現順序是否爲遞增
bool isSubsequence(const string& str, const string& subSeq) {
	int index = -1; //設爲-1而不是0,因爲後邊還要 +1;
	for (char ch : subSeq) {
		index = str.find_first_of(ch, index + 1); // 起始位置得是下一位,故需要 +1;
		if (index == str.npos) {
			return false;
		}
	}
	return true;
}

// 字符串轉字符環;
vector<string> stringToRing(const string& str) {
	vector<string> vs;
	for (int i = 1; i <= str.size(); ++i) {
		string str_right(&str[i], str.size() - i);
		// string s_right(str, i, str.size() - i); // 等效寫法 1 ;
		// string s_right = str.substr(i, str.size() - i); // 等效寫法 2 ;
		string str_left(str, 0, i);
		string s = str_right + str_left;
		// cout << s << endl;
		vs.push_back(s);
	}
	// 一個字符環 包含 n個與源字符串等長的字符串(n爲源字符串的元素個數,即長度);
	assert(vs.size() == str.length());
	return vs;
}

// 多個字符環的公共子序列判斷;
bool isComSeq(const vector<string>& vs, const string& subseq) {
	
	for (string str : vs) {
		// 在當前字符環中是否存在該子序列,存在一次即可滿足條件;
		bool flag = false; 
		for (string item : stringToRing(str)) {
			if (isSubsequence(item, subseq)) {
				flag = true; // 存在一次即可;
				break;
			}
		}
		// 當前環找不着該序列;
		if (!flag) {
			return false;
		}
	}
	return true;
}

// 2 ^ 64 是上限,故 只能處理母串長度 <64 的情況;
int main() {

	std::ios::sync_with_stdio(false);
	std::cin.tie(0);

	int n; // 帶輸入的字符串個數;
	vector<string> vs; // 待輸入的字符串(源字符串);
	string s; // 臨時存儲輸入的字符串;
	string s_little; // s輸入的最短的字符串;
	while (cin >> n) {
		while (n--) {
			cin >> s;
			vs.push_back(s);  // 存入字符環;
			if (s_little.length() < s.length()) {
				s_little = s;
			}
		}
		clock_t startTime = clock();

		string subSeq; // 臨時存儲子序列;
		set<string> ss; // 存儲所有公共子序列;
		int longest = 0; // 最長公共子序列長度;
		bool flag = false; // 公共子序列是否存在;

		for (string item : stringToRing(s_little)) {

			// 確保遍歷的是短字符串的所有子序列,並且是採用剔除元素的方式、自長到短遍歷
			int len = item.length();
			int cont = 1 << len;

			// i 將會直接影響到選擇子集元素個數的多少(二進制表示);
			for (int i = cont - 1; i >= 0; --i) {
				for (int j = 0; j < len; ++j) {
					if (i & (1 << j)) {
						//cout << s1[j];
						subSeq += item[j];
					}
				}
				//cout << "subSeq = " << subSeq << endl;
				// 接着判斷 s_little 的子序列 subSeq 是否也同時是 其他字符串 的子序列;

				if (isComSeq(vs, subSeq)) {
					if (longest <= subSeq.size()) { // 是公共子序列不行,還得是最長公共子序列才進行存儲;
						ss.insert(subSeq);
						longest = subSeq.size();
						flag = true; // 不放在 if 語句外邊實爲減少不必要的賦值次數;
					}
				}
				subSeq.clear(); // 記得重置爲空;

			}
		}
		// 不存在公共序列則輸出空串;
		//if (ss.empty()) {// 此判別條件存在漏洞,因爲兩個任意字符串至少存在  空串 作爲公共子序列,故需要立flag;
		if (!flag) {
			cout << endl;
		}
		else {
			for (auto item : ss) {
				/*cout << item << endl;*/ // 輸出所有公共子序列;
				if (item.length() == longest) {
					cout << item << endl;
					//break; // 只需要輸出ACSLL最小的最長公共子序列時,break;
				}
			}
		}
		// 重置容器爲空;
		s_little.clear();
		vs.clear();
		ss.clear(); // ss 是局部聲明,清空沒有必要; 
		longest = 0; // 同理;
		cout << "總共耗時:" << double(clock() - startTime) / CLOCKS_PER_SEC << "s" << endl;
	}
	return 0;
}


測試樣例:

鼠標置於此處預覽(Chrome)

題目補充:


題目來源:
ACM-ICPC 2018 北京賽區網絡預賽 Tomb Raider

特別說明:
該考題的輸出僅僅是一個ACSLL碼最小的最長公共子序列串,
自己的測試是左右公共子序列。
有上述 從N個字符串找最長公共子序列,到N個字符環找最長公共子序列的過程可以發現:環的公共子序列的結果數量明細增多。

並且值得注意的是:
代碼證容易被遺漏的地方是, s_little也需要先生成對應的字符環(多個字符串),再各自去生成各自的所有子序列,然後拿來校驗。
而不是僅僅針對 s_little 的源字符串生成所有子序列。


其他說明:


博客補充以強化理解:

助你深刻理解——最長公共子串、最長公共子序列(應該是全網數一數二的比較全面的總結了)

轉載請註明出處:
https://blog.csdn.net/I_love_you_dandan/article/details/103227017
以上代碼皆爲本人親自碼字、親自測試,如有問題需要諮詢或者指正,可以直接在評論區留言或者發送私信進行友好交流。
聯繫方式:[email protected]
2019/11/24  20:43
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章