求集合的子集

最近看算法和数据结构书,从网上摘取了两种不同的方法:

一。不用递归

http://blog.chinaunix.net/u/15929/showart_156126.html

思路:   
      如何求出一个集合的所有子集呢?方法很简单。我们知道一个集合的子集是这个集合元素的2的幂,那么就可以用一个n位的二进制来模拟它。比如说有这么一个集合{1,2,3,4,5,6},那么再构造出这样的一个数组a[6]={0,0,0,0,0,0}.其中a[6] = {0,0,0,0,0,0}对应空集,a[6] = {1,0,0,0,0,0}对应集合{1},以此类推直到a[6] = {1,1,1,1,1,1};
 
代码实现:
#include<iostream>
#include<math.h>
using namespace std;
 
void change(bool*ptr, int len);
 
int main()
{
 char aa[]={'1','2','3','4','5','6'};
 int n=sizeof(aa)/sizeof(aa[0]); 
 bool bo[]={0,0,0,0,0,0};
 
 int pw=pow(2,n);
 for(int i = 0; i < pw; i++)
 {
  cout<<'{';
  for(int k = 0; k < n; k++)
  {
   if(bo[k])
   {
    cout<<aa[k]<<' ';
   }
  }
  cout<<'}'<<endl;
  change(bo,n);
 }
 return 0;
}

void change(bool*ptr, int len)
{
 for(int i = 0; i < len; i++)
 {
  if(*ptr)
  {
   *ptr = 0;
  }
  else
  {
   *ptr = 1;
   break;
  }
  ptr++;
 }
}

 

http://hi.baidu.com/walkpigs/blog/item/3f3ad345c997613e8794733e.html

    一般来说,求集合自己的一个很实用的方法是用1代表该位置的元素存在于子集中,利用0表示不存在于子集中。按照这个思路只要定义一个变量,让它从0递增到2^n-1即可,每次递增把它除以2求余数,并且将余数存储在一个大小为n的bool型数组中,在逐次检验就可以了。一下是用另一种方法实现的:

/*编写递归函数,求n个元素集合的所有子集。
不妨令集合元素为小写字母,原集合为{'a', 'b', …, 'a' + n - 1}。
输入:input.txt,仅包含整数n(1-26)。
输出:若输入合法,输出集合的所有子集;否则输出"WRONG"。
子集输出格式为每行一个子集,空集用空行表示,非空集合每个元素间用一个空格间隔,
最后一个元素之后不能有空格。例如,对n=3,可能的输出为:
―――――――――――

a
a b
a b c
a c
b
b c
c

――――――――――――-*/
#include<iostream.h>

void move(char a[], int m, int n)
{
     char tmp=a[m];
     for(int i=m;i<n-1;i++)
         a[i]=a[i+1];
     a[n-1]=tmp;
}

void faction(char a[],int k,int m)
{
     if(k==m)
         for(int j=0;j<2; j++,k--)
   {
    for(int i=0; i<k; i++)
     cout<<a[i];
    cout<<endl;
         }
     else
         for(int i=0; i<m-k; i++)
   {
    faction(a,k+1,m-i);
             move(a,k,m);
         }
}

void main()
{
int m,k=0;                      //m为输入的整数,即集合个数
char char_a='a',a[26];       
cout<<"请输入一个(1—26)的整数:";
cin>>m;
for(int i=0;i<m;i++,char_a++)
   a[i]=char_a;
cout<<"----------------------------"<<endl;
if(m<1)
   cout<<"WRONG!!!"<<endl;
else
{
   if(m>26)
    cout<<"WRONG!!!"<<endl;
   else
    faction(a,k,m);
}
cout<<"-----------------------------"<<endl;
}

 

