标签:des blog http ar 使用 for sp strong 数据
给出一个数组,比如 {1,2,3,4},请求出数组的所有子集(1)?给出一个存在重复元素的数组,比如 {1,2,2,3,4},请求出数组的所有子集(2)?请求出所有子集并且不允许出现重复子集(3)?
/// <summary>
/// 列表深拷贝
/// </summary>
public static List<T> Clone<T>(this List<T> source)
{
List<T> newList = new List<T>(source.Count);
foreach (var item in source)
{
newList.Add(item);
}
return newList;
}
/// <summary>
/// 比较两个列表是否等同,不考虑列表元素的顺序
/// 比如 {1,2,3,4}与{2,1,4,3}比较返回true. {2,3}与{1,2}比较返回false
/// </summary>
public static bool EqualsList<T>(this List<T> source, List<T> dest)
where T : IEquatable<T>
{
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;
}
/// <summary>
/// 目标列表是否包含于源列表集合中,不考虑列表元素顺序
/// </summary>
public static bool ContainList<T>(this List<List<T>> source, List<T> destList)
where T : IEquatable<T>
{
for (int i = 0; i < source.Count; i++)
{
List<T> sourceList = source[i];
if (sourceList.EqualsList(destList)) return true;
}
return false;
}
/// <summary>
/// 获取集合的所有子集
/// </summary>
/// <param name="source">源数组集合</param>
/// <param name="allowRepeat">是否允许重复子集</param>
/// <param name="rightSplitLength">可选参数(默认1),初始分割长度(数组右侧)</param>
/// <returns></returns>
public static List<List<T>> GetSubList<T>(T[] source, bool allowRepeat, int rightSplitLength = 1)
where T : IEquatable<T>
{
// 返回子集集合
List<List<T>> rSet = new List<List<T>>();
// 数组长度为length
int length = source.Length;
// 递归基准情形,当数组长度为1时,子集为数组本身
if (length == 1)
{
rSet.Add(source.ToList<T>());
}
else
{
// 左侧数组
T[] leftArray = source.Where((r, index) => index < length - rightSplitLength).ToArray();
// 右侧数组
T[] rightArray = source.Where((r, index) => index >= length - rightSplitLength).ToArray();
// 递归计算左侧数组子集集合
List<List<T>> leftSubSet = GetSubList(leftArray, allowRepeat);
// 递归计算右侧数组子集集合
List<List<T>> 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<List<T>> combineSubSet = new List<List<T>>();
foreach (var leftSubList in leftSubSet)
{
foreach (var rightSubList in rightSubSet)
{
// 左右侧集合项交叉合并
List<T> combineList = new List<T>();
combineList.AddRange(leftSubList.Clone<T>());
combineList.AddRange(rightSubList.Clone<T>());
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<List<T>> GetSubList2<T>(T[] source)
where T : IEquatable<T>
{
List<List<T>> rList = new List<List<T>>();
for (int i = 0; i < source.Length; i++)
{
List<List<T>> combineList = new List<List<T>>();
foreach (var list in rList)
{
List<T> tmpList = list.Clone();
tmpList.Add(source[i]);
combineList.Add(tmpList);
}
rList.AddRange(combineList);
rList.Add(new List<T>() { 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>(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。请设计一个高效算法实现这个需求
使用上述方法对此方法进行求解,代码如下:
/// <summary>
/// 获取集合的所有子集,要求集合内元素相加之和为sum
/// </summary>
public static List<List<int>> GetSubList4Sum(List<int> source, int sum)
{
// 返回子集集合
List<List<int>> rSet = new List<List<int>>();
// 数组长度为length
int length = source.Count;
for (int i = length - 1; i >= 0; i--)
{
// 选取右侧数据
int rightNum = source[i];
// 获取左侧集合
List<int> leftList = source.Where((r, index) => index < i).ToList();
if (rightNum > sum)
{
continue;
}
else if (rightNum == sum)
{
List<int> rightList = new List<int>() { rightNum };
// 避免重复 2
if (!rSet.ContainList(rightList))
{
rSet.Add(rightList);
}
}
else
{
List<List<int>> leftSet = GetSubList4Sum(leftList, sum - rightNum);
foreach (var leftAvail in leftSet)
{
List<int> combineList = new List<int>();
combineList.AddRange(leftAvail);
combineList.Add(rightNum);
if (!rSet.ContainList(combineList))
{
rSet.Add(combineList);
}
}
}
}
return rSet;
}
其实,这些题目是以前做过的题目,并且以前还发过博客,最近突然想把那些做过的题目找回来,重新做了一遍,温故而知新
标签:des blog http ar 使用 for sp strong 数据
原文地址:http://www.cnblogs.com/fecktty2013/p/4069875.html