题目叙述:
给出一个含有不重复元素的数组,列出它的所有子集。
输入描述:
共两行输入:
第一行一个整数n,示意数组的元素个数。
第二行,数组的各元素。
输出描述:
按集合的形式,每行输出其中一个子集,不要求顺序。
示例:
输入:
3
1 2 3
输出:
[3]
[1]
[2]
[1,2,3]
[1,3]
[2,3]
[1,2]
[]
本题目参考:https://blog.csdn.net/u012118523/article/details/24884803
方法一:通过二叉树向下回溯
原数组中的每一个数在其子集中只有两种状态,存在与不存在,这种只存在两种选择的结构显然可以通过一棵二叉树去模拟,以父节点的左右子树来模拟父节点是否被选中。
代码:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
//向下回溯
//原数组 临时数组 层数
void temp(const vector<int> arr,vector<int> ans,int n)
{
//当访问完最后一层时
if(n==arr.size())
{
cout << "[";
//实际上这里需要判断最后一个逗号的,包裹这不是重点
for(vector<int>::iterator iter=ans.begin();iter!=ans.end();iter++)
{
cout << *iter << ",";
}
cout << "]" << endl;
return;
}
//设左子树为不选入
temp(arr, ans, n + 1);
//将数组选入
ans.push_back(arr[n]);
temp(arr, ans, n + 1);
}
int main()
{
//数字数量
int n;
cin >> n;
vector<int> arr,ans;
//读入
for(int i=0;i<n;i++)
{
int x;
cin >> x;
arr.push_back(x);
}
temp(arr, ans, 0);
}
这方法的优点是可以原理易懂,让原数组的元素可以有很多,缺点是,由于根节点到达每个叶节点都是有n步,叶节点共2n个,所以总时间复杂度为n*2n
……显然在n较大的情况下这么大的时间复杂度是不合理的
方法二:通过递归
对于数组S的全部子集,我们可以看做数组中的其中一个元素与剩下的元素集合S-{a}的所有子集再次交乘,以此方法递归至最少子集。
代码:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
//递归函数
//原数组,数组下标,数组大小
vector<vector<int>> temp(const vector<int> &arr,int idx,int n)
{
vector<vector<int>> ans;
//在递归最后添加一个空数组,表示空集
if(idx==n)
{
vector<int> temp;
ans.push_back(temp);
}
else
{
vector<vector<int>> vec = temp(arr, idx + 1, n);
//获取被切割下来的独立元素
int x = arr[idx];
//开始叉乘
for(int i=0;i<vec.size();i++)
{
vector<int> v = vec[i];
//先压入未选入
ans.push_back(v);
//将独立的数压入集合中后再压入
v.push_back(x);
ans.push_back(v);
}
}
return ans;
}
int main()
{
//数字数量
int n;
cin >> n;
vector<int> arr;
//读入
for(int i=0;i<n;i++)
{
int x;
cin >> x;
arr.push_back(x);
}
//制造一个二维数组,存储所有的子集
vector<vector<int>> ans=temp(arr,0,arr.size());
//输出
for(int i=0;i<ans.size();i++)
{
cout << "[";
for(vector<int>::iterator iter=ans[i].begin();iter!=ans[i].end();iter++)
{
cout << *iter << ",";
}
cout << "]" << endl;
}
}
解析:设数组为[1,2,3]
1、在最后一层递归中,我们将一个空集塞了进去
2、对于倒数第二层递归中,我们取出原数组中最后一个数,与后面的集合做叉乘,即{3}X{[]},其得到的结果是{[],[3]};
3、对于倒数第三层,同样的元素2对后面的集合做叉乘,{2}X{[],[3]},结果为{[],[2],[3],[2,3]}
4、最后是第一层,{1}X{[],[2],[3],[2,3]},结果为{[],[1],[2],[2,1],[3],[3,1],[2,3],[2,3,1]}
这个方法有n层递归,每层有一个n规模的循环,所以时间复杂度为n2;空间复杂度也为n2,算是较好的一种算法,个人推荐这种。
方法三:通过逻辑与位运算
前面第一种方法我们用二叉树实现对数组元素的选取,分析这点,对于一个数,选取结果只有两种,我们自然可以联想到二进制,以0,1来视为一个元素是否被选中。
代码:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
//数字数量
int n;
cin >> n;
vector<int> arr;
//读入
for(int i=0;i<n;i++)
{
int x;
cin >> x;
arr.push_back(x);
}
//转换二进制表示中上界
int max = 1 << n;
for(int i=0;i<max;i++)
{
cout << "[" ;
//原数组的元素下标
int idx = 0;
//获取每个数的选取状态
int j = i;
while (j>0)
{
//判断最后一位是否为1,即是否被选中
if(j&1)
{
//将对应选中的数字输出
cout << arr[idx] << ",";
}
//判断下一位
idx++;
j = j >> 1;
}
cout << "]" << endl;
}
}
这个方法最大的好处就是易懂,但时间复杂度原超过n2因为max>>n;同时,由于int的存储限制,原数组的元素个数也无法太多。