题目来源: 计算机考研408真题2016年43题
题目描述:
- 描述: 43.已知由n(n≥2)个正整数构成的集合A={},将其划分为两个不相交的子集A;和A2,元素个数分别是n1和n2,A1和A2中元素之和分别为S1和S2。设计一个尽可能高效的划分算法,满足|n1-n2|最小且|S1-S2|最大。
要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C、C++或Java语言描述算法,关键之处给出注释.
3)说明你所设计算法的时间复杂度和空间复杂度。
思路:基于快速排序思想
- 因为|n1-n2|≥0,所以想要|n1-n2|最小,只有让A1和A2的元素个数尽可能接近
- 若A的元素个数n为奇数,那么找到中间数,是该数所在位置的左右两边的元素个数相同
- 且要将中间数归右边,因为还要保证|S1-S2|最大
- 若A的元素个数n为偶数,则将其一分为二,使得两部分元素个数相同
- 若A的元素个数n为奇数,那么找到中间数,是该数所在位置的左右两边的元素个数相同
- 使用
快速排序的思想,基于枢轴将n个整数划分为两个子集,根据划分后枢轴所处的位置i分别处理:- 1.若i=,则分组完成,算法结束;
- 2.若i<,则枢轴及之前的所有元素均属于A1,继续对i之后的元素进行划分;
- 3.若i>,则枢轴及之后的所有元素均属于A2,继续对i之前的元素进行划分:
- 图示:
- 基于
快速排序的思想,分为前后两个子集,而不需要对全部元素进行全排序
具体实现:
public class day408_16 {
static int setPartition(int a[],int n){
int pivotKey,low=0,low0=0,high=n-1,high0=n-1,flag=1,k=n/2,i;
int s1=0,s2=0;
while(flag==1){ //java中的0和1并不代表布尔值中的FALSE与TRUE,与C++不同
pivotKey = a[low]; //选择枢轴
while (low<high){ //基于枢轴对数据进行划分
while(low<high&&a[high]>=pivotKey)
--high;
if(low!=high)
a[low]=a[high];
while(low<high&&a[high]<=pivotKey)
++low;
if(low!=high)
a[high]=a[low];
}
a[low]=pivotKey;
if(low==k-1) //如果枢轴是第n/2个元素,划分成功
flag = 0;
else{
if(low<k-1){
low0 = ++low;
high = high0;
}else{
high0 = --high;
low = low0;
}
}
}
for (i = 0; i < k; i++) {
s1+=a[i];
}
for (i = k; i < n; i++) {
s2+=a[i];
}
return s2-s1;
}
public static void main(String[] args) { //测试
int[] arr = {1,5,9,7,3}; //最终划分为1 3 和 5 9 7
int res = setPartition(arr,5);
System.out.println(res); //|S1-S2|结果为17
}
}
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 24 天,点击查看活动详情”