二进制模拟串实现暴力破解——暴力枚举出(最长)公共子序列







前言

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

实现基础


二进制模拟

本人博客:

通过二进制串“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

发布了89 篇原创文章 · 获赞 159 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/I_love_you_dandan/article/details/103220733