728x90
반응형

안녕하세요 이번 포스팅은 바로

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

입니다

그래프는 컴퓨터공학과의 경우 자료구조 시간에 배우게 되는데

이때 그래프의 탐색을 같이 배우게 됩니다.

탐색중에 가장 유명한 탐색은

깊이우선탐색인(DFS),너비우선탐색(BFS)입니다.

 

 

그래프(Graph)


그래프에 있어서 가장 유명한 것은 바로 konigsberg의 pregel강에 문제이다.

이 다리 문제는 임의의 지역에서 출발해서 모든 다리를 한번만 건너 처음 출발지역으로

돌아올 수 있는 지에 관한 문제였는데 스위스의 수학자 (오일러)Euler가 그래프를 이용해 풀었다.

이를 "오일러 행로"라고 한다.

그래프는 정점(Vertex)와 edge(간선) 두 가지 구성요소를 가지고 있습니다.

또한, 무방향성,방향성 두 가지 종류의 그래프가 있습니다.

- 무방향성 그래프 -

각 간선에 방향성이 존재하는 그래프입니다.

<u,v> : u -> v 간선을 표현합니다.

이 경우 u는 꼬리(tail)이고 v는 머리(head)이다.

- 방향성 그래프 -

vertex의 쌍을 나타내는 간선이 방향성이 없습니다.

(u,v),(v,u) : 동일한 간선을 표현합니다.

 

 

- 그래프 예 -

 

<1번 그림>

노드 : 0,1,2,3

간선 : (0,1),(0,2),(0,3),(1,2),(1,3),(2,3)

<2번 그림>

노드 : 0,1,2,3,4,5,6

간선 : (0,1),(0,2),(1,3),(1,4),(2,5),(2,6)

<3번 그림>

노드 : 0,1,2

간선 : <0,1>,<1,0>,<1,2>

- 그래프 표현 법 -

​<인접행렬>

 

인접행렬의 경우

무방향성 그래프는 1번그림과 같이 대칭성을 보입니다.

반면에 방향성 그래프는 2번그림과 같이 비대칭성을 보입니다.

<인접리스트>

인접리스트의 경우

연결되는 vertex를 한 개의 연결 리스트로 표현합니다.

 

 

너비우선탐색(BFS)


- 너비우선탐색을 하는 방법 -

1. 출발 vertex에서 인접한 정점을 방문한다.

2. vertex에 인접한 모든 vertex들을 방문한다.

3. v에 인접한 모든 vertex들을 방문하면서, v에 인접한 첫번째 vertex부터

인접한 vertex중에서 아직 방문하지 않은 vertex들을 차례대로 방문

(이때 Queue를 사용한다)

- 그래프 예제 -

- BFS 풀이 순서 -

위의 보시는 거 처럼 제일 먼저 방문한 vertex를 queue저장합니다.

그 후 visit[vertex]를 true로 만들고 뺀 후 출력합니다.

이렇게 계속 반복해줍니다.

 

결과 : V0, V1, V2, V3, V4, V5, V6, V7

- 소스코드 -

 

public class BFS {
	public static void main(String arg[]) {
		int n, n2, v, w, start;

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt(); // 정점의 개수
		bfs_Node node = new bfs_Node(n);
		n2 = sc.nextInt(); // 간선의 개수

		for (int i = 0; i < n2; i++) {
			v = sc.nextInt();
			w = sc.nextInt();
			node.put(v, w);
		}
		start = sc.nextInt(); // 시작점
		node.bfs(start);
	}
}

정점과,간선의 개수를 입력받습니다.

그 후 간선의 개수만큼 간선을 받아줍니다.

이 때 v는 출발, w는 도착점입니다.

 

	void put(int v, int w) {
		list[v][w] = 1;
		list[w][v] = 1;
	}

put 함수에서 반복문 수 만큼 v,w를 계속 받아줍니다.

이때 무방향성이기 때문에 w,v 역시 저장해줍니다.

 

void bfs(int start) {
		visit[start] = true;
		queue.add(start);

		while (queue.size() != 0) {
			start = queue.poll();
			System.out.print(start + " ");
			for (int i = 0; i < visit.length; i++) {
				if (list[start][i] == 1 && visit[i] != true) {
					queue.add(i);
					visit[i]=true;
				}
			}
			
		}
	}

시작점을 저장하고 queue에 저장합니다.

그 후 queue.size를 통해 저장된 게 없을 때 까지 계속 반복해줍니다.

정점의 개수만큼 for문을 돌려줍니다.

