贪心算法应用:线性规划贪心舍入问题详解

article/2025/6/24 13:53:38

在这里插入图片描述

贪心算法应用:线性规划贪心舍入问题详解

贪心算法是一种在每一步选择中都采取当前状态下最优的选择,从而希望导致结果是全局最优的算法策略。在线性规划问题中,贪心算法特别是贪心舍入技术有着广泛的应用。下面我将全面详细地讲解这一主题。

一、线性规划与贪心算法基础

1.1 线性规划简介

线性规划(Linear Programming, LP)是数学优化中的一个重要领域,它研究的是在给定的线性约束条件下,如何最大化或最小化一个线性目标函数。

标准形式的线性规划问题可以表示为:

最大化 cᵀx
约束条件: Ax ≤ bx ≥ 0

其中x是决策变量向量,c是目标系数向量,A是约束矩阵,b是约束向量。

1.2 贪心算法基本概念

贪心算法的核心思想是:

  1. 将问题分解为若干子问题
  2. 对每个子问题做出局部最优选择
  3. 将这些局部最优解组合起来形成全局解

贪心算法不是对所有问题都能得到最优解,但对于某些特定问题(如具有贪心选择性质的问题)非常有效。

二、贪心舍入技术详解

2.1 什么是贪心舍入

贪心舍入(Greedy Rounding)是一种将线性规划松弛问题的分数解转换为整数解的技术。基本步骤是:

  1. 求解线性规划松弛问题(允许变量取分数值)
  2. 对得到的分数解进行舍入(通常是向上或向下取整)
  3. 验证舍入后的解是否满足所有约束条件

2.2 贪心舍入的基本方法

2.2.1 简单舍入
public int simpleRound(double fractionalValue) {return (int) Math.round(fractionalValue);
}
2.2.2 确定性舍入
public int deterministicRound(double fractionalValue, double threshold) {return fractionalValue >= threshold ? 1 : 0;
}
2.2.3 随机舍入
public int randomizedRound(double fractionalValue) {Random rand = new Random();double randomValue = rand.nextDouble();return randomValue <= fractionalValue ? 1 : 0;
}

2.3 贪心舍入的数学基础

贪心舍入的有效性依赖于以下数学原理:

  1. 线性期望:E[round(x)] = x
  2. 集中不等式:如切尔诺夫界(Chernoff Bound),用于分析舍入后约束被违反的概率

对于0-1整数规划问题,设x*是LP松弛的最优解,则随机舍入得到的解X满足:

  • E[Xᵢ] = x*ᵢ
  • 对于任何约束∑aᵢxᵢ ≤ b,有Pr[∑aᵢXᵢ > (1+δ)b] ≤ exp(-δ²b/(3∑aᵢx*ᵢ))

三、经典问题与应用实例

3.1 集合覆盖问题(Set Cover)

问题描述:

给定全集U和U的子集族S={S₁,S₂,…,Sₙ},找到S的最小子集C,使得C中所有子集的并等于U。

