1697 숨바꼭질



문제 해석


수빈이가 동생을 찾아가는데 몇초만에 찾는지 알아내는 문제입니다. 수빈이와 동생은 점 0~100000사이에 있습니다. 근데 왜 수빈이만 동생을 찾는 건지는 이해가 가지 않는군요. 

수빈이는 다음과 같은 이상한 능력이 있습니다.


1. 수빈이가 위치 X에 있을때 1초에 X+1, X-1의 위치로 걸을 수 있습니다. 물론 범위 0~100000의 범위 안에서만 말이죠.

2. 수빈이는 순간이동을 할 수 있는데, 현재 위치 X에서 1초만에 X*2의 위치로 이동할 수 있습니다.


두개의 입력이 주어지는데, 가장 첫번째 입력은 수빈이의 위치, 두번째 입력은 동생의 위치입니다.

5와 17이라는 입력이 주어진다면 답은 4가 됩니다. 5->10->9->18->17로 찾을 수 있습니다.


이런 조건 속에서 몇 초만에 동생을 찾을 수 있을까요?




풀이


BFS(Breadth First Search)를 이용하면 이 문제를 풀 수 있습니다. 그 때문에 큐(Queue)를 이용해야합니다. 수빈이가 있는 점을 start라고 하고 동생이 있는 위치를 end라고 합시다. 결국 우리가 구현해야하는 것은 start에서부터 BFS를 통해 end까지 도달을 몇번에 할 수 있는가입니다.

아래의 전체적인 코드를 우선 보고 설명하도록 하겠습니다.


#include <cstdio>
#include <queue>

using namespace std;
#define MAX_N 100001
#define LIMIT 100000
int bfs(int start, int end) {
	if (start==end) return 0;
	queue<int> q;
	q.push(start);
	int length[MAX_N] = { 0, };

	while (!q.empty()) {
		int here = q.front();
		q.pop();
		if (here + 1 <= LIMIT) {
			if (here + 1 == end)
				return length[here] + 1;
			if (!length[here + 1]) {
				length[here + 1] = length[here] + 1;
				q.push(here + 1);
			}
		}
		if (here - 1 >= 0) {
			if (here - 1 == end) return length[here] + 1;
			if (!length[here - 1]) {
				length[here - 1] = length[here] + 1;
				q.push(here - 1);
			}
		}
		if (here * 2 <= LIMIT) {
			if (here * 2 == end) return length[here] + 1;
			if (!length[here * 2]) {
				length[here * 2] = length[here] + 1;
				q.push(here * 2);
			}
		}

	}
	return -1;
}
int main() {
	int start, end;
	scanf("%d %d", &start, &end);
	printf("%d\n", bfs(start, end));
	return 0;
}

코드에서 가장 눈여겨 봐야할 곳이 bfs함수입니다. bfs는 두개의 인자를 전달받는데 우리가 아까 이야기했던 start와 end이지요.
bfs내부에서는 start와 end가 같은 위치일 수 있으므로 가장 먼저 start와 end가 같은지 확인하며 참이라면 bfs를 순회할 필요없이 바로 0을 반환해줍니다.




이제 본격적인 bfs 탐색을 시작합니다. q에는 다음 순회할 노드들이 들어가며, length배열에는 그 점까지 몇번에 걸쳐 오는가를 저장하게 됩니다. 예를 들어 5->10->11->12까지 순회했다고 친다면 length[5]=0, length[10]=1, length[11]=2, length[12]=3이라는 값을 갖게 되는 겁니다. 이 변수에 0이 아닌 양수의 값이 있다는 것은 이미 그 점을 방문한 것이 되므로 q에 이 점을 push하지 않습니다.

처음 start의 점을 큐에 push한 이후에 q가 빌때까지 while루프를 돕니다. while루프 안에는 here라는 변수로 큐의 점을 꺼내옵니다. 처음에는 start가 들어있으니, start의 점을 꺼내오겠죠?
그 점이 here가 되고, here에서 +1, -1, *2가 범위에 속해있으며, 그 점이 바로 end와 같은 점이라면 현재 length[here]에서 1을 추가한 값을 반환합니다.
end와 같은 점이 아닐 경우에는 length에 다음 q에 추가될 점(here+1, here-1, here*2)이 기록이 되었나(length[here+1], length[here-1], length[length*2]에 0이 아닌 양수의 값이 있으면 이미 그 점을 전에 방문했다는 겁니다.)를 확인하고 length에 기록이 되어있지 않다면 length를 기록하고 q에 push합니다.

bfs함수에서 while루프의 if조건문에 의한 반환이 반드시 이루어집니다. +1,-1을 통해서 0~100000까지 전부 방문 가능하니까요.

결론은 bfs를 이용하면 그리 어렵지 않게 풀수 있는 문제였습니다.