그리고 queue의 저장된 수를 하나 뺀 후 출력합니다.

그 후 조건문에서 인접하고 아직 방문하지 않는 정점을 queue에 저장시켜 줍니다.

그리고 true로 바꿔 다시 그 정점에 방문하지 않도록 합니다.

 

- 최종 소스코드 -

 

import java.util.Scanner;
import java.util.LinkedList;
import java.util.Queue;
public class BFS {
	public static void main(String arg[]) {
		int n, n2, v, w, start;
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt(); // 정점의 개수
		bfs_Node node = new bfs_Node(n);
		n2 = sc.nextInt(); // 간선의 개수
		for (int i = 0; i < n2; i++) {
			v = sc.nextInt();
			w = sc.nextInt();
			node.put(v, w);
		}
		start = sc.nextInt(); // 시작점
		node.bfs(start);
	}
}
class bfs_Node {
	int[][] list, n;
	boolean[] visit;
	Queue<Integer> queue = new LinkedList<>();
	bfs_Node(int n) {
		list = new int[n][n];
		visit = new boolean[n];
	}
	void put(int v, int w) {
		list[v][w] = 1;
		list[w][v] = 1;
	}
	void bfs(int start) {
		visit[start] = true;
		queue.add(start);
		while (queue.size() != 0) {
			start = queue.poll();
			System.out.print(start + " ");
			for (int i = 0; i < visit.length; i++) {
				if (list[start][i] == 1 && visit[i] != true) {
					queue.add(i);
					visit[i]=true;
				}
			}			
		}
	}
}

소스코드는 정답이 없기 때문에 참고만 하시고 원하는 식으로 코드를 작성하시면 됩니다.

이상으로 오늘은 그래프의 탐색중 너비우선탐색(BFS)에 대해 알아보았습니다.

이해가 안되거나 궁금한 사항이 있으면 댓글로 남겨주시면 됩니다.

읽어주셔서 감사합니다.

반응형
728x90
반응형

안녕하세요 이번 포스팅은 바로

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

입니다

그래프는 컴퓨터공학과의 경우 자료구조 시간에 배우게 되는데

이때 그래프의 탐색을 같이 배우게 됩니다.

탐색중에 가장 유명한 탐색은

깊이우선탐색인(DFS),너비우선탐색(BFS)입니다.

그래프(Graph)


그래프에 있어서 가장 유명한 것은 바로 konigsberg의 pregel강에 문제이다.

이 다리 문제는 임의의 지역에서 출발해서 모든 다리를 한번만 건너 처음 출발지역으로

돌아올 수 있는 지에 관한 문제였는데 스위스의 수학자 (오일러)Euler가 그래프를 이용해 풀었다.

이를 "오일러 행로"라고 한다.

그래프는 정점(Vertex)와 edge(간선) 두 가지 구성요소를 가지고 있습니다.

또한, 무방향성,방향성 두 가지 종류의 그래프가 있습니다.

- 무방향성 그래프 -

각 간선에 방향성이 존재하는 그래프입니다.

<u,v> : u -> v 간선을 표현합니다.

이 경우 u는 꼬리(tail)이고 v는 머리(head)이다.

- 방향성 그래프 -

vertex의 쌍을 나타내는 간선이 방향성이 없습니다.

(u,v),(v,u) : 동일한 간선을 표현합니다.

- 그래프 예 -

 

<1번 그림>

노드 : 0,1,2,3

간선 : (0,1),(0,2),(0,3),(1,2),(1,3),(2,3)

<2번 그림>

노드 : 0,1,2,3,4,5,6

간선 : (0,1),(0,2),(1,3),(1,4),(2,5),(2,6)

<3번 그림>

노드 : 0,1,2

간선 : <0,1>,<1,0>,<1,2>

- 그래프 표현 법 -

<인접행렬>

인접행렬의 경우

무방향성 그래프는 1번그림과 같이 대칭성을 보입니다.

반면에 방향성 그래프는 2번그림과 같이 비대칭성을 보입니다.

<인접리스트>

인접리스트의 경우

연결되는 vertex를 한 개의 연결 리스트로 표현합니다.

 

 

깊이우선탐색(DFS)


- 깊이우선탐색을 하는 방법 -

1. 출발 vertex에서 인접한 정점을 방문한다.

2. 방문한 정점 중에 방문하지 않은 정점을 w로 선택한다.

3. w를 출발 vertex로 바꾸고 다시 깊이우선탐색을 실시한다.

4. 1번 과정을 모든 정점을 방문할 때 까지 계속 반복한다.

- 그래프 예제 -

