Divide and conquer = 분할 정복

1. Divide    -> 나누기

2. Conquer   -> 나눈것을 해결하기

3. Combine  -> 합치기

단계를 거친다.

ex) 합병정렬

 

Dynamic Programming

다이나믹 프로그래밍을 사용하기 위해 필요한 2가지 조건

1. 최적 부분 구조 (Optimal Substructure)

 > 길찾기에서 최단거리를 구할때 최종단계 바로 단계의 최적값을 구하면
최종단계 값을 간단히 구할 수 있다. 이렇듯 부분 구조의 최적의 답을 구해서 전체의 최적의 답을 찾는 경우를 최적 부분구조라고 한다.

 

2. 중복되는 부분 문제 (Overlapping Subproblems)

 > 피보나치 수열과 같이 fib(5) 알기 위해선 fib(3) + fib(4)값을 알아야하는데
이때 fib(2)가 여러 사용된다. 이런 케이스를 중복되는 부분문제라고 한다.
1.최적부분구조 와 2.중복되는 부분문제 두가지 조건이 충족되는 경우 다이나믹프로그래밍으로 해결 있다.

다이나믹프로그래밍을 구현하는 방법은 2가지가 있다.

1) memozation  (top - down)

 > 재귀함수 기반이기때문에 너무 많이 호출되면 아웃오브메모리

2. tabulation (bottom - up)

 > 중복 계산을 하게되는 단점 존재
 -> 리스트 저장이 아닌 변수 2개로 계산한다면 공간복잡도는 O(1) 활용 가능

 

Greedy

미래를 보지 않고 당장 보이는 최적값을 고름

  • 장점 : 간단하고 빠름
  • 단점 : 최적의 답이 보장되지 않음

 

사용처

  • 최적의 답이 필요없는 경우
  • 당장 보이는 값이 최적의 답을 보장해 주는 경우

 

Greedy Algorithm을 사용해서 최적의 솔루션을 구하기 위한 필수 조건을 모두 고르세요

  • 최적 부분 구조
  • 탐욕적 선택 속성 : 큰값부터 처리하는게 최적의 답인 경우

풀이 
> distance 배열을 만들어서 노드의 레벨을 저장해두고, 다음 번 방문시 기존 레벨이 저장되어있으면 레벨 값을 바꾸지 않는다.

코드

import java.util.*;

/**
 * 그래프에서 노드간 최소단거리 탐색 / 노드별 레벨 구하기
 * 간선에 가중치 없음 (크루스칼 아님)
 *
 * 출발은 항상 1번
 * 출발지 1번을 제외한 나머지 노드로 가는 최소값을 가진 배열 return
 *
 */
public class graphSrch {

    public void solution(int[] n, int[][] edges) {
        boolean[] ck = new boolean[n.length];
        List[] link = new List[n.length];
        int[] distance = new int[n.length];
        Stack<Integer> st = new Stack<Integer>();
        Queue<Integer> q = new LinkedList<>();

        int cur = 0;
        int end = 0;
        int cnt = 0;  //레벨

        //거리 초기화
        for(int i =1; i<distance.length; i++){
            distance[i] = n.length;
        }
        for (int i = 1; i < link.length; i++) {
            link[i] = new ArrayList<Integer>();
        }

        //간선 연결 작업
        for (int i = 0; i < edges.length; i++) {
            link[edges[i][0]].add(edges[i][1]);
            link[edges[i][1]].add(edges[i][0]);
        }
        
        //BFS 풀이
        ck = new boolean[n.length];
        cnt = 0;
        //end = i;
        //출발은 항상 1
        q.add(n[1]);
        while(!q.isEmpty()){
            cur = q.peek();
            q.poll();
            if(cnt<distance[cur]){
                distance[cur] = cnt;
            }

            //노드 방문하지 않은 경우 
            if(!ck[cur]){
                //방문 체크
                System.out.println("방문노드 : "+cur+" 현재 level :"+cnt);
                ck[cur] = true;
                //방문지점에서 갈 수 있는 다음 level add
                for(int j=0; j<link[cur].size(); j++){
                    cnt = distance[cur];

                    if(!ck[(int)link[cur].get(j)]){
                        //다음 방문할 노드기 떄문에, 거리는 현재 거리+1, 다음 방문지가 현재 거리보다 큰 경우만 변경함
                        if((cnt+1)<distance[(int)link[cur].get(j)]){
                            distance[(int)link[cur].get(j)] = cnt+1;
                        }
                        System.out.println("next = "+link[cur].get(j));
                        q.add((int)link[cur].get(j));
                    }

                }
                cnt++; // 레벨 증가
            }
        }

        System.out.println(end+"방문 최솟값 : "+cnt);


        for(int i=1; i<distance.length; i++){
            System.out.println(i +" = "+ distance[i]);
        }
    }


