Development Palette

BFS, DFS, Backtracking 본문

Algorithm/개념

BFS, DFS, Backtracking

징주 2021. 8. 19. 22:19

나무위키

너비우선탐색 (Breadth First Search, BFS)

  • 루트 노드의 자식노드들을 먼저 모두 차례로 방문, 이렇게 방문했던 자식 노드들의 그 자식들을 차례로 방문하는 방식.
  • 즉, 루트 -> 자식1 -> 자식2 -> 자식3 -> 손자(?)1 -> 손자2.. 이런식
  • Queue 사용 : 인접한 노드들을 탐색 후, 차례로 다시 너비(높이(레벨)가 같은) 우선 탐색을 해야함.. 따라서 선입선출을 이용해야한다
  • 이때 index를 이용해 탐색한다. 즉, Integer타입 (왼쪽자식은 x2, 오른쪽자식은 x2+1)
Queue <Integer> q = new LinkedList<>();

깊이우선탐색 (Depth First Search, DFS)

  • 루트 노드에서 출발해 깊이 탐색하다가 더 이상 갈 수 없으면, 가장 마지막에 만났던 노드로 돌아가 다른 방향의 노드로 탐색을 반복하여 모든 노드를 방문하는 방식
  • Stack 또는 재귀 사용 : 마지막에 만났던 갈림길의 노드로 되돌아가서 다시 깊이우선탐색을 반복하기 때문에 ,,

 

무방향 그래프 인접리스트로 구현

 

1260번: DFS와 BFS

첫째 줄에 정점의 개수 N(1 ≤ N ≤ 1,000), 간선의 개수 M(1 ≤ M ≤ 10,000), 탐색을 시작할 정점의 번호 V가 주어진다. 다음 M개의 줄에는 간선이 연결하는 두 정점의 번호가 주어진다. 어떤 두 정점 사

www.acmicpc.net

package com.hotfive.workbook.b1260_DFS와BFS;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
//https://www.acmicpc.net/problem/1260

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        int N = Integer.parseInt(st.nextToken());   //정점개수
        int M = Integer.parseInt(st.nextToken());   //간선갯수
        int V = Integer.parseInt(st.nextToken());   //탐색을 시작할 정점의 번호

        //방문체크 배열
        boolean [] visited = new boolean[N+1];

        // 정점마다 연결해 줄 인접리스트 배열생성
        LinkedList<Integer>[] adjlist = new LinkedList[N+1];
        for (int n = 1; n <= N; n++) {
            adjlist[n] = new LinkedList<>(); // 해당 정점에 연결된 정점들이 담겨있는 리스트
        }

        // 정점 연결 // 무방향 간선 연결
        for (int m = 0; m < M; m++) {
            st = new StringTokenizer(br.readLine());
            int v1 = Integer.parseInt(st.nextToken());  // 연결할 정점 v1, v2
            int v2 = Integer.parseInt(st.nextToken());
            adjlist[v1].add(v2);
            adjlist[v2].add(v1);
        }

        // 방문할 수 있는 정점이 여러 개인 경우에는 정점 번호가 작은 것을 먼저 방문하기 위해 오름차순 정렬
        for (int n = 1; n <=N; n++) {
            Collections.sort(adjlist[n]);
        }

        // DFS, BFS 실행
        dfs(V, adjlist, visited);
        visited = new boolean[N+1];
        System.out.println();
        bfs(V, adjlist, visited);

        br.close();
    }

    private static void dfs(int v, LinkedList<Integer>[] adjlist, boolean[] visited) {
        visited[v] = true;
        System.out.print(v + " ");

        for (int n : adjlist[v]) {
            if (visited[n]) continue;
            dfs(n,adjlist,visited);
        }
    }

    public static void bfs(int v, LinkedList<Integer>[] adjList, boolean[] visited) {

        Queue<Integer> queue = new LinkedList<>();
        queue.add(v);
        visited[v]= true;

        while (!queue.isEmpty()){
            v = queue.poll();
            System.out.print(v + " ");

            for (int n : adjList[v]) {
                if(visited[n]) continue;
                visited[n] = true;
                queue.add(n);
            }
        }
    }
}
/*
4 5 1
1 2
1 3
1 4
2 4
3 4
 */

 

Backtracking

  1. DFS를 수행한다.
  2. 각 노드의 유망성을 확인.
  3. 유망하지 않으면 가지치기(prunig) --> 유망하지 않으면 그 노드의 부모로 돌아가서 다른 노드의 검색을 계속한다.(불필요한 경로 미리 차단)
  • 주의 : 1%의 가능성이 있으면 탐색해야한다.
  • 또한, 최악의 경우 지수함수(선택지^depth) 형태로 시간복잡도가 나타나기 때문에 처리 불가할 수 있다.

