两种求集合全部子集的方法

如果我们有一个求集合的所有子集(包括集合自身)的需求,即有一个集合s,包括两个元素 <a,b>,则其所有的子集为<a,ab,b>.

不难求得,子集个数sn与原集合元素个数n之间的关系为:sn=2^n-1。

本文分别讲述两种实现方法:

一:位图法:

1)构造一个和集合一样大小的数组A,分别与集合中的某个元素相应,数组A中的元素仅仅有两种状态:“1”和“0”,分别代表每次子集输出中集合中相应元素是否要输出。这样数组A能够看作是原集合的一个标记位图。

2)数组A模拟整数“加一”的操作,每“加一”之后,就将原集合中全部与数组A中值为“1”的相相应的元素输出。

设原集合为<a,b,c,d>。数组A的某次“加一”后的状态为[1,0,1,1],则本次输出的子集为<a,c,d>。

详细代码例如以下:

/*使用非递归的思想 假设有一个数组  大小为n
  那么就使用n 位的二进制 假设对应的位为1 那么就输出这个位
  假设对应的位为0 那么就不输出这个位*/

/*
   使用位图的思想 构造一个位集合 大小和数组大小一样,假设位图中对应的
   位为1,表示能够输出这个数组中的元素 假设位图中对应位为0 表示数组中对应位不输出
   这里模拟位图使用的数组 ,这里的重点是模拟数组加1的操作
 */

/*使用数组模拟位图加1的操作  数组能够一直加1  直到数组内全部元素都是1

  函数返回值为bool 数组初始化最高位为1*/
#define MAX_LEN 10
void bitmap(char str[],const int n)
{
	bitset<MAX_LEN> count;
	wang.set();
	int i=0;
	unsigned long value = wang.to_ulong();
	cout<<wang<<" "<<value<<endl;
	int count=0;
	while(value)
	{
		bitset<MAX_LEN> bit(value--);

		for(i=0;i<bit.size();i++)
			if(bit[i])
				cout<<str[i];
		cout<<endl;
		count++;
	}
	cout<<count<<endl;
}

3)时间复杂度:O(n*2^n)。事实上,在遍历输出子集的过程中。能够对程序做进一步的优化。

比如。在第m次迭代中。仅仅须要遍历前k个元素,k=log2(m)+1。这样,不考虑数组模拟"加一"操作的话,总遍历次数为Sn=(n-2)*2^n+2,n>=2;Sn=1,n=1。尽管复杂度不变,但总执行时间会降低。

4)空间复杂度:该方法每次迭代都是独立进行,与上次迭代的结果没有不论什么关系。因此每次输出子集之后内存都能够被反复利用。

仅仅须要一个与原集合相同大小的数组。空间复杂度为O(n)。

二:递归迭代法:

1)採用递归迭代。详细步骤例如以下,

设。原始集合s=<a,b,c,d>,子集结果为r:

第一次迭代:

r=<a>

第二次迭代:

r=<a ab b>

第三次迭代:

r=<a ab b ac abc bc c>

第四次迭代:

r=<a ab b ac abc bc c ad abd bd acd abcd bcd cd d>

每次迭代,都是上一次迭代的结果+上次迭代结果中每一个元素都加上当前迭代的元素+当前迭代的元素。

详细代码例如以下:

/*上述方法不可用 明确递归的思想 以下每次都是输出back中的字符就可以
  这次输出的子集就是上次输出的子集 +这次迭代的元素 + 这次迭代的元素的本身*/
#if 1
void print(char* str)
{
	/*使用两个数组。一个记录上次迭代的结果
	  一个记录这次须要输出的结果
	  vec记录的是下次迭代须要參考的子集
	  back记录的是參考vec迭代以后生成新的子集
	 */
	int count=0;
	vector<char> vec;
	vector<char> back;
	int j;
	for(int i=0;i<strlen(str);i++)
	{
		if(i == 0)
		{
			vec.push_back(str[i]);
			vec.push_back(',');
			back=vec;
		}
		else
		{
			for(j=0;j<back.size();j++)
				if(back[j] == ',')
				{
					back.insert(back.begin() +j,str[i]);
					j++;
				}
			back.push_back(str[i]);
			back.push_back(',');
		}
		for(j=0;j<back.size();j++)
		{
			if(back[j] == ',')
			{
				printf("\r\n");
				count++;
			}
			else
				printf("%c",back[j]);
			if(i)
				vec.push_back(back[j]);

		}
		back=vec;
	}
	printf("sub_set count is %d \r\n",count);
}
#endif

2)时间复杂度

依据上述过程,不难求的。第k次迭代的迭代次数为:2^k-1。

n>=k>=1。迭代n次,总的遍历次数为:2^(n+1)-(2+n),n>=1。

则时间复杂都为O(2^n)。

3)空间复杂度

因为该算法。下一次迭代过程都须要上一次迭代的结果,而最后一次迭代之后就没有下一次了。

因此如果原始集合有n个元素。则在迭代过程中,总共须要保存的子集个数为2^(n-1)-1,n>=1。

