알고리즘/알고리즘(Java)

24.[다이나믹]조립라인 스케줄링(Assembly Line Scheduling)

산을좋아한라쯔 2015. 4. 25. 11:23
반응형

문제

공장에 제품을 생산하는 두 조립라인이 있다. 

조립 라인은 아래 그림처럼, 처음에 부품을 준비하는 준비단계를 거쳐(s0,s1), 자신의 조립라인 단계를 통과하거나(a0,0~a0,n, a1,0~a1,n), 혹은 다른 조립라인으로 이동해서(t0,1 등) 단계를 마친 후, 마지막에  최종점검 단계를 거쳐서(e0,e1) 조립이 완료된다. 



각 단계별 소요시간 a와, 다른 라인으로 이동할 때 걸리는 시간 t 등이 각각 다르게 주어졌을 때, 최소 시간안에 조립이 완료될 수 있는 경로를 제시하고 그 때 소요되는 총 소요시간을 구하시오.


예를 들어, 아래 그림처럼 문제가 주어졌을 때, 최소 이동단계는 E0 -> A0,0 -> A1,1 -> C1 이고, 최소소요시간은 10이다.

 (2+3+1+2+2=10)

이동단계를 단계당 라인번호로 표현하면, Step0:0 -> Step1:1



입력파일

문제로 주어지는 입력파일내 데이터 형식은 다음과 같다.


테스트_개수

n

e0 c0

a0,0 a0,1 ... a0,n-2 a0,n-1

e1 a1,0 a1,1 ... a1,n-2 a1,n-1 c1

t0,0 t0,1 ... t0,n-2 

t1,0 t1,1 ... t1,n-2 


입력파일 예는 다음과 같다.

2

2

2 4

3 3

1

3 2

5 2

3

6

2 3

7 9 3 4 8 4

2 3 1 3 4

4 2

8 5 6 4 5 7

2 1 2 2 1


출력 형식(예)

Test #1 

최소소요시간: 10

실행 순서:E0 -> 0 -> 1 -> C1



문제 소스

다음 코드를 활용해서 작성하시오.

package dynamic;

 

import java.io.File;

import java.util.Scanner;

 

public class AssemblyLine {

 

         public static void main(String[] args) throws Exception {

                  Scanner sc = new Scanner(new File("res/AssemblyLine.in"));

 

                  int T, N;

                  T = sc.nextInt();

 

                  for (int testNum = 1; testNum <= T; testNum++) {

                           N = sc.nextInt(); // 단계

 

                           int[] e = new int[2];// e[0]:0 라인

                           int[] c = new int[2];

                           int[][] a = new int[2][N];

                           int[][] t = new int[2][N - 1];

                           // 1. 0번라인: e0, c0

                           e[0] = sc.nextInt();

                           c[0] = sc.nextInt();

 

                           // 2. 0번라인: a[0][0]~a[0][n-1]

                           for (int i = 0; i < N; i++) {

                                   a[0][i] = sc.nextInt();

                           }

 

                           // 3. 0번라인: t[0][0]~t[0][n-2]

                           for (int i = 0; i < N - 1; i++) {

                                   t[0][i] = sc.nextInt();

                           }

 

                           // 4. 1번라인: e1, c1

                           e[1] = sc.nextInt();

                           c[1] = sc.nextInt();

 

                           // 5. 1번라인: a[1][0]~a[1][n-1]

                           for (int i = 0; i < N; i++) {

                                   a[1][i] = sc.nextInt();

                           }

 

                           // 6. 1번라인: t[1][0]~t[1][n-2]

                           for (int i = 0; i < N - 1; i++) {

                                   t[1][i] = sc.nextInt();

                           }

 

                           // path={0,0,1,1,0,...}

                           int[] path = new int[N];

                           int min_time = assembly(N, e, a, t, c, path);

 

                           print(testNum,min_time,path);                       

                           System.out.println("");

                  }

         }

        