반응형
블로그 이미지

REAKWON

와나진짜

,

2178번 미로탐색



문제 해석



추석인데 할게 없으니, 알고리즘 하나 풀어보자구요!

NxM행렬이 있습니다. N은 행의 수, M은 열의 수인데, 1은 통과 가능한 구역, 0은 통과하지 못하는 공간이라고 문제 설명에서 말합니다.

(1,1)부터 시작해 (N,M)까지 오는데, 가장 적게 1을 통과하는 수를 구하는 게 문제의 답입니다.


아래처럼 빨간 색으로 된 1을 통과하는 게 답이 되겠고, 15가 답이 됩니다.

1 

0

 1

 1

 1

 1

1

0

 1

 0

 1

 0

1

0

 1

 0

 1

 1

1

1

 1

 0

 1

 1



물론 답은 여러가지가 있을 수도 있겠죠?

위에 빨간점대로 이동하지 않고도 녹색점을 거쳐서 오는 경우도 가능한데, 어차피 답은 똑같다라는 점입니다.

한칸 띄어서 정수형으로 주지 귀찮게




제약 조건
N과 M 둘 다 2이상 100이하가 되겠습니다. 


풀이
음...
최소라... 

일단 어떤 지도같은 것을 주고, "최단거리를 구하라" 라는 문제는 BFS로 풀어볼만 합니다. BFS가 최단 거리를 구하는데 안성맞춤이거든요. 어떤 정점이 있을때 그 주위에 있는 정점들만 우선적으로 돌기 때문이지요.

그리고 N과 M, 모든 점을 계산했을 때 10000개 이하가 되니까 Queue에 담기도 충분합니다.


우선 어떤 점 (y,x)가 있을때, 사방에 1인 녀석들을 일단 찾습니다. 그놈들이 그래프의 정점 중 다음에 방문해야할 후보들에 속하거든요.
그렇다면 상하좌우이니까 (y, x-1), (y, x+1), (y+1, x), (y-1, x)로 주변 정점들을 찾고 이미 방문된 정점이라면 그냥 skip하면 되겠네요.




#include <cstdio>
#include <queue>
#include <string>
#include <string.h>

using namespace std;

int n, m;
bool visited[101][101];
char map[101][101];

struct Point {
	int x, y;
	Point(int _y, int _x) {
		x = _x; y = _y;
	}
};

queue<Point> q;

void pushQ(int y, int x) {

	if (y >= n || x >= m || y<0 || x<0 || visited[y][x] || map[y][x] == '0') return;
	q.push(Point(y, x));

	visited[y][x] = true;
}

int bfs() {

	pushQ(0, 0);
	int count = 1;
	while (!q.empty()) {

		int size = q.size();

		for (int i = 0; i<size; i++) {
			Point here = q.front();
			q.pop();

			if (here.y == n - 1 && here.x == m - 1) return count;

			pushQ(here.y + 1, here.x);
			pushQ(here.y - 1, here.x);
			pushQ(here.y, here.x + 1);
			pushQ(here.y, here.x - 1);
		}
		count++;
	}
	return count;
}

int main() {
	scanf("%d %d", &n, &m);

	memset(visited, false, sizeof(visited));
	for (int i = 0; i < n; i++)
		scanf("%s", map[i]);

	printf("%d\n", bfs());
	return 0;
}


위 코드는 단순하게 (0,0) (문제에서는 1,1라고 했지만, 배열은 0,0부터 첫번째 요소이기 때문에)부터 시작해서 (n-1, m-1)( 역시 배열의 가장 마지막 요소는 n-1, m-1)까지 문자열로 입력을 받고,  bfs를 돕니다.


bfs에서는 우선 시작 정점(0,0)을 큐에 집어 넣습니다. 그 후에 bfs의 size를 호출해 포문을 돌고 그 상하좌우의 정점을 집어 넣게 되는 겁니다. 주의해야할 점은 for루프 조건문에서 직접적으로 q.size()를 호출하면 안된다는 점입니다. for루프 안에서 q에 push되므로 for문을 한번 돌때마다 q.size()가 유동적으로 바뀌게 됩니다.

                int size = q.size();

		for (int i = 0; i<size; i++) {
			Point here = q.front();
			q.pop();

			if (here.y == n - 1 && here.x == m - 1) return count;

			pushQ(here.y + 1, here.x);
			pushQ(here.y - 1, here.x);
			pushQ(here.y, here.x + 1);
			pushQ(here.y, here.x - 1);
		}
		count++;

그렇게 for문을 한 번 돌면 그 주위에 있는 정점 한번을 탐색한 것이니까 count를 하나 증가시켜 주고요. 만약 n-1, m-1 정점까지 도달했다면 바로 이 count를 return하게 되면 몇 번만에 (n-1, m-1)까지 도달했는지 알 수 있습니다.




