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







前言

真可謂是歪打正着,這不就是暴力破解肉口令了嘛 …… 原理是一致的。

實現基礎


二進制模擬

本人博客:

通過二進制串“01”模擬元素取捨進而解決組合問題(數組元素實現排列組合、字符串生成所有子序列、集合生成所有子集)

最長公共子序列

本人博客

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


子序列判斷

參考博客:

判斷子序列

C++ 沒有 indexOf函數,不過可以通過 find_first_of設計出同樣功能。
因爲原理是不變的:
判斷字符串subSeq中的每一個字符在字符串str中是否存在,
存在的話它們的出現順序是否爲遞增。
// 判斷字符串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;
}

二進制暴力枚舉

篩選(最長)公共子序列

代碼實現:

#include <iostream>
#include <string>
#include <ctime>
#include <algorithm>
#include <set>
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;
}

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

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

	string s1, s2; // 待輸入的字符串;
	while (cin >> s1 >> s2) {
		clock_t startTime = clock();
		// 確保遍歷的是短字符串的所有子序列,並且是採用剔除元素的方式、自長到短遍歷
		if (s1.length() > s2.size()) {
			swap(s1, s2);
		}
		int len = s1.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 += s1[j];
				}
			}
			//cout << "subSeq = " << subSeq << endl;
			// 接着判斷 s1 的子序列 subSeq 是否也同時是 s2 的子序列;
			if ( isSubsequence(s2,subSeq)) {
				ss.insert(subSeq);
				if (longest < subSeq.size()) {
					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;
				}
			}
		}
		cout << "總共耗時:" << double(clock() - startTime) / CLOCKS_PER_SEC << "s" << endl;
	}
	return 0;
}

測試樣例:

在這裏插入圖片描述

後記

是不是應該再來個(最長)公共非降序子序列、非增序列子序列、公共子串也再來一下?
再說吧。

轉載請註明出,
如需交流可直接評論區留言或者發送私信。
聯繫方式:[email protected]
可發郵件,可加好友,需要等到本人上線。
2019/11/24 01:05

代碼優化補充

這是上邊的代碼:可用於存儲所有公共子序列。
// 接着判斷 s1 的子序列 subSeq 是否也同時是 s2 的子序列;
	if ( isSubsequence(s2,subSeq)) {
		ss.insert(subSeq);
		if (longest < subSeq.size()) {
			longest = subSeq.size();
			flag = true; // 不放在 if 語句外邊實爲減少不必要的賦值次數;
		}
	}
若只是要求輸出最長公共子序列,則可以進一步優化代碼,如下所示:
if ( isSubsequence(s2,subSeq)) {
		if (longest <= subSeq.size()) { 
		 // 記得改爲 <=符號,否則僅僅會存下一個最長公共子序列,由於子集個數是 2^N,此條件語句篩掉了絕大多數的無關子序列,提高了代碼性能
			ss.insert(subSeq);
			longest = subSeq.size();
			flag = true; // 不放在 if 語句外邊實爲減少不必要的賦值次數;
		}
	}

推薦相關知識點

算法分析之蠻力法(暴力法)
二進制數及其運算基礎
知識點圖解補充

拓展學習:

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

殊途同歸:“DFS+回溯”實現元素取捨模擬並暴力枚舉所有子集

(待更)

2019/11/24 18:10

發佈了88 篇原創文章 · 獲贊 230 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章