algorithm - 找到以一定精度求和到给定数组的 K 个数组

标签 algorithm combinations

假设我设置了包含数千个非负值的固定大小 (size = 8) 的数组(让我们将其固定为 5000 个数组)。我得到了另一个具有非负值的相同大小的数组(输入数组)。我的任务是选择数组的一些子集,条件是如果我将它们加在一起(向量求和),我将得到结果数组,该数组非常接近具有所需精度(+-m)的给定输入数组。

例如,如果所需的结果(输入数组)是 (3, 2, 5) 且准确度 = 2 那么当然最好的集合将是一个总和为 (3,2,5) 的集合,但也可以是以下形式的任何解决方案 (3 +- m, 2 +- m, 5 +- m) .

问题是这里正确的算法方法是什么?它类似于多维麻袋问题,但我的任务中没有成本优化部分。

至少需要一个满足约束条件的解决方案。但有几个会更好,这样就可以有选择。

最佳答案

这是一种扩展背包问题。我们知道这是 NPC 的任务,这意味着我们不能使用暴力并尝试所有可能性。目前的计算机无法计算它。

我们可以做的是使用一些启发式方法。一种简单而有用的方法是模拟退火。原理很简单——在你的算法开始时,当温度很高时——你甚至不怕采用“目前最糟糕的解决方案”(这实际上可以导致最好的解决方案)。所以一开始你几乎什么都拿。然后你开始冷静下来,你越冷静,你就越谨慎,所以你会越来越多地尝试改进你的解决方案,风险越来越小。

wiki 上的 gif 实际上是很好的例子:https://en.wikipedia.org/wiki/Simulated_annealing

我还实现了解决方案,最后打印什么是 inputArray 以及你的解决方案是什么以及“负分”(越少越好)。

你不能保证得到最佳/有效的解决方案,但你基本上可以在某个 while 循环中运行它,直到你找到足够好的解决方案或者你达到某个阈值(比如如果你在运行 100 次后没有找到好的解决方案,你说“数据无效”或采取最好的这些“不好”的解决方案)

class Simulation {
    constructor(size, allArrSize, inputArrayRange, ordinarySize, maxDif, overDifPenalisation) {
        this.size = size;
        this.allArrSize = allArrSize;
        this.inputArrayRange = inputArrayRange;
        this.ordinarySize = ordinarySize;
        this.maxDif = maxDif;
        this.overDifPenalisation = overDifPenalisation;

        this.allArr = [];
        this.solutionMap = new Map();
        for (let i = 0; i < allArrSize; i++) {
            let subarr = [];
            for (let j = 0; j < size; j++) {
                subarr.push(Math.round(Math.random() * ordinarySize));
            }
            this.allArr.push(subarr);
        }

        this.temperature = 100;
        this.inputArray = [];
        for (let i = 0; i < size; i++) {
            this.inputArray.push(Math.round(Math.random() * inputArrayRange));
        }
    }

    findBest() {
        while (this.temperature > 0) {
            const oldScore = this.countScore(this.solutionMap);
            // console.log(oldScore);
            let newSolution = new Map(this.solutionMap);

            if (this.addNewOrRemove(true)) {
                const newCandidate = Math.floor(Math.random() * this.allArrSize);
                newSolution.set(newCandidate, true);
            } else if (this.addNewOrRemove(false)) {
                const deleteCandidate = Math.floor(Math.random() * this.solutionMap.size);
                Simulation.deleteFromMapByIndex(newSolution, deleteCandidate);
            } else {
                const deleteCandidate = Math.floor(Math.random() * this.solutionMap.size);
                Simulation.deleteFromMapByIndex(newSolution, deleteCandidate);
                const newCandidate = Math.floor(Math.random() * this.allArrSize);
                newSolution.set(newCandidate, true);
            }

            const newScore = this.countScore(newSolution);
            if (newScore < oldScore) {
                this.solutionMap = newSolution;
            } else if ((newScore - oldScore) / newScore < this.temperature / 300) {
                this.solutionMap = newSolution;
            }

            this.temperature -= 0.001;
        }

        console.log(this.countScore(this.solutionMap), 'Negative Score');
        console.log(this.sumTheSolution(this.solutionMap).toString(), 'Solution');
        console.log(this.inputArray.toString(), 'Input array');
        console.log('Solution is built on these inputs:');            
        this.solutionMap.forEach((val, key) => console.log(this.allArr[key].toString()))
    }

    addNewOrRemove(addNew) {
        const sum = this.sumTheSolution(this.solutionMap);

        let dif = 0;
        sum.forEach((val, i) => {
            const curDif = this.inputArray[i] - val;
            if (curDif < -this.maxDif) {
                dif -= 1;
            }
            if (curDif > this.maxDif) {
                dif += 1;
            }
        });

        let chance;
        if (addNew) {
            chance = (dif + this.size - 1) / (this.size * 2);
        } else {
            chance = (-dif + this.size - 1) / (this.size * 2);
        }

        return chance > Math.random();
    }

    countScore(solution) {
        const sum = this.sumTheSolution(solution);

        let dif = 0;
        sum.forEach((val, i) => {
            let curDif = Math.abs(this.inputArray[i] - val);
            if (curDif > this.maxDif) {
                curDif += (curDif - this.maxDif) * this.overDifPenalisation;
            }
            dif += curDif;
        });

        return dif;
    }

    sumTheSolution(solution) {
        const sum = Array(this.size).fill(0);
        solution.forEach((unused, key) => this.allArr[key].forEach((val, i) => sum[i] += val));
        return sum;
    }

    static deleteFromMapByIndex(map, index) {
        let i = 0;
        let toDelete = null;
        map.forEach((val, key) => {
            if (index === i) {
                toDelete = key;
            }
            i++;
        });
        map.delete(toDelete);
    }
}

const simulation = new Simulation(8, 5000, 1000, 100, 40, 100);
simulation.findBest();

你可以玩一些数字来获得你需要的东西(冷却速度,它如何影响概率,构造函数中的一些值等)

关于algorithm - 找到以一定精度求和到给定数组的 K 个数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50930570/

相关文章:

algorithm - 在matlab中查找所有可能的排列/组合以等于特定的和

c# - 子串深度扫描

c++ - 图中的点对点路径

python - 在 numpy 中创建不同大小列表的所有可能组合

algorithm - Scala:如何以所有可能的方式将列表拆分为元组

python - 从组合列表中选择对的最佳策略

python - 根据共性对字符串数组进行分类

c - 乘法的算法复杂度

c++ - 边的顺序在 union 查找中重要吗?

java字符串排列组合查找