결과 : V0, V1, V3, V7, V4, V5, V2, V6

- 소스코드 -

 

public class DFS {
	public static void main(String args[]) {
		int n, start, v, w, n2;
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt(); // 정점의 개수
		Node node = new Node(n);
		n2 = sc.nextInt(); // 간선의 개수
		for (int i = 0; i < n2; i++) {
			v = sc.nextInt();
			w = sc.nextInt();
			node.put(v, w);
		}
		start = sc.nextInt();
		node.dfs(start);
		sc.close();
	}
}

정점, 노드의 개수를 받아줍니다.

그리고, v,w를 노드의 개수만큼 받아줍니다.

이때 v는 출발 w는 도착입니다.

void put(int v, int w) {
		list[v][w] = 1;
		list[w][v] = 1;
	}

put 함수에서 v,w를 입력받으면

v,w를 저장해줍니다. 이때 무방향성이기 때문에 w,v역시 저장합니다.

 

 

void dfs(int start) {
		visit[start] = true;
		System.out.print(start + " ");
		for (int i = 0; i < visit.length; i++) {
			if (list[start][i] == 1 && visit[i] != true) {
				dfs(i);
			}
		}
	}

정점에 방문했을 경우 방문한 정점을 true로 바꿔줍니다.

정점의 개수만큼 반복을 해주고

if 조건문을 통해 인접하고 방문하지 않은 정점을 다시 한번 더

함수로 돌려줍니다.

 

- 최종 소스코드 -

import java.util.Scanner;
 
public class DFS {
	public static void main(String args[]) {
		int n, start, v, w, n2;
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt(); // 정점의 개수
		Node node = new Node(n);
		n2 = sc.nextInt(); // 간선의 개수
		for (int i = 0; i < n2; i++) {
			v = sc.nextInt();
			w = sc.nextInt();
			node.put(v, w);
		}
		start = sc.nextInt();
		node.dfs(start);
		sc.close();
	}
}
class Node {
	int list[][];  // v,w저장
	boolean visit[];  //지나갔는 지 확인
	Node(int n) {
		this.list = new int[n][n];
		this.visit = new boolean[n];
	} 

	void put(int v, int w) {
		list[v][w] = 1;
		list[w][v] = 1;
	}
	void dfs(int start) {
		visit[start] = true;
		System.out.print(start + " ");
		for (int i = 0; i < visit.length; i++) {
			if (list[start][i] == 1 && visit[i] != true) {
				dfs(i);
			}
		}
	}
}

코드는 참고만 하시고 자신이 원하는 식의 코드르 작성하시면 됩니다.

이상으로 오늘은 그래프의 탐색인 DFS에 대해 알아보았습니다.

이해가 안되거나 궁금한 사항이 있으면 댓글로 남겨주시면 됩니다.

읽어주셔서 감사합니다.

반응형
728x90
반응형

안녕하세요 이번 포스팅은 바로

백준 알고리즘 1004번(자바)

문제입니다



문제

어린 왕자는 소혹성 B-664에서 자신이 사랑하는 한 송이 장미를 위해 살아간다. 어느 날 장미가 위험에 빠지게 된 것을 알게 된 어린 왕자는, 장미를 구하기 위해 은하수를 따라 긴 여행을 하기 시작했다. 하지만 어린 왕자의 우주선은 그렇게 좋지 않아서 행성계 간의 이동을 최대한 피해서 여행해야 한다. 아래의 그림은 어린 왕자가 펼쳐본 은하수 지도의 일부이다.




빨간 실선은 어린 왕자가 출발점에서 도착점까지 도달하는데 있어서 필요한 행성계 진입/이탈 횟수를 최소화하는 경로이며, 원은 행성계의 경계를 의미한다. 이러한 경로는 여러 개 존재할 수 있지만 적어도 3번의 행성계 진입/이탈이 필요하다는 것을 알 수 있다.

위와 같은 은하수 지도, 출발점, 도착점이 주어졌을 때 어린 왕자에게 필요한 최소의 행성계 진입/이탈 횟수를 구하는 프로그램을 작성해 보자. (행성계의 경계가 맞닿거나 서로 교차하는 경우는 없다고 가정한다. 또한, 출발점이나 도착점이 행성계 경계에 걸쳐진 경우 역시 입력으로 주어지지 않는다.)


입력