二。递归算法
<think>嗯,用户让我用递归的方法来写集合子集算法。首先,我得回忆一下集合子集是怎么生成的。比如,一个集合{a, b, c}的子集包括空集、单个元素、两个元素和整个集合本身。总共有2^n个子集,n是元素个数。 递归的话,通常是把大问题分解成小问题。那对于子集生成,假设我已经有了一个更小集合子集,然后如何扩展到大集合呢?比如,如果我已经有了集合S' = S[1:]的所有子集,那么对于每个子集,我可以选择是否加入当前元素S[0]。这样,每次递归处理剩下的元素,然后把当前元素加到已有的子集里。 举个例子,假设现在集合是[1,2]。递归到最底层,当集合为空时,子集只有空集。然后返回上一层,处理元素2,这时候已有的子集是空集,加上2就得到包含2的子集。所以总共有空集和{2}。再上一层处理元素1,把每个已有的子集分成两种情况,不包含1和包含1,这样原来的空集变成空集和{1},而原来的{2}变成{2}和{1,2}。这样就得到了所有四个子集。 那具体怎么实现呢?递归函数可能需要接收当前的元素列表,然后逐步减少元素数量。比如,每次取第一个元素,剩下的元素递归处理。然后将当前元素添加到所有已有的子集中,生成新的子集,再合并原来的子集和新的子集。 比如,伪代码可能像这样: def subsets(s): if not s: return [[]] rest = subsets(s[1:]) return rest + [[s[0]] + subset for subset in rest] 不过要注意边界条件,当输入为空时返回包含空集的列表。每次递归调用处理剩下的元素,然后合并不包含当前元素和包含当前元素的情况。 那这个思路是否正确呢?测试一下,比如输入[1,2,3]。第一次调用处理[1,2,3],拆分成1和剩下的[2,3]。递归到处理[2,3],再拆分成2和[3]。递归到处理[3],拆分成3和空。空时返回[[]]。处理3的时候,rest是[[]],然后加上[3]+每个子集,即[3],所以结果是[[], [3]]。回到处理2的层级,rest是[[], [3]],所以返回[[], [3]] + [[2], [2,3]],得到四个子集。再回到处理1的层级,rest是这四个子集,加上每个加上1的情况,总共有八个子集。这样看来是对的。 那代码实现的时候需要注意列表的引用问题,比如在Python中,要确保每次添加的是新的列表,而不是修改原来的。所以在生成新子集的时候,要用[ [s[0]] + subset for subset in rest ],这里每次都会创建新的列表,避免修改原来的。 另外,用户可能需要具体的代码示例。所以最后应该给出Python的实现,并解释递归的过程。比如,函数接收一个列表,返回所有子集的列表。每个子集用列表表示。 总结一下步骤: 1. 基准情况:当集合为空时,子集只有空集。 2. 递归步骤:处理剩余元素,生成子集。 3. 将当前元素添加到所有剩余子集中,合并得到新的子集。 4. 返回合并后的结果。 这样就能生成所有可能的子集了。需要注意元素顺序是否重要,但子集元素顺序无关紧要,所以无论怎么组合都是正确的。不过代码中的顺序可能取决于输入列表的顺序,但作为集合来说,子集的内容正确即可。</think>我们可以使用递归方法逐步生成集合的所有子集。以下是分步解释和示例代码: --- ### **递归思路分析** 1. **基准情况** 当集合为空时,唯一子集是空集:$$ \text{subsets}(\emptyset) = [\emptyset] $$ 2. **递归分解** 假设已知集合前 $n-1$ 个元素的所有子集,则第 $n$ 个元素有两种选择: - **不加入**当前元素:保留原有子集 - **加入**当前元素:将当前元素添加到所有已有子集中 数学表达为: $$ \text{subsets}(S) = \text{subsets}(S[1:]) \cup \{ \{S[0]\} \cup \text{subset} \mid \text{subset} \in \text{subsets}(S[1:]) \} $$ --- ### **Python代码实现** ```python def get_subsets(nums): if not nums: # 基准情况:空集的子集是空集 return [[]] # 递归获取剩余元素子集 rest_subsets = get_subsets(nums[1:]) # 当前元素可加或不加 current = nums[0] new_subsets = [[current] + subset for subset in rest_subsets] return rest_subsets + new_subsets # 示例 nums = [1, 2, 3] print(get_subsets(nums)) # 输出:[[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]] ``` --- ### **递归过程演示** 以集合 $[1, 2]$ 为例: 1. **初始调用**:`get_subsets([1, 2])` - 分解为 `current=1`, 递归处理 `[2]` 2. **递归调用**:`get_subsets([2])` - 分解为 `current=2`, 递归处理 `[]` 3. **基准情况**:`get_subsets([])` 返回 `[[]]` 4. **向上返回**: - `rest_subsets = [[]]`(对应不包含 `2` 的子集) - `new_subsets = [[2] + []] = [[2]]`(对应包含 `2` 的子集) - 合并后得到 `[[], [2]]` 5. **继续返回**: - `rest_subsets = [[], [2]]`(不包含 `1` 的子集) - `new_subsets = [[1] + [], [1] + [2]] = [[1], [1, 2]]`(包含 `1` 的子集) - 合并后得到 `[[], [2], [1], [1, 2]]` --- ### **复杂度分析** - **时间复杂度**:$O(2^n)$,每个元素有“选”或“不选”两种选择,共生成 $2^n$ 个子集 - **空间复杂度**:$O(n \cdot 2^n)$,递归栈深度为 $n$,结果存储需要 $2^n$ 个列表 通过递归逐步合并子集,可以高效生成所有可能的子集
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值