DP / 일반재귀 퍼포먼스 관련 질문 (BOGGLE 문제로 테스트함)

  • lee890211
    lee890211

    BOGGLE 문제를 두가지 방법으로 풀어보았는데
    첫번째 방법은 시작전에 불가능한 케이스를 전부제거하고 재귀호출로 답을 구하였고
    두번째 방법은 동적프로그래밍으로 결과를 전부 메모해가면서 중복호출이없는 재귀호출을 하엿습니다. (이경우엔 시작전에 불가능한 케이스를 제거하는 로직자체가 실행속도를 딜레이시키더라구요, 그래서 그부분은 제거했습니다)
    첫번째 어프로치는 그냥 제가 막푼거고
    두번째 방법은 자바로 짠로직중에 제일빠른게 heekyu님꺼여서 참고해서 사용하였는데 속도 차이가 많이나네요.

    속도는 첫번째가 약 30% 가량 더 빠르게 나왔습니다. 알고리즘특성상 dp가 더 느릴수가 없는거같은데 혹시 제가 구현을 잘못한건가요? 아래 코드 첨부합니다.

    -------------첫번째
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.LinkedList;
    import java.util.Queue;
    import java.util.StringTokenizer;

    public class Main {
    private static int boardSize = 5;
    public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int cNum = Integer.parseInt(br.readLine());
    for (int i=0; i<cNum; i++){
    char[][] board = new char[boardSize][];
    for (int j=0; j<boardSize; j++)
    board[j] = br.readLine().toCharArray();

    int searchStrNum = Integer.parseInt(br.readLine());
            for (int j=0; j<searchStrNum; j++){
                String str = br.readLine();
                System.out.println(String.format("%s %s", str, solve(board, str.toCharArray(), 0, -1, -1)?"YES":"NO"));
            }
        }
        br.close();
    }
    private static boolean solve(char[][] board, char[] search, int pt, int prev_x, int prev_y) {
        boolean res = false;
        if (pt == search.length) return true;
        else if (pt == 0){
            if (!check(board, search)) return false;
            for (int i=0; i<boardSize; i++)
                for (int j=0; j<boardSize; j++)
                    if (board[i][j] == search[pt])
                        res = res || solve(board, search, pt+1, i, j);
        } else{
            if (prev_x > 0){
                if (prev_y > 0 && board[prev_x-1][prev_y-1] == search[pt]) {
                    res = res || solve(board, search, pt+1, prev_x-1, prev_y-1);}
                if (board[prev_x-1][prev_y] == search[pt]) {
                    res = res || solve(board, search, pt+1, prev_x-1, prev_y);}
                if (prev_y < 4 && board[prev_x-1][prev_y+1] == search[pt]) {
                    res = res || solve(board, search, pt+1, prev_x-1, prev_y+1);}
            }
            if (prev_x < boardSize-1){
                if (prev_y > 0 && board[prev_x+1][prev_y-1] == search[pt]) {
                    res = res || solve(board, search, pt+1, prev_x+1, prev_y-1);}
                if (board[prev_x+1][prev_y] == search[pt]) {
                    res = res || solve(board, search, pt+1, prev_x+1, prev_y);}
                if (prev_y < 4 && board[prev_x+1][prev_y+1] == search[pt]) {
                    res = res || solve(board, search, pt+1, prev_x+1, prev_y+1);}
            }
            if (prev_y > 0 && board[prev_x][prev_y-1] == search[pt]) {
                res = res || solve(board, search, pt+1, prev_x, prev_y-1);}
            if (prev_y < 4 && board[prev_x][prev_y+1] == search[pt]) {
                res = res || solve(board, search, pt+1, prev_x, prev_y+1);}
        }
        return res;
    }
    private static boolean check(char[][] board, char[] search) {
        int len = search.length;
        boolean[] memo = new boolean[len];
        for (int i=0; i<boardSize; i++){
            for (int j=0; j<boardSize; j++){
                for (int k=0; k<len; k++){
                    if (memo[k])
                        continue;
                    if (board[i][j] == search[k])
                        memo[k] = true;
                }
            }
        }
        for (int k=0; k<len; k++)
            if (!memo[k]) return false;
        return true;
    }

    }

    ---------------두번째
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;

    public class Main {
    private static final int boardSize = 5;
    private static final int[] dx = {-1, -1, -1, 1, 1, 1, 0, 0};
    private static final int[] dy = {-1, 0, 1, -1, 0, 1, -1, 1};
    private static char[][] board = new char[boardSize][boardSize];
    public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int cNum = Integer.parseInt(br.readLine());
    for (int i=0; i<cNum; i++){
    for (int j=0; j<boardSize; j++)
    board[j] = br.readLine().toCharArray();

    int searchStrNum = Integer.parseInt(br.readLine());
            for (int j = 0; j < searchStrNum; j++) {
                String str = br.readLine();
                char[] chArr = str.toCharArray();
                boolean ret = false;
                boolean[][][] boardMem = new boolean[boardSize][boardSize][chArr.length];
                for (int x = 0; x < boardSize; x++) {
                    for (int y = 0; y < boardSize; y++) {
                        if (solve(boardMem, chArr, 0, x, y)) {
                            x = boardSize;
                            y = boardSize;
                            ret = true;
                        }
                    }
                }
                System.out.println(String.format("%s %s", str, ret?"YES":"NO"));
            }
        }
        br.close();
    }
    private static boolean solve(boolean[][][] boardMem, char[] search, int pt, int x, int y) {
        if (x>=boardSize || x<0 || y<0 || y>=boardSize) return false;
        if (board[x][y] != search[pt]) return false;
        if (pt == search.length-1) return true;
        if (boardMem[x][y][pt]) return false;
        boardMem[x][y][pt] = true;
        for (int dir = 0; dir<8; dir++){
            int nextX = x + dx[dir], nextY = y + dy[dir];
            if (solve(boardMem, search, pt+1, nextX, nextY))
                return true;
        }
        return false;
    }

    }


    10년 전