    public static void main(String[] args) {
        graphSrch tc = new graphSrch();

        int[] n = {0,1,2,3,4,5,6};
       // int[][] edges = {{1,3},{1,4},{2,1},{2,5},{3,4},{4,5},{4,6},{6,5},{6,2}};
        int[][] edges = {{1,2},{1,4},{1,6},{2,4},{2,5},{4,5},{5,6},{5,2},{6,3}};

        tc.solution(n,edges);
    }

}

0을 root로 둔 트리에서
leaf 노드를 출력 해보기

n개의 노드를 담은 배열 n, 각 노드간 연결정보를 가지고 있는 배열 edges를 제공

1. 각 노드별 간선정보를 저장한 배열 link 만들기
2. 탐색 시작 (stack 구현)
3. 방문한 노드와 연결된 노드를 방문하지 않았으면 push
4. 현재 노드와 연결된 노드가 1개일 경우 Leaf Node

 

 

    public void solution(int[] n, int[][] edges){
        boolean[] ck = new boolean[n.length];
        List[] link = new List[n.length];
        Stack<Integer> st = new Stack<Integer>();
        int cur =0;
        int next = 0;

        for(int i=0; i<link.length; i++){
            link[i] = new ArrayList<Integer>();
        }
        for(int i=0; i<edges.length; i++){
            link[edges[i][0]].add(edges[i][1]);
            link[edges[i][1]].add(edges[i][0]);
        }
        //1. 0부터 시작
        st.add(0);
        while(!st.isEmpty()){
            cur = st.pop();
            //System.out.println(cur+" 방문");
            ck[cur] = true; //방문처리
            //연결지점 탐색
            for(int i=0; i<link[cur].size(); i++){
                next = (int) link[cur].get(i);
                if(ck[next]){
                    if(link[cur].size() == 1){
                        System.out.println(cur+" is leafNode");
                    }
                    continue;
                }else{
                    st.push(next);
                }
            }
        }

/*
        for(int i=0; i<link.length; i++){
            System.out.println(link[i]);
        }
*/
    }

    public static void main(String[] args) {
        test06 tc = new test06();

        int[] n = {0,1,2,3,4,5};
        int[][] edges = {{0,1},{2,0},{4,5},{1,3},{1,4}};

        tc.solution(n,edges);

    }

BFS의 가장 기초인 최단거리 구하기문제!

이 문제를 통해서 최단거리 구할때는 BFS가 유리하다는 것을 깨닫게 되었습니다!

 

일단 문제 풀기

 

1. 문제

N×M크기의 배열로 표현되는 미로가 있다.

1 0 1 1 1 1
1 0 1 0 1 0
1 0 1 0 1 1
1 1 1 0 1 1

미로에서 1은 이동할 수 있는 칸을 나타내고, 0은 이동할 수 없는 칸을 나타낸다. 이러한 미로가 주어졌을 때, (1, 1)에서 출발하여 (N, M)의 위치로 이동할 때 지나야 하는 최소의 칸 수를 구하는 프로그램을 작성하시오. 한 칸에서 다른 칸으로 이동할 때, 서로 인접한 칸으로만 이동할 수 있다.

위의 예에서는 15칸을 지나야 (N, M)의 위치로 이동할 수 있다. 칸을 셀 때에는 시작 위치와 도착 위치도 포함한다.

입력