pushQ함수는 조건에 맞지 않는 정점은 그냥 지나쳐 버립니다.

조건에 맞지 않는 정점이라는 것은 범위밖의 정점( 0보다 작거나 n또는 m보다 큰 정점), 이미 방문된 정점, 또는 0인 정점을 말하는 것이죠.

방문해야 할 정점이라면 큐에 push하고 방문되었다는 표시만 해주는 아주 단순한 함수입니다. visited[y][x] = true가 바로 방문되었다는 표시를 해주는 작업이랍니다.


단순하긴 하지만 이 함수가 없다면 for루프 안에 일일히 if문으로 검사를 해주어야하는 뻘짓을 하게 될겁니다.

void pushQ(int y, int x) {

	if (y >= n || x >= m || y<0 || x<0 || visited[y][x] || map[y][x] == '0') return;
	q.push(Point(y, x));

	visited[y][x] = true;
}

그리고 또 한가지는 pushQ를 총 4번 호출하죠?

for문으로 돌릴 수 있으나, 거기까지 생각하면서 귀찮게 코딩 안하잖아요 우리는...

그냥 짧은거는 복붙합시다... 티도 안나요.


점(point)을 조금 편하게 관리하게 위해서 Point라는 구조체를 사용했습니다.

뭐... 사용하지 않고 그냥 배열로만 해도 상관없구요.


십쥬?

반응형
블로그 이미지

REAKWON

와나진짜

,

BOJ [1260] DFS와 BFS



문제해석


그래프를 DFS와 BFS 순으로 탐색한 결과를 반환하면 되겠습니다.


제약조건


간선 M은 10000개 이하, 정점 N은 1000 이하입니다.

그냥 묻지도 따지지도 않고 DFS, BFS 오지게 코딩하시면 됩니다.


문제풀이



DFS와 BFS에 대한 설명은 아래의 링크를 통해서 확인해주세요.


DFS https://reakwon.tistory.com/4?category=300866

BFS https://reakwon.tistory.com/7?category=300867





코드

#include <cstdio>
#include <vector>
#include <algorithm>
#include <string>
#include <queue>
using namespace std;

vector<int> dfs_order = vector<int>();
vector<int> bfs_order = vector<int>();
vector<bool> visited;
int map[1001][1001];

int V,E;

void dfs(int here) {
	
	visited[here] = true;
	dfs_order.push_back(here);

	for (int to = 1; to<=V; to++) 
		if (!visited[to] && map[here][to] == 1) 
			dfs(to);
}

void bfs(int start) {

	queue<int> q;
	q.push(start);
	bfs_order.push_back(start);
	visited[start] = true;

	while (!q.empty()) {
		int here = q.front();
		q.pop();
		for (int next = 1; next<=V; next++) 
			if (!visited[next] && map[here][next] == 1) {
				q.push(next);
				bfs_order.push_back(next);
				visited[next] = true;
			}
	}
}

void print_order(vector<int> &order) {
	for (int i = 0; i < order.size(); i++)
		printf("%d ", order[i]);
	printf("\n");
}


int main() {
	
	scanf("%d %d", &V, &E);

	int start;
	scanf("%d", &start);
	for (int i = 0; i<E; i++) {
		int a, b;
		scanf("%d %d", &a, &b);
		map[a][b] = map[b][a] = 1;
	}

	visited = vector<bool>(V + 1);
	dfs(start);
	visited = vector<bool>(V + 1);
	bfs(start);

	print_order(dfs_order);
	print_order(bfs_order);

}

dfs_order와 bfs_order는 각각 dfs 순으로 방문된 순서를 저장하는 벡터, bfs 순으로 방문된 순서를 저장하는 벡터입니다.



반응형
블로그 이미지

REAKWON

와나진짜

,

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


그래프 이론 DFS와 BFS 중 BFS는 그래프를 가까운 정점부터 순서대로 탐색하는 그래프 탐색 알고리즘이랍니다. 그래프에서 탐색 시 아주 많이 사용되는 탐색기법이지요. 우선 다음과 같은 무향그래프가 있다고 가정해보겠습니다. 


우선 그래프 탐색을 하기 전에 조건이 있습니다.

1. 처음 정점 0부터 시작한다고 칩시다.

2. 정점을 방문한 적이 있다면 다시 방문하지 않는다.

3. 방문해야 할 정점이 여러 개일 경우 가장 번호가 낮은 정점부터 방문한다.




BFS로 위 그래프를 탐색하는 과정을 보겠습니다 


1. 0을 방문한다.

2. 0과 가장 가까운 정점을 찾는다 ( 1, 5, 6).

3. 1이 방문되지 않았으므로 1을 방문한다.

4. 5가 방문되지 않았으므로 5를 방문한다.

