问题描述:在一个圆形操场的四周摆放着n堆石子,现要将石子有次序的合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
动态规划引入:如果N-1 次合并的全局最优解包含了每一次的子问题的最优解,那么经这样的N-1次合并后的得分总和必然是最优的。因此我们需要引入动态规划来求出最优解。
假设有石头Ai,Ai+1,……,Ai+j-1共j堆需要合并,简记为A[i+0,i+j-1].如果设最后一次合并发生在Ak与Ak+1之间(i<=k <=i+j-1),则最后一个合并的得分为Ai,Ai+1,……,Ai+j-1堆石头的个数的总和记为sum(i,j).(不管你最后一次合并发生在哪个位置,sum(i,j)的值都是一样的)因此总的得分等于:A[i,k]+A[k+1,i+j-1]+tsum(i,j)。
动态规划思路:
阶段:石子的每一次合并过程,先两两合并,再三三合并,最后N堆合并。
状态:每一阶段中各个不同合并方法的石子合并总得分用arr[j][i]表示。
决策:把当前阶段的合并方法细分成前一阶段已计算出的方法,选择其中的最优方案。
例:(3 4 6 5 4 2)
第一阶段:arr[0][1] =0,arr[1][1] =0,arr[2][1]=0,arr[3][1]=0,arr[4][1=0],arr[5][1]=0
第二阶段:两两合并的过程如下,其中sum(j,i)表示从j开始i个数的和。
arr[0][2]=arr[0][1]+arr[1][1]+sum(0,2) =7
arr[1][2]=arr[1][1]+arr[2][1]+sum(1,2) =10
arr[2][2]=arr[2][1]+arr[3][1]+sum(2,2) =11
arr[3][2]=arr[3][1]+arr[4][1]+sum(3,2) =9
arr[4][2]=arr[4][1]+arr[5][1]+sum(4,2) =6
arr[5][2]=arr[5][1]+arr[0][1]+sum(5,2) =5
第三阶段:三三合并可以拆分成两两合并,拆分方法有两种 ,前两个为一组或后两个为一组
arr[0][3]= arr[0][1]+arr[1][2]+sum(0,3)=23或arr[0][3]= arr[0][2]+arr[2][1]+sum(0,3)=20
arr[1][3]= arr[1][1]+arr[2][2]+sum(1,3)=26或arr[1][3]= arr[1][2]+arr[3][1]+sum(1,3)=25
第四阶段:四四拆分的拆分方法有三种,同理求出三种分法的得分。以后在第五阶段、第六阶段以此类推。
public class TsetPebbleDynamic {
//计算从 begin开始 ,合并n个数的得分
public static int sum( int begin,int n,int numArr[]){
int total = 0;
int l = numArr.length;//6
for(int i = begin;i<begin+n;i++){
total = total+numArr[i%l];//对l取余实现石子尾成一起的效果。
}
return total;//将得分返回
}
public static void main(String[] args)throws Exception {
System.out.println("从input。txt文件中读取的数据是:3 7 6 4 2 5 8 并将其存入数组中。");
// TODO Auto-generated method stub
BufferedReader br = new BufferedReader(new FileReader("E:/input.txt"));//从文件中读取数据
BufferedWriter bw = new BufferedWriter(new FileWriter("E:/output.txt"));//向文件中写入数据
String s = null;//用于临时存放从文件中读取的字符串
String[] str = null;//将从文件中读取的字符串写入数组
int[][] arr = new int[100][100];//二维数组用来存放 最小值
int [][] arrMax = new int[100][100];//二维数组用来存放最大值
int temp=0;//临时存放数据
//从文件中读取字符串 分割后放入数组
while((s=br.readLine())!= null){
str = s.split(" ");
}
int num = str.length;//获取数组的长度
int numArr[] = new int[num];//一位数组 用来存放石子的数目
//将String 数组转换为 int类型写入数组
for(int i =0;i<num;i++){
numArr[i] = Integer.valueOf(str[i]);
}
//初始阶段,当为一堆时 初始化为0
for(int i= 0;i<num;i++){
arr[i][1] = 0;
}
//求最小值
//初始化多行进行合并
for(int i = 2;i<=num;i++){
//合并的起始位置
for(int j = 0;j<num;j++){
arr[j][i] = arr[j][1]+arr[(j+1)%num][i-1];//合并的起始位置
for(int k=2;k<i;k++){//开始进行分割
temp = arr[j][k]+arr[(j+k)%num][i-k];//将后来的分割存入临时变量中,方便后来进行比较
if(temp<arr[j][i])//两组数据进行比较,将最小的数值赋值给arr[j][i]
arr[j][i]=temp;
}
arr[j][i]+=sum(j,i,numArr);//调用sum函数 计算出从j开始,合并i个数据的最小值
System.out.print("arr["+j+"]["+i+"] = "+arr[j][i]+" ");//将合并后的数据进行打印
}
System.out.println();
}
//求最大值 同理
for(int i = 2;i<=num;i++){
//合并的起始位置
for(int j = 0;j<num;j++){
arrMax[j][i] = arrMax[j][1]+arrMax[(j+1)%num][i-1];
for(int k=2;k<i;k++){
temp = arrMax[j][k]+arrMax[(j+k)%num][i-k];
if(temp>arrMax[j][i])
arrMax[j][i]=temp;
}
arrMax[j][i]+=sum(j,i,numArr);
System.out.print("arrMax["+j+"]["+i+"] = "+arrMax[j][i]+" ");
}
System.out.println();
}
int min = 65525;
int max= 0;
//比较所有从j开始,合并num个数的最小值和最大值
for(int i=0;i<num;i++){
if(min>arr[i][num])
min = arr[i][num];
if(max<arrMax[i][num])
max= arrMax[i][num];
}
bw.write(String.valueOf(min));//写入数据
bw.write("\r\n");//换行
bw.write(String.valueOf(max));//写入数据
bw.flush();//刷新
bw.close();//关闭
br.close();
System.out.println("最小得分是:"+min);
System.out.println("最大得分是:"+max);
}
}