입력의 첫 줄에는 테스트 케이스의 개수 T가 주어진다. 그 다음 줄부터 각각의 테스트케이스에 대해 첫째 줄에 출발점 (x1, y1)과 도착점 (x2, y2)이 주어진다. 두 번째 줄에는 행성계의 개수 n이 주어지며, 세 번째 줄부터 n줄에 걸쳐 행성계의 중점과 반지름 (cx, cy, r)이 주어진다. 입력제한은 다음과 같다. (-1000 ≤ x1, y1, x2, y2, cx, cy ≤ 1000, 1 ≤ r ≤ 1000, 1 ≤ n ≤ 50)

좌표와 반지름은 모두 정수이다.


출력

각 테스트 케이스에 대해 어린 왕자가 거쳐야 할 최소의 행성계 진입/이탈 횟수를 출력한다.


예제 입력


예제 출력


문제풀이

이번 1004번 문제는 앞서 풀었던 1002번 문제랑 비슷하다고 보시면 됩니다.



문제에서 두가지 가정을 주었습니다.
하나는 경계가 맞닿거나 서로 교차하는 경우가 없다고 가정한거고
또 다른 하나는 출발점이나 도착점이 경계에 걸쳐진 채로 입력이 주어지지 않는다고 가정했습니다.
이 의미는 출발점이나 도착점이 행성계에 속하지 않는다면 통과되지 않고 갈 수 있다고 보시면 됩니다.


 

<출발점이 행성계 안 & 도착점이 행성계 밖에 있을 경우>

출발점이 행성계 안에 있으면 도착점으로 어떤 방향으로 가든
행성계 이탈이 필수적으로 나타날 수 밖에 없습니다.

x1,y1이 출발점 좌표라고 가정하고 cx,cy,r이 행성계 좌표 및 반지름 이라고 가정하면

Math.pow(x1 - cx, 2) + Math.pow(y1 - cy, 2) < Math.pow(r, 2)

이런 식에 조건 식 코드를 구현할 수 있습니다.




<출발점이 행성계 밖 & 도착점이 행성계 안에 있을 경우>


도착점이 행성계 안에 있으면 출발점에서 도착점으로 어떤 방향으로 가든
행성계 집입이 필수적으로 나타날 수 밖에 없습니다.

x2,y2이 도착점 좌표라고 가정하고 cx,cy,r이 행성계 좌표 및 반지름 이라고 가정하면

Math.pow(x2 - cx, 2) + Math.pow(y2 - cy, 2) < Math.pow(r, 2)

이런 식에 조건 식 코드를 구현할 수 있습니다.





<출발점이 행성계 안 & 도착점이 행성계 안에 있을 경우>


출발점이 행성계 안에 있고 도착점도 같은 행성계 안에 있으면 어떤 방향으로 가든
행성계 진입/이탈이 일어나지 않을 수 있습니다.

x1,y1이 출발점 좌표 그리고 x2,y2가 도착점 좌표라고 가정하고
 cx,cy,r이 행성계 좌표 및 반지름 이라고 가정하면

Math.pow(x2 - cx, 2) + Math.pow(y2 - cy, 2) < Math.pow(r, 2)
 &&
Math.pow(x1 - cx, 2) + Math.pow(y1 - cy, 2) < Math.pow(r, 2)


이런 식으로 앞서 사용한 조건 두개를 and문으로 묶어 조건 식 코드를 구현합니다.




<출발점,도착점이 행성계 밖에 있을 경우>

출발점이랑 도착점이 행성계 밖에 있다면 위에 그림과 같이 어떻게든 돌아갈 수 있기 때문에
별도의 행성계 진입/이탈을 하지 않아도 가능합니다.

또한 앞서 만들어 놓은 조건식에 else로 두면 되기 때문에

별도의 조건식 코드를 만들지 않아도 됩니다. 


최종코드


if문에는 같은 행성계 안에 출발점과 도착점이 모두 있을 경우

else if문에는 출발점만 행성계 안에 있을 경우와 도착점만 행성계 안에 있을 경우를 적어

위에 보이는 코드처럼 작성해줍니다.






느낀점

기존의 1002번 문제를 해결할 때 공부했던 좌표사이의 거리 공식을 이용하면

1004번 문제의 코드는 어렵지 않게 작성할 수 있었다.

하지만 이 문제는 처음 문제를 봤을 때 이해하는데 시간이 조금 걸렸다.






반응형
728x90
반응형

안녕하세요 이번 포스팅은 바로

백준 알고리즘 1003번(자바)

문제입니다


문제


다음 소스는 N번째 피보나치 수를 구하는 C++ 함수이다.








fibonacci(3)을 호출하면 다음과 같은 일이 일어난다.

●fibonacci(3)fibonacci(2)fibonacci(1) (첫 번째 호출)을 호출한다.