LP松弛与贪心舍入实现:
public class SetCover {public static List<Integer> greedySetCover(List<Set<Integer>> sets, Set<Integer> universe) {List<Integer> cover = new ArrayList<>();Set<Integer> uncovered = new HashSet<>(universe);while (!uncovered.isEmpty()) {// 选择覆盖最多未覆盖元素的集合int maxCoverIndex = -1;int maxCover = 0;for (int i = 0; i < sets.size(); i++) {if (cover.contains(i)) continue;int currentCover = 0;for (int element : sets.get(i)) {if (uncovered.contains(element)) {currentCover++;}}if (currentCover > maxCover) {maxCover = currentCover;maxCoverIndex = i;}}if (maxCoverIndex == -1) break; // 无法完全覆盖cover.add(maxCoverIndex);uncovered.removeAll(sets.get(maxCoverIndex));}return uncovered.isEmpty() ? cover : null;}// LP舍入版本public static List<Integer> lpRoundingSetCover(List<Set<Integer>> sets, Set<Integer> universe) {// 1. 构造LP问题int n = sets.size();double[] x = new double[n]; // LP解// 这里简化表示,实际需要调用LP求解器// 假设我们已经得到了分数解x[]// 2. 贪心舍入List<Integer> cover = new ArrayList<>();for (int i = 0; i < n; i++) {if (x[i] >= 1.0 / sets.get(i).size()) { // 基于元素频率的舍入阈值cover.add(i);}}// 3. 验证覆盖Set<Integer> covered = new HashSet<>();for (int i : cover) {covered.addAll(sets.get(i));}return covered.containsAll(universe) ? cover : null;}
}

3.2 背包问题(Knapsack)

分数背包问题的贪心解法:
public class FractionalKnapsack {static class Item {int value;int weight;double ratio;Item(int v, int w) {value = v;weight = w;ratio = (double)v / w;}}public static double greedyFractionalKnapsack(int capacity, Item[] items) {// 按价值密度排序Arrays.sort(items, (a, b) -> Double.compare(b.ratio, a.ratio));double totalValue = 0;int remaining = capacity;for (Item item : items) {if (remaining <= 0) break;int take = Math.min(item.weight, remaining);totalValue += take * item.ratio;remaining -= take;}return totalValue;}// 0-1背包问题的贪心舍入近似解public static int greedy01Knapsack(int capacity, Item[] items) {// 先计算分数解Arrays.sort(items, (a, b) -> Double.compare(b.ratio, a.ratio));int greedyValue = 0;int remaining = capacity;int maxSingle = 0;for (Item item : items) {if (item.weight <= remaining) {greedyValue += item.value;remaining -= item.weight;}if (item.value > maxSingle) {maxSingle = item.value;}}// 返回分数解和最大单物品的较大者return Math.max(greedyValue, maxSingle);}
}

3.3 调度问题(Scheduling)

贪心舍入在调度问题中的应用:
public class Scheduling {static class Job {int processingTime;int deadline;double x; // LP解中的分数值Job(int p, int d) {processingTime = p;deadline = d;}}public static List<Integer> scheduleJobs(List<Job> jobs) {// 1. 构造LP松弛问题并求解(简化表示)// 假设已经得到了每个作业的x值(被调度的概率)// 2. 随机舍入List<Integer> schedule = new ArrayList<>();Random rand = new Random();for (int i = 0; i < jobs.size(); i++) {if (rand.nextDouble() <= jobs.get(i).x) {schedule.add(i);}}// 3. 验证调度可行性(处理时间不超过期限)int totalTime = 0;for (int i : schedule) {totalTime += jobs.get(i).processingTime;if (totalTime > jobs.get(i).deadline) {return null; // 不可行调度}}return schedule;}// 另一种确定性舍入方法public static List<Integer> deterministicSchedule(List<Job> jobs) {jobs.sort((a, b) -> Double.compare(b.x, a.x)); // 按x值降序List<Integer> schedule = new ArrayList<>();int totalTime = 0;for (int i = 0; i < jobs.size(); i++) {if (totalTime + jobs.get(i).processingTime <= jobs.get(i).deadline) {schedule.add(i);totalTime += jobs.get(i).processingTime;}}return schedule;}
}

四、Java实现细节与优化

4.1 使用LP求解器

在实际应用中,我们需要使用专业的LP求解器来获得分数解。Java中可以使用以下库:

