C++解决划分带条件的两子集合
作者:互联网
写好每一份题解,本博文源于胡凡的《算法笔记》,旨在解决分集合,给定一个由整数组成的集合,集合中的整数各不相同,现在要分为两个子集合,使得这两个子集合的并为原集合、交为空集,同时在两个子集合的元素个数n1与n2之差的绝对值 ∣ n 1 − n 2 ∣ |n_1-n_2| ∣n1−n2∣尽可能小的前提下,要求它们各自的元素之和 S 1 S_1 S1与 S 2 S_2 S2之差的绝对值 ∣ S 1 − S 2 ∣ |S_1-S_2| ∣S1−S2∣尽可能大,求这两个|S1-S2|等于多少。
核心思路
最直接的思路是将原集合中的元素从小到大排序,取排序后的前n/2个元素作为其中一个子集合,剩下的元素作为另一个子集合即可。
还有一种就是随机选择,解决的方法是求原集合中的元素的第n/2大,同时根据这个数把集合分为两个部分,使得其中一个子集合都大于这个数,另一个都小于这个数。因此时间复杂度为O(n).
完整代码
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include <cmath>
using namespace std;
const int maxn = 10010;
int A[maxn],n;
int randPartition(int A[],int left,int right) {
//生成[left,right]内的随机数p
int p = (int)(round(1.0*rand()/RAND_MAX*(right-left)+left));
int temp1 = A[p]; // 将A[left]存放至临时变量temp
A[p] = A[left];
A[left] = temp1;
int temp = A[left];
while(left<right){
//只要left与right不相遇
while(left<right && A[right] > temp) right--; // 反复左移right
A[left] = A[right]; //将A[right]挪到
while(left<right && A[left] <= temp) left++; // 反复右移left
A[right] = A[left];
}
A[left] = temp;
return left;
}
void randSelect(int A[],int left,int right,int K){
if(left == right) return ; //边界
int p = randPartition(A,left,right); //划分后主元的位置P
int M = p - left + 1; // A[p]是A[left,right]中的第M大
if(K==M) return ;
if(K<M){
//第k大的数在主元左测
randSelect(A,left,p-1,K);//划分后元素的位置为p
}else{
//第K大的数在主元右侧
randSelect(A,p+1,right,K-M);//往主元右侧找第K-M大的数
}
}
int main(){
srand((unsigned)time(NULL));
//sum和sum1记录所有着呢概述
int sum = 0,sum1 = 0;
scanf("%d",&n);
for(int i = 0;i<n;i++){
scanf("%d",&A[i]);
sum += A[i];
}
randSelect(A,0,n-1,n/2);
for(int i = 0;i<n/2;i++)
sum1 += A[i];
//求两个子集合的元素和之差
printf("%d\n",(sum-sum1)-sum1);
return 0;
}
代码效果
标签:right,int,子集合,C++,划分,集合,include,left 来源: https://blog.csdn.net/m0_37149062/article/details/122492567