1.问题描述:已知由n(n≥2)个正整数构成的集合A={ak}(0≤k<n),将其划分为两个不相交的子集A1和A2,元素个数分别是n1和n2,A1和A2中元素之和分别为S1和S2。设计一个尽可能高效的划分算法,满足|n1-n2|最小且|S1-S2|最大。
2.设计思路:根据题目的要求,如果要满足n1-n2最小,那么两个子集中的元素个数是平均的,也就是n1=n/2;在此基础下,去满足S1-S2最大,那么就需要把n中的元素分为两部分:较大的一部分和较小的一部分;分别把这两个部分放入n1、n2中;把n分成两部分就需要中位数,这里可以使用快速排序完成每一次的中位数求解;如果基准在靠近左边,说明中位数应该在右边,那么在右边递归函数;如果基准在右边,说明中位数应该在左边,那么在左边递归函数;直到基准的位置为n/2时结束,此时找到整个数组的中位数;最后以n/2为边界输出两个子集即可;
3.代码:
/*数组子集划分问题*/
#include <stdio.h>
#define N 10
int a[N]={9,8,7,6,5,4,3,2,1,0};
int FindMidPosision(int a[],int low,int high) //利用快速排序思想查找中间位置
{
int temp=a[low]; //基准
while(1)
{
while(low<high&&a[high]>=temp)//从右边开始与基准进行比较,小于基准时把数转移到基准位置
{
high--;
}
if(low>=high) //如果从右到左已经比较完成,就退出循环
{
break;
}
a[low++]=a[high]; //a[high]<temp时,把a[high]转移到a[low]位置
while(high>low&&a[low]<=temp)//从左边开始与基准进行比较,大于基准时把数转移到基准位置
{
low++;
}
if(low>=high) //如果从右到左已经比较完成,就退出循环
{
break;
}
a[high--]=a[low];
}
a[low]=temp; //把temp的值放到大小子集的中间位置,刚刚好把子集分为一边大,一边小
return low;
}
void Divide(int a[],int low,int high,int n) //递归调用快速排序,找到整个数组的中间位置
{
int mid;
while(low<high)
{
mid=FindMidPosision(a,low,high);
if(mid==n/2)
{
break;
}
else if(mid<n/2) //基准小于n/2时,在右边继续递归
{
low=mid+1;
}
else{
high=mid-1;
}
}
}
int main()
{
Divide(a,0,N-1,N); //调用函数把数组从中间位置分为一边大和一边小
printf("n1=%d :",N/2);
for(int i=0;i<N/2;i++)
{
printf("%d ",a[i]);
}
printf("\nn1=%d :",N-N/2);
for(int i=N/2+1;i<N;i++)
{
printf("%d ",a[i]);
}
return 0;
}
4.运行结果:
5.总结:
数组子集划分问题,使用快速排序的思路把数组分为两个部分,并且这两个部分是一边大、一边小,这样就可以满足题目的要求。