첫째 줄에 두 정수 N, M(2 ≤ N, M ≤ 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은 붙어서 입력으로 주어진다.

출력

첫째 줄에 지나야 하는 최소의 칸 수를 출력한다. 항상 도착위치로 이동할 수 있는 경우만 입력으로 주어진다.

 

 

2. 코드 구현

 

package java_BFS_DFS_basic;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack;

class location{
	int w,h,num;
	
	location(int h, int w,int num){
		this.h = h;
		this.w = w;
		this.num = num;
	}
}
public class findMinPath {
	
	static Queue<location> q = new LinkedList<location>();
	static Stack<location> s = new Stack<location>();
	public static void findPath_dfs(int[][] map, boolean[][] ck,int sum) {
	
		location start;
		while(!s.isEmpty()) {
			start = s.peek();
			map[start.h][start.w] = start.num;
			s.pop();
			
			
			//아래로 이동
			if(start.h+1 < map.length && !ck[start.h+1][start.w] && map[start.h+1][start.w] !=0) {
				s.push(new location(start.h+1,start.w,map[start.h][start.w]+1));
				ck[start.h+1][start.w] = true;
			}
			//오른쪽 이동
			if(start.w+1 < map[start.h].length && !ck[start.h][start.w+1] && map[start.h][start.w+1] !=0) {
				s.push(new location(start.h,start.w+1,map[start.h][start.w]+1));
				ck[start.h][start.w+1] = true;
			}
			//왼쪽 이동
			if(start.w-1 >= 0 && !ck[start.h][start.w-1] && map[start.h][start.w-1] !=0) {
				s.push(new location(start.h,start.w-1,map[start.h][start.w]+1));
				ck[start.h][start.w-1] = true;
			}
			//위로 이동
			if(start.h-1 >= 0 && !ck[start.h-1][start.w] && map[start.h-1][start.w] != 0) {
				s.push(new location(start.h-1,start.w,map[start.h][start.w]+1));
				ck[start.h-1][start.w] = true;
			}
			
		}
	}
	
	public static void findPath_bfs(int[][] map, boolean[][] ck,int sum) {
		location start;
		while(!q.isEmpty()) {
			start = q.peek();
			map[start.h][start.w] = start.num;
			q.poll();
			
			
			//아래로 이동
			if(start.h+1 < map.length && !ck[start.h+1][start.w] && map[start.h+1][start.w] !=0) {
				q.add(new location(start.h+1,start.w,map[start.h][start.w]+1));
				ck[start.h+1][start.w] = true;
			}
			//오른쪽 이동
			if(start.w+1 < map[start.h].length && !ck[start.h][start.w+1] && map[start.h][start.w+1] !=0) {
				q.add(new location(start.h,start.w+1,map[start.h][start.w]+1));
				ck[start.h][start.w+1] = true;
			}
			//왼쪽 이동
			if(start.w-1 >= 0 && !ck[start.h][start.w-1] && map[start.h][start.w-1] !=0) {
				q.add(new location(start.h,start.w-1,map[start.h][start.w]+1));
				ck[start.h][start.w-1] = true;
			}
			//위로 이동
			if(start.h-1 >= 0 && !ck[start.h-1][start.w] && map[start.h-1][start.w] != 0) {
				q.add(new location(start.h-1,start.w,map[start.h][start.w]+1));
				ck[start.h-1][start.w] = true;
			}
			
		}
		
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//int[][] map = {{1,1,1,1},{0,0,1,0},{1,1,1,0},{0,0,1,2}};
		Scanner sc = new Scanner(System.in);
		int n,m;
		n = sc.nextInt();
		m = sc.nextInt();
		
		String[] temp = new String[n];
		int map[][] = new int[n][m];
		for(int i=0; i<n; i++) {
				temp[i] = sc.next();
		}
		
		for(int i=0; i<n; i++) {
			for(int j=0; j<m; j++) {
				map[i][j]=Integer.parseInt(temp[i].substring(j, j+1));
			}
		}
		  
		
		boolean[][] ck = new boolean[n][m];
		
		/*DFS 탐색*/
		//s.push(new location(0,0,1));
		//ck[0][0] = true;
		//findPath_dfs(map,ck,1);
		
		/*BFS 탐색*/
		q.add(new location(0,0,1));
		ck[0][0] = true;
		findPath_bfs(map,ck,1);
		
		System.out.println(map[n-1][m-1]);
		
		for(int i=0; i<n; i++) {
			for(int j=0; j<m; j++) {
				System.out.print(map[i][j]+" ");
			}
			System.out.println();
		}
	}
}

 

 

3. 문제 풀이

 

이 문제를 보면 왜 BFS가 최단거리 탐색에 유리한지 알 수 있습니다.

그림을 볼 필요도 없이..
최단거리에 BFS가 유리한 이유는 사실 DFS는 한 방향만 있는힘껏 탐색하기 때문입니다.

 

아래 그림과 같은 문제가 있다고 가정하겠습니다.

그림 1. 미로 문제

 

 

 

3-1. BFS 탐색

먼저 BFS 탐색을 보겠습니다.

BFS는 큐(Queue)로 구성되어 있기 때문에 분기점으로 나누어져도 분할된 채로 한칸씩 이동합니다.

(1,0) 지점에서 2갈래 길로 나누어지는 시점에서 큐의 상태를 보겠습니다.

그림 2. BFS 탐색 (1)

 

위 그림과 같이 코드에 작성했던 순서대로 아래->오른쪽 탐색을 통해 (2,0)과 (1,1) 두개가 큐에 삽입 됩니다.

그 다음 poll 값 2,0에서 탐색을 진행합니다.

그림 3. BFS 탐색 (2)

 

(2,0)에서는 (3,0)만 갈 수 있으므로, 큐에 (3,0)을 삽입 후 다음으로 진행합니다.

그 다음 poll 값은 (1,1) 입니다.

그림 4. BFS 탐색(3)

 

(1,1)에서 갈 수 있는 길은 (1,2)뿐 이므로 (1,2)를 큐에 삽입합니다.

이런식으로 동작하기 때문에 BFS는 분할 되어도 하나씩 진행이 가능한 것이죠.

 

 

 

3-2. DFS 탐색

같은 시점 DFS의 상태를 보겠습니다.

DFS는 스택으로 구현되기 때문에 아래 그림과 같이 동작합니다.

 

그림 5. DFS 탐색(1)

마찬가지로 (1,0)에서 갈 수 있는 2가지 길 모두 Stack에 Push 합니다.
설정한 탐색 순서에 따라 (2,0), (1,0)을 삽입 합니다.

그 다음 하나를 pop을 합니다.

그림 6. DFS 탐색(2)

(1,1)에서 갈 수 있는 (1,2)를 stack에 push 합니다.

그리고 하나를 pop 합니다.

 

그림 7. DFS 탐색(3)

 

이렇듯 DFS 탐색은 한쪽 길을 끝까지 탐색합니다.

 

 

 

4. 결과

결과적으로 BFS/DFS 탐색은 아래 그림과 같은 결과를 보여줍니다.

 

그림 8. DFS BFS 결과 값

 

 

위 결과에서 보여주듯이 DFS 탐색은 한 방향을 쭉 탐색하기 최소 거리 구하기에는 적합하지 않습니다.

그래서 최소거리를 구할땐 BFS를 사용하는 것이죠!

 

'알고리즘 공부 > 알고리즘 문제' 카테고리의 다른 글

트리 dfs 탐색  (0) 2021.04.23
나누어 떨어지는 숫자 배열  (0) 2019.03.12
문자열 내 맘대로 정렬하기  (0) 2019.03.12
빙고 개수 카운트하기  (0) 2019.01.31
배열 회전 결과값 확인하기  (1) 2019.01.26

 

DFS / BFS 탐색 알고리즘 코드로 구현하기

 

알고리즘은 형태로 구현합니다.

1. Queue/Stack 에 있는 노드를 꺼내 출력한다.
2. 방금 꺼낸 노드의 자식들을 Queue/Stack에 add/push 한다.
3. Queue/Stack이 비어있지 않다면 1번으로 이동

 

 

 

1. 행렬 구현

아래와 같잉 이진트리를 행렬로 표현하면 그림 1의 표의 내용과 같습니다.

그림 1. 이진 트리 행렬로 표현

 

 

 

2. 코드 구현

그림 1의 내용을 하나씩 탐색하는 코드는 아래와 같습니다.

package java_BFS_DFS_basic;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

public class Matrics_DFS_BFS {
	static Stack<Integer> s = new Stack<Integer>();
	static Queue<Integer> q = new LinkedList<Integer>();
	
	public static void bfs(int start, int[][] map, boolean[] visitied) {
		
		// Queue가 빌때까지 반복
		while(!q.isEmpty()) {
			start = q.peek();
			System.out.print((q.poll()+1)+" ");
			// 1. 자식 노드를 모두 Queue에 삽입
			for(int i=0; i<map.length; i++) {
				if(map[start][i] == 1 && !visitied[i]) { q.add(i); visitied[i] = true;}
			}
		}
	}
	
	public static void dfs(int start, int[][] map, boolean[] visitied) {
		//범위 넘어가면 return
		if(start < 0 || start > map.length) {return ;}
		
		// 1. 출력한다.
		System.out.print((s.pop()+1) +" ");
		
		// 2. 자식 노드를 모두 Stack에 삽입
		for(int i=0; i<map.length; i++) {
			if(map[start][i] == 1 && !visitied[i]) { s.push(i); visitied[i] = true;}
		}
		
		// 3. Stack이 빌때까지 반복
		if(!s.isEmpty()) {
			dfs(s.peek(),map,visitied);
		}
		
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		int[][] map = {{1,1,1,0,0,0,0},{1,1,0,1,1,0,0},{1,0,1,0,0,1,1},{0,1,0,1,0,0,0},{0,1,0,0,1,0,0},{0,0,1,0,0,1,0},{0,0,1,0,0,0,1}};
		boolean[] visitied; 
		
		System.out.println("DFS 시작");
		visitied = new boolean[map.length];
		s.push(0);
		visitied[0] = true;
		dfs(0,map,visitied);
		System.out.println();
		System.out.println();
		
		System.out.println("BFS 시작");
		visitied = new boolean[map.length];
		q.add(0);
		visitied[0] = true;
		bfs(0,map,visitied);
	}

}

 

 

3. 결과 값

그림 2. 행렬 순회 결과

 

이렇게 해서 BFS DFS 탐색은 끝!

'알고리즘 공부 > 코딩테스트 준비' 카테고리의 다른 글

진수 변환  (0) 2019.05.20
피보나치 수열  (0) 2019.05.20
행렬의 회전  (0) 2019.05.20

DFS에 이어서 BFS 개념 보기

DFS는 트리 행부터 탐색(=세로로 탐색)하는 반면
BFS는 트리 열부터 탐색(=가로로 탐색) 하는 방법 입니다.

 

구현 부분에서는 DFS랑 다를게 없습니다. 단지, Stack -> Queue로 변경되는 것 뿐이죠.

 

----------------------오늘의 요약 정리 ---------------------

BFS 알고리즘 순서!
1. Queue에 있는 노드를 꺼내 출력한다.
2. 방금 꺼낸 노드의 자식들을 Queue에 Add 한다.
3. Queue가 비어있지 않다면 1번으로 이동

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

 

 

바로 그림으로 정리하겠습니다.

이진 트리 BFS 순회는 아래 트리를 이용해서 진행하겠습니다.

그림 1. 예제로 사용 할 이진 트리

 

 

 

 

시작

알고리즘 순서는 간단합니다.

1. Queue에 있는 노드를 꺼내 출력한다.
2. 방금 꺼낸 노드의 자식들을 Queue에 Add 한다.
3. Queue가 비어있지 않다면 1번으로 이동

 

 

1부터 방문할 것이기 때문에 1을 Queue에 삽입한 상태로 시작합니다.

그림 1. BFS 탐색(1)

 

 

 

 

1. Queue에 있는 노드를 하나 꺼내면서 출력합니다.

그림 2. BFS 탐색(2)

 

 

 

2. 노드 1을 방문처리 하고, 1의 자식 노드를 Queue에 삽입한다.

 

그림 3. BFS 탐색(3)

 

 

 

3. 자식노드 2,3을 방문처리 후 Queue에서 하나 꺼내 출력합니다.

그림 4. BFS 탐색(4)

 

 

 

 

4. 꺼낸 노드 2의 자식들을 Queue에 삽입 합니다.

그림 5. BFS 탐색(5)

 

 

 

5. Queue에 삽입한 노드를 방문처리 후 Queue에서 다시 하나 꺼내 출력 합니다.

그림 6. BFS 탐색(6)

 

 

 

 

6. 꺼낸 3의 자식노드를 Queue에 삽입한다.

그림 7. BFS 탐색(7)

 

 

 

 

7. 삽입한 노드를 방문처리 후 Queue에 있는 노드(4)를 하나 꺼내 출력합니다.

그림 8. BFS 탐색(8)

 

 

 

 

8. 자식노드가 없기때문에 다시 Queue에서 노드(5)를 하나 꺼내서 출력합니다.

그림 9. BFS 탐색(9)

 

 

 

 

9. 5는 자식이 없기때문에 Queue에서 다음 노드(6)을 꺼내 출력합니다.

그림 10. BFS 탐색(10)

 

 

 

 

 

10. 6은 자식 노드가 없기 때문에 Queue에서 다음 노드(7)를 꺼내서 출력합니다.

그림 11. BFS 탐색(11)

 

 

 

 

 

(마지막)11. Queue에 더이상 값이 없으므로, 탐색을 종료합니다!

그림 12. BFS 탐색(12)

 

 

이렇듯 BFS 탐색에 대해 알아보았습니다.

알고리즘 순서만 지킨다면 어렵지 않은 알고리즘입니다.

 

코드로 구현하는 것은 아래 링크로 연결하겠습니다!

https://mr-dan.tistory.com/45

 

저번 Union Find에 이어서

Kruskal 알고리즘 정리하겠습니다.

 

목차

- 크루스칼 알고리즘 이란?
- 예제를 통해 크루스칼 이론 학습하기
- 코드로 구현하기

 

- 6줄 요약 -
크루스칼 알고리즘은 최소비용 신장트리를 만드는 알고리즘이다. 알고리즘은 다음과 같다.

1) 간선 가중치 중 가장 작은 값을 선택한다.
2) 간선을 연결하여 순환(Cycle)이 생기지 않는다면 연결 후 1)로 이동.
3) 연결 시 순환(Cycle)이 생기면 연결하지 않고 1)로 이동.
(순환이 여부는 Union Find 알고리즘을 활용하여 알아냅니다!)
최소비용 신장트리의 간선의 수는 "노드의 수 - 1" 

 