●fibonacci(2)fibonacci(1) (두 번째 호출)과 fibonacci(0)을 호출한다.

두 번째 호출한 fibonacci(1)은 1을 출력하고 1을 리턴한다.

●fibonacci(0)은 0을 출력하고, 0을 리턴한다.

●fibonacci(2)fibonacci(1)fibonacci(0)의 결과를 얻고, 1을 리턴한다.

첫 번째 호출한 fibonacci(1)은 1을 출력하고, 1을 리턴한다.

●fibonacci(3)fibonacci(2)fibonacci(1)의 결과를 얻고, 2를 리턴한다.

1은 2번 출력되고, 0은 1번 출력된다. N이 주어졌을 때, fibonacci(N)을 호출했을 때, 0과 1이 각각 몇 번 출력되는지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 테스트 케이스의 개수 T가 주어진다.

각 테스트 케이스는 한 줄로 이루어져 있고, N이 주어진다. N은 40보다 작거나 같은 자연수 또는 0이다.

출력

각 테스트 케이스마다 0이 출력되는 횟수와 1이 출력되는 횟수를 공백으로 구분해서 출력한다.

예제 입력




















예제 출력



문제풀이

이 문제는 두 가지 방법으로 나눠서 풀 수 있습니다.

- 방법 1 : 호출 시 카운트(시간초과 문제 발생)

fibonacci(0)이 호출될 때 zero_cnt++ 해주고 fibonacci(1)이 호출될 때 one_cnt++를 해줍니다.

이렇게 하면 문제에서 원하는 것 처럼 fibonacci(0),fibonacci(1) 호출 수를 알 수 있고 이를 그대로 출력해주면 됩니다.



위의 코드처럼 카운트 해주시면 이클립스에서는 문제없이 돌아가지만 백준 사이트에서는 시간초과가 날 가능성이 높습니다.

왜냐하면 이 방식은 시간이 오래걸린다는 단점이 있습니다. 그 이유는 0,1의 호출 수를 알기 위해 피보나치 함수를 계속 이용해야 한다는 점인데

이렇게 된다면 N의 값이 커지면 커질수록 피보나치 함수의 호출 수는 훨씬 더 많아지고 이로 인해 시간을 많이 잡아먹을 수 밖에 없습니다.

이를 해결하기 위해서 다른 방법을 이용해야 합니다.

-방법 2 : 규칙 찾기( 1. Scanner(시간초과) 2. BufferedReader)

n을 0부터 시작해서 1씩 값을 올려 결과를 얻으면 아래처럼 일정한 규칙이 보이게 됩니다.

n(0) -> zero[0] = 1 , one[0] = 0

n(1) -> zero[1] = 0 , one[1] = 1

n(2) -> zero[2] = 1 , one[2]= 1

n(3) -> zero[3] = 1 , one[2] = 2

n(4) -> zero[4] = 2 , one[3] = 3

n(5) -> zero[5] = 3 , one[3] = 5

이러한 규칙들을 정리해보면

zero[n] = zero[n-1] + zero[n-2]

one[n] = one[n-1]+one[n-2]

위와 같은 규칙으로 정리할 수 있는데 이렇게 정리된 규칙을 이용한다면

시간이 많이 걸릴 fibonacci 함수를 계속 쓰지 않아도 원하는 값을 구할 수 있습니다.



이러한 규칙을 이용하기 위해

0,1의 호출 수를 저장하기 위한 배열 선언을 해줍니다.

그리고 나서 zero,one의 첫번째 두번째 인덱스 값을 넣어주는데 이렇게 먼저 집어 넣는 이유는 [n] = [n-1]+[n-2]이기 때문입니다.



정리해서 코드를 짜면 위와 같이 나옵니다.

이렇게 풀고 내면






시간초과가 나옵니다.

이유는 바로 Scanner 입니다.

Scanner를 사용하면 쉽게 풀 수 있지만 Scanner를 사용하면 시간이 많이 걸린다는 단점이 있습니다.

이를 해결하기 위해서 Scanner 대신 BufferedReader,Writer를 이용합니다.


이를 BufferedReader,Writer로 해결하면 위에 보이는 코드처럼 정리할 수 있습니다.

느낀점

이때까지 scanner만 사용했는데 scanner가 시간이 많이 잡아먹는 지는 몰랐다.

scanner 대신 bufferedreader,writer로 시간이 얼마 걸리지 않을 수 있다는 것도 알 수 있었고

reader,writer를 사용하는 방법도 알 수 있었다.





















반응형

+ Recent posts