2019独角兽企业重金招聘Python工程师标准>>>
问题描述:如何将一个具有n个元素的集合N划分为k个子集N1…Nk,其中1≤k≤n,使得任意两个子集Ni∩Nj={∅},1<=i,j(i≠j)<=k,且N1∪...∪Nk=N,计算出每个子集的最优结果R1…Rk,使得F(R1…Rk)为最优的结果。
这个问题可以分成3步解决:
- 求出集合所有子集
- 求出所有子集N1…Nk组合
- 遍历子集的所有组合,求出最优的F(R1…Rk)值
问题一:子集表达
用二进制表达式来表达集合,0代表该位置的元素不存在,1代表该位置的元素存在,其中二进制从右边开始数第n位代表集合从左边开始数第n位,依次类推。
对于集合 {1, 2},表示如下
子集:{ } 没有元素 00
{1} 元素1位于集合中左边开始数第一位,那么用二进制表示就是从右边开始数第一位用1表示, 二进制表示为 01
{2} 元素2位于集合中左边开始数第二位,那么用二进制表示就是从右边开始数第二位用1表示, 二进制表示为 10
{1, 2} 元素1位于集合中左边开始数第一位,那么用二进制表示就是从右边开始数第一位用1表示 ,元素2位于集合中左边开始数第二位,那么用二进制表示就是从右边开始数第二位用1表示, 二进制表示为11
从上面的规律可以看出子集的表示就是从 0…0 ~ 1…1 的表示,
转为数字就是0~2^n-1
根据子集的数值特点我们就可以定义一个长度为2^n的数组来表示子集的最优结果。数组的下标就是子集的二进制表示。
问题二:集合拆分
我们可以使用下面的方式来计算子集组合:
如集合{1,2,3}
当 k=1 时,那么只能划分为{1,2,3}
当 k=2 时,也就是需要划分成两个子集,可以划分为 {1, 2} {3} 或者 {1} {2, 3} 或者 {1, 3} {2}
当 k=3 时,需要划分成3个子集,{1} {2} {3}
注:其中1<=k<=n,当k=1或者k=n时,子集的划分都只有一种情况
其实每加一个元素,就是在原来的划分情况为新元素找到合适的位置放进去。
例如对于集合{1,2,3},现在我们加入第4个元素,值为4,那么应该怎么弄呢?
当 k = 1 时,我们只能找到{1,2,3}划分为1个子集的情况,然后将元素4插进去,得 {1,2,3,4}
当 k = 2时,我们有两种情况可以组成:
①找到{1,2,3}划分为k-1=1个元素的子集组合{1,2,3},然后元素4子集作为一个子集,即{1,2,3} {4}
②找到{1,2,3}划分为k=2个元素的子集组合, {1, 2} {3} 或者 {1} {2, 3} 或者 {1, 3} {2},然后逐个子集插入元素4,形成新的组合
对于{1,2} {3} 将元素4插入{1,2} => {1,2,4} {3}, 将元素插入{3} => {1,2} {3,4}
对于{1} {2,3} 将元素插入{1} => {1,4} {2,3} , 将元素插入{2,3} => {1} {2,3,4}
对于{1,3} {2} 将元素插入{1,3} => {1,3,4} {2},将元素插入{2} => {1,3} {2, 4}
当 k = 3时,步骤跟k=2时一样
当 k = 4时,步骤跟k=2时的①一样,由于{1,2,3}最多只有3个元素,所以步骤②不存在
设F(n, m)表示将一个各数为n的集合拆分成有m个子集表示的情况,其中1≤m≤n,
可以推导出F(n, m) = F(n-1, m-1) + m * F(n-1,m)
其中:F(n, 1) = 1, F(n, n) = 1
问题三:求最优结果
自行定义F的计算方法即可。
下面是一个实现例子。
1、Strategy
/**
* 策略接口,该接口主要从集合中获取策略算法结果,T为元素类型,R为结果类型
*
* @param
* @param
*/
public interface Strategy {
/**获取平均值**/
Strategy AVG = (datas) -> (int)datas.stream().mapToInt(e -> (e != null ? e.intValue() : 0)).average().getAsDouble();
/**求最大值**/
Strategy MAX = (datas) -> datas.stream().max((e1, e2) -> e1.compareTo(e2)).get();
/**
* 策略器的唯一ID表示,用于判断是否为相同的策略器
* @return
*/
default String getId() {
return Integer.toBinaryString(this.hashCode());
}
/**
* 这里给定一个集合datas,计算该集合的策略值并返回
* @param datas
* @return
*/
R get(Collection datas);
}
2、StrategyNode
/**
* 策略节点
*
* @param 原始的数据来源类型
* @param 为该节点所有策略器Strategy返回的结果类型
*/
public class StrategyNode {
final T value;//集合中的原始元素
final Set> strategies = new HashSet<>();//元素拥有的策略
public StrategyNode(T value, Strategy[] strategies) {
Objects.requireNonNull(value);
this.value = value;
if (strategies != null && strategies.length > 0) {
for (Strategy st : strategies) {
this.strategies.add(st);
}
}
}
/**
* 判断集合中元素是否满足某种策略,通过策略Id匹配
* @param strategy
* @return
*/
public boolean existStrategy(Strategy strategy) {
if (strategy != null && !this.strategies.isEmpty()) {
for (Strategy s : this.strategies) {
if (s.getId().equals(strategy.getId())) {
return true;
}
}
}
return false;
}
}
3、StrategySelector, T, R>
/**
* 策略选择,从所有策略中选择最优的策略
* @param
* @param
* @param
*/
interface StrategySelector, T, R> {
/****/
StrategySelector, Integer, Integer> MAX = (strategies, values) -> {
Set> rtSet = new HashSet<>();
Number rt = null;
for (Strategy st : strategies) {
Integer r = st.get(values);
if (r != null && (rt == null || r.intValue() >= rt.intValue())) {
if (!r.equals(rt)) {
rtSet.clear();
}
rt = r;
rtSet.add(st);
}
}
return rtSet;
};
/**
* 返回最优策略,如果有多个,返回多个
*
* 给定集合values,并且共有的策略strategies,从strategies取出使得结果最优的strategies集合并返回
*
* @param strategies 策略
* @param values 集合
* @return
*/
Set get(Set strategies, Collection values);
}
4、StrategyCalculate
/**
* 对子集组合进行运算
*
* @param 子集策略结果的集合元素
* @param 子集计算后得到的数据类型
*/
interface StrategyCalculate {
StrategyCalculate SUM = (datas) -> datas.stream().mapToInt(e -> e != null ? e.intValue() : 0).sum();
StrategyCalculate AVG = (datas) -> (int) datas.stream().mapToDouble(e -> e != null ? e.doubleValue() : 0.0D).average().getAsDouble();
StrategyCalculate MAX = (datas) -> datas.stream().max((e1, e2) -> e1.compareTo(e2)).get();
/**
* 获取最优答案
*
* R 为集合N 通过 StrategySelector 选择最优的策略 Strategy 所计算出的值
* 这里是对每个子集的最优策略结果进行合并运算
*
* @return
*/
K get(Collection datas);
}
5、StrategyComparator
/**
* 组合结果比较器
* @param
*/
interface StrategyComparator {
StrategyComparator GREAT = (r1, r2) -> r1.compareTo(r2) >= 0;
/**
* K 为 StrategyCalculate 计算出的结果,这里对结果进行比较取优
* 判断k1 是否优于 k2
* @param k1
* @param k2
* @return
*/
boolean better(K k1, K k2);
}
6、SubSet
/**
* 表示子集
*
* @param
* @param
*/
public class SubSet {
/**子集元素**/
private List elements;
/**子集使用的策略算法**/
private Set> strategies;
/**子集的策略计算结果**/
private R result;
public SubSet(List elements, Set> strategies, R result) {
this.elements = elements;
this.strategies = strategies;
this.result = result;
}
public List getElements() {
return elements;
}
public void setElements(List elements) {
this.elements = elements;
}
public Set> getStrategies() {
return strategies;
}
public void setStrategies(Set> strategies) {
this.strategies = strategies;
}
public R getResult() {
return result;
}
public void setResult(R result) {
this.result = result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("{");
if (this.elements != null && !this.elements.isEmpty()) {
for (int i = 0, len = this.elements.size(); i < len; ++i) {
if (i != 0) {
builder.append(", ");
}
builder.append(String.valueOf(this.elements.get(i)));
}
}
builder.append("} = ").append(String.valueOf(this.result));
return builder.toString();
}
}
7、Plan
/**
* 表示子集的一种组合方案
*
* @param
* @param
* @param
*/
public class Plan {
/**子集组合**/
private List> subSets;
/**子集组合的计算结果**/
private K result;
public Plan(List> subSets, K result) {
this.subSets = subSets;
this.result = result;
}
public List> getSubSets() {
return subSets;
}
public void setSubSets(List> subSets) {
this.subSets = subSets;
}
public K getResult() {
return result;
}
public void setResult(K result) {
this.result = result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (this.subSets != null && !this.subSets.isEmpty()) {
for (int i = 0, len = this.subSets.size(); i < len; ++i) {
if (i != 0) {
builder.append(", ");
}
builder.append(String.valueOf(this.subSets.get(i)));
}
}
builder.append("] = ").append(String.valueOf(this.result));
return builder.toString();
}
}
8、Answer
/**
* 最优答案的组合
*
* @param
* @param
* @param
*/
public class Answer {
/**最优结果**/
private K result;
/**最优的方案集合**/
private List> plans;
public Answer(List> plans, K result) {
this.plans = plans;
this.result = result;
}
public K getResult() {
return result;
}
public void setResult(K result) {
this.result = result;
}
public List> getPlans() {
return plans;
}
public void setPlans(List> plans) {
this.plans = plans;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("最优值:").append(String.valueOf(this.result)).append("\n");
builder.append("最优方案个数:").append(this.plans == null ? 0 : this.plans.size()).append("\n");
if (this.plans != null && !this.plans.isEmpty()) {
this.plans.forEach(e -> builder.append(String.valueOf(e)));
}
return builder.toString();
}
}
9、
/**
* 集合划分求最优的算法构造器
*
* @param
* @param
* @param
* @param
*/
public class SetSplitBuilder, T, R, K> {
static Logger logger = Logger.getLogger(SetSplitBuilder.class.getName());
private static final Map> cache = new HashMap<>();//子集组合缓存
private static final int cache_size = 10;
static {
initCache();
}
private static void initCache() {
for (int i = 1; i <= cache_size; ++i) {
List rows = new ArrayList<>(s(i));
initCache(0, i, new int[0], rows);
cache.put(i, rows);
}
}
private static void initCache(int idx, int len, int[] subs, List combs) {
if (idx < len) {
for (int j = 0; j < subs.length; ++j) {
int[] newSubs = Arrays.copyOf(subs, subs.length);
newSubs[j] = newSubs[j] | (1 << idx);
initCache(idx + 1, len, newSubs, combs);
}
int[] newSubs = Arrays.copyOf(subs, subs.length + 1);
newSubs[subs.length] = (1 << idx);
initCache(idx + 1, len, newSubs, combs);
} else {
combs.add(Arrays.copyOf(subs, subs.length));
}
}
/**
* n个元素的集合有多少种划分方法
* @param n
* @return
*/
private static int s(int n) {
int sum = 0;
for (int i = 1; i <= n; ++i) {
sum += f(n, i);
}
return sum;
}
/**
* n个元素的集合划分成m个子集的个数
* @param n
* @param m
* @return
*/
private static int f(int n, int m) {
if (m == 1 || n == m) {
return 1;
}
int[][] mt = new int[n + 1][m + 1];
for (int i = 0; i < mt.length; ++i) {
mt[i][1] = 1;
}
for (int i = 1; i <= m; ++i) {
mt[m][m] = 1;
}
for (int i = 2; i <= n; ++i) {
for (int j = 2; j <= m; ++j) {
mt[i][j] = mt[i - 1][j - 1] + mt[i - 1][j] * j;
}
}
return mt[n][m];
}
private N[] values;
private Object[] subsetResults;//每个子集的最优结果
private Object[] subsetResultStrategies;//每个子集对应的最优策略,可以有多个,用set表示
private StrategySelector, T, R> strategySelector;
private StrategyCalculate strategyCalculate;
private StrategyComparator strategyComparator;
private K result;
private List optimalSubsetCombs = new ArrayList<>();//子集最优组合
List> plans;
public SetSplitBuilder(N[] values, StrategySelector, T, R> strategySelector, StrategyCalculate strategyCalculate, StrategyComparator strategyComparator) {
Objects.requireNonNull(values);
Objects.requireNonNull(strategySelector);
Objects.requireNonNull(strategyCalculate);
Objects.requireNonNull(strategyComparator);
this.values = values;
this.strategySelector = strategySelector;
this.strategyCalculate = strategyCalculate;
this.strategyComparator = strategyComparator;
}
public void setValues(N[] values) {
Objects.requireNonNull(values);
this.values = values;
}
public void setStrategySelector(StrategySelector, T, R> strategySelector) {
Objects.requireNonNull(strategySelector);
this.strategySelector = strategySelector;
}
public void setStrategyCalculate(StrategyCalculate strategyCalculate) {
Objects.requireNonNull(strategyCalculate);
this.strategyCalculate = strategyCalculate;
}
public void setStrategyComparator(StrategyComparator strategyComparator) {
Objects.requireNonNull(strategyComparator);
this.strategyComparator = strategyComparator;
}
/**
* 计算结果
*/
public void build() {
logger.info("子集初始化开始----->");
initSubsetResults();
logger.info("子集初始化结束----->");
logger.info("计算最优路径开始----->");
calculate();
logger.info("计算最优路径结束----->");
logger.info("组装结果开始----->");
packageResult();
logger.info("组装结果结束----->");
}
public Answer getAnswer() {
return new Answer<>(this.plans, this.result);
}
/**
* 计算每个子集的最优策略
*/
private void initSubsetResults() {
int len = 2 << this.values.length;
this.subsetResults = new Object[len];
this.subsetResultStrategies = new Object[len];
for (int i = 0; i < len; ++i) {
//int sub = i;//子集组合
List subNodes = new ArrayList<>(this.values.length);
for (int j = 0; j < this.values.length; ++j) {
if (((i >> j) & 1) == 1) {
subNodes.add(this.values[j]);
}
}
//获取最少的策略算法
Set> nodesStrategies = null;
for (N n : subNodes) {
if (nodesStrategies == null || n.strategies.size() < nodesStrategies.size()) {
nodesStrategies = n.strategies;
}
}
//满足子集的策略
Set> satisfiedStrategy = new HashSet<>();
if (nodesStrategies != null && !nodesStrategies.isEmpty()) {
boolean flag = true;
for (Strategy s : nodesStrategies) {
for (N n : subNodes) {
if (!n.existStrategy(s)) {
flag = false;
break;
}
}
if (flag) {
satisfiedStrategy.add(s);
}
}
}
//获取子集最优的策略集合
Set> r = null;
if (!satisfiedStrategy.isEmpty()) {
r = this.strategySelector.get(satisfiedStrategy, subNodes.stream().map(e -> e.value).collect(Collectors.toList()));
}
this.subsetResults[i] = r != null && !r.isEmpty() ? r.iterator().next().get(subNodes.stream().map(e -> e.value).collect(Collectors.toList())) : null;
this.subsetResultStrategies[i] = r;
}
}
/**
* 计算最优路径
*/
private void calculate() {
List rows = cache.get(this.values.length);
if (rows != null) {
calculateByCache(rows);
} else {
calculate(0, this.values.length, new int[0]);
}
}
private void calculateByCache(List rows) {
for (int[] subs : rows) {
cacheCurrentOptimalResults(subs);
}
}
private void calculate(int idx, int len, int[] subs) {
if (idx < len) {
for (int j = 0; j < subs.length; ++j) {
int[] newSubs = Arrays.copyOf(subs, subs.length);
newSubs[j] = newSubs[j] | (1 << idx);
calculate(idx + 1, len, newSubs);
}
int[] newSubs = Arrays.copyOf(subs, subs.length + 1);
newSubs[subs.length] = (1 << idx);
calculate(idx + 1, len, newSubs);
} else {
cacheCurrentOptimalResults(subs);
}
}
/**
* subs 为子集的组合结果
* 缓存当前最优结果
*/
@SuppressWarnings("unchecked")
private void cacheCurrentOptimalResults(int[] subs) {
//记录每个集合的策略结果
List datas = new ArrayList<>();
for (Integer st : subs) {
if (st != null) {
R r = (R)this.subsetResults[st.intValue()];
if (r != null) {
datas.add(r);
}
}
}
K rt = this.strategyCalculate.get(datas);//获取子集计算结果
if (this.result == null || this.strategyComparator.better(rt, this.result)) {
if (!rt.equals(this.result)) {
this.optimalSubsetCombs.clear();
}
this.optimalSubsetCombs.add(subs);
this.result = rt;
}
}
/**
* 组装结果
*/
@SuppressWarnings("unchecked")
private void packageResult() {
this.plans = new ArrayList<>(this.optimalSubsetCombs.size());
for (int[] rows : this.optimalSubsetCombs) {
List> subsets = new ArrayList<>(rows.length);
for (int sub : rows) {
subsets.add(new SubSet(getSubSetValues(sub), (Set>)this.subsetResultStrategies[sub], (R)this.subsetResults[sub]));
}
this.plans.add(new Plan(subsets, this.result));
}
}
/**
* 获取子集中的元素集合
* @param sub
* @return
*/
private List getSubSetValues(int sub) {
List values = new ArrayList<>();
for (int i = 0; i < this.values.length; ++i) {
if ((sub & (1 << i)) != 0) {
values.add(this.values[i].value);
}
}
return values;
}
}
10、测试
public class Test {
public static void main(String[] args) {
for (int l = 1; l <= 10; ++l) {
StrategyNode[] nodes = new StrategyNode[l];
for (int i = 0; i < nodes.length; ++i) {
nodes[i] = new StrategyNode(i + 1, Arrays.asList(Strategy.AVG, Strategy.MAX, Strategy.AVG, Strategy.MAX, Strategy.AVG, Strategy.MAX, Strategy.AVG, Strategy.MAX).toArray(new Strategy[0]));
}
SetSplitBuilder,Integer,Integer,Integer> strategyBuilder =
new SetSplitBuilder<>(nodes, StrategySelector.MAX, StrategyCalculate.AVG, StrategyComparator.GREAT);
strategyBuilder.build();
Answer answer = strategyBuilder.getAnswer();
System.out.println(answer.toString());
}
}
}