Backtracking 예시

Q1. 체스를 둘 때 서로 위협하지 않게 8개의 Queen을 배치하려면 몇가지 경우가 있는지?

  • 위협 조건 : 같은 행, 같은 열, 양쪽 대각선
package com.ssafy.w0819;

import java.util.Scanner;

//backtracking : 유망성과 가지치기
//유망하지 않으면 부모로 돌아가 다음 자식 노드로 간다

//같은 행에 두지 않는 방식
// N개의 퀸을 위협적이지 않게 놓는 모든 경우의 수
public class NQueenTest_backtracking {

    static int N, cnt;
    static int col[];


    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);

        N = sc.nextInt();

        // 퀸을 놓을 때 상태를 나타낼 때의 배열?
        // -> 같은 행에 두지 않는 전제 조건으로 1차원 배열..로 열을 나타내기
        col = new int[N + 1]; // 1부터 쓰기 위해 0 index 사용안하겠음
        cnt = 0;

        setQueens(1);    //    1행부터 놓는 시도
        System.out.println(cnt);
    }

    // 퀸을 놓는 메소드
    private static void setQueens(int rowNo) {    // 행번호를 파라미터로 받기

        //유망성 체크 : rowNo - 1 까지 (직전까지 놓았던 퀸을) 체크해야한다.
        if(!isAvailable(rowNo-1)) return;    //    가능하지 않으면 리턴으로 종료
        // 만약 Q3까지 유망성을 체크했다면 이전인 Q2 와만 비교하면된다. 이미 유망성 체크한 것을 다시 체크할 필요X


        //기저 조건
        if(rowNo>N) {    //N행(마지막행)까지 놓고 종료
            cnt++;        
            return;
        }

        // 유도조건
        // 현재 퀸 1열부터 N열까지 놓아보기 (상태를 기억)
        // 놓았으면 다음 퀸 놓으러 가기
        for(int i = 1; i <= N; i++) {
            col[rowNo]  = i; //각 행에 놓아진 열 위치를 저장.. 계속 덮어서 재사용??

            //두가지 방법..
            // 첫 번째 : 유망하지 않으면 backtracking
            setQueens(rowNo+1);

        }

    }

    //유망성 체크
    private static boolean isAvailable(int rowNo) {    //  rowNo : 현재 검사하려는 퀸

        //    1행퀸이면 2행 퀸까지 비교하면된다.
        for(int k = 1; k < rowNo; k++) {        //    k : 이전 퀸 //직전 퀸을 비교해야하므로 k <= rowNo가 아니다.
            if(col[rowNo] == col[k]  ||  Math.abs(col[rowNo] - col[k]) == (rowNo - k)    ) return false;        // 같은열 : 현재 퀸열과 직전 퀸 열과 같으면 유망 X
                                                   // 대각선 조건은?@@ 행차이와 열차이가 같다!!!!!!!!!!!!!!!! 개신기행..
        }
        return true;    //가능성있다!~
    }
}

 

package com.ssafy.w0819;

import java.util.Scanner;
// 두 번째 방법 : 유망성체크를하고 돌아가지 않아도 되는 방법
//같은 행에 두지 않는 방식
// N개의 퀸을 위협적이지 않게 놓는 모든 경우의 수
public class NQueenTest2_backtracking {

	static int N, cnt;
	static int col[];

	public static void main(String[] args) {

		Scanner sc = new Scanner(System.in);

		N = sc.nextInt();

		// 퀸을 놓을 때 상태를 나타낼 때의 배열?
		// -> 같은 행에 두지 않는 전제 조건으로 1차원 배열..로 열을 나타내기
		col = new int[N + 1]; // 1부터 쓰기 위해 0 index 사용안하겠음
		cnt = 0;
		
		setQueens(1);	//	1행부터 놓는 시도
		System.out.println(cnt);
	}

	// 퀸을 놓는 메소드
	private static void setQueens(int rowNo) {	// 행번호를 파라미터로 받기

		//첫번째 방법
		//유망성 체크 : rowNo - 1 까지 (직전까지 놓았던 퀸을) 체크해야한다.
		//if(!isAvailable(rowNo-1)) return;	//	가능하지 않으면 리턴으로 종료	
		// 만약 Q3까지 유망성을 체크했다면 이전인 Q2 와만 비교하면된다. 이미 유망성 체크한 것을 다시 체크할 필요X
		//유망성체크하고 돌아와서 진행하는 방법 //돌아오는 것을 보이게끔 표현하려고 이렇게 했다고 하심
		
		//기저 조건
		if(rowNo>N) {	//N행(마지막행)까지 놓고 종료
			cnt++;		
			return;
		}
		
		// 유도조건
		// 현재 퀸 1열부터 N열까지 놓아보기 (상태를 기억)
		// 놓았으면 다음 퀸 놓으러 가기
		for(int i = 1; i <= N; i++) {
			col[rowNo]  = i; //각 행에 놓아진 열 위치를 저장.. 계속 덮어서 재사용??
			
			//두번째 방법 : 유망성 체크를하고 바로 가는 방법~!! 
			if(isAvailable(rowNo)) {
				setQueens(rowNo+1);
			}
		}
		
	}
	