4개의 댓글이 있습니다.
  • heekyu
    heekyu

    두 번째 코드를 제가 짠 코드랑 비교해서 다른 점 중심으로 비슷하게 만들어 가면서 테스트를 해봤는데..
    결과적으로는 왜 시간 차이가 나는지 잘 모르겠습니다..;
    제가 시도해 본 건 다음과 같습니다.

    1. method call을 줄이기 위해 조건 체크를 method call 전으로 올김.
      대상 로직:
      if (x>=boardSize || x<0 || y<0 || y>=boardSize) return false;
      if (board[x][y] != search[pt]) return false;
      if (boardMem[x][y][pt]) return false;
      결과: 효과 없음

    2. IO write 변경
      System.out.println 하는 대신 PrintWriter를 써서 마지막에 한꺼번에 disk에 쓰도록 변경
      결과 : 소폭 향상

    3. 탐색 순서 변경(dx, dy 순서 변경)
      결과 : 효과 없음

    4. method 를 static -> non-static으로 변경
      결과 : 소폭 향상(가장 효과가 큼)

    5. check 함수를 가져다가 찾는 String이 없을 경우 NO 처리
      결과 : 효과 없음

    첫 번째 코드와 두 번째 코드의 차이는
    한 번 지나갔던 길을 또 가는지 마는지 정도 차이가 있는데
    결과적으로 차이가 안나는 걸로 봐서는 추측해보면
    테스트 케이스에서 여러 가지 방식으로 String이 만들어지는 경우가 많지 않나 봅니다.

    그리고 수행 시간의 경우에(특히 Java가 C에 비해 심한데)
    같은 코드를 judge 돌려도 몇십ms 씩 차이가 나거나 100ms 이상 차이가 나는 경우도 있고, 특히 IO 같은 알고리즘 이외에 변수 같은 것도 있으니 알고리즘 적으로 더 파볼 게 있는 게 아니면 의미를 크게 둘 필요가 없는 경우가 많은 것 같습니다.


    10년 전 link
  • lee890211
    lee890211

    답변 감사합니다 ㅎㅎ 많은 도움이 되었습니다.


    10년 전 link
  • heekyu
    heekyu

    문제 댓글에 있는
    1
    AAAAA
    AAAAA
    AAAAA
    AAACC
    AAACB
    1
    AAAAAAAAAB

    이 input 넣어보면 두 알고리즘이 확실히 시간 차이가 많이 납니다.


    10년 전 link
  • lee890211
    lee890211

    음 그러네요 테스트케이스에는 위와같은 반복된 계산을 필요하는 인풋이없어서 우연의 일치로 일반재귀가 빠르게 나왓나 보네요


    10년 전 link
  • 정회원 권한이 있어야 커멘트를 다실 수 있습니다. 정회원이 되시려면 온라인 저지에서 5문제 이상을 푸시고, 가입 후 7일 이상이 지나셔야 합니다. 현재 문제를 푸셨습니다.