【算法】石子合并问题

184 阅读1分钟

问题描述:在一个圆形操场的四周摆放着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);


    }

}