├── readme.md ├── 代码文件 ├── HHA.cpp ├── JMP.cpp ├── PSO.cpp └── readme.txt ├── 基于启发式算法的车间调度问题的三步优化.pdf ├── 基于启发式算法的车间调度问题的三步优化.tex └── 实验用例 ├── readme.txt ├── 国际标准用例 ├── ft06.in ├── ft10.in ├── la01.in ├── la05.in ├── la10.in └── la12.in ├── 学长提供的用例 ├── test1.in ├── test2.in ├── test3.in └── test4.in └── 用例转换器.cpp /readme.md: -------------------------------------------------------------------------------- 1 | ### Proposed three approaches to the Job Shop Problem 2 | 3 | - Basic heuristic way, use a fixed set of heuristic rules to solve the problem. 4 | - Particle swarm algorithm, use particles to represent a solution. 5 | - Generic algorithm & Super heuristc, use chromosome to represent a solution. 6 | - Implemented and bechmarked in C++. 7 | -------------------------------------------------------------------------------- /代码文件/HHA.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // JSP-HHA 4 | // 5 | // Created by 林理露 on 16/5/28. 6 | // Copyright © 2016年 林理露. All rights reserved. 7 | // 8 | // 使用高级超启发式算法解决JSP问题 9 | // 用高层GA框架选择低阶启发式规则,从而寻找最优解 10 | // 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #ifndef DEBAG 20 | #define DEBAG 0 21 | #endif 22 | using namespace std; 23 | //参数 24 | #define Pc 0.65 //基因重组概率 25 | #define Pm 0.005 //基因突变概率 26 | #define Gmax 600 //最多代数 27 | #define K 400 //最多解不变跳出次数 28 | #define Cmax 50 //染色体数量 29 | #define Hctr 6 //启发式规则数量 30 | #define EV 15 //环境变量 越高环境越好 种群生存的概率越大 31 | class step; 32 | 33 | //启发式规则列表 34 | //用于判断 工序s1 是否优先于 工序s2 35 | bool FA(step* s1,step* s2); //先到先得 36 | bool EFT(step* s1,step* s2); //最早完工时间 37 | bool LU(step* s1,step* s2); //最低使用率优先 38 | bool MA(step* s1,step* s2); //最高空闲率 39 | bool SPT(step* s1,step* s2); //最短加工时间 40 | bool TIS(step* s1,step* s2); //在机器中所处时间最长 41 | 42 | class step{ 43 | public: 44 | //工序 45 | int j_NO;//对应工件序号 46 | int m_NO;//对应机器序号 47 | int duration;//完成工序所需时间 48 | step():j_NO(0),m_NO(0),duration(0){} 49 | step(int j_NO,int m_NO,int t):j_NO(j_NO),m_NO(m_NO),duration(t){} 50 | }; 51 | 52 | class pairs{ 53 | public: 54 | //用于辅助同一机器上工序排序 55 | step* stp;//存储该工序的地址 56 | int idx;//存储该工序在对应工件中的位置 57 | pairs():stp(nullptr),idx(INT_MAX){} 58 | pairs(step* st,int idx):stp(st),idx(idx){} 59 | pairs(const pairs& pr){ 60 | stp = pr.stp; 61 | idx = pr.idx; 62 | } 63 | 64 | pairs& operator=(const pairs& pr){ 65 | stp = pr.stp; 66 | idx = pr.idx; 67 | return *this; 68 | } 69 | 70 | friend bool operator==(const pairs& p1,const pairs& p2){ 71 | return (p1.stp == p2.stp); 72 | } 73 | }; 74 | 75 | class chromosome{ 76 | public: 77 | //染色体 78 | int n,p;//染色体容量信息 79 | double fitness;//适应度(= 1/(obj(g)+1)) 80 | int ms;//工作时长 81 | int* DNA;//存储对应的基因编码 82 | //用于存储可能会用到的启发式规则 83 | bool (*hf[Hctr])(step*,step*)={FA,EFT,LU,MA,SPT,TIS}; 84 | chromosome():n(0),p(0),fitness(0),ms(0),DNA(nullptr){} 85 | chromosome(int n,int p):n(n),p(p),ms(0),fitness(0){ 86 | DNA = new int[p]; 87 | } 88 | chromosome(const chromosome& cr){ 89 | assert(cr.n > 0),assert(cr.p > 0); 90 | n = cr.n, p = cr.p; 91 | fitness = cr.fitness,ms = cr.ms; 92 | DNA = new int[p]; 93 | for (int i = 0; i < p; ++i) { 94 | DNA[i] = cr.DNA[i]; 95 | } 96 | } 97 | 98 | void init(int n,int p){ 99 | //随机分配算法 100 | assert(n > 0),assert(p > 0); 101 | this->n = n, this->p = p; 102 | DNA = new int[p]; 103 | for (int i = 0; i < p; ++i) { 104 | DNA[i] = rand()%Hctr; 105 | } 106 | } 107 | 108 | friend void cross_over(chromosome c1,chromosome c2){ 109 | //交叉操作 110 | if (DEBAG) { 111 | cout<<"p"< b ? a : b; 115 | int temp = 0; 116 | for (int i = begin; i <= end; ++i) { 117 | temp = c1.DNA[i]; 118 | c1.DNA[i] = c2.DNA[i]; 119 | c2.DNA[i] = temp; 120 | } 121 | } 122 | 123 | chromosome& operator=(const chromosome& cr){ 124 | assert(cr.n > 0),assert(cr.p > 0); 125 | n = cr.n, p = cr.p; 126 | fitness = cr.fitness,ms = cr.ms; 127 | DNA = new int[p]; 128 | for (int i = 0; i < p; ++i) { 129 | DNA[i] = cr.DNA[i]; 130 | } 131 | return *this; 132 | } 133 | 134 | void mutate(double avg){ 135 | //变异操作 136 | for (int i = 0; i < p; ++i) { 137 | if (rand()/double(RAND_MAX) < Pm*(1+EV*(fitness-avg))){ 138 | //适应度越差 变异率越高 139 | DNA[i] = rand()%Hctr; 140 | } 141 | } 142 | } 143 | 144 | ~chromosome(){ 145 | delete[] DNA; 146 | } 147 | }; 148 | 149 | //多个全局变量 方便启发式规则访问 150 | int *Tm = nullptr,*Tp = nullptr,*idxs = nullptr; 151 | step** steps = nullptr; 152 | list* same_machine = nullptr; 153 | chromosome* chromo = nullptr; 154 | int *sum_j_time = nullptr; 155 | int main(int argc, const char * argv[]) { 156 | //主函数 157 | // freopen("/users/LLL/Desktop/ft10.in", "r", stdin); 158 | // freopen("/users/LLL/Desktop/ft06.out", "w", stdout); 159 | //基础输入 160 | int n = 0,m = 0,p = 0; 161 | clock_t t1 = clock(); 162 | char tp; 163 | cin>>n>>tp>>m>>tp>>p>>tp; 164 | srand(int(time(NULL))); 165 | 166 | //内存分配 167 | steps = new step*[n];//用于存储工序信息 168 | same_machine = new list[m];//用于存储同一机器上的工序 169 | chromo = new chromosome[Cmax];//存储染色体 170 | sum_j_time = new int[n];//存储对应工件所有工序所用总时间 171 | 172 | //工序、时间 信息输入 173 | for (int i = 0; i< n; ++i) { 174 | int sum_j = 0; 175 | steps[i] = new step[p];//顺带分配内存 176 | for (int j = 0 ; j < p; ++j) { 177 | cin>>steps[i][j].duration; 178 | sum_j += steps[i][j].duration; 179 | } 180 | sum_j_time[i] = sum_j; 181 | } 182 | 183 | if (DEBAG) { 184 | cout<<"duration"<>steps[i][j].m_NO; 197 | steps[i][j].j_NO = i;//顺带初始化其它 198 | same_machine[steps[i][j].m_NO-1].\ 199 | push_back(pairs(&steps[i][j],j)); 200 | } 201 | } 202 | 203 | if (DEBAG) { 204 | cout<<"machines:"<duration<<'\t'; 219 | } 220 | cout< match; 240 | int iter_times = 0,ctr = 0,make_span_old = 0,make_span_new = 0; 241 | Tm = new int[m],Tp = new int[n];//存储机器、工件加工用时 242 | idxs = new int[n];//存储当前工件进行到的工序号 243 | bool *selected = new bool[m];//用于防止重复选择,陷入在一处 244 | list* same_machine_temp = new list[m]; 245 | double sum = 0; 246 | for (iter_times = 0; iter_times < Gmax; ++iter_times) { 247 | //计算适应值 248 | for (int k = 0; k < Cmax; ++k) { 249 | //初始化Tm,Tp,idxs,selected,same_machine_temp 250 | for (int i = 0; i < m; ++i) { 251 | same_machine_temp[i] = same_machine[i]; 252 | } 253 | if (DEBAG) { 254 | for (int i = 0; i < m; ++i) { 255 | cout<<"same_machine_temp:"<duration<<" "; 259 | } 260 | cout< max) { 285 | max = Tm[i]; 286 | } 287 | } 288 | for (int i = 0; i < m; ++i) { 289 | if (Tm[i] > min && Tm[i] < min2) { 290 | min2 = Tm[i]; 291 | } 292 | } 293 | 294 | bool is_em = true; 295 | for (int i = 0; i < Cmax; ++i) { 296 | if (!same_machine_temp[i].empty()) { 297 | is_em = false; 298 | } 299 | } 300 | 301 | if (!same_machine_temp[min_idx].empty() && min_idx != -1) { 302 | if (DEBAG) { 303 | cout<<"current machine: "<j_NO "<j_NO<j_NO] ) { 331 | //是当前的前沿工序 且 根据其对应启发式规则更佳 332 | if (todo == nullptr || \ 333 | chromo[k].hf[chromo[k].DNA[s.stp->m_NO-1]](s.stp,todo)){ 334 | if (DEBAG) { 335 | cout<<"selected->"<j_NO<duration; 360 | int R = (Tp[todo->j_NO] > Tm[todo->m_NO-1] ? \ 361 | Tp[todo->j_NO] : Tm[todo->m_NO-1]); 362 | Tp[todo->j_NO] = R + T; 363 | Tm[todo->m_NO-1] = R + T; 364 | ++idxs[todo->j_NO]; 365 | //从same_machine_temp中删掉已完成的工序 366 | same_machine_temp[min_idx].remove(pairs(todo,-1)); 367 | if (DEBAG) { 368 | cout<<"list:"<duration<<" "; 371 | } 372 | cout<= 22); 417 | chromo[k].ms = max; 418 | chromo[k].fitness = (1/(double(max)+1)); 419 | break; 420 | } 421 | } 422 | } 423 | 424 | //轮盘赌选入匹配集 425 | if(DEBAG){ 426 | cout<<"last gen"< max) { 491 | max = chromo[i].ms; 492 | } 493 | } 494 | make_span_new = max; 495 | 496 | if (make_span_old == make_span_new && ++ctr > K) { 497 | break; 498 | } 499 | make_span_old = make_span_new; 500 | match.clear(); 501 | } 502 | 503 | //输出 504 | // if (DEBAG) { 505 | cout<<"iter_times: "<j_NO] < Tp[s2->j_NO]); 531 | } 532 | 533 | bool EFT(step* s1,step* s2){ 534 | //最早完工时间 535 | return (sum_j_time[s1->j_NO] < sum_j_time[s2->j_NO]); 536 | } 537 | 538 | bool LU(step* s1,step* s2){ 539 | //最低使用率优先 540 | return (Tp[s1->j_NO]/double(sum_j_time[s2->j_NO]) < \ 541 | Tp[s2->j_NO]/double(sum_j_time[s2->j_NO])); 542 | } 543 | 544 | bool MA(step* s1,step* s2){ 545 | //最高空闲率 546 | return ((sum_j_time[s1->j_NO]-s1->duration-Tp[s1->j_NO])/(sum_j_time[s1->j_NO]) > \ 547 | (sum_j_time[s2->j_NO]-s2->duration-Tp[s2->j_NO])/(sum_j_time[s1->j_NO])); 548 | } 549 | 550 | bool SPT(step* s1,step* s2){ 551 | //最短加工时间 552 | return (s1->duration < s2->duration); 553 | } 554 | 555 | bool TIS(step* s1,step* s2){ 556 | //在机器中所处时间最长 557 | return ((sum_j_time[s1->j_NO]-Tp[s1->j_NO]) < \ 558 | (sum_j_time[s2->j_NO]-Tp[s2->j_NO])); 559 | } 560 | -------------------------------------------------------------------------------- /代码文件/JMP.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // JSP 4 | // 5 | // Created by 林理露 on 16/3/7. 6 | // Copyright © 2016年 林理露. All rights reserved. 7 | // 8 | // 使用初级启发式规则解决JSP问题 9 | // 使用前沿贪心方法,即只考虑前沿工序,辅以繁忙度因素 10 | // 使其在合法的情况下,开工时间最小 11 | // 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #ifndef DEBAG 18 | #define DEBAG 0 19 | #endif 20 | using namespace std; 21 | 22 | class step{ 23 | public: 24 | //工序 25 | int m_NO;//对应机器序号 26 | int j_NO;//对应工件序号 27 | double busy;//繁忙度 28 | int duration;//完成工序所需时间 29 | int min_t;//最早开工时间 30 | step():j_NO(0),m_NO(0),busy(0),duration(0),min_t(INT_MAX){} 31 | step(int j_NO,int m_NO,int t):j_NO(j_NO),m_NO(m_NO),busy(0),duration(t),min_t(0){} 32 | }; 33 | 34 | class job{ 35 | public: 36 | //工件 37 | int NO;//序号 38 | int time_sum;//总时间 39 | double job_busy;//工件繁忙度 40 | deque steps;//工件工序集合 41 | job():NO(0),time_sum(0),job_busy(0){} 42 | }; 43 | 44 | class machine{ 45 | public: 46 | //机器 47 | int NO;//编号 48 | int time_sum;//总时间 49 | int to_be_handle;//未处理的工序数 50 | double machine_busy;//机器繁忙度 51 | machine():NO(0),time_sum(0),to_be_handle(0),machine_busy(0){} 52 | }; 53 | 54 | int compare(const void* a,const void*b); 55 | int main(int argc, const char * argv[]) { 56 | //主函数 57 | // freopen("/users/LLL/Desktop/test1.in", "r", stdin); 58 | //数量输入 59 | clock_t t1 = clock(); 60 | 61 | int n=0,m=0,p=0; 62 | char tmp; 63 | // cout<<"Please enter the essential data:"<>n>>tmp>>m>>tmp>>p>>tmp; 66 | 67 | //初始化 68 | job* jobs = new job[n+1]; 69 | machine* machines = new machine[m+1]; 70 | int *buffer[n+1]; 71 | for (int i=1; i<=n; ++i) {//输入缓冲区将时间先存储起来 72 | buffer[i] = new int[p+1];//随后同步使用 73 | } 74 | for (int i=1; i<= n; ++i) { 75 | for (int j = 1 ; j <= p; ++j) { 76 | cin>>buffer[i][j]; 77 | } 78 | } 79 | for (int i=1; i<=n; ++i) { 80 | jobs[i].NO = i; 81 | } 82 | for (int i=1; i<=m; ++i) { 83 | machines[i].NO = i; 84 | } 85 | 86 | //工序、时间 信息输入 87 | // cout<<"Now enter steps with time."<>NO; 92 | jobs[i].steps.push_back(step(i,NO,t)); 93 | jobs[i].time_sum += t; 94 | machines[NO].time_sum += t; 95 | ++machines[NO].to_be_handle; 96 | } 97 | } 98 | 99 | if (DEBAG) { 100 | cout<<"job info"< 0) {//还有剩下的工序则提出来一个 141 | is_empty = 0; 142 | int j = last_j[i].min_t+last_j[i].duration;//更新min_t 143 | int m = last_m[jobs[i].steps.front().m_NO].min_t+\ 144 | last_m[jobs[i].steps.front().m_NO].duration; 145 | jobs[i].steps.front().min_t = (j > m ? j : m); 146 | front_steps[i] = jobs[i].steps.front(); 147 | if (DEBAG) { 148 | cout< make_span) { 219 | make_span = last_j[i].min_t+last_j[i].duration; 220 | } 221 | } 222 | 223 | //输出 224 | clock_t t2 = clock(); 225 | cout<<"time:"< 246 | if (s1->j_NO == 0) {//用于把空组排至最后 247 | return 1; 248 | } 249 | if (s2->j_NO == 0) { 250 | return -1; 251 | } 252 | if (s1->min_t < s2->min_t) 253 | { 254 | return -1; 255 | } 256 | else if (s1->min_t == s2->min_t) 257 | { 258 | if (s1->busy/s1->duration < s2->busy/s2->duration) 259 | { 260 | return -1; 261 | } 262 | else if (s1->busy/s1->duration == s2->busy/s2->duration) 263 | { 264 | if (s1->j_NO < s2->j_NO) 265 | {//编号不存在相等的可能性 266 | return -1; 267 | } 268 | else 269 | { 270 | return 1; 271 | } 272 | } 273 | else 274 | { 275 | return 1; 276 | } 277 | } 278 | else 279 | { 280 | return 1; 281 | } 282 | } -------------------------------------------------------------------------------- /代码文件/PSO.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // JSP-PSO 4 | // 5 | // Created by 林理露 on 16/4/25. 6 | // Copyright © 2016年 林理露. All rights reserved. 7 | // 8 | // 使用中级元启发式算法解决JSP问题 9 | // 使用PSO粒子群优化算法,多次迭代使其收敛于最优解 10 | // 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #ifndef DEBAG 19 | #define DEBAG 0 20 | #endif 21 | using namespace std; 22 | //参数 23 | #define Vmax 1 //最高速度 24 | #define Xmax 5 //最大范围 25 | #define Wmax 2.2 //最大惯性 26 | #define Wmin 0.4 //最小惯性 27 | #define Itermax 600 //最高迭代次数 28 | #define Pmax 50 //种群规模 29 | #define K 5 //最高不变解跳出次数 30 | 31 | class step{ 32 | public: 33 | //工序 34 | int j_NO;//对应工件序号 35 | int m_NO;//对应机器序号 36 | int duration;//完成工序所需时间 37 | step():j_NO(0),m_NO(0),duration(0){} 38 | step(int j_NO,int m_NO,int t):j_NO(j_NO),m_NO(m_NO),duration(t){} 39 | }; 40 | 41 | class pairs{ 42 | public: 43 | //用于把连续粒子离散化 44 | double part_x;//粒子位置 45 | int idx;//对应下标 46 | pairs():part_x(-1),idx(-1){} 47 | pairs(double part_x,int idx):part_x(part_x),idx(idx){} 48 | 49 | friend bool operator<(const pairs &a,const pairs &b){ 50 | return (a.part_x > b.part_x); 51 | } 52 | }; 53 | 54 | class particle{ 55 | public: 56 | //粒子 57 | int n,p,size;//粒子内部信息容量 58 | int value;//粒子的价值(对应make_span的长短) 59 | double* x;//位置 60 | double* v;//速度 61 | double* Pbest;//个人所到达过的佳点 62 | int Pb_value;//个人最佳点价值 63 | int* operations;//用于存储解码出的规划 64 | 65 | particle():n(0),p(0),size(0),x(nullptr),v(nullptr),\ 66 | Pbest(nullptr),value(INT_MAX),Pb_value(INT_MAX){} 67 | 68 | particle(int n,int p):n(n),p(p),size(n*p),value(INT_MAX),Pb_value(INT_MAX){ 69 | x = new double[size]; 70 | v = new double[size]; 71 | Pbest = new double[size]; 72 | operations = new int[size]; 73 | } 74 | 75 | particle(const particle& r){ 76 | n = r.n, p = r.p, size = r.size; 77 | value = r.value; 78 | Pb_value = r.Pb_value; 79 | x = new double[size]; 80 | v = new double[size]; 81 | Pbest = new double[size]; 82 | operations = new int[size]; 83 | for (int i = 0; i < size; ++i) { 84 | x[i] = r.x[i]; 85 | v[i] = r.v[i]; 86 | Pbest[i] = r.Pbest[i]; 87 | operations[i] = r.operations[i]; 88 | } 89 | } 90 | 91 | void init(){ 92 | //粒子初始化 93 | for (int i = 0; i < size; ++i) {//随机生成 94 | x[i] = 2*Xmax*rand()/double(RAND_MAX)-Xmax; 95 | v[i] = 2*Vmax*rand()/double(RAND_MAX)-Vmax; 96 | } 97 | } 98 | 99 | particle& operator=(const particle& r){ 100 | n = r.n,p = r.p,size = r.size; 101 | value = r.value; 102 | Pb_value = r.Pb_value; 103 | x = new double[size]; 104 | v = new double[size]; 105 | Pbest = new double[size]; 106 | operations = new int[size]; 107 | for (int i = 0; i < size; ++i) { 108 | x[i] = r.x[i]; 109 | v[i] = r.v[i]; 110 | Pbest[i] = r.Pbest[i]; 111 | operations[i] = r.operations[i]; 112 | } 113 | return *this; 114 | } 115 | 116 | bool operator<(const particle& r){ 117 | return (value < r.value); 118 | } 119 | 120 | void ROV() { 121 | //ROV规则 离散化(解码) 122 | priority_queue q; 123 | for (int i = 0; i < p; ++i) { 124 | for (int j = 0; j < n; ++j) { 125 | q.push(pairs(x[i*n+j],j)); 126 | } 127 | for (int j = 0; j < n; ++j) { 128 | operations[i*n+q.top().idx] = j; 129 | q.pop(); 130 | } 131 | } 132 | } 133 | 134 | ~particle(){ 135 | // if (x != nullptr) 136 | delete[] x; 137 | // if (v != nullptr) 138 | delete[] v; 139 | // if (Pbest != nullptr) 140 | delete[] Pbest; 141 | // if (operations != nullptr) 142 | delete[] operations; 143 | } 144 | 145 | }; 146 | 147 | 148 | int main(int argc, const char * argv[]) { 149 | //主函数 150 | // freopen("/users/LLL/Desktop/la12.in", "r", stdin); 151 | //基础输入 152 | clock_t t1 = clock(); 153 | int n = 0,m = 0,p = 0,size = 0,size2 = 0;//n个工件,m台机器,p道工序,矩阵规模size 154 | char tp; 155 | cin>>n>>tp>>m>>tp>>p>>tp; 156 | size = n*p,size2 = n*m; 157 | srand (int(time(NULL))); 158 | //内存分配 159 | step** steps = new step*[n+1]; 160 | particle *parts = new particle[Pmax]; 161 | int* Tm = new int[size2];//代表每台机器上的累计加工时间 162 | int* Tp = new int[size];//代表每个工件上的累计加工时间 163 | int* idx = new int[n];//代表每个工件当前进行到的工序数 164 | 165 | //工序、时间 信息输入 166 | for (int i = 0; i< n; ++i) { 167 | steps[i] = new step[p];//顺带分配内存 168 | for (int j = 0 ; j < p; ++j) { 169 | cin>>steps[i][j].duration; 170 | } 171 | } 172 | 173 | if (DEBAG) { 174 | cout<<"duration"<>steps[i][j].m_NO; 187 | steps[i][j].j_NO = i;//顺带初始化其它 188 | } 189 | } 190 | 191 | if (DEBAG) { 192 | cout<<"machines:"< Tm[steps[NO][idx[NO]].m_NO-1] ? \ 254 | Tp[NO] : Tm[steps[NO][idx[NO]].m_NO-1]); 255 | Tp[NO] = R+T; 256 | Tm[steps[NO][idx[NO]].m_NO-1] = R+T; 257 | ++idx[NO]; 258 | } 259 | for (int j = 0 ; j < size2; ++j) { 260 | if (Tm[j] > max) { 261 | max = Tm[j]; 262 | } 263 | } 264 | if (DEBAG) { 265 | cout<<"max: "< 0); 269 | //搜索局部最优 270 | if (parts[i].value < parts[i].Pb_value) { 271 | parts[i].Pb_value = parts[i].value; 272 | for (int j = 0; j < size; ++j) { 273 | parts[i].Pbest[j] = parts[i].x[j]; 274 | } 275 | } 276 | //搜索全局最优 277 | if (parts[i].value < (*min).value) { 278 | min = &parts[i]; 279 | } 280 | } 281 | 282 | //更新全局最优 283 | 284 | if (min->value < Gbest.value) { 285 | if (DEBAG) { 286 | cout<<"updated:"<"<value<value< 0); 300 | cout< Vmax) { 322 | parts[k].v[i] = (1-0.1*rand()/double(RAND_MAX))* \ 323 | (parts[k].v[i]>0 ? Vmax : -Vmax); 324 | } 325 | if (fabs(parts[k].x[i]) > Xmax) { 326 | parts[k].x[i] = (1-0.1*rand()/double(RAND_MAX))* \ 327 | (parts[k].x[i]>0 ? Xmax : -Xmax); 328 | } 329 | } 330 | } 331 | 332 | 333 | //动态参数更新 334 | w = Wmin + (Wmax - Wmin)/double(n) * iter_times; 335 | 336 | make_span_new = Gbest.value; 337 | 338 | //判定是否继续迭代(连续K次解都不变则跳出) 339 | if (updated && fabs(make_span_new - make_span_old) < 0.000000001 && ++ctr > K) { 340 | break; 341 | } 342 | make_span_old = make_span_new; 343 | } 344 | 345 | //输出 346 | // if (DEBAG) { 347 | cout<<"iterate times: "<,>=stealth] 17 | \tikzstyle{process2} = [rectangle, minimum width=0.01cm, minimum height=0cm, text centered, text width=5cm, draw=black, ] 18 | \geometry{margin=0.7in} 19 | \title{\huge 基于启发式方法的车间作业排序问题的三步优化} 20 | \author{林理露} 21 | \date{\today} 22 | \begin{document} 23 | \maketitle 24 | \paragraph{摘要:}启发式规则实现简单,运行效率高,但是求解效果差,且有对问题形式的较强的依赖性,本文采用了JMP启发式规则,引入多个优先级因素,充分考虑了机器、工件、工序间的负载均衡。元启发式算法概念简明,实现方便,在解决复杂组合优化类问题方面具有优越性能,但容易早熟,而陷入局部最优状态,全局寻优能力有限,本文采用了PSO粒子群算法,并针对早熟问题,引入权重自适应、随机性限定解空间范围、使用ROV规则解码等技术,提高了PSO算法的全局寻优能力,使其具有了较快的收敛速度。超启发式算法通过上层的元启发框架来控制底层的多个启发式规则,兼具了启发式规则的运算高效性和元启发式算法的全局寻优能力,是现今复杂优化问题的最前沿、最优秀的技术,本文使用GA算法作为元启发框架,另使用6个基础的启发式规则,实现了最佳的问题求解水平。通过单一启发式规则到元启发式算法到超启发式算法的三步优化,有效地解决了最基本的车间调度问题中的调度规划问题,同时也为柔性作业车间调度问题、多工艺路线车间调度问题、多时间因素作业车间调度问题提供了思路。 25 | \paragraph{关键词:}车间作业排序 \space 启发式规则 \space 粒子群算法 \space 超启发式算法 \space 遗传算法 26 | \begin{multicols}{2} 27 | \section{引言}车间调度问题(Job Shop Scheduling Poblem, JSP)是一个多约束组合优化和非多项式确定(Non-Deterministic Polynomial, NP)难题。求解JSP问题主要有精确算法和启发式优化方法(近似算法)两种方向。前者主要有解析、整数规划、分支界限法等,由于时间复杂度较高,只能用于求解一些小规模的问题。而实际生产中的调度问题,由于其问题规模大,复杂度高,且因素多,精确调度算法很难得到应用,故主要研究方向是各种近似算法。早期出现的最基础的启发式规则求解效率高,在很多情况下可以快速地得到很不错的计算结果,但是按照启发式规则所得到的调度的精度在大多数的情况下还不能满足实际生产的要求。近年来,模拟退火算法、禁忌搜索、遗传算法、蚁群算法、粒子群算法、免疫算法等元启发式算法相应被用于求解JSP问题,这类算法优化能力强,取得了一定的优化的效果,基本上能满足现代制造业企业的要求。但是由于车间作业调度优化问题的复杂性,以及启发式算法存在的各种不足,至今尚未形成系统的理论与方法。而最新出现的超启发式算法基于前两者,使用高层的元启发式算法框架来控制底层多个启发式规则,结合了前两者的优势,兼具了启发式规则的运算高效性和元启发式算法的全局寻优能力,是现今复杂优化问题的最前沿、最优秀的技术。本文分别使用以上三种优化方法来逐步寻求对基础JSP问题的优化,并从中展现出启发式方法的进化历程及最新的启发式方法的优越性。本文中,启发式规则使用了JMP算法(考虑繁忙度的前沿贪心方法),元启发式算法使用了PSO算法(粒子群算法),超启发式算法使用了基于GA(遗传算法)的框架,控制底层FA、EFT、LU、MA、SPT、TIS六个启发式规则。并基于C++对以上三种启发式方法分别进行性能测试。 28 | \section{问题描述与模型的建立} 29 | \subsection{问题描述} 30 | 一个最经典的车间作业排序问题可描述为:一个 加工系统中有m 台机器和n 个待加工的工件, 所 有工件的加工路径(即机器约束)预先给定, 但 不要求一致, 各工件在各机器上的操作时间已知。 排序的任务是通过一定的优化,合理地安排每台机器上工件的 加工次序, 使约束条件得到满足, 同时使车间作业的总调度时间最短。\\\\ 31 | 最典型的Job Shop 问题一般会有以下约束条件: \begin{enumerate} 32 | \item 同一时刻每台机器只能加工一个工序, 且每个工序只能被一台机器所加工, 同时加工过程不可间断; 33 | \item 在整个加工过程中, 每个工件不能在同一台机器上加工多次; 34 | \item 各工件必须按照工艺路线以指定的次序在机器上加工, 工件i 的第j 道工序必须在第(j -1)道工序完成后才能开始; 35 | \item 不考虑工件的优先权, 允许操作等待; 36 | \item 工件的加工时间事先给定,且在整个加工过程中保持不变。 37 | \end{enumerate} 38 | 目标函数是最小化最大完工时间,即 39 | $$ T = min\{ \underset{{1 \leq j \leq m}}{Max}(c_j)\}$$ 40 | 其中 $c_j$ 表示第j号机器的完工时间 41 | 42 | \subsection{数学模型} 43 | \subsubsection{已知条件} 44 | 首先要建立工序排列矩阵J,各工件加工时间矩阵T,各机器加工工序的调度矩阵$M_J$ 45 | \begin{enumerate} 46 | \item 需要加工的工件集为: $J=(j_1,j_2,j_3,\ldots,j_n)$,$j_i$为第i个要加工的工件; 47 | \item 能够加工的机器集为: $M=(m_1,m_2,m_3,\ldots,m_m)$,$m_i$为第i个加工用的机器; 48 | \item 各工件的工序集为: $S=(S_1,S_2,S_3,\ldots,S_n)^T$;$J_i=(j_{i1},j_{i2},j_{i3},\ldots,j_{i_m})$;$j_{ik}$表示第i个工件的第k道工序; 49 | \item 工件各工序的加工时间$T=(T_1,T_2,T_3,\ldots,T_n)^T$;$T_i=(t_{i1},t_{i2},t_{i3},\ldots,t_{i_m})$;$t_{ik}$表示第i个工件的第k道工序的加工时间; 50 | \end{enumerate} 51 | \subsubsection{约束条件} 52 | \[ 53 | \begin{cases} 54 | St_{ij}+t_{ij}\leq St_{i(j+1)},\\ 55 | St_{ijl}+t_{ij}\leq St_{ghk},\\ 56 | t_{ij} \geq 0,\\ 57 | i,g,k= 1,2,3,\ldots,n,\\ 58 | h,j= 1,2,3,\ldots,m,\\ 59 | \end{cases} 60 | \] 61 | 式中,$St_{ij}$代表工件i的第j道工序的开工时间 62 | \subsubsection{目标函数} 63 | 车间作业调度问题优化的目标函数为: 64 | \[ Min(T(J_m))= min\{max[T_1,T_2,T_3,\ldots,T_m]\}\] 65 | \section{JMP启发式规则} 66 | \subsection{流程图} 67 | 68 | \begin{tikzpicture}[node distance=1.2cm] 69 | %定义流程图具体形状 70 | \node (start) [startstop] {开始}; 71 | \node (in1) [io, below of=start] {输入三个矩阵}; 72 | \node (pro1) [process, below of=in1] {获取当前前沿工序列}; 73 | \node (pro2) [process, below of=pro1] {更新机器、工件、工序繁忙度}; 74 | 75 | \node (pro3) [process2, below of=pro2,yshift=-0.5cm] {根据<最小开工时间/繁忙度/对应工件编号>的三元组对前沿工序列中工序进行排序}; 76 | \node (pro4) [process, below of=pro3,yshift=-0.5cm] {选择排在最前的工序执行}; 77 | \node (dec1) [decision, below of=pro4] {列空?}; 78 | \node (pro5) [process, below of=dec1,yshift = -0.5cm] {根据存储的开工时间信息计算MakeSpan}; 79 | \node (out1) [io, below of=pro5] {输出MakeSpan}; 80 | \node (stop) [startstop, below of=out1] {结束}; 81 | 82 | %连接具体形状 83 | \draw [arrow](start) -- (in1); 84 | \draw [arrow](in1) -- (pro1); 85 | \draw [arrow](pro1) -- (pro2); 86 | \draw [arrow](pro2) -- (pro3); 87 | \draw [arrow](pro3) -- (pro4); 88 | \draw [arrow](pro4) -- (dec1); 89 | \draw [arrow](dec1) -- node[anchor=east] {是} (pro5); 90 | \draw [arrow](dec1) -- ++(3.5cm,0cm)|- node[anchor=west] {否}(pro1); 91 | \draw [arrow](pro5) -- (out1); 92 | \draw [arrow](out1) -- (stop); 93 | \end{tikzpicture} 94 | 95 | \subsection{启发式规则的车间作业调度求解} 96 | \subsubsection{名词定义} 97 | \paragraph{前沿工序}指所有未被调度的工序当中,在所在工件中所排最靠前的工序集合 98 | \paragraph{繁忙度}通过特定的规律计算出的反映繁忙程度的量 99 | \subparagraph{工件繁忙度}JobBusy(i)=(属于工件i的未处理的工序数$\times$未处理的工序所需时间和) 100 | \subparagraph{机器繁忙度}MachineBusy(k)=(属于机器k的的未处理的工序数$\times$未处理的工序所需时间和) 101 | \subparagraph{工序繁忙度}Busy(i)=工件繁忙度+机器繁忙度 102 | \paragraph{最小开工时间}即在当前已安排的工序的格局之下,该工序能开始执行的最早的时间点 103 | \subsubsection{算法流程}该基于繁忙度的计算过程被命名为JMP启发式规则,步骤如下: 104 | \begin{enumerate} 105 | \item 在最开始的格局之下,所有工序都还没有执行 106 | \item 选择出当前的前沿工序 107 | \item 利用<最小开工时间/工件繁忙度/工件所在编号>的三元组,对前沿工序进行排序,最小开工时间小的、工件繁忙度高的、工件序号小的优先。由约束条件可知,两个前沿工序工件序号不可能相同,故该排序肯定能产生一个有序序列 108 | \item 选取排序后序列中的最佳工序执行 109 | \item 按照如上规则执行完所有工序,并得到一个合法的相应的加工周期MakeSpan 110 | \end{enumerate} 111 | \subsection{优缺点分析} 112 | \paragraph{优点} 113 | \begin{itemize} 114 | \item 时间复杂度低,消耗计算资源较少 115 | \item 逻辑简单,易于实现 116 | \item 代码量低,嵌入硬件难度小 117 | \end{itemize} 118 | \paragraph{缺点} 119 | \begin{itemize} 120 | \item 过分依赖具体问题类型,不具备通用性 121 | \item 很难保证解的质量,可能产生很差的解 122 | \item 问题条件发生变化时,需完全重构,适应性差 123 | \end{itemize} 124 | 125 | \section{PSO粒子群算法}PSO粒子群算法,是一种模拟鸟群体飞行的群智能算法,与其它的优化算法基本思想相似,在PSO中,一个粒子表示一只鸟,每一个粒子均具有初始位置X和速度V,在粒子群飞行过程不断调整飞行速度和方向,最终找到最优解,它用无质量、无体积的粒子作为个体, 并为每个粒子规定简单的行为规则, 从而使整个粒子群表现出复杂的特性, 可用来求解复杂的优化问题。 126 | \subsection{数学描述} 127 | 设在一个n 维的搜索空间中, 由m 个粒子组成的种群$X=\{x_1,\ldots,x_i,\ldots,x_m\}$,其中第i 个粒子位置$x=(x_{i1},x_{i2},\ldots,x_{in})^T$,其速度$V_i = v_{i1},v_{i2},\ldots,v_{in})^T$,个体极值$Pb_i=(P_{i1},P_{i2},\ldots,P_{in})$,种群的全局极值$P_g=min(P_{i1},P_{i2},\ldots,P_{in})$\\ 128 | \indent 设粒子i的速度和位置分别定义为$X_i(t)$和$V_i(t)$,在每一次迭代过程中,粒子跟随自身最优解($P_{best}$)和种群最优解($G_{best}$)来更新自己的速度和位置,具体的更新公式如下: 129 | \[\begin{aligned}V_{ik}(t+1) &= \omega V_{ik} + c_1r_1(P_{ik}(t)-X_{ik}(t)) \\&+ c_2r_2(P_{gk}(t) - X_{ik}(t))\end{aligned}\] 130 | \[X_{ik}(t+1)=X{ik}(t)+V_{ik}(t+1)\] 131 | 式中,$\omega$表示粒子的惯性权重,$c_1,c_2$表示加速因子数,$r_1,r_2$为分布于[0,1]之间的随机数,t为当前进化代数,$k=1,2,\ldots,n;i = 1,2,\ldots,m$为种群规模。 132 | \subsection{参数设置} 133 | \subsubsection{最大速度$Vmax$} 134 | 在上述粒子速度、位置更新公式中,速度$V_{ik}$和$X_{ik}$的绝对值可能会过大,从而使粒子一下子飞出解空间。因此要将其限制在$[-V_{max},V_{max}]$和$[-X_{max},X_{max}]$内,本文将$V_{max}$设为1,$X_{max}$设为5,同时引入了随机化的因素,当粒子脱离限制的范围时,按如下方法规范粒子速度和位置: 135 | \[ \pm X_{ij} = \pm X_{max} \times(1-r),r\in[0, 0.1]\] 136 | \[ \pm V_{ij} = \pm V_{max} \times(1-r),r\in[0, 0.1]\] 137 | \subsubsection{权重因子$\omega$} 138 | 惯性权重$\omega$使粒子保持运动惯性, 使其有扩展搜索空间的趋势,文献[2]表明,当$\omega \in [0.9,1.2]$时,粒子群算法能获得较好的优化效果,而当$\omega$从0.9递减至0.4时,算法能很快地向最优解收敛,故本文使用自适应的惯性权重系数,其在PSO的搜索中线性变化,计算公式如下: 139 | \[\omega = \omega_{min} +\frac{\omega_{max} - \omega_{min}}{n} \times i \] 140 | \subsubsection{加速度常数$c_1,c_2$} 141 | 加速度常数c1 和c2 代表将每个粒子推向$P_{best}$和$G_{best}$位置的统计加速项的权重。较低的值允许粒子在被拉回之前可以在目标区域外徘徊, 而较高的值则导致粒子突然越过目标区域。根据文献[2],本文将$c_1,c_2$取值为2。 142 | \subsection{编码}车间作业调度问题是一个离散、动态、多变量的问题,而粒子群优化算法是一种连续空间的优化算法,所以在利用PSO求解车间作业调度问题时,粒子位置不能直接用于表示最终调度的序列,二者之间需要建立一种映射关系。由于寻优目标为排序问题, 每一个粒子代表的是工件的一个排序,因此粒子的运动代表的是工序的改变,如此便很难保证工序的合理性,为此,本文采用了基于操作的编码方式,使用ROV规则来实现了粒子位置到工序操作的映射。\\ 143 | \indent 该编码方式方式使用m$\times$n个代表操作位置值表示微粒的位置矢量,即所有操作的一个序列,其中每个工件号均出现m次。解码过程是:先将微粒位置转化为一个有序的操作表,然后基于操作表和工艺约束将操作表中的元素按照从左到右的顺序解释为相应工件的操作顺序,进而产生调度方案。\\ 144 | \indent 基于操作的编码方式的基本思想是将所有工件的操作进行编码,不同工件用不同的编码表示,同一工件则用相同的编码表示。编码步骤如下: 145 | \paragraph{ROV规则编码} 146 | \begin{enumerate} 147 | \item 随机初始化微粒$X_k=[x_{1,1},x_{1,2},\ldots,x_{1,n},x_{2,1},x_{2,2}\\,\ldots, x_{2,n},\ldots,x_{m,1},x_{m,2},\ldots,x_{m,n}]$其中 n 表示工件数,m 表示机器数量。 148 | \item 分别对$X_k$中的子部分$[x_{i,1},x_{i,2},\ldots,x_{i,n}],i=(1,2,\ldots, m)$使用 ROV 规则。ROV 规则为:比较数组中元素值的大小,按从小到大依次将其标注为 1,2,...,n。 149 | \item 将上述微粒位置经过ROV规则转换后,将微粒位置矢量映射为相应的整数,而整数则可用来代表相应的工件序号。操作则可用其对应的工件号来表示,操作可用符号 $O_{ijk}$表示,表示第 i 个工件的第 j 道工序在第 k 台机器上加工。 150 | \end{enumerate} 151 | \indent 如,n=3,m=2 时,设微粒初始化位置为:$X_k$=[1.32 1.16 2.10 2.13 1.83 1.56] ROV变换之后,则$X_k$=[2 1 3 3 2 1];工艺约束和加工时 间如下表所示,则该微粒位置对应的工件加工顺序为$[O_{212}O_{111}O_{312}O_{321}O_{221}O_{122}]$。\\\\ 152 | \indent 153 | \begin{tabular}{cccc} 154 | \multicolumn{4}{c}{\textbf{加工时间和工艺约束表}} \\ 155 | \hline 156 | \multicolumn{4}{r}{操作序列} \\ 157 | \cline{3-4} 158 | 项目 & 工件 & 1 & 2 \\ 159 | \hline 160 | \multirow{3}{*}{操作时间} 161 | & $J_1$ & 3 & 3 \\ 162 | & $J_2$ & 1 & 5 \\ 163 | & $J_3$ & 3 & 2 \\ 164 | \hline 165 | \multirow{3}{*}{机器} 166 | & $J_1$ & $M_1$ & $M_2$ \\ 167 | & $J_2$ & $M_2$ & $M_1$ \\ 168 | & $J_3$ & $M_2$ & $M_1$ \\ 169 | \hline 170 | \end{tabular} 171 | 172 | \subsection{目标函数和适应值的计算} 173 | 已知每个工件每道工序在机器上的加工时间 段$t_ij$ , 向量$T_m =[ t_{m1} , t_{m2} ,\ldots , t_{mm}]$,其中, $t_{mj}$代表工件在第j 台机器上的累计加工时间, 向量 $T_P =[ t_{P1} , t_{P2} , \ldots , t_{Pn}]$, 其中, $t_{Pi}$代表第i 个工件的累计加工时间· 初始化向量$T_m , T_P$ 各分量都为0· 按编码所确定的顺序将工件分配到相应的机器上, 在分配时同时考虑机器约束和工件约束, 将加工时间$t_{ij}$分别对应地加到向量$T_m$和向量$T_P$上· 按照上述方法得到向量$T_m$中最大的分量值即为加工完成时间。 174 | \subsection{流程图} 175 | 176 | \begin{tikzpicture}[node distance=1.2cm] 177 | 178 | \node (start) [startstop] {开始}; 179 | \node (in1) [io, below of=start] {信息输入}; 180 | \node (pro1) [process, below of=in1] {随机初始化种群}; 181 | \node (pro2) [process2, below of=pro1,yshift=-0.3cm] {对粒子位置向量进行排序,映射为合法离散的工序操作表}; 182 | \node (pro3) [process, below of=pro2,yshift=-0.3cm] {对每个粒子进行解码}; 183 | \node (pro4) [process, below of=pro3] {计算目标函数值}; 184 | \node (pro5) [process, below of=pro4] {粒子群系统迭代更新}; 185 | \node (dec1) [decision, below of=pro5] {终止?}; 186 | \node (out1) [io, below of=dec1,yshift=-0.3cm] {输出全局最佳粒子}; 187 | \node (stop) [startstop, below of=out1] {结束}; 188 | 189 | \draw [arrow](start) -- (in1); 190 | \draw [arrow](in1) -- (pro1); 191 | \draw [arrow](pro1) -- (pro2); 192 | \draw [arrow](pro2) -- (pro3); 193 | \draw [arrow](pro3) -- (pro4); 194 | \draw [arrow](pro4) -- (pro5); 195 | \draw [arrow](pro5) -- (dec1); 196 | \draw [arrow](dec1) -- node[anchor=east] {是} (out1); 197 | \draw [arrow](dec1) -- ++(3.5cm,0cm)|- node[anchor=west] {否}(pro1); 198 | \draw [arrow](out1) -- (stop); 199 | \end{tikzpicture} 200 | 201 | \subsection{PSO的车间作业调度求解} 202 | 主要算法流程如上图所示,部分详细操作如下: 203 | \subsubsection{粒子更新操作}将每个粒子适应度(本文直接使用MakeSpan)值与$P_{best}$和$G_{best}$比较,若优于$P_{best}$或$G_{best}$,就采用该粒子适应度值代替$P_{best}$或$G_{best}$。 204 | \subsubsection{终止条件判断}当已达到最大迭代数或者连续K代粒子群全局最优值不发生大的变化,则表示满足终止条件,输出最优解。 205 | \subsection{优缺点分析} 206 | \paragraph{优点} 207 | \begin{itemize} 208 | \item 个体数目少,相比ACO等算法,PSO个体数目较少,便于操作 209 | \item 计算简单,粒子更新公式单一,逻辑相对简单 210 | \item 健壮性强,对初始解的依赖性小,即使是随机生成的初值也总能找到较优的解 211 | \end{itemize} 212 | \paragraph{缺点} 213 | \begin{itemize} 214 | \item 容易收敛过早,陷入局部最优解 215 | \item 代码量大,且运算复杂,不便于嵌入硬件 216 | \item 编码方式导致搜索空间为规则集,部分特殊粒子难以被选中 217 | \item 随机策略较少,多次求解结果相似度高 218 | \end{itemize} 219 | 220 | 221 | \section{基于GA的HHA超启发算法} 222 | \subsection{流程图} 223 | 224 | \begin{tikzpicture}[node distance=1.2cm] 225 | 226 | \node (start) [startstop] {开始}; 227 | \node (in1) [io, below of=start] {信息输入}; 228 | \node (pro1) [process, below of=in1] {随机初始化染色体(向量)}; 229 | \node (pro2) [process, below of=pro1] {确定当前种群P(t)}; 230 | \node (pro3) [process, below of=pro2] {计算每个染色体的适应度}; 231 | \node (pro4) [process2, below of=pro3,yshift=-0.3cm] {使用轮盘赌策略将染色体选入匹配集}; 232 | \node (pro5) [process2, below of=pro4,yshift= -0.5cm] {匹配集中使用复制、交叉、变异产生新种群P(t+1)}; 233 | \node (pro6) [process, right of= pro4,xshift= 3.5cm]{P(t)$\leftarrow$P(t+1)}; 234 | \node (dec1) [decision, below of=pro5,yshift= -0.5cm] {指标满足?}; 235 | \node (out1) [io, below of=dec1,yshift=-0.3cm] {输出最佳染色体}; 236 | \node (stop) [startstop, below of=out1] {结束}; 237 | 238 | \draw [arrow](start) -- (in1); 239 | \draw [arrow](in1) -- (pro1); 240 | \draw [arrow](pro1) -- (pro2); 241 | \draw [arrow](pro2) -- (pro3); 242 | \draw [arrow](pro3) -- (pro4); 243 | \draw [arrow](pro4) -- (pro5); 244 | \draw [arrow](pro5) -- (dec1); 245 | \draw [arrow](dec1) -- node[anchor=east] {是} (out1); 246 | \draw [arrow](dec1.east) -| node[anchor=west] {否} (pro6); 247 | \draw [arrow](pro6) |- (pro2); 248 | \draw [arrow](out1) -- (stop); 249 | \end{tikzpicture} 250 | 251 | \subsection{HHA的车间作业调度求解} 252 | 超启发式算法作为最新、最有前景的组合优化式方法,采用高层的元启发式方法搜索低层的启发式规则,使得算法兼备寻优能力与计算效率。本文使用了GA的高层框架,并使用了6个底层启发式规则。 253 | \subsubsection{GA框架} 254 | \paragraph{染色体}染色体为GA算法的基础,全部遗传算子都是建立在染色体上的,编码的 优劣决定了遗传算法的质量。而对于传统的元启发式遗传算法,遗传算子直接代表着工序,而工序间有诸多约束,于是仍然需要添加额外的检测和克服不可行调度的算法, 增加了系统不必要的开销。同时在执行交叉和突变的操作上仍需避免产生不可行调度。而基于GA的超启发式方法中,染色体对应着某一具体的启发式规则,而启发式规则间并无任何约束,因此可以自然的全空间的编码,而不用担心产生不可行调度的问题。本文选取$D_t=(d_{1},d_{2},\ldots,d_{m})$作为染色体,其中$d_k = (1,2,\ldots,6) ,k = (1,2,\ldots,m)$代表着对应编号的启发式规则,t代表当前染色体代数。 255 | \paragraph{匹配集}代表着能适应当前环境,能够遗传产生后代的候选染色体集,相当于一个缓冲区,为以后染色体交换、变异,产生新的染色体作准备。 256 | \paragraph{选择}即从原有的染色体集中直接选择一条复制至匹配集中,即$D_{t+1}\leftarrow D_t$。 257 | \paragraph{交叉}选择操作不能创新,交叉操作可以解决染色体的创新,交叉即为随机选择两个染色体(双亲染色体),随机指定一点或多点, 进行交换,可得两个新的染色体(子代染色体),本文采用随机选择起始点和终止点,交换中间所有部分的方式,模拟了自然界的染色体交叉现象,即$D_k=(d_{i1},d_{i2},\ldots,d_{js},d_{js+1},\ldots,d_{jt},d_{it+1},\ldots,d_{im}) \leftarrow (Di,Dj)$ $s,t = 1,2,\ldots,m$分别代表交叉开始和结束的位置。本文根据文献[7]将交叉概率设为$P_c$0.65。 258 | \paragraph{突变}突变模拟生物在自然界环境变化,引起基因的突变。在染色体编码中,由原来的基因突变为其他基因。即$D_i=(d_{i1},d_{i2},\ldots,d_k,d\ldots,d_m)$其中$d_k=(1,2,3,\ldots,6)$和其他基因是否发生变化相互独立。突变产生染色体的多样性,避免进化中早期成熟,陷入局部极值点。突变的概率很低,本文根据经验设置突变概率$P_m$=0.005。 259 | \paragraph{解码方式}以染色体中编码对应编号调用相应的启发式规则,即可求得对应MakeSpan。 260 | \paragraph{适应度} 261 | 适应度计算,根据文献[9],本文采用如下公式计算每个染色体的适应度: 262 | \[fit(x) = \frac{1}{Obj(x)+1} \] 263 | 其中x代表当前染色体,Obj(x)代表染色体解码后的Makespan长度。 264 | \paragraph{环境变量}由于直接使用轮盘赌,染色体被选入的概率过小,往往还未进行迭代就全部灭绝,导致GA框架无法运行,故本文引入了环境变量$EV$来提高染色体被选入匹配集的概率。而单一环境变量会导致算法寻优能力受限,故本文使用动态环境变量,更新公式如下: 265 | \[EV_{t+1} = EV_t\times \frac{t_m - \frac{2}{3}t}{t_m} \] 266 | 其中t为当前染色体遗传代数,$t_m$为最高代数,整个环境在逐渐变差,从而更高效地淘汰掉不能很好地适应环境的染色体,使算法结果更快收敛向最优解。 267 | \paragraph{轮盘赌策略} 268 | 采用适应度比例法(轮盘赌)按各染色体适应度大小比例来决定其被选择数目的多少。 269 | 轮盘赌决定概率,引入环境变量调整宏观平均生存率。公式如下: 270 | \[P_s = EV\times \frac{f(x_i)}{\sum f(x_i)} \] 271 | 其中$EV$是环境变量,$f$为适应度函数,$x_i$代表特定的一条染色体,$P_s$代表被选入匹配集的概率。 272 | \subsubsection{启发式规则集} 273 | \begin{tabular}{clll} 274 | \hline 275 | \multicolumn{4}{c}{启发式规则表} \\ 276 | \hline 277 | 编号 & 规则名& 描述 278 | & 公式 \\ 279 | \hline 280 | 1 & FA &先到先得 281 | & $min(Tp_{d})$\\ 282 | 2 & EFT &最早完工时间 283 | & $min(Max(Tp_{s}))$\\ 284 | 3 & LU &最低使用率 285 | & $min(\frac{Tp_{d}}{Tp_{s}} )$\\ 286 | 4 & MA &最高空闲率 287 | & $max(\frac{Tp_{s}-T_s-Tp_{d}}{Tp_{s}} )$ \\ 288 | 5 & SPT &最短加工时间 289 | & $min(T_{s_i})$ \\ 290 | 6 & TIS &机器中时间最长 291 | & $min(Tp_{s}-Tp_{d})$ \\ 292 | \hline 293 | \end{tabular} 294 | \\\\\\ 295 | \indent 其中,$Tp_{d}$指当前格局下,该工序对应工件已完成的工序所用时间,$Tp_{s}$为完成整个工件所需总时间,$T_s$为加工该工序所需时间。 296 | 297 | \subsection{优缺点分析} 298 | \paragraph{优点} 299 | \begin{itemize} 300 | \item 结合了元启发式算法的寻优能力和启发式规则的计算效率 301 | \item 可以将底层启发式规则嵌入硬件,用高层程序控制底层启发式规则 302 | \item 灵活性高,对问题适应性强,条件改变时算法修改较少 303 | \item 编码对应启发式规则,基本无约束,实现方便 304 | \end{itemize} 305 | \paragraph{缺点} 306 | \begin{itemize} 307 | \item 逻辑复杂,实现难度高 308 | \item 由于本文启发式规则量少,难以搜索整个解空间 309 | \end{itemize} 310 | 311 | \section{算法仿真} 312 | \subsection{实验环境} 313 | 硬件仿真环境:Intel i5-5257U 2.7-2.9GHz/8GB 1867MHz DDR3 \\ 314 | \indent 软件平台:OSX 10.11.5系统,编译器Apple LLVM version 7.3.0 (clang-703.0.29) 315 | \subsection{结果} 316 | 为了验证三种启发式算法求解JSP的性能,考虑采用一个典型JSP算例(LA类),这些测试问题在文献中被广泛用于测试。FT类问题由Fisher and Thompson给出,LA类问题由 Lawrence给出,在此选取其中6个不同规模的问题:FT06,FT10,LA01,LA05,LA10,LA12,各算法均独立运行25次。其中迭代次数上限均为600,种群规模均为50,其余参数按前文所述设置,实验结果如下表所示:\\\\ 317 | \begin{tabular}{l*{6}{c}r} 318 | \hline 319 | 问题 & 算法 & 最优解 & 平均解 & 最差解 & 平均用时 \\ 320 | \hline 321 | \multirow{3}{*}{$\text{FT06}_{6\times 6}$} 322 | & JMP & 77 & 77 & 77 & 0.171ms\\ 323 | & PSO & 59 & 59 & 59 & 0.193s \\ 324 | & HHA & 65 & 68 & 74 & 0.905s \\ 325 | \hline 326 | \multirow{3}{*}{$\text{FT10}_{10\times 10}$} 327 | & JMP & 1260 & 1260 & 1260 & 0.171ms\\ 328 | & PSO & 1212 & 1220 & 1231 & 0.598s \\ 329 | & HHA & 1254 & 1363 & 1444 & 4.712s \\ 330 | \hline 331 | \multirow{3}{*}{$\text{LA01}_{5 \times 5}$} 332 | & JMP & 822 & 822 & 822 & 0.171ms\\ 333 | & PSO & 724 & 724 & 725 & 0.303s \\ 334 | & HHA & 755 & 797 & 933 & 1.249s \\ 335 | \hline 336 | \multirow{3}{*}{$\text{LA05}_{10\times 5}$} 337 | & JMP & 759 & 759 & 759 & 0.362ms\\ 338 | & PSO & 593 & 593 & 594 & 0.311s \\ 339 | & HHA & 624 & 657 & 712 & 1.007s \\ 340 | \hline 341 | \multirow{3}{*}{$\text{LA10}_{10\times 5}$} 342 | & JMP & 1189 & 1189 & 1189 & 0.537ms\\ 343 | & PSO & 958 & 958 & 959 & 0.485s \\ 344 | & HHA & 973 & 1025 &1105 & 1.688s \\ 345 | \hline 346 | \multirow{3}{*}{$\text{LA12}_{20\times 5}$} 347 | & JMP & 1240 & 1240 & 1240 & 0.612ms\\ 348 | & PSO & 1039 & 1039 & 1040 & 0.695s \\ 349 | & HHA & 1039 & 1134 & 1315 & 2.225s \\ 350 | \hline 351 | \end{tabular} 352 | 353 | \subsection{三种算法的对比分析} 354 | 从启发式规则到元启发式算法再到超启发式算法,代码逻辑逐渐变得复杂、构建难度逐步提升,有必要对三者进行比较。显然单纯的启发式在速度上有绝对优势,比其他两种算法快了3个数量级。而PSO粒子群算法很好的兼顾了性能与结果,而对于的超启发式算法,由于代码逻辑复杂,运行时间较长,且本文中仅有6个启发式规则,启发式规则集过小,因此寻优能力一般,但显然强于单纯的启发式规则,在大规模问题下寻优能力稍有增强。 355 | 356 | \section{结论} 357 | 通过基于启发式方法的对车间作业调度问题的三步优化,本文充分体现出了启发式方法在求解NP完全问题中的成长、进化过程。虽然本文中由于启发式规则集容量较小而导致了超启发式算法的性能与结果不够突出,但可以预见的未来之中,超启发式算法应该会是求解NP完全问题中的最有前景、最具发展潜力的算法,将会是今后研究的重点方向。 358 | \end{multicols} 359 | 360 | \medskip 361 | \begin{thebibliography}{9} 362 | \bibitem{Greedy} 363 | 曾立平,黄文奇. 364 | \textit{一种用于车间作业调度问题的智能枚举算法[J].} 365 | 计算机工程与应用. 2004(30).\\ 366 | Zeng Liping,Huang Wenqi. 367 | \textit{An Intelligent Enumeration Algorithm for Job Scheduling Problem[J].} 368 | Computer Engineering and Applications. 2004(30) 369 | 370 | \bibitem{PSO1} 371 | 何 利, 刘永贤, 谢华龙, 刘笑天. 372 | \textit{基于粒子群算法的车间调度与优化[J].} 373 | 东北大学学报(自然科学版). 2008(04)\\ 374 | HE Li,LIU Yong-xian,XIE Hua-long,LIU Xiao-tian 375 | \textit{Job Shop Scheduling and Its Optimization Based on Particle Swarm Optimizer[J].}Journal of Northeastern University(Natural Science). 2008(04) 376 | 377 | \bibitem{PSO2} 378 | 黄慧,黎向锋,左敦稳,薛善良. 379 | \textit{基于改进粒子群算法的车间作业排序的优化设计[J].} 380 | 中国制造业信息化. 2011(21)\\ 381 | HUANG Hui,LI Xiang-feng,ZUO Dun-wen,XUE Shan-liang 382 | \textit{Design of Job Shop Scheduling Based on Improved Particle Swarm Algorithm} 383 | Manufacture Information Engineering of China. 2011(21). 384 | 385 | \bibitem{PSO3} 386 | 张飞,耿红琴 387 | \textit{基于混沌粒子群算法的车间作业调度优化[J].} 388 | 山东大学学报(工学版). 2013(03)\\ 389 | ZHANG Fei,GENG Hong-qin. 390 | \textit{Optimization of job shop scheduling problem based on chaos particle swarm optimization algorithm[J].} 391 | Journal of Shandong University(Engineering Science). 2013(03) 392 | 393 | \bibitem{PSO4} 394 | 毛 帆,傅 鹂,蔡 斌 395 | \textit{求解作业车间调度问题的微粒群遗传退火算法[J]} 396 | 计算机工程与应用. 2011(05)\\ 397 | MAO Fan,FU Li,CAI Bin. 398 | \textit{Particle swarm genetic annealing algorithm for job-shop scheduling.Computer Engineering and Applications[J].} 399 | 2011,47(5):227-231. 400 | 401 | \bibitem{PSO5} 402 | YAN Ping, JIAO Minghai. 403 | \textit{An Improved PSO Search Method for the Job Shop Scheduling Problem} 404 | 2011 Chinese Control and Decision Conference(CCDC) 405 | 406 | \bibitem{GA1} 407 | 谢胜利,黄强,董金祥. 408 | \textit{求解JSP的遗传算法中不可行调度的方案[J].} 409 | 计算机集成制造系统-CIMS. 2002(11)\\ 410 | XIE Sheng-li 1,2 ,HUANG Qiang 2 ,DONG Jin-xiang 2 411 | \textit{A Method to Resolve Unfeasible Scheduling of JSP by GA[J].} 412 | Computer Integrated Manufacturing Systems, 2002(11) 413 | 414 | \bibitem{HHA1} 415 | Ender O$\ddot{\mathrm{z}}$can,Burak Bilgin and Emin Erkan Korkmaz. 416 | \textit{A comprehensive analysis of hyper-heuristics} 417 | Intelligent Data Analysis 12 (2008) 3–23 418 | 419 | \bibitem{HHA2} 420 | Dongni Li, Rongxin Zhan, Dan Zheng, Miao Li, and Ikou Kaku. 421 | \textit{A Hybrid Evolutionary Hyper-Heuristic Approach for Intercell Scheduling Considering Transportation Capacity} 422 | IEEE TRANSACTIONS ON AUTOMATION SCIENCE AND ENGINEERING, VOL. 13, NO. 2, APRIL 2016. 423 | \end{thebibliography} 424 | Written using \LaTeX \space by $\mathcal{L}in\mathcal{L}i\mathcal{L}u$\\ 425 | 426 | Special Thanks to $\mathcal{D}ong\mathcal{N}i\text{ }\mathcal{L}i, \mathcal{R}ong \mathcal{X}in \text{ }\mathcal{Z}han$ 427 | \end{document} 428 | 429 | -------------------------------------------------------------------------------- /实验用例/readme.txt: -------------------------------------------------------------------------------- 1 | 注意,我按照学长所给用例形式写了代码,输入部分与国际标准不太相符,于是便写了一个转换器来实现对国际标准用例的转换。 2 | 学长用例结果 report里没地方写了 如下(最好值): 3 | test 1 2 3 4 4 | JMP 79 185 458 569 5 | PSO 79 135 270 485 6 | HHA 79 135 289 504 7 | -------------------------------------------------------------------------------- /实验用例/国际标准用例/ft06.in: -------------------------------------------------------------------------------- 1 | 6p6m6j 2 | 1 3 6 7 3 6 3 | 8 5 10 10 10 4 4 | 5 4 8 9 1 7 5 | 5 5 5 3 8 9 6 | 9 3 5 4 3 1 7 | 3 3 9 10 4 1 8 | 9 | 3 1 2 4 6 5 10 | 2 3 5 6 1 4 11 | 3 4 6 1 2 5 12 | 2 1 3 4 5 6 13 | 3 2 5 6 1 4 14 | 2 4 6 1 5 3 15 | -------------------------------------------------------------------------------- /实验用例/国际标准用例/ft10.in: -------------------------------------------------------------------------------- 1 | 10p10m10j 2 | 3 | 29 78 9 36 49 11 62 56 44 21 4 | 43 90 75 11 69 28 46 46 72 30 5 | 91 85 39 74 90 10 12 89 45 33 6 | 81 95 71 99 9 52 85 98 22 43 7 | 14 6 22 61 26 69 21 49 72 53 8 | 84 2 52 95 48 72 47 65 6 25 9 | 46 37 61 13 32 21 32 89 30 55 10 | 31 86 46 74 32 88 19 48 36 79 11 | 76 69 76 51 85 11 40 89 26 74 12 | 85 13 61 7 64 76 47 52 90 45 13 | 14 | 1 2 3 4 5 6 7 8 9 10 15 | 1 3 5 10 4 2 7 6 8 9 16 | 2 1 4 3 9 6 8 7 10 5 17 | 2 3 1 5 7 9 8 4 10 6 18 | 3 1 2 6 4 5 9 8 10 7 19 | 3 2 6 4 9 10 1 7 5 8 20 | 2 1 4 3 7 6 10 9 8 5 21 | 3 1 2 6 5 7 9 10 8 4 22 | 1 2 4 6 3 10 7 8 5 9 23 | 2 1 3 7 9 10 6 4 5 8 24 | -------------------------------------------------------------------------------- /实验用例/国际标准用例/la01.in: -------------------------------------------------------------------------------- 1 | 10p5m5j 2 | 21 53 95 55 34 3 | 21 52 16 26 71 4 | 39 98 42 31 12 5 | 77 55 79 66 77 6 | 83 34 64 19 37 7 | 54 43 79 92 62 8 | 69 77 87 87 93 9 | 38 60 41 24 83 10 | 17 49 25 44 98 11 | 77 79 43 75 96 12 | 13 | 2 1 5 4 3 14 | 1 4 5 3 2 15 | 4 5 2 3 1 16 | 2 1 5 3 4 17 | 1 4 3 2 5 18 | 2 3 5 1 4 19 | 4 5 2 3 1 20 | 3 1 2 4 5 21 | 4 2 5 1 3 22 | 5 4 3 2 1 23 | -------------------------------------------------------------------------------- /实验用例/国际标准用例/la05.in: -------------------------------------------------------------------------------- 1 | 10p5m5j 2 | 3 | 72 87 95 66 60 4 | 5 35 48 39 54 5 | 46 20 21 97 55 6 | 59 19 46 34 37 7 | 23 73 25 24 28 8 | 28 45 5 78 83 9 | 53 71 37 29 12 10 | 12 87 33 55 38 11 | 49 83 40 48 7 12 | 65 17 90 27 23 13 | 14 | 2 1 5 3 4 15 | 5 4 1 3 2 16 | 2 4 3 1 5 17 | 1 4 5 2 3 18 | 5 3 4 2 1 19 | 4 1 5 2 3 20 | 1 4 2 5 3 21 | 5 3 4 2 1 22 | 3 4 2 1 5 23 | 3 4 1 5 2 24 | -------------------------------------------------------------------------------- /实验用例/国际标准用例/la10.in: -------------------------------------------------------------------------------- 1 | 15p5m5j 2 | 3 | 58 44 5 9 58 4 | 89 97 96 77 84 5 | 77 87 81 39 85 6 | 57 21 31 15 73 7 | 48 40 49 70 71 8 | 34 82 80 10 22 9 | 91 75 55 17 7 10 | 62 47 72 35 11 11 | 64 75 50 90 94 12 | 67 20 15 12 71 13 | 52 93 68 29 57 14 | 70 58 93 7 77 15 | 27 82 63 6 95 16 | 87 56 36 26 48 17 | 76 36 36 15 8 18 | 19 | 2 3 4 1 5 20 | 2 1 5 4 3 21 | 1 2 3 5 4 22 | 4 2 3 1 5 23 | 3 1 2 4 5 24 | 4 5 3 1 2 25 | 2 5 1 3 4 26 | 3 4 2 5 1 27 | 1 4 5 2 3 28 | 3 5 4 1 2 29 | 1 5 4 3 2 30 | 3 1 2 5 4 31 | 4 3 2 5 1 32 | 2 3 5 1 4 33 | 4 3 1 5 2 34 | -------------------------------------------------------------------------------- /实验用例/国际标准用例/la12.in: -------------------------------------------------------------------------------- 1 | 20p5m5j 2 | 3 | 23 82 84 45 38 4 | 50 41 29 18 21 5 | 16 54 52 38 52 6 | 62 57 37 74 54 7 | 68 61 30 81 57 8 | 89 89 11 79 81 9 | 66 91 33 20 20 10 | 8 24 55 32 84 11 | 7 64 39 56 54 12 | 19 40 7 8 83 13 | 63 64 91 40 6 14 | 42 61 15 98 74 15 | 80 26 75 6 87 16 | 39 22 75 24 44 17 | 15 79 8 12 20 18 | 26 43 80 22 61 19 | 62 36 63 96 40 20 | 33 18 22 5 10 21 | 64 64 89 96 95 22 | 18 23 15 38 8 23 | 24 | 2 1 5 3 4 25 | 4 5 2 1 3 26 | 5 4 2 3 1 27 | 2 4 5 3 1 28 | 4 2 3 1 5 29 | 2 3 4 1 5 30 | 2 1 4 5 3 31 | 4 5 3 1 2 32 | 1 3 2 5 4 33 | 1 5 4 3 2 34 | 1 3 4 5 2 35 | 2 4 5 3 1 36 | 2 1 4 5 3 37 | 3 5 1 4 2 38 | 2 4 5 1 3 39 | 4 3 1 5 2 40 | 3 2 1 4 5 41 | 2 4 1 5 3 42 | 3 5 1 2 4 43 | 3 5 4 2 1 44 | -------------------------------------------------------------------------------- /实验用例/学长提供的用例/test1.in: -------------------------------------------------------------------------------- 1 | 3p3m3j 2 | 32 15 19 3 | 27 12 32 4 | 5 17 21 5 | 6 | 1 2 3 7 | 3 1 2 8 | 2 3 1 -------------------------------------------------------------------------------- /实验用例/学长提供的用例/test2.in: -------------------------------------------------------------------------------- 1 | 6p4m4j 2 | 8 19 21 15 3 | 21 9 18 11 4 | 5 23 12 31 5 | 19 8 15 41 6 | 15 34 24 14 7 | 11 19 17 22 8 | 9 | 4 1 2 3 10 | 3 2 1 4 11 | 4 2 1 3 12 | 2 4 3 1 13 | 4 1 2 3 14 | 1 3 2 4 -------------------------------------------------------------------------------- /实验用例/学长提供的用例/test3.in: -------------------------------------------------------------------------------- 1 | 10p6m6j 2 | 17 20 6 28 12 13 3 | 8 13 33 29 6 51 4 | 21 31 45 9 22 18 5 | 7 21 19 32 24 10 6 | 15 37 26 4 31 17 7 | 9 13 27 11 47 21 8 | 5 19 32 26 12 7 9 | 45 17 19 41 9 15 10 | 29 34 17 6 43 10 11 | 16 12 39 28 17 25 12 | 13 | 1 2 3 4 5 6 14 | 2 6 5 1 4 3 15 | 5 6 2 3 1 4 16 | 6 1 3 2 4 5 17 | 4 2 3 6 5 1 18 | 1 2 3 6 5 4 19 | 2 4 3 5 6 1 20 | 3 4 1 2 6 5 21 | 5 1 2 6 3 4 22 | 6 3 4 5 1 2 -------------------------------------------------------------------------------- /实验用例/学长提供的用例/test4.in: -------------------------------------------------------------------------------- 1 | 15p12m12j 2 | 11 13 7 9 19 21 27 25 10 17 6 33 3 | 19 25 16 19 40 22 21 15 19 16 37 21 4 | 41 38 27 9 29 11 23 13 19 20 14 13 5 | 22 31 17 29 12 21 17 15 11 10 26 30 6 | 33 11 27 19 12 23 37 15 6 3 14 18 7 | 11 13 7 9 19 21 27 25 10 17 6 33 8 | 24 19 35 16 24 11 29 20 18 8 41 9 9 | 5 17 47 22 11 32 20 26 18 19 16 4 10 | 1 9 4 15 3 14 21 7 2 9 16 5 11 | 36 12 24 6 28 9 17 15 5 13 26 10 12 | 19 33 45 33 29 13 19 5 15 4 9 21 13 | 13 23 30 29 18 21 25 28 29 10 6 7 14 | 8 16 25 40 31 35 27 21 17 9 16 23 15 | 51 12 27 39 14 26 21 10 7 18 12 13 16 | 17 23 17 29 39 11 20 24 15 14 26 3 17 | 18 | 1 3 5 6 2 11 7 4 8 12 9 10 19 | 12 10 11 8 9 6 3 2 1 4 5 7 20 | 8 9 1 5 3 4 10 2 12 7 11 6 21 | 2 3 11 9 5 6 8 12 7 10 1 4 22 | 1 2 3 4 5 6 7 8 9 10 11 12 23 | 12 11 10 9 8 7 6 5 4 3 2 1 24 | 5 6 1 2 3 4 9 8 12 10 7 11 25 | 7 1 9 8 4 10 3 12 2 11 5 6 26 | 10 8 4 2 3 11 7 5 12 1 6 9 27 | 4 10 1 6 7 9 12 5 2 11 3 8 28 | 11 5 2 1 10 9 12 7 6 4 8 3 29 | 6 10 7 1 4 11 5 2 9 3 12 8 30 | 9 7 8 10 2 5 4 12 1 6 3 11 31 | 3 12 1 5 7 9 8 2 6 4 11 10 32 | 1 12 9 10 2 5 11 7 4 3 8 6 -------------------------------------------------------------------------------- /实验用例/用例转换器.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // TransfromLA 4 | // 5 | // Created by 林理露 on 16/6/4. 6 | // Copyright © 2016年 林理露. All rights reserved. 7 | // 8 | 9 | #include 10 | using namespace std; 11 | 12 | int a[100][100],b[100][100]; 13 | int main(int argc, const char * argv[]) { 14 | freopen("/users/LLL/Desktop/la122.in", "r", stdin); 15 | freopen("/users/LLL/Desktop/la12.in", "w", stdout); 16 | int n=0,m=0; 17 | cin>>n>>m; 18 | cout<>a[i][j]>>b[i][j]; 22 | } 23 | } 24 | cout<