博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
编程拾趣--集合子集问题
阅读量:6693 次
发布时间:2019-06-25

本文共 6033 字,大约阅读时间需要 20 分钟。

问题

给出一个数组,比如 {1,2,3,4},请求出数组的所有子集(1)?给出一个存在重复元素的数组,比如 {1,2,2,3,4},请求出数组的所有子集(2)?请求出所有子集并且不允许出现重复子集(3)?

准备方法

/// /// 列表深拷贝/// public static List
Clone
(this List
source){ List
newList = new List
(source.Count); foreach (var item in source) { newList.Add(item); } return newList;}
/// /// 比较两个列表是否等同,不考虑列表元素的顺序/// 比如 {1,2,3,4}与{2,1,4,3}比较返回true. {2,3}与{1,2}比较返回false/// public static bool EqualsList
(this List
source, List
dest) where T : IEquatable
{ if (source.Count != dest.Count) return false; for (int i = 0; i < source.Count; i++) { if (source.Count(item => item.Equals(source[i])) != dest.Count(item => item.Equals(source[i]))) return false; } return true;}
/// /// 目标列表是否包含于源列表集合中,不考虑列表元素顺序/// public static bool ContainList
(this List
> source, List
destList) where T : IEquatable
{ for (int i = 0; i < source.Count; i++) { List
sourceList = source[i]; if (sourceList.EqualsList(destList)) return true; } return false;}

递归解法

/// /// 获取集合的所有子集/// /// 源数组集合/// 是否允许重复子集/// 可选参数(默认1),初始分割长度(数组右侧)/// 
public static List
> GetSubList
(T[] source, bool allowRepeat, int rightSplitLength = 1) where T : IEquatable
{ // 返回子集集合 List
> rSet = new List
>(); // 数组长度为length int length = source.Length; // 递归基准情形,当数组长度为1时,子集为数组本身 if (length == 1) { rSet.Add(source.ToList
()); } else { // 左侧数组 T[] leftArray = source.Where((r, index) => index < length - rightSplitLength).ToArray(); // 右侧数组 T[] rightArray = source.Where((r, index) => index >= length - rightSplitLength).ToArray(); // 递归计算左侧数组子集集合 List
> leftSubSet = GetSubList(leftArray, allowRepeat); // 递归计算右侧数组子集集合 List
> rightSubSet = GetSubList(rightArray, allowRepeat); if (allowRepeat) { // A.左侧子集作为源数组子集 允许重复 rSet.AddRange(leftSubSet); // B.右侧子集作为源数组子集 允许重复 rSet.AddRange(rightSubSet); } else { // A.左侧子集作为源数组子集 不允许重复 foreach (var lefttemp in leftSubSet) { if (!rSet.ContainList(lefttemp)) { rSet.Add(lefttemp); } } // B.右侧子集作为源数组子集 不允许重复 foreach (var righttemp in rightSubSet) { if (!rSet.ContainList(righttemp)) { rSet.Add(righttemp); } } } // 左右侧子集合并集 List
> combineSubSet = new List
>(); foreach (var leftSubList in leftSubSet) { foreach (var rightSubList in rightSubSet) { // 左右侧集合项交叉合并 List
combineList = new List
(); combineList.AddRange(leftSubList.Clone
()); combineList.AddRange(rightSubList.Clone
()); combineSubSet.Add(combineList); } } if (allowRepeat) { // C.左右侧子集合并集,形成源数组子集 允许重复 rSet.AddRange(combineSubSet); } else { // C.左右侧子集合并集,形成源数组子集 不允许重复 foreach (var combinetemp in combineSubSet) { if (!rSet.ContainList(combinetemp)) { rSet.Add(combinetemp); } } } } return rSet;}

测试结果

输入1){1,2,3,4}结果:

输入2){1,2,2,3,4}允许重复,结果:

输入3){1,2,2,3,4} 不允许重复,结果:

非递归解法

{1,2,3,,,,N-1,N} 集合子集表示为f(N)

将数组进行拆分

f(N-1) = {1,2,3,,,,N-1} ,f(1) = {N}

很显然,问题已经拆分为具有相同情况的子问题

{1} => {1}

{1,2} => {1},{2} => {1}+{2}+{1,2}