5. 6이 방문되지 않았으므로 6을 방문한다.

6. 1에서 가장 가까운 정점을 찾는다 (0, 2, 3).

7. 0은 이미 방문했으므로 skip한다.

8. 2는 방문되지 않았으므로 2를 방문한다.

9. 3이 방문되지 않았으므로 3을 방문한다.

10. 5에서 가장 가까운 정점을 찾는다 (0, 6).

11. 0과 6은 방문됐으므로 skip한다.

12. 6과 가장 가까운 정점을 찾는다 (0, 5).

13. 0과 5는 이미 방문됐으므로 skip한다.

14. 2와 가장 가까운 정점을 찾는다 (1, 4). 

15. 1은 이미 방문됐으므로 skip한다.

16. 4는 방문되지 않았으므로 4를 방문한다.

17. 모든 정점을 지나쳤으므로 종료한다.


이제 방문된 점을 차례대로 나열해본다면  0 - 1 - 5 - 6 - 2 - 3 - 4 로 방문이 되는 것을 확인 할 수 있습니다. 코드로 표현하면 어떻게 될까요? 아래 C++ 소스코드로 BFS를 구현해보았습니다.


#include <cstdio> #include <vector> #include <queue> #define V 7 using namespace std; int g[V][V] = { {0,1,0,0,0,1,1}, {1,0,1,1,0,0,0}, {0,1,0,0,1,0,0}, {0,1,0,0,0,0,0}, {0,0,1,0,0,0,0}, {1,0,0,0,0,0,1}, {1,0,0,0,0,1,0} }; vector

bfs(int start) { vector discovered(V, false); queue next; vector order; visited[start] = true; next.push(start); while (!next.empty()) { int from = next.front(); next.pop(); order.push_back(from); for (int to = 0; to < V; to++) { if (g[from][to] == 1 && !visited[to]) { next.push(to); visited[to] = true; } } } return order; } int main() { vector bfsOrder = bfs(0); for (int i = 0; i < V; i++) printf("%d ", bfsOrder[i]); }


이 코드가 어떻게 동작이 되는 지 하나하나 살펴봅시다. 그 전에 준비물이 있습니다. 바로 자료구조 큐를 준비해야 된다는 겁니다. 큐를 통해서 어떻게 BFS가 구현될까요? 다음과 같이 하늘색 상자는 큐를 나타낸다고 하겠습니다. 0부터 노드가 방문되기 때문에 큐 안에는 0이 저장되어 있습니다.



이 후 0은 방문될때 큐에서 꺼내어 집니다. 그 다음에 0에서 가장 가까운 정점 중에 방문되지 않은 정점을 큐에 저장합니다. 이렇게 말입니다.


0을 방문합니다. 0은 큐에서 꺼내어 지고 0과 가장 가까운 정점 1 5 6이 큐에 저장됐습니다. 그 후에는 다시 1을 큐에서 꺼내오고 1과 가장 가까운 정점을 큐에 집어 넣습니다.

단, 방문되지 않은 녀석들로요.



1은 방문이 되어 큐에서 나오고 2와 3이 추가 돼었군요. 역시 과정은 계속 반복됩니다. 다음 5가 방문될 차례입니다. 5를 꺼내고 5와 가장 가까운 정점을 추가합니다.



5와 가장 가까운 정점 0과 6이 있는데, 6은 아직 큐에 존재하니까 방문하지 않은 걸로 간주합니다. 그래서 6을 다시 큐에 집어 넣습니다. 다음 6을 방문합니다. 



6과 가장 가까운 정점 0과 5는 이미 방문된 상태랍니다. 그래서 큐에 집어 넣지 않습니다. 다음 2를 방문합니다. 





2과 가장 가까운 정점은 1, 4로 1은 이미 방문된 상태로 넘어가고 4는 큐에 들어갑니다. 왜냐면 방문될 정점으로 큐에 아직 존재하니까요. 다음 3을 방문합니다.


3과 가장 가까운 정점 1은 이미 방문된 상태라 넘어갑니다. 다음 4를 방문합니다.

4와 가장 가까운 정점 2는 이미 큐에서 나와 방문이 되었습니다. 어느 정점하나 큐에 들어가지 않습니다. 이제 큐에 남아있는 방문될 정점 6과 4는 이미 방문이 되었으므로 큐에서 그냥 빠져나옵니다. 방문되지 않는 다는 뜻입니다. 버립니다.


이로써 큐는 모두 비워졌습니다. 큐가 비워졌으니 더 이상 방문할 정점이 없으니 BFS과정은 끝나게 됩니다. 이로써 BFS가 어떻게 동작하는 지 살펴보았습니다. BFS는 그럼 도대체 언제 사용하게 될까요? 그건 다음에 알아보도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,