         static void print(int testNum, int min_time, int[] path){

                  System.out.println("Test #" + testNum);

                  System.out.println("min process time:" + min_time);

                 

                  if(path[0]==0){

                           System.out.print("E0 -> ");

                  }else{

                           System.out.print("E1 -> ");

                  }

                  for (int i = 0; i < path.length; i++) {

                           System.out.print(path[i] + " -> ");

                  }

                  if(path[path.length-1]==0){

                           System.out.print("C0");                    

                  }else{

                           System.out.print("C1");

                  }

                  System.out.println("");

         }

 

         private static int assembly(int n, int[] e, int[][] a, int[][] t, int[] c, int[] path) {

                  return 0;

         }

}

 

-------------------------------------------------------------------------------

-------------------------------------------------------------------------------

풀이

전형적인 다이니믹 알고리즘 형태의 문제이다. 최종단을 기준으로 했을 때, 문제 조건을 만족하는 앞 단을 재귀적으로 찾는.


먼저 점화식을 도출해보자.


어떤 단계까지의 소요시간을 f라하면 다음과 같은 식이 성립된다.

 단계: 0~(n-1)

 0라인의 소요시간: f[0][0], f[0][1] ... f[0][n-1]  

 1라인의 소요시간: f[1][0], f[1][1] ... f[1][n-1]


 f[0][0] = e[0] + a[0][0]

 f[1][0] = e[1] + a[1][0]

 

 if (j>0)

 f[0][j] = min(f[0][j-1]+a[0][j] , f[1][j-1]+t[1][j-1]+a[0][j])

 f[1][j] = min(f[1][j-1]+a[1][j] , f[0][j-1]+t[0][j-1]+a[1][j])

f[0][0]이라고 하면, 0번 라인의 0단계까지 걸리는 시간을 의미하고,

이것은 초기 부품준비시간인 e[0]에, 0단계에서 소요되는 시간인 a[0][0]을 더한 값이 된다.

즉, f[0][0] = e[0] + a[0][0]   

1번라인에 대해서도, f[1][0] = e[1] + a[1][0]


1단계 이상에서의 값은, 같은 라인으로부터 온 경로만 생각할 것이 아니라, 다른 라인에서 온 경로도 고려해야 한다. 다른 경로에서 오는 경우는 라인 전환비용인 t가 추가되므로, 1단계 이상의 값인 j단계에서의 최소시간은 아래와 같이 표현될 수 있다.

 f[0][j] = min (0번 라인의 j-1번째 값 + j번 단계 소요시간 , 1번라인의 j-1번째 값 + 1번라인에서 0번라인으로의 전환비용 + j번 단계 소요시간)

       =min(f[0][j-1]+a[0][j] , f[1][j-1]+t[1][j-1]+a[0][j]) 

 f[1][j] = min(f[1][j-1]+a[1][j] , f[0][j-1]+t[0][j-1]+a[1][j])


* 2차원 배열이어서, 꺽쇄가 많아 보기에 헷갈릴 수 있지만, 같은 라인에서 오는가, 아니면 다른 라인쪽에서 오는가를 형상화하며 생각하면, 그리 어렵지 않게 이해할 수 있을 것이다.


이렇게 정의하면, 주어진 문제는 0번라인의 최종단계에서의 값인 (f[0][n-1] + c[0])와 , 1번라인 최종값인 (f[1][n-1] + c[1])중에서, 작은 값을 찾아내는 것이 된다. 

  f_min = min(f[0][n-1] + c[0] , f[1][n-1] + c[1])


이제 하나씩 구현을 해보자.


먼저, 문제코드를 보면, 최소소요시간을 리턴하고, path[]에 최소경로를 넣어야하는 assembly() 메서드를 구현하도록 되어 있다.

int[] path = new int[N];

int min_time = assembly(N, e, a, t, c, path);


이 assembly()를, 위에서 얘기했던 함수 f를 호출해서, f_min을 구해내는 형태로 작성하자.

         private static int assembly(int n, int[] e, int[][] a, int[][] t, int[] c, int[] path) {

                  int[] path0 = new int[n];

                  int[] path1 = new int[n];

                  int minF0 = F(0, n - 1, e, a, t, c, path0) + c[0];

                  int minF1 = F(1, n - 1, e, a, t, c, path1) + c[1];

 

                  if (minF0 < minF1) {

                           path0[n-1]=0;

                           System.arraycopy(path0, 0, path, 0, path.length);                     

                           return minF0;

                  } else {

                           path1[n-1]=1;

                           System.arraycopy(path1, 0, path, 0, path.length);                     

                           return minF1;

                  }

         }

