贪心算法应用:半导体晶圆生产问题详解
Java中的贪心算法应用:半导体晶圆生产问题详解
1. 问题背景与定义
半导体晶圆生产是芯片制造的核心环节,涉及复杂的生产调度和资源分配问题。在这个领域中,贪心算法常被用于解决以下典型问题:
1.1 晶圆生产调度问题
给定:
- 一组晶圆生产任务,每个任务有处理时间、截止时间和优先级
- 一组可用的生产设备(机器)
- 各种生产约束(如设备准备时间、温度转换时间等)
目标:
- 安排晶圆生产顺序,以优化某些指标(如总完成时间最小、按时完成的任务数最多等)
1.2 问题形式化定义
对于n个晶圆生产任务和m台机器:
- 每个任务j有处理时间p_j,截止时间d_j,权重w_j(表示重要性)
- 每台机器一次只能处理一个任务
- 任务一旦开始不能中断
常见优化目标:
- 最小化最大完成时间(Makespan)
- 最小化总加权完成时间
- 最大化按时完成的任务数
2. 贪心算法基本原理
贪心算法在每一步选择当前看起来最优的选项,希望这种局部最优选择能导致全局最优解。对于调度问题,常见的贪心策略包括:
- 最短处理时间优先(SPT): 优先安排处理时间短的任务
- 最早截止时间优先(EDD): 优先安排截止时间早的任务
- 最高权重优先(WSPT): 优先安排权重高的任务
- 最小松弛时间优先: 优先安排松弛时间(截止时间-剩余处理时间)小的任务
3. Java实现:最小化最大完成时间问题
3.1 问题描述
有n个晶圆生产任务和m台相同机器,如何分配任务到机器上使得所有任务完成时间中的最大值最小。
3.2 贪心策略:最长处理时间优先(LPT)
将任务按处理时间从大到小排序,每次将当前最长任务分配给当前负载最轻的机器。
3.3 Java实现代码
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;public class WaferScheduling {// 表示晶圆任务static class Task {int id;int processingTime;public Task(int id, int processingTime) {this.id = id;this.processingTime = processingTime;}}// 表示机器及其当前负载static class Machine {int id;int load; // 当前机器上所有任务的总处理时间public Machine(int id, int load) {this.id = id;this.load = load;}}/*** 使用LPT贪心算法调度晶圆生产任务* @param tasks 晶圆任务数组* @param m 机器数量* @return 各机器分配的任务列表和最大完成时间*/public static Object[] scheduleTasks(Task[] tasks, int m) {// 1. 按处理时间降序排序Arrays.sort(tasks, new Comparator<Task>() {@Overridepublic int compare(Task t1, Task t2) {return Integer.compare(t2.processingTime, t1.processingTime);}});// 2. 使用优先队列(最小堆)来管理机器负载PriorityQueue<Machine> pq = new PriorityQueue<>(m, new Comparator<Machine>() {@Overridepublic int compare(Machine m1, Machine m2) {return Integer.compare(m1.load, m2.load);}});// 初始化机器for (int i = 0; i < m; i++) {pq.offer(new Machine(i, 0));}// 3. 分配任务@SuppressWarnings("unchecked")java.util.List<Task>[] assignments = new java.util.ArrayList[m];for (int i = 0; i < m; i++) {assignments[i] = new java.util.ArrayList<>();}for (Task task : tasks) {Machine machine = pq.poll();machine.load += task.processingTime;assignments[machine.id].add(task);pq.offer(machine);}// 4. 计算最大完成时间int maxLoad = 0;while (!pq.isEmpty()) {maxLoad = Math.max(maxLoad, pq.poll().load);}return new Object[]{assignments, maxLoad};}public static void main(String[] args) {// 示例:5个晶圆生产任务,3台机器Task[] tasks = {new Task(1, 5), // 任务ID=1,处理时间=5new Task(2, 3),new Task(3, 7),new Task(4, 2),new Task(5, 4)};int m = 3; // 3台机器Object[] result = scheduleTasks(tasks, m);@SuppressWarnings("unchecked")java.util.List<Task>[] assignments = (java.util.List<Task>[]) result[0];int maxCompletionTime = (int) result[1];System.out.println("最大完成时间: " + maxCompletionTime);System.out.println("任务分配情况:");for (int i = 0; i < m; i++) {System.out.print("机器" + (i+1) + ": ");for (Task t : assignments[i]) {System.out.print("任务" + t.id + "(时间=" + t.processingTime + ") ");}System.out.println("=> 总时间: " + assignments[i].stream().mapToInt(t -> t.processingTime).sum());}}
}
3.4 代码解析
- Task类:表示晶圆生产任务,包含任务ID和处理时间
- Machine类:表示生产机器,跟踪当前负载
- scheduleTasks方法:
- 将任务按处理时间降序排序
- 使用最小堆优先队列管理机器,总是选择当前负载最小的机器
- 将每个任务分配给当前负载最轻的机器
- 计算并返回最大完成时间
- main方法:演示如何使用该算法
3.5 算法分析
-
时间复杂度:
- 排序:O(n log n)
- 优先队列操作:每次插入和删除O(log m),共n次 → O(n log m)
- 总时间复杂度:O(n log n + n log m) = O(n log n)(因为n通常远大于m)
-
近似比:LPT算法是4/3-近似的,即最坏情况下解不超过最优解的4/3倍
4. Java实现:最大化按时完成任务数问题
4.1 问题描述
给定n个晶圆生产任务和1台机器,每个任务有处理时间和截止时间,如何安排任务顺序使按时完成的任务数最多。
4.2 贪心策略:最早截止时间优先(EDD)
按截止时间从早到晚排序,依次尝试将任务加入调度,如果加入后会导致某些任务超期,则舍弃当前任务。
4.3 Java实现代码
import java.util.Arrays;
import java.util.Comparator;
import java.util.ArrayList;
import java.util.List;public class WaferDeadlineScheduling {static class Task {int id;int processingTime;int deadline;public Task(int id, int processingTime, int deadline) {this.id = id;this.processingTime = processingTime;this.deadline = deadline;}}/*** 最大化按时完成的任务数* @param tasks 任务数组* @return 按时完成的任务列表和数量*/public static Object[] maximizeOnTimeTasks(Task[] tasks) {// 1. 按截止时间升序排序Arrays.sort(tasks, new Comparator<Task>() {@Overridepublic int compare(Task t1, Task t2) {return Integer.compare(t1.deadline, t2.deadline);}});List<Task> scheduled = new ArrayList<>();int currentTime = 0;// 2. 依次尝试安排任务for (Task task : tasks) {if (currentTime + task.processingTime <= task.deadline) {scheduled.add(task);currentTime += task.processingTime;}}return new Object[]{scheduled, scheduled.size()};}public static void main(String[] args) {// 示例:5个晶圆生产任务Task[] tasks = {new Task(1, 3, 5), // 任务ID=1,处理时间=3,截止时间=5new Task(2, 2, 4),new Task(3, 1, 7),new Task(4, 4, 8),new Task(5, 2, 6)};Object[] result = maximizeOnTimeTasks(tasks);@SuppressWarnings("unchecked")List<Task> scheduled = (List<Task>) result[0];int count = (int) result[1];System.out.println("按时完成的任务数: " + count);System.out.println("按时完成的任务:");int time = 0;for (Task t : scheduled) {System.out.println("任务" + t.id + ": 开始=" + time + ", 完成=" + (time + t.processingTime) + ", 截止=" + t.deadline);time += t.processingTime;}}
}
4.4 代码解析
- Task类:增加了截止时间属性
- maximizeOnTimeTasks方法:
- 按截止时间升序排序
- 依次尝试安排任务,如果加入后不会导致超期则接受
- main方法:演示算法使用
4.5 算法分析
- 时间复杂度:主要由排序决定,O(n log n)
- 最优性:对于单机问题,EDD策略可以最大化按时完成的任务数
5. Java实现:最小化总加权完成时间问题
5.1 问题描述
给定n个晶圆生产任务和1台机器,每个任务有处理时间、权重,如何安排顺序使Σw_jC_j最小,其中C_j是任务j的完成时间。
5.2 贪心策略:最高权重/处理时间比优先(WSPT)
按w_j/p_j的比值从高到低排序,依次安排任务。
5.3 Java实现代码
import java.util.Arrays;
import java.util.Comparator;public class WeightedWaferScheduling {static class Task {int id;int processingTime;int weight;double ratio;public Task(int id, int processingTime, int weight) {this.id = id;this.processingTime = processingTime;this.weight = weight;this.ratio = (double)weight / processingTime;}}/*** 最小化总加权完成时间* @param tasks 任务数组* @return 任务顺序和总加权完成时间*/public static Object[] minimizeWeightedCompletionTime(Task[] tasks) {// 1. 按权重/处理时间比降序排序Arrays.sort(tasks, new Comparator<Task>() {@Overridepublic int compare(Task t1, Task t2) {return Double.compare(t2.ratio, t1.ratio);}});// 2. 计算总加权完成时间int currentTime = 0;int totalWeightedCompletionTime = 0;for (Task task : tasks) {currentTime += task.processingTime;totalWeightedCompletionTime += task.weight * currentTime;}return new Object[]{tasks, totalWeightedCompletionTime};}public static void main(String[] args) {// 示例:4个晶圆生产任务Task[] tasks = {new Task(1, 3, 10), // 任务ID=1,处理时间=3,权重=10new Task(2, 4, 12),new Task(3, 2, 8),new Task(4, 5, 15)};Object[] result = minimizeWeightedCompletionTime(tasks);Task[] scheduled = (Task[]) result[0];int total = (int) result[1];System.out.println("总加权完成时间: " + total);System.out.println("任务顺序:");int time = 0;for (Task t : scheduled) {System.out.println("任务" + t.id + ": 开始=" + time + ", 完成=" + (time + t.processingTime) + ", 权重=" + t.weight);time += t.processingTime;}}
}
5.4 代码解析
- Task类:增加了权重属性和权重/处理时间比
- minimizeWeightedCompletionTime方法:
- 按权重/处理时间比降序排序
- 计算总加权完成时间
- main方法:演示算法使用
5.5 算法分析
- 时间复杂度:O(n log n)
- 最优性:WSPT规则对于单机问题可以得到最优解
6. 更复杂的晶圆生产调度问题
实际半导体制造中的调度问题更加复杂,需要考虑:
6.1 多阶段流水线调度
晶圆生产通常包含多个工艺阶段(光刻、蚀刻、沉积等),每个阶段可能有多个并行机器。
6.2 带准备时间的调度
更换产品类型时需要准备时间(温度调整、模具更换等)
6.3 Java实现:带序列相关准备时间的调度
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;public class WaferSetupTimeScheduling {static class Task {int id;int processingTime;int family; // 任务所属的家族(相同家族不需要准备时间)public Task(int id, int processingTime, int family) {this.id = id;this.processingTime = processingTime;this.family = family;}}/*** 带序列相关准备时间的调度* @param tasks 任务数组* @param setupTime 不同家族间的准备时间* @return 调度顺序和总完成时间*/public static Object[] scheduleWithSetupTimes(Task[] tasks, int setupTime) {// 1. 按家族分组,家族内按SPT排序Arrays.sort(tasks, new Comparator<Task>() {@Overridepublic int compare(Task t1, Task t2) {if (t1.family != t2.family) {return Integer.compare(t1.family, t2.family);}return Integer.compare(t1.processingTime, t2.processingTime);}});// 2. 计算总完成时间int totalCompletionTime = 0;int currentTime = 0;int lastFamily = -1;for (Task task : tasks) {if (task.family != lastFamily) {currentTime += setupTime;lastFamily = task.family;}currentTime += task.processingTime;totalCompletionTime += currentTime;}return new Object[]{tasks, totalCompletionTime};}public static void main(String[] args) {// 示例:6个晶圆生产任务,属于3个不同家族Task[] tasks = {new Task(1, 3, 1), // 任务ID=1,处理时间=3,家族=1new Task(2, 2, 2),new Task(3, 4, 1),new Task(4, 1, 3),new Task(5, 5, 2),new Task(6, 2, 1)};int setupTime = 2; // 家族切换需要2单位时间Object[] result = scheduleWithSetupTimes(tasks, setupTime);Task[] scheduled = (Task[]) result[0];int total = (int) result[1];System.out.println("总完成时间: " + total);System.out.println("任务顺序:");int time = 0;int lastFamily = -1;for (Task t : scheduled) {if (t.family != lastFamily) {time += setupTime;System.out.println("切换到家族" + t.family + ", 准备时间=" + setupTime);lastFamily = t.family;}System.out.println("任务" + t.id + ": 开始=" + time + ", 完成=" + (time + t.processingTime) + ", 家族=" + t.family);time += t.processingTime;}}
}
7. 性能优化与工业实践
在实际半导体制造中,还需要考虑以下优化:
7.1 启发式改进
- 迭代贪心:多次运行贪心算法,每次移除部分任务后重新调度
- 局部搜索:对贪心解进行邻域搜索改进
7.2 混合方法
- 贪心算法与动态规划结合
- 贪心算法与整数规划结合
7.3 并行计算
利用多线程加速贪心算法的执行:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;public class ParallelWaferScheduling {static class ParallelLPT extends RecursiveAction {private static final int THRESHOLD = 1000;private Task[] tasks;private int start, end;private Machine[] machines;public ParallelLPT(Task[] tasks, int start, int end, Machine[] machines) {this.tasks = tasks;this.start = start;this.end = end;this.machines = machines;}@Overrideprotected void compute() {if (end - start <= THRESHOLD) {// 直接处理小任务for (int i = start; i < end; i++) {assignTask(tasks[i]);}} else {// 分而治之int mid = (start + end) >>> 1;invokeAll(new ParallelLPT(tasks, start, mid, machines),new ParallelLPT(tasks, mid, end, machines));}}private void assignTask(Task task) {// 找到负载最轻的机器Machine lightest = machines[0];for (Machine m : machines) {if (m.load < lightest.load) {lightest = m;}}lightest.load += task.processingTime;}}// 其他代码与前面示例类似...
}
8. 总结
贪心算法在半导体晶圆生产调度中有着广泛应用,尽管它不能保证总是得到最优解,但在许多情况下可以提供高质量的近似解,且计算效率高。Java作为一种面向对象的语言,非常适合实现这些调度算法,通过合理设计类和接口,可以构建灵活、可扩展的调度系统。
关键要点:
- 不同调度目标需要不同的贪心策略
- 贪心算法实现简单、运行快速
- 实际应用中常需要结合其他技术进行改进
- Java的面向对象特性和丰富类库为算法实现提供了良好支持
在实际半导体制造系统中,通常会结合多种调度算法,并根据实时生产数据进行动态调整,以达到最佳的生产效率和资源利用率。