  1. Apache Commons Math - 提供基本的线性规划支持
  2. ojAlgo - 纯Java的优化库
  3. JOptimizer - 面向凸优化的Java库
使用ojAlgo的示例:
import org.ojalgo.optimisation.ExpressionsBasedModel;
import org.ojalgo.optimisation.Variable;public class LPRoundingExample {public static void main(String[] args) {// 创建模型ExpressionsBasedModel model = new ExpressionsBasedModel();// 定义变量Variable x = model.addVariable("x").lower(0).upper(1);Variable y = model.addVariable("y").lower(0).upper(1);// 添加约束model.addExpression("约束1").set(x, 1).set(y, 2).upper(5);model.addExpression("约束2").set(x, 3).set(y, 1).upper(10);// 设置目标:最大化 x + ymodel.setExpression(model.addExpression("目标").set(x, 1).set(y, 1).weight(1));// 求解model.maximise();// 获取结果System.out.println("x = " + x.getValue());System.out.println("y = " + y.getValue());// 贪心舍入int roundedX = x.getValue().doubleValue() > 0.5 ? 1 : 0;int roundedY = y.getValue().doubleValue() > 0.5 ? 1 : 0;System.out.println("舍入后: x = " + roundedX + ", y = " + roundedY);}
}

4.2 性能优化技巧

  1. 预处理:在舍入前对变量进行排序或分组
  2. 增量舍入:逐步舍入变量并检查约束
  3. 后处理:舍入后进行局部搜索改进解质量
  4. 并行舍入:对独立变量进行并行舍入
并行舍入示例:
public class ParallelRounding {public static int[] parallelRound(double[] fractionalSolution) {int n = fractionalSolution.length;int[] rounded = new int[n];IntStream.range(0, n).parallel().forEach(i -> {rounded[i] = fractionalSolution[i] > 0.5 ? 1 : 0;});return rounded;}
}

五、理论保证与近似比分析

贪心舍入算法的质量通常用近似比来衡量:

5.1 集合覆盖问题的近似比

贪心算法对集合覆盖问题的近似比为Hₙ,其中Hₙ是第n个调和数(Hₙ ≈ ln n + 0.577)。

LP舍入方法可以达到O(log n)的近似比。

5.2 顶点覆盖问题的近似比

对于顶点覆盖问题:

  • 简单贪心算法的近似比为2
  • LP舍入随机算法的期望近似比也是2

5.3 背包问题的近似比

分数背包问题的贪心算法是精确的(近似比1),而0-1背包问题的贪心舍入算法近似比为2。

六、高级主题与变种

6.1 迭代舍入(Iterative Rounding)

比基本贪心舍入更复杂的技术,逐步舍入变量并重新求解LP:

public class IterativeRounding {public static int[] iterativeRounding(double[][] A, double[] b, double[] c) {int n = c.length;int[] rounded = new int[n];Arrays.fill(rounded, -1); // -1表示未舍入while (true) {// 1. 构造并求解剩余LP// 这里简化表示,实际需要处理固定变量// 2. 选择最接近整数的变量进行舍入int toRound = -1;double maxDiff = 0;for (int i = 0; i < n; i++) {if (rounded[i] != -1) continue;double diff = Math.abs(x[i] - 0.5);if (diff > maxDiff) {maxDiff = diff;toRound = i;}}if (toRound == -1) break; // 所有变量已处理// 3. 舍入最接近整数的变量rounded[toRound] = x[toRound] > 0.5 ? 1 : 0;}return rounded;}
}

6.2 依赖舍入(Dependent Rounding)

处理变量间存在依赖关系的情况,保持某些相关性:

public class DependentRounding {public static int[] dependentRound(double[] x) {int n = x.length;int[] rounded = new int[n];Random rand = new Random();// 确保某些和保持不变double sum = 0;for (double val : x) sum += val;int targetSum = (int) Math.round(sum);// 实现依赖舍入逻辑// 这里简化表示,实际需要更复杂的处理double accum = 0;for (int i = 0; i < n; i++) {accum += x[i];if (accum >= targetSum) {rounded[i] = 1;accum -= 1;} else {rounded[i] = 0;}}return rounded;}
}

七、实际应用中的注意事项

  1. 数值稳定性:处理浮点数精度问题
  2. 可行性检查:舍入后必须验证约束是否满足
  3. 多次舍入:可以尝试多次舍入取最好结果
  4. 混合策略:结合多种舍入技术
  5. 问题特定启发式:针对特定问题设计定制舍入规则

八、完整案例:设施选址问题

让我们看一个完整的贪心舍入应用案例——设施选址问题(Facility Location)。

问题描述:

给定一组客户和潜在的设施位置,每个设施有开设成本,客户到设施的连接有服务成本,目标是选择开设哪些设施并分配客户,以最小化总成本(开设成本+服务成本)。

Java实现:

import java.util.*;public class FacilityLocation {static class Facility {int id;double openingCost;double x; // LP解中的开设分数Facility(int id, double cost) {this.id = id;this.openingCost = cost;}}static class Customer {int id;Map<Integer, Double> connectionCosts; // facilityId -> costdouble[] y; // LP解中的连接分数Customer(int id, int facilityCount) {this.id = id;this.connectionCosts = new HashMap<>();this.y = new double[facilityCount];}}public static Solution greedyRoundingFLP(List<Facility> facilities, List<Customer> customers) {// 1. 假设已经求解了LP松弛,得到了设施的x值和客户的y值// 2. 按x值降序排序设施facilities.sort((a, b) -> Double.compare(b.x, a.x));// 3. 贪心舍入Set<Integer> openedFacilities = new HashSet<>();Map<Integer, Integer> assignments = new HashMap<>();double totalCost = 0;// 第一轮:舍入x ≥ 1/2的设施for (Facility f : facilities) {if (f.x >= 0.5) {openedFacilities.add(f.id);totalCost += f.openingCost;}}// 分配客户到已开设的设施for (Customer c : customers) {double minCost = Double.MAX_VALUE;int bestFacility = -1;for (Facility f : facilities) {if (openedFacilities.contains(f.id)) {double cost = c.connectionCosts.get(f.id);if (cost < minCost) {minCost = cost;bestFacility = f.id;}}}if (bestFacility != -1) {assignments.put(c.id, bestFacility);totalCost += minCost;}}// 检查是否所有客户都被服务boolean allServed = assignments.size() == customers.size();// 如果还有未服务的客户,进行第二轮舍入if (!allServed) {// 可以尝试其他舍入策略或启发式// 这里简化处理for (Customer c : customers) {if (!assignments.containsKey(c.id)) {// 找到y值最大的连接double maxY = 0;int bestF = -1;for (Facility f : facilities) {if (c.y[f.id] > maxY) {maxY = c.y[f.id];bestF = f.id;}}if (bestF != -1) {// 开设该设施(即使x值较小)if (!openedFacilities.contains(bestF)) {openedFacilities.add(bestF);totalCost += facilities.get(bestF).openingCost;}assignments.put(c.id, bestF);totalCost += c.connectionCosts.get(bestF);}}}}return new Solution(openedFacilities, assignments, totalCost);}static class Solution {Set<Integer> openedFacilities;Map<Integer, Integer> assignments; // customerId -> facilityIddouble totalCost;Solution(Set<Integer> opened, Map<Integer, Integer> assign, double cost) {openedFacilities = opened;assignments = assign;totalCost = cost;}}
}

九、测试与验证

编写测试用例验证贪心舍入算法的正确性和性能:

import org.junit.Test;
import static org.junit.Assert.*;public class GreedyRoundingTest {@Testpublic void testSetCover() {Set<Integer> universe = Set.of(1, 2, 3, 4, 5);List<Set<Integer>> sets = List.of(Set.of(1, 2, 3),Set.of(2, 4),Set.of(3, 4),Set.of(4, 5));List<Integer> cover = SetCover.greedySetCover(sets, universe);assertNotNull(cover);Set<Integer> covered = new HashSet<>();for (int i : cover) {covered.addAll(sets.get(i));}assertEquals(universe, covered);}@Testpublic void testKnapsackRounding() {FractionalKnapsack.Item[] items = {new FractionalKnapsack.Item(60, 10),new FractionalKnapsack.Item(100, 20),new FractionalKnapsack.Item(120, 30)};int capacity = 50;double fractionalValue = FractionalKnapsack.greedyFractionalKnapsack(capacity, items);int roundedValue = FractionalKnapsack.greedy01Knapsack(capacity, items);assertTrue(roundedValue <= fractionalValue);assertTrue(roundedValue >= fractionalValue / 2); // 近似比保证}
}

十、总结

贪心算法在线性规划舍入问题中的应用是一个强大而灵活的技术,关键点包括:

  1. LP松弛:首先求解问题的线性规划松弛
  2. 舍入策略:根据问题特性设计合适的舍入规则
  3. 可行性验证:确保舍入后的解满足所有约束
  4. 近似保证:分析算法的近似比和理论保证
  5. 实现优化:利用Java特性和库提高效率和代码质量

贪心舍入技术在实际应用中需要根据具体问题进行调整和优化,结合理论分析和工程实践才能得到最佳效果。

更多资源:

https://www.kdocs.cn/l/cvk0eoGYucWA

本文发表于【纪元A梦】!


http://www.hkcw.cn/article/NVhEKCDdsi.shtml

相关文章

【LLM vs Agent】从语言模型到智能体,人工智能迈出的关键一步

目录 一、什么是 LLM&#xff1f;语言的天才&#xff0c;思维的起点 ✅ 特点小结&#xff1a; 二、什么是 Agent&#xff1f;智能的执行者&#xff0c;自主的决策者 ✅ 特点小结&#xff1a; 三、LLM 与 Agent 的关系&#xff1a;是工具&#xff0c;更是大脑 四、案例实战…

esp32 platformio lvgl_gif的使用和踩坑情况

踩坑一&#xff1a;白屏 不显示 开启custom内存这里 以及 gif显示 踩坑二&#xff1a;只有图片 不显示动态 没开时间 要打开这个时基 网站:转成c数组的官方网站 Image Converter — LVGL 以及显示代码&#xff1a;在setup里面调用 LV_IMG_DECLARE(my_gif);lv_obj_t *img;img…

华南沿海等地较强降雨持续 局地伴有强对流天气

今明两天(6月3日至4日),我国降雨主要出现在云南和华南沿海、东北地区等地,局地还可能伴有强对流天气。随着高压脊东移,北方大部气温逐渐升高,华北、黄淮等地高温天气将发展增多,南方多地5日起也将加入高温行列。昨天,冷空气南下导致南方强降雨区域南压至华南和云南一带…

黄金白银原油大涨 市场热度持续升温

黄金原油市场收盘大涨,贵金属与能源品种展现出强劲涨势。COMEX黄金期货和白银期货分别以显著涨幅收盘,其中黄金期货收涨2.74%,报3406.4美元/盎司;白银期货收涨5.76%,报34.93美元/盎司。原油市场同样表现抢眼,WTI原油期货收于每桶62.52美元,上涨1.73美元,涨幅为2.85%;布…

“蹦床外长”当选联大主席 外交新挑战

蹦床外长当选联大主席!一夜醒来,联合国大会发生了重大变化。周一下午,在纽约联合国总部举行的全体会议上,被称为“蹦床外长”的安娜莱娜贝尔伯克以167票当选为联合国大会主席。这距离她离开德国外长之位不到一个月。这份工作需要相当强的外交技巧,一些批评人士认为这是贝尔…

python依赖库管理工具

软件名称&#xff1a;Python依赖管理工具 版本&#xff1a;1.0适用系统&#xff1a;Windows 7/10/11&#xff0c;Python 项目环境管理辅助工具 开发语言&#xff1a;Python 3 PyQt5 开发者&#xff1a;Thebzk 软件功能简介&#xff1a; 本工具是专为Python 开发者设计的图…

labuladong刷题之前缀和与差分数组技巧(空间换时间)

Q1题目链接&#xff1a;https://leetcode.com/problems/range-sum-query-immutable/ leetcode 303 看到题目的第一思路&#xff1a; 代码随想录之后的想法和总结&#xff1a; 如何用 prefix 查询区间和&#xff1f; 如果你想查询&#xff1a;nums[left] nums[left1] ... …

基于爬取的典籍数据重新设计前端界面

1.BooksView(书籍列表页) 2.ClassicsView&#xff08;目录页&#xff09; 3.管理员端

Origin将杂乱的分组散点图升级为美观的带颜色映射的气泡图

图形初步了解 带颜色映射的气泡图&#xff0c;是一种含有三个变量的高级散点图&#xff0c;前两个变量分别为横纵坐标&#xff0c;第三个变量通过气泡的大小和颜色来呈现。在视觉上辅助读者直观地比较三个变量的关系。 在本例图中&#xff0c;通过pH、Group和Solubility三个指…

初识Linux指令(笔记2)

&#xff08;1&#xff09;man指令&#xff08;重要&#xff09; Linux的命令有很多参数&#xff0c;我们不可能全记住&#xff0c;我们可以通过查看联机手册获取帮助。访问Linux手册页的命令是man 语法: man [选项] 命令 &#xff08;2&#xff09;cp指令&#xff08;重要&…

w374预报名管理系统设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

JAVA-springboot整合Mybatis

SpringBoot从入门到精通-第15章 MyBatis框架 学习MyBatis心路历程 2022年学习java基础时候&#xff0c;想着怎么使用java代码操作数据库&#xff0c;咨询了项目上开发W同事&#xff0c;没有引用框架&#xff0c;操作数据库很麻烦&#xff0c;就帮我写好多行代码&#xff0c;就…

Torch Geometric GCN训练心得

我训练的是直推式的图卷积神经网络GCN 对于直推式GCN训练&#xff0c;控制参数量是非常重要的&#xff0c;我的网络大小30个节点&#xff1a; 不像归纳式学习训练&#xff0c;可以通过提升样本数量来承受巨大的模型参数量&#xff0c;直推式学习一次训练的目标就一个样本&…

张雪峰宣布暂停直播两个月 深情感言引共鸣

张雪峰宣布暂停直播两个月 深情感言引共鸣。5月31日晚,有网友发布视频称张雪峰结束了2025届高考季的直播,并宣布将暂停直播两个月。在直播中,张雪峰表示这个行业不容易,因为他触动了太多人的利益,有些事情不能说得太直白。他希望能在8月1日恢复直播,如果不行的话,9月1日…

线程池详细解析(一)

java中的线程池是运用场景最多的并发框架&#xff0c;几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中&#xff0c;合理的使用线程池能够带来三个好处。 1. 降低资源消耗&#xff1a;频繁的创建线程和销毁线程会产生不少的资源上的消耗&#xff0c;通过重…

韩国大选今日开始投票 五候选人竞逐总统

韩国第21届总统大选于当地时间6月3日6时正式开始,全国共设有14295个投票站。未参加提前投票的选民凭身份证件前往指定投票站即可参与投票,投票将于当日20时结束。本次大选共有7位候选人登记,但其中两位宣布退出并支持国民力量党候选人金文洙。因此,选民将从以下五位候选人中…

米奇盖顿:音乐架起中美交流桥梁 纯粹情感共鸣

米奇盖顿在湖南卫视《歌手2025》的舞台上演唱完《If I Were a Boy》后,不禁潸然泪下。这位来自美国得克萨斯州的乡村音乐女歌手,曾在2021年获得格莱美最佳乡村独唱表演提名,成为首位提名该奖项的黑人女歌手。在中国观众毫无保留的情感反馈中,她感受到了一种久违的真诚。在美…

字节跳动社招面经 —— BSP驱动工程师(6)

接前一篇文章&#xff1a;字节跳动社招面经 —— BSP驱动工程师&#xff08;5&#xff09; 本文内容参考&#xff1a; 嵌入式硬件平台修改启动地址-CSDN博客 特此致谢&#xff01; 上一回开始针对于“嵌入式充电站”发的一篇文章字节跳动社招面经——BSP驱动工程师中的面试题…

检索器组件深入学习与使用技巧 VectorStoreRetriever 检索器

1. VectorStoreRetriever 检索器 VectorStoreRetriever 是 BaseRetriever 的子类&#xff0c;这是一个专门针对向量数据库的基础检索器&#xff0c;在 VectorStoreRetriever 的内部实现了 _get_relevant_documents() 方法&#xff0c;还定义了单独的属性&#xff1a; vectors…

深度学习pycharm debug

深度学习中&#xff0c;Debug 是定位并解决代码逻辑错误&#xff08;如张量维度不匹配&#xff09;、训练异常&#xff08;如 Loss 波动&#xff09;、数据问题&#xff08;如标签错误&#xff09;的关键手段&#xff0c;通过打印维度、可视化梯度等方法确保模型正常运行、优化…