通过二进制串“01”模拟元素取舍进而解决组合问题(数组元素实现排列组合、字符串生成所有子序列、集合生成所有子集)



理论参考:

Efficiently Enumerating the Subsets of a Set

借鉴博客:

枚举子集的3种方式 – C++描述

代码实现:

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

// 十进制转二进制理解原理;
string numTobinary(int n) {
	string str;
	while (n / 2) {
		str = to_string(n % 2) + str;
		n /= 2;
	}
	str = to_string(n) + str; // 二进制的最后一位(个)位数;
	while (str.length() < 8) {
		str = "0" + str;
	}
	return str;
}
// 2 ^ 64 是上限,故 只能处理母串长度 <64 的情况;
string binaryStringGenerator(int strLenAsExp, int runs) {
	cout << endl << runs + 1 << " runs:" << endl;
	string str(strLenAsExp,'2'); // 随便赋给非0、非1的字符即可
	//cout << endl << "str = " << str << endl;
	for (int exponent = 0; exponent < strLenAsExp; exponent++) {

		// 通过输出查看原理;
		cout << endl;
		cout << "runs = " << runs << "\t\t" << numTobinary(runs) << endl;
		cout << "1<<" << exponent << " = " << (1 << exponent) << "\t\t" << numTobinary(1 << exponent) << endl;
		cout << "(runs & (1<<" << exponent << ") = " << (runs & (1 << exponent)) << "\t" << numTobinary(runs & (1 << exponent)) << endl;

		// 关键代码;
		if (runs & (1 << exponent)) {
			str[exponent] = '1'; // 1 表示选择;
		}
		else {
			str[exponent] = '0'; // 0 表示不选择;
		}
	}
	return str;
}
set<string> subStringGenerator(const string& str){
	set<string> ss;
	int len = str.length();
	int cont = 1 << len; // 子集个数 = 1 * 2 ^ N 
	// i 从1开始时,无 空集;默认从0开始,存在空集;
	for (int i = 1; i < cont; i++) {
		string binStr = binaryStringGenerator(len, i);
		cout << "binStr = " << binStr << endl;
		string temp;
		for (int i = 0; i < (int)str.length(); ++i) {
			// 所构造的二进制串出现非法元素时,引发中断;
			assert('0' == binStr[i] || binStr[i] == '1'); 
			if (binStr[i]=='1') {
				temp += str[i];
			}
		}
		ss.insert(temp);
	}
	return ss;
}
template<typename T>
void outRsult(const set<T>& myset) {
	for (auto item : myset) {
		cout << item << "\t";
	}
	cout << endl;
}

int main() {
	string str; // 待输入的字符串;
	while (cin >> str) {
		auto ss = subStringGenerator(str);
		//cout << ss.size() << endl;
		outRsult(ss);
	}
	return 0;
}

样例测试与与案例分析:

单个元素

双个元素

多个元素

其他测试

代码优化:

上述代码运行效率测试:

以 所耗时间为参照标准,不进行数据输出(注释掉)、记录运行时长:

程序运行时长测试办法(两行代码)

#include<ctime>
…
clock_t startTime = clock();
… 需要测试的代码运行过程 …
cout << "总共耗时:" << double(clock() - startTime) / CLOCKS_PER_SEC << "s" << endl;

测试结果表明:时间成线性增长:


直接在main函数运行二进制模拟:

此时,既不存储、也不进行数据输出,仅仅是二进制模拟,所耗时长大大降低;
理论上,待测试的字符串长度可达64位,但一般程序设计都有运行时长要求,
若只是二十位以内的字符串长,可以考虑使用二进制模拟解决。
#include<iostream>
#include<string>
#include <ctime>
using namespace std;

// 2 ^ 64 是上限,故 只能处理母串长度 <64 的情况;
int main() {
	string str; // 待输入的字符串;
	while (cin >> str) {
		clock_t startTime = clock();
		int len = str.length();
		int cont = 1 << len;
		for (int i = 1; i < cont; ++i) { // i 表示即将要选择子集元素个数;
			for (int j = 0; j < len; j++) {
				if (i & (1 << j)) {
					//cout << str[j];
				}
			}
			//cout << " ";
		}
		//cout << endl;
		cout << "总共耗时:" << double(clock() - startTime) / CLOCKS_PER_SEC << "s" << endl;
	}
	return 0;
}

直接模拟则运行时长大大降低

二进制模拟在查找共同子序列问题上的应用:

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

排列问题详见笔者另外一篇博客:

函数模板实现——动态数组各元素的全排列问题

转载请注明出处。
如需交流,可直接评论区留言或者发送私信。
联系方式:[email protected]
2019/11/23 22:33

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

猜你喜欢

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