数组分割

《编程之美》 2.18 数组分割

问题:有一个没有排序,元素个数为2*n的正整数数组,要求:如何能把这个数组分割成元素个数为n的两个数组,并使两个子数组的和最接近?

解法一:排序,根据下标奇偶性分组,不过结果不是最优的。

代码一:

int ArrayCut(int a[],int N)
{
	qsort(a,N,sizeof(int),compare);
	int sum1,sum2;
	sum1 = sum2 = 0;	
	for ( int i = 0; i < N; i = i + 2) {
		sum1 += a[i];
		printf("%-4d",a[i]);
	}
	printf("\n");
	for ( int i = 1; i < N; i = i + 2) {
		sum2 += a[i];
		printf("%-4d",a[i]);
	}
	printf("\n");
	printf("Sum of even indexes: %d , sum of odd indexes: %d .\n",sum1,sum2);
} 

解法二:DP解法。详见《编程之美》和代码。

代码二:

char dp[MAXNUM][MAXNUM];
int path[MAXNUM][MAXNUM];

int min(int a,int b)
{
	return (a>b) ? b : a;
} 
int erase(int x, int a[],int n)
{
	for ( int i = 0; i < n; i++) {
		if (a[i] == x) {
			a[i] = 0;
			return 1;
		}
	}
	return 0;
}
int ArraySplit(int a[], int N)
{ 
	//dp[j][s]表示可否找到j个数,使得它们的和为s 
	memset(dp,0,sizeof(char));
	memset(path,0,sizeof(int));
	dp[0][0] = 1; 
	qsort(a,N,sizeof(int),compare);
	int sum = 0;
	for (int i = 0; i < N; i++) {
		sum += a[i];
	}
	printf("sum = %d\n",sum);
	for (int i = 0; i < N; i++){
		for (int j = min(i+1,N/2); j >= 1; j--) {
			for( int s = 1; s <= sum >> 1; s++) {
				if ((s >= a[i]) && dp[j-1][s-a[i]]) {
					dp[j][s] = 1;
					path[s-a[i]][s] = 1;
				}
			} 
		} 
	}
	int i;
	int halfsum;
	for (i = sum >> 1; i >= 0; i--) {
		if (dp[N >> 1][i]) {
			printf("Sum of half of Elements : %d .\n",i);
			halfsum = i;
			break;
		} 
	}
	
	int j = N >> 1;
	int sumx;
	int *v = (int *)malloc(N*sizeof(int));
	memcpy(v,a,N*sizeof(int));
	while (--j) {
		sumx = i; 
		while (i--) {
			if (path[i][sumx] && dp[j][i] && dp[j+1][sumx] && erase(sumx-i,v,N)) {
				printf ("%4d -> %-4d : %-4d\n",i,sumx,sumx - i);
				break;
			}
		}
	}
	printf ("%4d -> %-4d : %-4d\n",0,i,i);
	show(a,N);

//	printf("\n");
	return halfsum;
} 

解法三:DP解法二。详见代码。

代码三:

int dp3[20][20][MAXNUM];
int max(int a,int b)
{
	return (a > b) ? a : b;
}
int ArraySeparation(int arr[],int N)
{
	int i , j , s;
    //int dp3[2*N+1][N+1][SUM/2+2];

    /*
    用dp(i,j,c)来表示从前i个元素中取j个、且这j个元素之和不超过c的最佳(大)方案,在这里i>=j,c<=S
    状态转移方程:   
    限第i个物品       不取  
    dp(i,j,c)=max{dp(i-1,j-1,c-a[i])+a[i],dp(i-1,j,c)}
    dp(2N,N,SUM/2+1)就是题目的解。
    */
    //初始化
    memset(dp3,0,sizeof(dp3));
	qsort(arr,N,sizeof(int),compare);
	int sum = 0;
	for (i = 0; i < N; i++) {
		sum += arr[i];
	}
    for(i = 1 ; i <= N ; ++i)
    {
        for(j = 1 ; j <= min(i,N >> 1) ; ++j)
        {
            for(s = (sum >> 1); s >= arr[i-1] ; --s)
            {
                dp3[i][j][s] = max(dp3[i-1][j-1][s-arr[i-1]]+arr[i-1] , dp3[i-1][j][s]);
            }
        }
    }

    //因为这为最终答案 dp[2*N][N][SUM/2+1];
    i = N , j= N >> 1 , s = (sum >> 1);
    while(i > 0)
    {
        if(dp3[i][j][s] == dp3[i-1][j-1][s-arr[i-1]] + arr[i-1])   //判定这个状态是由哪个状态推导出来的
        {
            //cout<<arr[i]<<" ";    //取中arr[i]
            //j--;
            //s -= arr[i];
            //printf("%-4d",arr[i-1]);
            printf("%-4d",arr[i-1]);
            j--;
            s -= arr[i-1];
        }    
        i--;
    }
    printf("\nResult : %d .\n",dp3[N][N >> 1][(sum >> 1)]);
    return dp3[N][N >> 1][(sum >> 1)];
}

测试程序:

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

#define MAXNUM 10000

int compare(const void*a,const void*b)
{
	return -(*(int*)a - *(int*)b);
}

void show(int a[],int N)
{
	for(int i = 0; i < N; i++){
		printf("%-4d",a[i]);
	}	
	printf("\n"); 
}