시작 합시다!

 

 

1. Kruskal 알고리즘이란?

Kruskal 알고리즘이란 모든 노드를 싸이클이 없도록 연결하는 트리를 만들기 위해 사용되는 알고리즘 입니다.

즉, 최소비용 신장트리를 찾는 알고리즘 이죠.

 

각 노드를 연결하는 간선에는 가중치가 있고, 해당 가중치 중 가장 적은 비용으로 모두 연결해야 할때 이 알고리즘을 사용합니다.

아래 예시를 보면서 이해를 해보져

그림 1. 크루스칼 알고리즘 예제

 

먼저 그림 1과 같이 5개의 노드가 있고, 연결 된 간선에는 가중치가 포함되어있을때,

크루스칼 알고리즘을 이용해서 가장 적은 비용으로 최소신장 트리만드는 방법을 알아보겠습니다.

 

크루스칼 알고리즘 적용 방식은 아래 3가지 입니다.

1) 간선 가중치 중 가장 작은 값을 선택한다.
2) 간선을 연결하여 순환(Cycle)이 생기지 않는다면 연결 후 1)로 이동.
3) 연결 시 순환(Cycle)이 생기면 연결하지 않고 1)로 이동.
(순환이 여부는 Union Find 알고리즘을 활용하여 알아냅니다!)

