给定一个整数集合S,求集合S的一个划分S1和S2(即:S=S1∪S2且S1∩S2=Φ),使得S1中的元素之和等于S2中的元素之和。
1),证明此问题是NP难的。
2),设计一个搜索算法(回溯法或分支界限法)求解此问题。
解:
1)证明:由题目可知,只需要证明该集合划分问题是NP完全问题即可。为了证明任意一个问题A是NP完全问题,需要做到下面四个步骤。
1,存在一个非确定性多项式时间算法能解决A,例如A∈NP;
2,任意NP完全问题B能简化为A;
3,由B到A的简化在多项式时间里可以完成;
4,当且仅当B有解时原始问题A有解。
现在来证明集合划分问题是NP完全的。
1,SET-PARTITION∈NP:假设两个划分,证明这两个划分有相同的元素之和;
2,子集和问题到集合划分问题的简化:子集和问题定义如下:给定一个整数集合X和目标值t,找到一个子集Y⊆X使得集合Y中的成员之和为t。假设s为X中的成员之和。将X’= X∪{s – 2t}加入集合划分中;
3,这个简化可在多项式时间内完成;
4,只需证明当且仅当<X’>∈集合划分问题时<X,t>∈子集和问题。X’的成员之和为2s-2t;
如果X中存在一个子集,其和为t,那么X中剩余成员之和为s-t。因此,存在X’的一个划分使得划分的两个子集之和均为s-t。如果存在一个X’的划分使得划分的两个子集之和均为s-t。两个子集之一包含数字s – 2t。移除这个数字,我们得到一个子集,其和为t,并且所有子集成员均属于集合X。
证毕!
2)回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
从元素在与不在子集这两种状态来考虑,该解空间树为一棵二叉树,如下图:
Java代码如下:
package sum_subset;
public class Traceback {
public int getSum1(boolean[] visited, int[] A) {
int sum = 0;
for(int i = 0;i < A.length;i++) {
if(visited[i])
sum += A[i];
}
return sum;
}
public void getSubSet1(boolean[] visited, int[] A, int m, int step) {
if(step == A.length) {
if(getSum1(visited, A) == m) {
for(int i = 0;i < A.length;i++) {
if(visited[i])
System.out.print(A[i]+" ");
}
System.out.print("和");
for(int i = 0;i < A.length;i++) {
if(!visited[i])
System.out.print(A[i]+" ");
}
System.out.println();
}
return;
}
visited[step] = true;
getSubSet1(visited, A, m, step + 1);
visited[step] = false;
getSubSet1(visited, A, m, step + 1);
}
public static void main(String[] args) {
Traceback test = new Traceback();
int[] A = new int[8];
int sumA = 0;
boolean[] visited = new boolean[8];
for(int i = 0;i < 8;i++) {
A[i] = i + 1;
sumA = sumA + A[i];
visited[i] = false;
}
if(sumA%2 == 0){
test.getSubSet1(visited, A, sumA/2, 0);
}
else {
System.out.println("无法找到正确的解!");
}
}
}
用三个整数集合对代码进行实例测试的结果如下:
1,整数集合为{1,2,3,4,5,6,7,8}时,输出结果为:
每行的数字代表S1和S2集合中的元素。
2,整数集合为{1,2,3,4,5,6,7,8,9}时,输出结果为: