求子集
在所有子集中,生成各个字子集,例如 [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 = { [] }。
- 一开始的时候 tmp = [] res = { [] };
- 加入1 , 向res中每个数组填入1,再记录到res数组中,res = { [] , [1] };
- 加入2, 向res中每个数组填入2, 再记录到res数组中,res = { [] , [1], [2], [1,2] }
- 加入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变量作为标记,标记下一次改尝试的位置。