간단하죠?

그림 1 예제를 통해 진행하겠습니다.

 

 

 

2. 예제를 통해 크루스칼 이론 학습하기

그림 1 그래프를 최소 비용으로 연결한다면 아래 그림 2와 같이 연결 될 것 입니다. 

그림 2. 예상 결과 값

 

그럼 이렇게 연결이 되는지 확인해봅시다.

보기 편하도록 아래 그림 3과 같이 정리하고 시작할게요

그림 3. 최소비용 신장트리 만들기

 

 

최소비용 신장트리 만들기 시작!

2-1. 우선 첫번째로 가장 작은 가중치를 갖는 간선  '2<->5'를 선택 후 연결합니다.

그림 4. 최소비용 신장트리 만들기(1)

 

2-2. 이제 계속 반복 합니다. 다음 가장 작은 가중치 '4 <-> 5'를 선택하고 연결 합니다.

그림 5. 최소비용 신장트리 만들기(2)

 

2-3. 다음 작은 가중치 '1 <-> 3'을 선택하고 연결합니다.

그림 6. 최소비용 신장트리 만들기(3)

 

2-4 다음 작은 가중치 '2 <-> 4'를 선택합니다.

그런데! 2와 4를 연결하면 2 - 4 - 5 노드에 순환이 생기기 때문에 넘어가고 그 다음 작은 값 '1 <-> 2'를 선택 합니다.

