一组数中寻找加和最接近某个值的组合 JAVA实现

一组数中寻找加和最接近某个值的组合 JAVA实现

最近帮人做了一个题,就是在一组数中寻找一个子集合,使得这个子集合的数的加和最接近给定的某个值,此外,这些数的类型是浮点数。我第一想到是用01背包问题去解,但是感觉对于整型数好做,浮点数会难以控制精度。再次我给出三种做法,第一种是暴力做法,第二三中方法都是基于蒙特卡洛算法。

1、暴力法

这个方法的思路是使用深度优先遍历将给定数组的所有子集合给出,在代码中,我们按子集合内数字的个数依次给出,例如对于数组[1,2,3],我们先给出只有一个数字的子集合[1],[2],[3],再给出两个的和三个的。

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class baoli {
    public ArrayList<ArrayList<Double>> res = new ArrayList<>();

    /**
     *
     * @param input 需要产生排列组合的数组
     * @param weishu 需要产生排列组合的位数
     * @param nowweishu 当前已经完成的位数
     * @param nowres 当前已经装入数组的数字
     * @param num_weishu 需要使用该位数之后的数字
     */
    public void dfs(List<Double> input, int weishu, int nowweishu, ArrayList<Double> nowres, int num_weishu)//用来产生子集合
    {
        if(nowweishu == weishu)
        {
            res.add((ArrayList<Double>) nowres.clone());
            return;
        }
        for(int i = num_weishu + 1;i<input.size();i++)
        {
            nowres.add(input.get(i));
            dfs(input,weishu,nowweishu+1,nowres,i);
            nowres.remove(nowres.size()-1);
        }
    }

    public ArrayList<ArrayList<Double>> print(List<Double> input, int weishu) {
        res = new ArrayList<>();
        ArrayList<Double> nowres= new ArrayList<>();
        dfs(input,weishu,0,nowres,-1);
        return res;
    }



    public ArrayList<Double> sch_emuration(ArrayList<Double> nums,double target)
    {
        double lapse = 10000;
        ArrayList<Double> res = new ArrayList<>();
        for (int i = 1;i<=nums.size();i++)
        {
            ArrayList<ArrayList<Double>> ares = print(nums,i);

            for (int j = 0;j<ares.size();j++)
            {
                double thissum = 0;
                for (int k = 0;k<ares.get(j).size();k++)
                {
                    thissum+=ares.get(j).get(k);
                }
                if (Math.abs(thissum - target)<lapse)
                {
                    res = (ArrayList<Double>) ares.get(j).clone();
                    lapse = Math.abs(thissum - target);
                }
            }
        }

        return res;
    }


    public static void main(String args[]) {
        test t = new test();
        Random r = new Random();
        ArrayList<Double> nums =  new ArrayList<>();
        nums.add(8.05);nums.add(6.98);nums.add(6.19);nums.add(5.0);nums.add(22.96);nums.add(4.71);nums.add(4.74);nums.add(4.25);
        nums.add(6.34);nums.add(2.77);nums.add(7.31);nums.add(3.59);nums.add(19.55);

        ArrayList<Double> res = t.sch_emuration(nums,84.01);
        double sum = 0;
        for (int i=0;i<res.size();i++) {
            System.out.println(res.get(i));
            sum = sum + res.get(i);
        }
        System.out.println(sum);
    }
}

该方法的结果展示:
在这里插入图片描述

2、蒙特卡洛组合法

这个方法使用了随机的思想,每次随机得到一个子集合的数量,再依据这个数量随机选择数组中的数(可以使用HashMap避免重复选择)

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