F라는 함수를 호출해서 0번 라인의 끝단(n-1번째)에서의 최솟값과(minF0), 1번 라인에 대한 최솟값(minF1)을 구해서 비교하는 것을 볼 수 있겠다.


이제 핵심은 F함수를 구현하는 것이다.

다음과 같이 구현될 수 있다.

         private static int F(int line, int j, int[] e, int[][] a, int[][] t, int[] c, int[] path) {

                  if (j == 0) {

                           return e[line] + a[line][0];

                  }

 

                  int other = (line + 1) % 2;

                  int A = F(line, j - 1, e, a, t, c, path) + a[line][j];

                  int B = F(other, j - 1, e, a, t, c, path) + t[other][j - 1] + a[line][j];

                  if (A < B) {

                           path[j - 1] = line;

                           return A;

                  } else {

                           path[j - 1] = other;

                           return B;

                  }

         }

처음 부분에서 도출했던 점화식을 그대로 사용했음을 알 수 있다.

  Base: j==0 이면, 초기 0번째 단계를 의미하므로, 초기준비단계값인 e와 0단계 소요시간을 더한 값이 된다.

         f[0] = e[0] + a[0][0]

         f[1] = e[1] + a[1][0]

  점화식

        A: 같은 라인의 '전 단계'에서 왔을 때 값

        B: 다른 라인에서, 전환비용(t)을 감수하면서 왔을 때 값

        이 둘을 비교해서 작은 쪽을 선택

     

이처럼 재귀호출을 사용해서 일단 테스트를 해본다. (assembly와 F 메서드를 구현해서 돌려본다.)

그러면 아래 처럼 해답이 잘 출력될 것임.

Test #1

min process time:10

E0 -> 0 -> 1 -> C1


Test #2

min process time:38

E0 -> 0 -> 1 -> 0 -> 1 -> 1 -> 0 -> C0



그러나, 이처럼 입력수가 작을 때는 재귀호출이 별 문제 안되나, 단계수가 많은 문제에 대해서는 재귀호출을 하면 지정시간에 수행할 수 없을 수 있다. 아래처럼 메모이제이션 기법을 써서 F메서드를 개선하자.

         private static int F_memo(int line, int j, int[] e, int[][] a, int[][] t, int[] c,

int[] path, int[][] table) {

                  if (j == 0) {

                           table[line][j] = e[line] + a[line][0];

                           return table[line][j];

                  }

 

                  int other = (line + 1) % 2;

                  if(table[line][j-1]==0){

                           table[line][j-1] = F_memo(line, j - 1, e, a, t, c, path,table);

                  }

                 

                  if(table[other][j-1]==0){

                           table[other][j-1] = F_memo(other, j - 1, e, a, t, c, path,table);

                  }                

                 

                  int A = table[line][j-1] + a[line][j];

                  int B = table[other][j-1] + t[other][j - 1] + a[line][j];

                  if (A < B) {

                           path[j - 1] = line;

                           return A;

                  } else {

                           path[j - 1] = other;

                           return B;

                  }

         }


F()메서드를 호출하는 부분도 수정되야 한다.

                  int[][] table = new int[2][n];

                  int minF0 = F_memo(0, n - 1, e, a, t, c, path0,table) + c[0];

                  int minF1 = F_memo(1, n - 1, e, a, t, c, path1,table) + c[1];




전체 소스는 다음과 같다.

소스

package dynamic;

 

import java.io.File;

import java.util.Scanner;

 

public class AssemblyLine {

 