그림 7. 최소비용 신장트리 만들기(4)

 

2-5 이제 어떤 간선을 선택해도 Cycle이 생성됩니다. 그렇기 때문에 최소비용 신장트리가 완성되었기에
종료!


그림 8. 최소비용 신장트리 만들기 (완성)

 

맨 처음 예상했던 결과값이랑 동일한 것을 확인할 수 있습니다.

그럼 바로 코드 구현으로 들어가 보져

 

 

 

3. 코드 구현하기

사실 크루스칼 알로리즘은

Union Find 알고리즘에  
 * 1. 간선의 최솟값을 찾기 => Arrays.sort (정렬 이용)
 * 2. 정렬된 배열 순서대로, 간선을 연결할 때 Cycle이 생기지 않으면 연결하도록 하기

를 추가하면 Kruskal 알고리즘이 됩니다.
(아래 예제는 최소 비용 신장트리의 간선의 합을 구하는 코드 입니다.)

	//부모 노드를 찾을 수 있는 함수
	public int getParent(int parent[],int x) {
		if(parent[x] == x) return x;
		return parent[x] = getParent(parent,parent[x]);
	}
	//노드 연결해서 부모 값 작은걸로 변경하기
	public int[] changeParent(int parent[], int a, int b) {
		a = getParent(parent, a);
		b = getParent(parent, b);
		if(a<b) parent[b] = a;
		else parent[a] = b;
		
		return parent;
	}
	//부모 값이 동일한지 확인
	public boolean findParent(int parent[], int a, int b) {
		a = getParent(parent, a);
		b = getParent(parent, b);
		if (a==b) return true;
		return false;
	}
	
	
	public int solution(int n, int[][] costs) {
        int answer = 0;
        int[] parent = new int[n]; //1,2,3,4,5,6
        for(int i=0; i<n; i++) {parent[i] = i;}
        
        //간선 정렬
        Arrays.sort(costs, new Comparator<int[]>() {
			@Override
			public int compare(int[] arg0, int[] arg1) {
				//오름차순 정렬
				return arg0[2]-arg1[2];
			}
        });
        
        //간선 오름차순으로 정렬되었기 때문에 Cycle이 아니라면 하나씩 더해주면 됨.
        //0->1= 1 , 0->2 =2, 2->3 = 1 인경우 배열에 3의정보는 주지 않기때문에 costs.length 기준으로 하는것임
        for(int i=0; i<costs.length; i++) {
        	//Cycle이 아닌경우 간선 가중치 더하기
        	if(!findParent(parent,costs[i][0],costs[i][1])) {
        		//연결하기
        		System.out.print(costs[i][2]+" ");
        		answer += costs[i][2];
            	changeParent(parent,costs[i][0],costs[i][1]);
        	}
        }
        System.out.println();
        System.out.println(answer);
        
        return answer;
    }

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		island_connection ic = new island_connection();
		int n = 5;
		int[][] costs = {{0,1,3},{0,2,4},{0,3,9},{1,3,3},{1,4,1},{2,3,5},{3,4,2}};
		ic.solution(n, costs);
	}

 