但须要注意的是,这里之考虑了子集的个数,每一个子集元素的长度都视为1,这点要注意。

总结:

递归是非常耗时的。由于是递归,在第一种方法时,使用了C++中的bitset,这种方法效率非常高,在第二个方法中,使用两个向量的目的是,一个向量记录了这次迭代须要输出的集合,一个向量是为了这次迭代须要參考上次输出的情况。

时间: 2024-10-11 13:18:30

两种求集合全部子集的方法的相关文章

两种求集合所有子集的方法

假设我们有一个求集合的全部子集(包含集合自身)的需求,即有一个集合s,包含两个元素 <a,b>,则其全部的子集为<a,ab,b>. 不难求得,子集个数sn与原集合元素个数n之间的关系为:sn=2^n-1. 本文分别讲述两种实现方法: 一:位图法: 1)构造一个和集合一样大小的数组A,分别与集合中的某个元素对应,数组A中的元素只有两种状态:"1"和"0",分别代表每次子集输出中集合中对应元素是否要输出,这样数组A可以看作是原集合的一个标记位图.

【Leetcode】78. Subsets(求集合的子集问题)

78. Subsets(求集合的子集问题) [分析]:求集合的所有子集问题.题目要求子集中元素非递减序排列,因此我们先要对原来的集合进行排序.原集合中每一个元素在子集中有两种状态:要么存在.要么不存在.这样构造子集的过程中每个元素就有两种选择方法:选择.不选择,因此可以构造一颗二叉树,例如对于例子中给的集合[1,2,3],构造的二叉树如下(左子树表示选择该层处理的元素,右子树不选择),最后得到的叶子节点就是子集:{ 链接 } 1 class Solution 2 { 3 private: 4 v

求集合的子集

package com.xsz.demo; /**     * @author cwqi    * @date 2014-11-19 下午9:55:31    */ public class SubSet { /**  * 求集合的子集  * @param args  */ public static void main(String[] args) { int array[] = {1,2,3}; SubSet.getSubSet(array); } private static void g

聊聊JVM(三)两种计算Java对象大小的方法

这篇说说如何计算Java对象大小的方法.之前在聊聊高并发(四)Java对象的表示模型和运行时内存表示 这篇中已经说了Java对象的内存表示模型是Oop-Klass模型. 普通对象的结构如下,按64位机器的长度计算 1. 对象头(_mark), 8个字节 2. Oop指针,如果是32G内存以下的,默认开启对象指针压缩,4个字节 3. 数据区 4.Padding(内存对齐),按照8的倍数对齐 数组对象结构是 1. 对象头(_mark), 8个字节 2. Oop指针,如果是32G内存以下的,默认开启对

两种求幂集的方法

今天这一题是求幂集.小学数学都忘得差不多了- 幂集是什么? 幂集(power set)是一个集合的所有子集.比如[1, 2, 3]的幂集就是: [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]] 不过这道题有一个额外的要求: 在求幂集时要以"深度优先搜索"(depth-first search)的形式进行.也就是说,对于集合的每个元素,都有"取"和"舍"两种选择:在深度优先时,我们优先选择

【智能指针的延伸】两种定制删除器的实现方法

所谓定制删除器,就是向目标模板提供一个可以自由选择析构的接口,这样做的好处就是可以使智能指针模板不再只能单独管理内存,我们还可以用它管理文件指针之类的东西.其实现方法有两种,这里我们以share指针为例.   1.传递一个类的模板参数并给出缺省值,模板中将该类设定为成员变量,通过该类(删除类)的实例化,调用类中所存的删除方法进行删除. 代码如下: #include<iostream> template <class T> struct Del { void operator ()(

两种设置WebLogic启动内存的方法

我们经常在使用WebLoigc部署应用程序后,发现程序运行速度并不是很快,遇到这种情况我们可以尝试调整启动时分配的内存,设置方法有两种:    一.在..\domain\startWebLoigc.***文件中设置    在startWebLogic.bat或startWebLogic.sh中找到以下内容,在其下方添加需要设置的内存 Java代码   echo *************************************************** echo *  To start

两种改变matlab默认目录的方法

修改matlab的默认打开路径,即自己想要的工作目录,省去自己改来改去的麻烦,怎样才能设置成自己想要的目录呢,如下两种方法,本人比较喜欢第一种: 1.Matlab的快捷方式 ->  属性 ->  起始位置 -> 更改为所希望的默认目录 2.找到安装目录下Matlab里的toolbox\local\matlabrc.m ,打开,在文件最后新增cd yourpath(你要设置的工作目录),保存即可.

php 分享两种给图片加水印的方法

本文章向码农们介绍 php 给图片加水印的两种方法,感兴趣的码农可以参考一下本文章的源代码. 方法一:PHP最简单的加水印方法 <?php // http://www.manongjc.com $img = imagecreatefromjpeg($filename); $logo = imagecreatefromjpeg($filename); /*imagecraetefromjpeg-由文件或URL创建一个新图像 imagecreatefromjpeg(string $filename)