很容易推出 f(N) 的子集为 f(N-1) + f(1) + COMBINE(f(N-1),f(1))(取笛卡尔并集)

代码如下(此处去掉了重复子集判断):

public static List
> GetSubList2
(T[] source) where T : IEquatable
{ List
> rList = new List
>(); for (int i = 0; i < source.Length; i++) { List
> combineList = new List
>(); foreach (var list in rList) { List
tmpList = list.Clone(); tmpList.Add(source[i]); combineList.Add(tmpList); } rList.AddRange(combineList); rList.Add(new List
() { source[i] }); } return rList;}

另一种非递归解法

根据数学知识,很容易知道,N个元素的子集个数为2^N - 1,时间复杂度是指数级的,我们可以联想到位操作(比如位移就是2的指数级操作),我们用0和1为下标,标识每一个元素是否出现在子集中,因此可以如此标识子集 (此处比如N为5)

1(00001),2(00010),3(00011),,,,,31(11111)

我们可以发现,从1到2^N-1的十进制循环数据中,每一个数据的二进制位对应的下标的数据集合就是所有子集

代码如下:

public static void PrintSubList
(T[] source){ int length = source.Length; int loopCount = 1 << length; int subCount = 0; for (int i = 1; i < loopCount; i++) { int takeNumber = i; for (int bitIndex = 0; bitIndex < length; bitIndex++) { if ((takeNumber & 1) == 1) { Console.Write(" {0} ", source[bitIndex]); } takeNumber >>= 1; } Console.WriteLine(); subCount++; } Console.WriteLine("共有 {0} 个子集.", subCount);}

延伸题目

给定一个数t,以及n个整数,在这n个整数中找到相加之和为t的所有组合,例如t = 4,n = 6,这6个数为[4, 3, 2, 2, 1, 1],这样输出就有4个不同的组合,它们的相加之和为4:4, 3+1, 2+2, and 2+1+1。请设计一个高效算法实现这个需求

使用上述方法对此方法进行求解,代码如下:

///  /// 获取集合的所有子集,要求集合内元素相加之和为sum ///  public static List
> GetSubList4Sum(List
source, int sum){ // 返回子集集合 List
> rSet = new List
>(); // 数组长度为length int length = source.Count; for (int i = length - 1; i >= 0; i--) { // 选取右侧数据 int rightNum = source[i]; // 获取左侧集合 List
leftList = source.Where((r, index) => index < i).ToList(); if (rightNum > sum) { continue; } else if (rightNum == sum) { List
rightList = new List
() { rightNum }; // 避免重复 2 if (!rSet.ContainList(rightList)) { rSet.Add(rightList); } } else { List
> leftSet = GetSubList4Sum(leftList, sum - rightNum); foreach (var leftAvail in leftSet) { List
combineList = new List
(); combineList.AddRange(leftAvail); combineList.Add(rightNum); if (!rSet.ContainList(combineList)) { rSet.Add(combineList); } } } } return rSet;}

结语

其实,这些题目是以前做过的题目,并且以前还发过博客,最近突然想把那些做过的题目找回来,重新做了一遍,温故而知新

转载于:https://www.cnblogs.com/fecktty2013/p/4069875.html

你可能感兴趣的文章
jquery mobile 设置设备适配
查看>>
redis使用总结-redis命令总结
查看>>
创业浪潮:春天蓬勃而来
查看>>
阿里云Linux安装软件镜像源
查看>>
阿里云对象存储OSS支持版本管理特性
查看>>
用python 访问redis的几种常用方式
查看>>
我的友情链接
查看>>
Linux Shell 基本概念及编程(5)
查看>>
RDBMS DBMS MS DB
查看>>
我的友情链接
查看>>
svn 实践
查看>>
在 PowerShell 中使用 SQL Server (3)
查看>>
我的友情链接
查看>>
CSS元素定位
查看>>
质量时代——“Jolt大奖精选丛书”有奖征文
查看>>
DNS服务器维护命令
查看>>
六、用户与权限
查看>>
面向机器学习数据平台的设计与搭建
查看>>
centos6.7 编译安装mysql-5.6.27
查看>>
spring cloud 整合zpkin问题
查看>>