int ArrayCut(int a[],int N)
{
	qsort(a,N,sizeof(int),compare);
	int sum1,sum2;
	sum1 = sum2 = 0;	
	for ( int i = 0; i < N; i = i + 2) {
		sum1 += a[i];
		printf("%-4d",a[i]);
	}
	printf("\n");
	for ( int i = 1; i < N; i = i + 2) {
		sum2 += a[i];
		printf("%-4d",a[i]);
	}
	printf("\n");
	printf("Sum of even indexes: %d , sum of odd indexes: %d .\n",sum1,sum2);
} 

/*----------------------------------------------------------------------------*/
char dp[MAXNUM][MAXNUM];
int path[MAXNUM][MAXNUM];

int min(int a,int b)
{
	return (a>b) ? b : a;
} 
int erase(int x, int a[],int n)
{
	for ( int i = 0; i < n; i++) {
		if (a[i] == x) {
			a[i] = 0;
			return 1;
		}
	}
	return 0;
}
int ArraySplit(int a[], int N)
{ 
	//dp[j][s]表示可否找到j个数,使得它们的和为s 
	memset(dp,0,sizeof(char));
	memset(path,0,sizeof(int));
	dp[0][0] = 1; 
	qsort(a,N,sizeof(int),compare);
	int sum = 0;
	for (int i = 0; i < N; i++) {
		sum += a[i];
	}
	printf("sum = %d\n",sum);
	for (int i = 0; i < N; i++){
		for (int j = min(i+1,N/2); j >= 1; j--) {
			for( int s = 1; s <= sum >> 1; s++) {
				if ((s >= a[i]) && dp[j-1][s-a[i]]) {
					dp[j][s] = 1;
					path[s-a[i]][s] = 1;
				}
			} 
		} 
	}
	int i;
	int halfsum;
	for (i = sum >> 1; i >= 0; i--) {
		if (dp[N >> 1][i]) {
			printf("Sum of half of Elements : %d .\n",i);
			halfsum = i;
			break;
		} 
	}
	
	int j = N >> 1;
	int sumx;
	int *v = (int *)malloc(N*sizeof(int));
	memcpy(v,a,N*sizeof(int));
	while (--j) {
		sumx = i; 
		while (i--) {
			if (path[i][sumx] && dp[j][i] && dp[j+1][sumx] && erase(sumx-i,v,N)) {
				printf ("%4d -> %-4d : %-4d\n",i,sumx,sumx - i);
				break;
			}
		}
	}
	printf ("%4d -> %-4d : %-4d\n",0,i,i);
	show(a,N);

//	printf("\n");
	return halfsum;
} 

/*----------------------------------------------------------------------------*/
int dp3[20][20][MAXNUM];
int max(int a,int b)
{
	return (a > b) ? a : b;
}
int ArraySeparation(int arr[],int N)
{
	int i , j , s;
    //int dp3[2*N+1][N+1][SUM/2+2];

    /*
    用dp(i,j,c)来表示从前i个元素中取j个、且这j个元素之和不超过c的最佳(大)方案,在这里i>=j,c<=S
    状态转移方程:   
    限第i个物品       不取  
    dp(i,j,c)=max{dp(i-1,j-1,c-a[i])+a[i],dp(i-1,j,c)}
    dp(2N,N,SUM/2+1)就是题目的解。
    */
    //初始化
    memset(dp3,0,sizeof(dp3));
	qsort(arr,N,sizeof(int),compare);
	int sum = 0;
	for (i = 0; i < N; i++) {
		sum += arr[i];
	}
    for(i = 1 ; i <= N ; ++i)
    {
        for(j = 1 ; j <= min(i,N >> 1) ; ++j)
        {
            for(s = (sum >> 1); s >= arr[i-1] ; --s)
            {
                dp3[i][j][s] = max(dp3[i-1][j-1][s-arr[i-1]]+arr[i-1] , dp3[i-1][j][s]);
            }
        }
    }

    //因为这为最终答案 dp[2*N][N][SUM/2+1];
    i = N , j= N >> 1 , s = (sum >> 1);
    while(i > 0)
    {
        if(dp3[i][j][s] == dp3[i-1][j-1][s-arr[i-1]] + arr[i-1])   //判定这个状态是由哪个状态推导出来的
        {
            //cout<<arr[i]<<" ";    //取中arr[i]
            //j--;
            //s -= arr[i];
            //printf("%-4d",arr[i-1]);
            printf("%-4d",arr[i-1]);
            j--;
            s -= arr[i-1];
        }    
        i--;
    }
    printf("\nResult : %d .\n",dp3[N][N >> 1][(sum >> 1)]);
    return dp3[N][N >> 1][(sum >> 1)];
}

/*----------------------------------------------------------------------------*/

int main()
{
	int N = 10;
	int a[10];
	srand(time(NULL));
	for (int i = 0; i < N; i++) {
		a[i] = rand() % 15 + 1;
	}
	show(a,N);
	//ArrayCut(a,N);
	ArraySplit(a,N);
	ArraySeparation(a,N); 
	show(a,N);	
	
} 

测试输出:

8   6   7   4   12  10  12  9   6   6   
sum = 80
Sum of half of Elements : 40 .
  36 -> 40   : 4   
  30 -> 36   : 6   
  24 -> 30   : 6   
  12 -> 24   : 12  
   0 -> 12   : 12  
12  12  10  9   8   7   6   6   6   4   
4   6   6   12  12  
Result : 40 .
12  12  10  9   8   7   6   6   6   4   

REF:

1,编程之美 2.18 数组分割

2,http://www.cnblogs.com/freewater/archive/2012/08/23/2652974.html

3,http://blog.csdn.net/njufeng/article/details/11682751




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值