【算法】求子集(多解)

求子集

在所有子集中,生成各个字子集,例如 [1,2,3] 生成的子集中,[[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]],就是是否选[1],是否选[2],是否选[3]的问题。
如果只使用循环的话,假如第一次选择[1],循环继续往下走,没有办法回退到不选[1]的状态。也就是说,循环过程难以直接模拟是否选某一元素的过程。 那我们来想一想, 如果只是生成[1],[1,2],[1,2,3]三个子集,如何做呢?

利用循环不回退的过程
只将子集[1] [1,2] [1,2,3]放进数组

int main()
{
	vector<int> nums = { 1, 2, 3 };
	vector<int> tmp; //生成各个子集的数组
	vector<vector<int>> vv;

	for (int i = 0; i < nums.size(); i++)
	{
		tmp.push_back(nums[i]);
		vv.push_back(tmp);
	}
	
	//打印vv数组子集 [1] [1,2] [1,2,3]
	for (auto ee : vv)
	{
		for (auto e : ee)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	system("pause");
}

我们就利用上述的这种循环得到所有的子集

方法一

算法思想
不断的向每一次得到的数组中添加元素,直到所有元素添加到其中。也就是不断的向tmp中添加新元素后,在记录到二维数组res中
nums = [1,2,3], tmp = [], res = { [] }。

  1. 一开始的时候 tmp = [] res = { [] };
  2. 加入1 , 向res中每个数组填入1,再记录到res数组中,res = { [] , [1] };
  3. 加入2, 向res中每个数组填入2, 再记录到res数组中,res = { [] , [1], [2], [1,2] }
  4. 加入3,向res中每个数组填入3, 再记录到res数组中,res = { [],[1],[2],[1,2], [3], [1,3],[2,3] [1,2,3] }

代码实现:

	vector<vector<int>> GetSub(vector<int>& nums)
	{
		vector<vector<int>> res(1); // 1个位置留给空的子集
		for (int i = 0; i < nums.size(); i++)
		{
			int ans = res.size();
			for (int j = 0; j < ans; j++)
			{
				vector<int> tmp = res[j];
				tmp.push_back(nums[i]);
				res.push_back(tmp);
			}
		}
		return res;
	}

我们想一想能不能用递归来实现取出 [1],[1,2],[1,2,3]这个过程,思路还是一样的。
利用递归不回退的过程

// v = {1,2,3}
void dfs(vector<vector<int>>&vv, vector<int>& v, vector<int> tmp, int i)
{
	if (i > v.size())  //递归条件结束,当下标i超过nums数组的长度时结束
		return;       
	tmp.push_back(v[i]);
	vv.push_back(tmp);
	dfs(vv, v, tmp, i + 1); // 进行下一次递归调用
}

接下来的回溯法,就是利用递归模拟实现所有元素的放入与不放入的过程。

利用回溯法

利用回溯法生成子集,即对于每个元素,都有试探放入或不放入集合中的两个选择:
放入该元素,递归的进行后续元素的选择,完成放入该元素后续所有元素的试探;之后将这个元素拿出,即再进行一次选择不放入该元素,递归的进行后续的选择,完成不放入该元素后续所有的元素试探。

本来选择放入,再选择一次不放入的这个过程,就是回溯
例如对于数组 nums: [1,2,3] ,子集生成数组tmp[] = []
对于1元素:
放入tmp, tmp = [1] ,继续递归处理后续[2,3]元素
不放入tmp, tmp =[], j继续递归处理后续[2,3]元素

放法二: 通过回溯模拟放入与不放入的过程

void dfs(int i, vector<int>& v, vector<int> temp, vector<vector<int>>& vv)
{

	if (i >= v.size()) //递归结束的条件
		return;
	temp.push_back(v[i]);
	vv.push_back(temp);    
	dfs(i + 1, v, temp, vv);  //选择放入v[i]
	temp.pop_back();   
	dfs(i + 1, v, temp, vv);  //选择不放入v[i]
}

int main()
{
	vector<int> nums = { 1, 2, 3 };
	vector<int> tmp; //生成各个子集的数组
	vector<vector<int>> vv(1); // vv中开一个空间,这个是空集

	dfs(0, nums, tmp, vv);
	for (auto ee : vv)
	{
		for (auto e : ee)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	system("pause");
}

总结:放入和不放入其实就是一种选择,我们要尝试所有的选择,得到每一种元素放与不放的所有组合。就是子集。

方法三: 通过for循环完成回溯

void dfs(vector<vector<int>>& vv, vector<int>& v, vector<int> temp, int cur)
{
	vv.push_back(temp);

	for (int i = cur; i < v.size(); i++)
	{
		temp.push_back(v[i]);  // 放入v[i]元素
		dfs(vv, v, temp, i + 1); // 下一次我们应该从i+1位置开始
		temp.pop_back();       // 不放入v[i]元素
	}
}
int main()
{
	vector<int> nums = { 1, 2, 3 };
	vector<int> tmp; //生成各个子集的数组
	vector<vector<int>> vv(1); // vv中开一个空间,这个是空集

	dfs(vv, nums, tmp, 0);
	for (auto ee : vv)
	{
		for (auto e : ee)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	system("pause");
}

注意:方法二不同与方法一的是,没有判断条件,直接就将新求出的temp子集数组放到集合vv中,所以我们必须用一个变量标记下一次从哪里开始循环,以避免重复一直选一个元素的情况发生。所以我们用cur变量作为标记,标记下一次改尝试的位置。

猜你喜欢

转载自blog.csdn.net/weixin_43939593/article/details/105900099