	//유망성 체크
	private static boolean isAvailable(int rowNo) {	//  rowNo : 현재 검사하려는 퀸
		
		//	1행퀸이면 2행 퀸까지 비교하면된다.
		for(int k = 1; k < rowNo; k++) {		//	k : 이전 퀸 //직전 퀸을 비교해야하므로 k <= rowNo가 아니다.
			if(col[rowNo] == col[k] 		|| 	Math.abs(col[rowNo] - col[k]) == (rowNo - k)	) return false;		// 같은열 : 현재 퀸열과 직전 퀸 열과 같으면 유망 X
																																													// 대각선 조건은?@@ 행차이와 열차이가 같다!!!!!!!!!!!!!!!! 개신기행..
		}
		return true;	//가능성있다!~
	}
}

 

Q2. 부분집합의 합이 특정 숫자(자연수)를 만족시키려면 몇가지 경우의 수가 있는지?

--> 백트랙킹을 이용하면 매우 적은 호출 횟수로 탐색할 수 있다!

package com.ssafy.w0819;

import java.util.Scanner;
/**
 *w0805  S2_SubSetSumTest 코드 변환
 */

//부분집합 합 문제_  백트레킹
import java.util.Scanner;

public class SubSetSumTest {

	static int N,totalCnt2,totalCnt3,S;
	static int[] input;
	static boolean[] isSelected;
	static int callCnt2, callCnt3;
	
	public static void main(String[] args) {

		Scanner sc = new Scanner(System.in);
		N = sc.nextInt();
		S = sc.nextInt();
		input = new int[N];
		isSelected = new boolean[N];
		callCnt3 = callCnt2 = totalCnt3 = totalCnt2 = 0;
		
		for (int i = 0; i < N; i++) {
			input[i] = sc.nextInt();
		}
		
		generateSubset2(0,0);
		System.out.println("경우의 수 : "+totalCnt2+", 호출횟수 : "+callCnt2);
		generateSubset3(0,0);
		System.out.println("경우의 수 : "+totalCnt3+", 호출횟수 : "+callCnt3);
	}

	// 가지치기 하지 않은 버전 
	private static void generateSubset2(int cnt,int sum){ // cnt : 부분집합을 처리하기 위해 고려된 원소수 
															// sum : 기존까지 부분집합 구성원소들의 합
		callCnt2++;
		if(cnt == N) {
			// 부분집합 완성
			// 부분집합의 합 == 목표합 체크
			if(sum == S) {
				totalCnt2++;
				for (int i = 0; i < N; i++) {
					if(isSelected[i]) System.out.print(input[i]+" ");
				}
				System.out.println();
			}
			return;
		}
		
		// 현재 원소를 부분집합에 넣기
		isSelected[cnt] = true;
		generateSubset2(cnt+1, sum+input[cnt]);
		// 현재 원소를 부분집합에 넣지 않기
		isSelected[cnt] = false;
		generateSubset2(cnt+1, sum);
	}
	private static void generateSubset3(int cnt,int sum){ // cnt : 부분집합을 처리하기 위해 고려된 원소수 
														 // sum : 기존까지 부분집합 구성원소들의 합
		callCnt3++;
		// 부분집합의 합 == 목표합 체크
		if(sum == S) {
			totalCnt3++;
			for (int i = 0; i < N; i++) {
				if(isSelected[i]) System.out.print(input[i]+" ");
			}
			System.out.println();
			return;
		}
		
		if(sum>S || cnt==N) return;
					
		// 현재 원소를 부분집합에 넣기
		isSelected[cnt] = true;
		generateSubset3(cnt+1, sum+input[cnt]);
		// 현재 원소를 부분집합에 넣지 않기
		isSelected[cnt] = false;
		generateSubset3(cnt+1, sum);
	}
	
}

input

5
21
5 21 11 16 6 10

output

5 16 
21 
경우의 수 : 2, 호출횟수 : 63
5 16 
21 
경우의 수 : 2, 호출횟수 : 29

'Algorithm > 개념' 카테고리의 다른 글

무방향 그래프 인접 리스트 - BFS, DFS  (0) 2021.08.24
분할정복기법 응용2  (0) 2021.08.19
[순열] 비트마스크로 순열  (0) 2021.08.18
[순열, 조합] NextPermutation  (0) 2021.08.18
이진 검색  (0) 2021.08.18
Comments