실행하면 결과 값 : 10이 나오는 것을 확인할 수 있습니다.

 

이렇게 하면 Kruskal 알고리즘 정리 완료!

Kruscal 알고리즘을 공부하다보니,

Union Find 알고리즘 개념을 이해해야 하기때문에 우선 Union Find 알고리즘을 우선 정리하겠습니다.

 

목차
- Union Find란?
Union Find 동작 방식 분석
- 코드 작성

 

 

1. Union Find란?

Union 이란 합집합이라는 뜻을 가지고 있습니다. 그래서 Union Find 알고리즘이란 합집합을 찾는 알고리즘
이라고 이해하면 될 것 같습니다.

주로 그래프 내에서 집합, 순환을 찾기 위해 사용합니다.

 

1-1 .먼저, 집합을 찾는 경우 입니다.

7개의 노드의 연결을 보여주는 그림을 봅시다.

그림 1. Union Find 알고리즘

 

이 경우 위 사진과 같이 2가지 그룹으로 나누어지는데, 이렇게 2가지를 찾아낼때 Union Find 알고리즘을 사용합니다.

 

2-2. 그리고 두번째로 순환 구조를 찾을때 사용합니다.

 

그림 2. 그래프에서 순환 찾기

 

위 그림처럼 왼쪽 그래프처럼 연결되어 있는 상태에서 3번 노드와 5번 노드를 연결한다면, 그림 2의 오른쪽 그림처럼
3,4,5는 순환이 되게 됩니다. 그리고 Union Find로 이런 순환을 찾아낼 수 있죠.

크루스칼 알고리즘의 경우 최소 비용으로 그래프를 연결해야 하기 때문에, 순환이 되면 안되는 조건이 있습니다.
이때 Union Find 알고리즘을 이용하는 것이죠!

 

 

2. Union Find 동작 방식 분석

UnionFind 알고리즘을 간단하게 정리하면
"각각의 노드의 부모가 동일하면 합집합, 그렇지 않으면 다른 집합"
이라고 판단하는 것 입니다.

알고리즘 구성은 아래와 같습니다.

1. 노드를 탐색하여 연결되어 있으면 부모 노드를 얻어온다.
2. 부모의 노드가 다르다면 둘 중 큰 번호의 부모 노드를 작은 번호의 부모 노드로 변경

그렇기 때문에 노드들의 부모를 작성해 줄 배열이 하나 필요합니다.
예제는 아래 그림 3 처럼 노드의 수는 7개, 2개의 집합으로 구분 가능한 그래프를 가지고 진행하겠습니다.