         public static void main(String[] args) throws Exception {

                  Scanner sc = new Scanner(new File("res/AssemblyLine.in"));

 

                  int T, N;

                  T = sc.nextInt();

 

                  for (int testNum = 1; testNum <= T; testNum++) {

                           N = sc.nextInt(); // 단계

 

                           int[] e = new int[2];// e[0]:0 라인

                           int[] c = new int[2];

                           int[][] a = new int[2][N];

                           int[][] t = new int[2][N - 1];

                           // 1. 0번라인: e0, c0

                           e[0] = sc.nextInt();

                           c[0] = sc.nextInt();

 

                           // 2. 0번라인: a[0][0]~a[0][n-1]

                           for (int i = 0; i < N; i++) {

                                   a[0][i] = sc.nextInt();

                           }

 

                           // 3. 0번라인: t[0][0]~t[0][n-2]

                           for (int i = 0; i < N - 1; i++) {

                                   t[0][i] = sc.nextInt();

                           }

 

                           // 4. 1번라인: e1, c1

                           e[1] = sc.nextInt();

                           c[1] = sc.nextInt();

 

                           // 5. 1번라인: a[1][0]~a[1][n-1]

                           for (int i = 0; i < N; i++) {

                                   a[1][i] = sc.nextInt();

                           }

 

                           // 6. 1번라인: t[1][0]~t[1][n-2]

                           for (int i = 0; i < N - 1; i++) {

                                   t[1][i] = sc.nextInt();

                           }

 

                           // path={0,0,1,1,0,...}

                           int[] path = new int[N];

                           int min_time = assembly(N, e, a, t, c, path);

 

                           print(testNum,min_time,path);                       

                           System.out.println("");

                  }

         }

        

         static void print(int testNum, int min_time, int[] path){

                  System.out.println("Test #" + testNum);

                  System.out.println("min process time:" + min_time);

                 

                  if(path[0]==0){

                           System.out.print("E0 -> ");

                  }else{

                           System.out.print("E1 -> ");

                  }

                  for (int i = 0; i < path.length; i++) {

                           System.out.print(path[i] + " -> ");

                  }

                  if(path[path.length-1]==0){

                           System.out.print("C0");                    

                  }else{

                           System.out.print("C1");

                  }

                  System.out.println("");

         }

 

         private static int assembly(int n, int[] e, int[][] a, int[][] t, int[] c, int[] path) {

                  int[] path0 = new int[n];

                  int[] path1 = new int[n];

                  //int minF0 = F(0, n - 1, e, a, t, c, path0) + c[0];

                  //int minF1 = F(1, n - 1, e, a, t, c, path1) + c[1];

                 

                  int[][] table = new int[2][n];

                  int minF0 = F_memo(0, n - 1, e, a, t, c, path0,table) + c[0];

                  int minF1 = F_memo(1, n - 1, e, a, t, c, path1,table) + c[1];

                                  

 

                  if (minF0 < minF1) {

                           path0[n-1]=0;

                           System.arraycopy(path0, 0, path, 0, path.length);                     

                           return minF0;

                  } else {

                           path1[n-1]=1;

                           System.arraycopy(path1, 0, path, 0, path.length);                     

                           return minF1;

                  }

         }

 

         private static int F(int line, int j, int[] e, int[][] a, int[][] t, int[] c, int[] path) {

                  if (j == 0) {

                           return e[line] + a[line][0];

                  }

 

                  int other = (line + 1) % 2;

                  int A = F(line, j - 1, e, a, t, c, path) + a[line][j];

                  int B = F(other, j - 1, e, a, t, c, path) + t[other][j - 1] + a[line][j];

                  if (A < B) {

                           path[j - 1] = line;

                           return A;

                  } else {

                           path[j - 1] = other;

                           return B;

                  }

         }

        

         private static int F_memo(int line, int j, int[] e, int[][] a, int[][] t, int[] c, int[] path, int[][] table) {

                  if (j == 0) {

                           table[line][j] = e[line] + a[line][0];

                           return table[line][j];

                  }

 

                  int other = (line + 1) % 2;

                  if(table[line][j-1]==0){

                           table[line][j-1] = F_memo(line, j - 1, e, a, t, c, path,table);

                  }

                 

                  if(table[other][j-1]==0){

                           table[other][j-1] = F_memo(other, j - 1, e, a, t, c, path,table);

                  }                

                 

                  int A = table[line][j-1] + a[line][j];

                  int B = table[other][j-1] + t[other][j - 1] + a[line][j];

                  if (A < B) {

                           path[j - 1] = line;

                           return A;

                  } else {

                           path[j - 1] = other;

                           return B;

                  }

         }

}

 



-끝-


assemblyline.pptx
0.07MB
반응형