public class mtkl {
    public ArrayList<Double> MC_combination(ArrayList<Double> nums,double target)
    {
        ArrayList<Double> res = new ArrayList<>();
        double lapse = 10000;
        Random random = new Random();

        for (int i=0;i<1000000;i++)
        {
            if(i%10000==0)
                System.out.println(i);
            int numsize = random.nextInt(nums.size()  + 1);
            ArrayList<Double> ares = new ArrayList<>();
            HashMap<Integer,Integer> map = new HashMap<>();
            double thissum = 0;
            for (int j=0 ;j<numsize;j++)
            {
                int anumberindex = random.nextInt(nums.size());
                while (map.containsKey(anumberindex))
                {
                    anumberindex = random.nextInt(nums.size());
                }
                map.put(anumberindex,1);
                ares.add(nums.get(anumberindex));
                thissum += nums.get(anumberindex);
            }
            if(Math.abs(thissum - target)<lapse)
            {
                lapse = Math.abs(thissum - target);
                res = (ArrayList<Double>) ares.clone();
            }
            if(lapse<0.000001)
                break;
        }
        return res;
    }



    public static void main(String args[]) {
        Random r = new Random();
        ArrayList<Double> nums =  new ArrayList<>();
        nums.add(8.05);nums.add(6.98);nums.add(6.19);nums.add(5.0);nums.add(22.96);nums.add(4.71);nums.add(4.74);nums.add(4.25);
        nums.add(6.34);nums.add(2.77);nums.add(7.31);nums.add(3.59);nums.add(19.55);
        mtkl m = new mtkl();
        ArrayList<Double> res = m.MC_combination(nums,84.01);
        double sum = 0;
        for (int i=0;i<res.size();i++) {
            System.out.println(res.get(i));
            sum = sum + res.get(i);
        }
        System.out.println(sum);
        System.out.println(Math.abs(sum - 84.01));


    }
};

该方法的结果展示:
在这里插入图片描述

3、蒙特卡洛排列法

这个方法也是基于随机算法。该方法的思想是在每次计算前,都对数组进行一次打乱顺序,然后从头加和,直到加和的值大于给定目标值。这个方法没有前两个精度高,而且运算次数大于前两个,因此可以当一个思路。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class mtkl2 {
    public ArrayList<Double> MC_permutation(ArrayList<Double> nums,double target)
    {
        ArrayList<Double> res = new ArrayList<>();
        for (int i=0;i<9000000;i++)
        {
            double lapse = 10000;
            ArrayList<Double> newnum = (ArrayList<Double>) nums.clone();
            Collections.shuffle(newnum);
            double lastsum = 0;
            ArrayList<Double> alist = new ArrayList<>();
            for (int j = 0;j< newnum.size();j++)
            {
                double thissum = lastsum + newnum.get(j);
                if(thissum>target)
                {
                    double ds1 = Math.abs(lastsum - target);
                    double ds2 = Math.abs(thissum - target);
                    double ds = Math.min(ds1,ds2);
                    if(ds<lapse)
                    {
                        lapse = ds;
                        if(ds1<ds2)
                        {
                            res = (ArrayList<Double>) alist.clone();
                        }
                        else
                        {
                            alist.add(newnum.get(j));
                            res = (ArrayList<Double>) alist.clone();
                        }
                        break;
                    }
                }
                alist.add(newnum.get(j));
                lastsum = thissum;
            }

            if(lapse<0.00001)
                break;
        }
        return res;
    }

    public static void main(String args[]) {
        ArrayList<Double> nums =  new ArrayList<>();
        nums.add(8.05);nums.add(6.98);nums.add(6.19);nums.add(5.0);nums.add(22.96);nums.add(4.71);nums.add(4.74);nums.add(4.25);
        nums.add(6.34);nums.add(2.77);nums.add(7.31);nums.add(3.59);nums.add(19.55);
        mtkl2 m = new mtkl2();
        ArrayList<Double> res = m.MC_permutation(nums,84.01);
        double sum = 0;
        for (int i=0;i<res.size();i++) {
            System.out.println(res.get(i));
            sum = sum + res.get(i);
        }
        System.out.println(sum);
        System.out.println(Math.abs(sum - 84.01));


    }
}

该方法的结果展示:
在这里插入图片描述

  • 17
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值