그림 3. 예제 그래프

 

 

우선 아래 그림과 같이 노드가 7개니까 길이 7의 parent 배열을 생성하고, 각각의 부보는 자기를 가르키도록 초기화 합니다.

그림 4. parent 배열 생성 및 초기화

 

1부터 시작해서 7까지 탐색합니다.

 

2-1. 노드 1 탐색

1) 1의 노드는 2와 연결되어 있기 때문에 부모 노드를 얻어옵니다.
그리고 1과 2의 부모 노드는 다르기때문에 2의 부모 노드를 1로 변경 합니다.

2) 1의 노드는 4와 연결되어 있기 때문에 부모 노드를 얻어옵니다.
그리고 1과 4의 부모 노드는 다르기때문에 4의 부모 노드를 1로 변경 하며 1의 탐색은 종료합니다.

그림 5.  노드 1 탐색

 

 

2-2. 노드 2 탐색

다음 2 노드를 탐색합니다.

1) 2는 1과 연결되어있습니다.
2) 부모노드를 얻어옵니다.
3) 2개의 노드 모두 부모 노드가 1로 동일 하기때문에 넘어갑니다.

 

2-3. 노드 3 탐색

3 노드를 탐색합니다.

1) 3의 노드는 4와 연결되어 있습니다.
2) 부모노드를 얻어옵니다.
3) 3의 부모와 4의 부모는 다르기 때문에 작은 노드로 변경합니다.
4) 그런데 3의 부모는 3, 4의 부모는 1이기 때문에 3의 부모 노드를 1로 변경합니다.

 

그림 6. 노드 3 탐색

 

2-4. 노드 4 탐색

노드 4는 1, 3 노드와 연결되어 있습니다.
부모의 노드를 얻어오는데, 둘 다 부모의 노드가 동일함으로 넘어갑니다.

 

2-5. 노드 5 탐색

노드 5는 6과 연결되어 있습니다.
둘의 부모 노드가 다르기때문에 6의 부모 노드를 5로 변경합니다.

그림 7. 노드 5 탐색

 

2-6 노드 6 탐색

노드 6은 5와 연결되어있지만 부모 노드가 동일함으로 넘어갑니다.
그리고 노드 7과도 연결되어있습니다.
노드 6과 7의 부모는 다르기때문에 노드 7의 부모를 6의 부모 노드인 5로 변경합니다.

그림 8. 노드 6 탐색

 

2-7. 마지막 노드 7 탐색

노드 7은 6과 연결되어있지만, 둘의 부모노드가 동일하기 때문에 넘어갑니다.

이렇게 프로그램은 종료되고 부모 노드는 아래와 같이 정리 됩니다.

 

그림 9. 종류 후 parent 표 상태

 

이렇게 종료가 된후 부모노드가 1인 그룹과 부모노드가 5인 그룹 이렇게 2개로 구성되어 있음을 알 수 있습니다.

이것이 Union Find 알고리즘 동작 방법입니다.

 

3. 코드 작성

코드로 작성하면 아래와 같습니다.

	//부모노드 찾기
	public int getParent(int parent[],int search) {
		if(parent[search] == search) return search;
		else
			return parent[search] = getParent(parent, parent[search]);
	}
	//합친 후 부모 노드 작은걸로 바꾸어주기
	public int[] changeParent(int parent[], int a, int b) {
		a = getParent(parent, a);
		b = getParent(parent, b);
		if(a<b) parent[b] = a;
		else parent[a] = b;
		
		return parent;
	}
	//부모 노드 찾기
	public boolean findParent(int parent[], int a, int b) {
		a = getParent(parent,a);
		b = getParent(parent,b);
		if(a==b) return true;
		else return false;
	}
    
	public static void main(String[] args) {
		UnionFind f = new UnionFind();
		int n = 8;
		
		int[][] graph = {{1,2},{1,4},{4,3},{5,6},{6,7}};
		int[] parent = new int[n];
		for(int i=1; i<n; i++) { parent[i] = i;}
        
        //부모 노드 바꾸기
        for(int i=0; i<graph.length; i++) {
			f.changeParent(parent, graph[i][0], graph[i][1]);
		}
        //부모 노드 출력
        for(int i=1; i<n; i++)
			System.out.print(parent[i] +" ");
     }

 

 

이 개념을 알면, 크루스칼 알고리즘은 아주아주 쉽게 이해하고 사용할 수 있습니다!

 

 

 

+ Recent posts