给出一种作业调度方案,使所给的 n 个作业在尽可能短的时间内由 m 台机器加工处理完成。
约定,每个作业均可在任何一台机器上加工处理,但未完工前不允许中断处理。作业不能拆分成更小的子作业。
设有n个独立的作业{1,2,…,n},由m台相同的机器进行加工处理。作业i所需的处理时间为ti。现约定,任何作业可以在任何一台机器上加工处理,但未完工前不允许中断处理,任何作业不能拆分成更小的子作业。
多机调度问题要求给出一种作业调度方案,使所给的n个作业在尽可能短的时间内由m台机器加工处理完成。这个问题是一个NP完全问题,到目前为止还没有有效的解法。有时,这类问题用贪心选择策略可以设计出较好的近似算法。
采用最长处理时间作业优先的贪心选择策略,可以设计出解多机调度问题的较好的近似算法。按此策略,当n≤m时,只要将机器i的[0,t]时间区间分配给作业i即可。当n>m时,先将n个作业依其所需的处理时间从大到小排序,再依此顺序将作业分配给空闲的机器。
1、把作业按加工所用的时间从大到小排序
2、如果作业数目比机器的数目少或相等,则直接把作业分配下去
3、 如果作业数目比机器的数目多,则每台机器上先分配一个作业,如下的作业分配时,是选那个表头上 s 最小的链表加入新作业。
main.cpp:
// Project1: 矩阵连乘法
#include"Basic1.h"
int main() {
//原始数据
int n = 7, //作业数
m = 3, //机器数
maxtime;//最大作业时间
int timeJob[] = { 2,14,4,16,6,5,3 };
Job a[10];
Header N[10];
Console(timeJob, n);
//初始化作业数据
for (int i = 0; i < n; i++) {
a[i].ID = i + 1; //从1开始
a[i].time = timeJob[i];
}
maxtime = Dispatch(a, N, n, m);
cout << "\nThe shortest time required for all jobs to be completed is: "
<< maxtime << endl;
}
Basic1.h:
#pragma once
#ifndef __BASIC1__
#define __BASIC1__
#include
#include
#include
using namespace std;
// a[] : 作业数组
// M[] : 定义作业数组
// s值 : 记录总时间
//数据结构定义:
typedef struct Job {
//作业的信息,用于定义作业数组a[]
int ID;
int time;
}Job;
typedef struct JobNode {
//作业节点信息,记录每个机器运行的作业
int ID;
int time;
JobNode* next;
}JobNode, * pJobNode;
typedef struct Header {
//机器头节点,其中s为该机器需要运行作业的总时间,用于定义作业数组M[]
int s;
pJobNode next;
}Header, pHeader;
//功能函数定义
int SelectMin(Header* N, int m) {
//读取头节点的s值,得出运行时间最小的机器下标并返回
int k = 0;
for (int i = 1; i < m; i++)
if (N[i].s < N[k].s)
k = i;
return k;
}
int SelectMax(Header* N, int m) {
//用于最后得出所有作业完成至少需要多少时间的函数
//也是对头节点的s进行比较
int k = 0;
for (int i = 1; i < m; i++)
if (N[i].s > N[k].s)
k = i;
return k;
}
void Sort(Job* a, int n) {
//采用冒泡排序策略
Job temp;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
if (a[i].time < a[j].time) {
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
void Console(int t[], int n) {
cout << "The default job data is as follows: \n"
<< setw(20) << "#Job ID :";
for (int i = 0; i < n; i++) { cout << setw(3) << i + 1; }
cout << endl << setw(20) << "#Time consuming :";
for (int i = 0; i < n; i++) { cout << setw(3) << t[i]; }
cout << endl << endl;
}
//打印对应机器的作业数
void Print(Header* N, int m) {
JobNode* p;
for (int i = 0; i < m; i++) {
cout << "The job ID of the 'No." << i + 1 << "' machine running is: ";
p = N[i].next;
while (p) {
cout << p->ID << " ";
p = p->next;
}
cout << endl;
}
}
//核心作业调度函数
int Dispatch(Job* a, Header* N, int n, int m) {
int min, weight;
JobNode* p, * q; //对应的链表指针的中间变量
for (int i = 0; i < n; i++) {
N[i].s = 0; N[i].next = NULL; //将机器的总时间和作业指针初始化
}
Sort(a, n); //将a[]作业数组从小到大进行排序
if (n <= m)
return a[0].time; //作业数小于机器数的情况,直接取排好序的作业数组第一个元素
for (int j = 0; j < n; j++) {
//向堆区要块sizeof(int) * N 这么大的空间
q = (pJobNode)malloc(sizeof(JobNode)); //给中间变量分配空间
min = SelectMin(N, m); //得出作业时间总和最小的机器的下标
N[min].s += a[j].time; //累计机器的总耗时
q->ID = a[j].ID; //将a数组中的元素变成作业节点数据
q->time = a[j].time;
q->next = NULL; //尾指针置空
if (!N[min].next) //M未进行节点插入的情况,加入作业节点到头节点之后
N[min].next = q;
else { //M已有作业节点的情况,加入作业节点到尾部
p = N[min].next;
while (p->next)
p = p->next;
p->next = q;
}
}
Print(N, m); //输出每台机器的运行作业的ID
weight = SelectMax(N, m); //得出最大的s的机器的下标
return N[weight].s; //返回至少需要的时间
}
#endif
在对问题求解时,总是作出在当前看来是最好的选择。也就是说,不从整体上加以考虑,它所作出的仅仅是在某种意义上的局部最优解。
若要用贪心算法求解某问题的整体最优解,必须首先证明贪心思想在该问题的应用结果就是最优解!
很多贪心类型的题目,不是最朴素的贪心,而是需要做一些变化,关键是找到贪心的本质!
(1)贪心策略
首先要确定贪心策略,选择当前看上去最好的一个方案。例如,挑选苹果,如果你认为个大的是最好的,那你每次都从苹果堆中拿一个最大的,作为局部最优解,贪心策略就是选择当前最大的苹果;如果你认为最红的苹果是最好的,那你每次都从苹果堆中拿一个最红的,贪心策略就是选择当前最红的苹果。因此根据求解目标不同,贪心策略也会不同。
(2)局部最优解
根据贪心策略,一步一步地得到局部最优解。例如,第一次选一个最大的苹果放起来,记为a,第二次再从剩下的苹果堆中选择一个最大的苹果放起来,记为a,以此类推。
(3)全局最优解
把所有的局部最优解合成为原来问题的一个最优解(a1,a2,…)。
文档供本人学习笔记使用,仅供参考。