이 방법은 최단거리(최소 횟수, 최소 거리)를 구할 때 사용한다.
BFS와 같이 언급되는 DFS는 모든 경로를 탐색하여 모든 방법의 수를 구하는 방법이라면
BFS는 모든 경로가 아닌 최단 거리 혹은 최소의 횟수를 구하는 방법이다.
BFS는 Queue에 값을 저장하고 빼고를 반복하여 같은 레벨에 있는 값들을 출력하는 방식이다.
응용문제를 정리해서 어떤 방식으로 사용되는지 정리해보고자 한다.
같은 레벨의 노드를 추가하는 방식이며 레벨의 수가 횟수가 된다.
- Queue에 저장한다. 처음 시작 수를
- 그 값을 시작으로 동등하게 무언가를 더해간다. 그 경우의 수가 문제에 주어진다.
1,2,3을 더해가서 어떤 수에 도달할 수 있는 최소의 횟수처럼- 같은 레벨임을 저장할 수 있는 배열을 하나 만들어준다.
- 이미 그 전에 같은 수가 나왔는지 확인해줄 상태 배열이 필요하다. 왜냐하면 이미 그 전에 나왔다면 더 짧은 횟수로 그 수에 도달했다는 의미이다.
추가적으로 BFS를 사용할 때 메모리 초과가 발생할 수 있다.
원인은 Queue에 무한대로 데이터가 쌓여 스택오버 플로우가 발생하기 때문이다.
따라서 무조건 다 들어가게 하는 것이 아니라 조건을 걸어서 조건에 맞는 것만 Queue에 쌓도록 한다.
대부분 check 배열을 통해서 이 문제를 해결하는데
이 check 배열의 자료형도 int보다는 boolean을 사용해서 메모리 초과가 발생하지 않도록 주의해야 한다. (int 4byte, boolean 1byte)
import java.util.*;
import java.io.*;
public class Main{
public static int BFS(int S, int E){
Queue<Integer> Q = new LinkedList<>();
Q.offer(S);
int[] ch = new int[10001];
int result =-1;
boolean tag = true;
while(!Q.isEmpty()){
int len = Q.size();
result++;
for(int i=0;i<len;i++){
int output= Q.poll();
//System.out.println(output);
if(output==E) {
tag=false;
break;
}
if(ch[output+1] ==0) {
Q.offer(output+1);
ch[output+1] =1;
}
if(output>0&&ch[output-1] ==0) {
Q.offer(output-1);
ch[output-1] =1;
}
if(ch[output+5] ==0) {
Q.offer(output+5);
ch[output+5] =1;
}
}
if(tag==false) break;
}
return result;
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st = new StringTokenizer(br.readLine()," ");
int S = Integer.parseInt(st.nextToken());
int E = Integer.parseInt(st.nextToken());
int result =BFS(S,E);
bw.write(Integer.toString(result));
bw.flush();
bw.close();
br.close();
}
}
을 풀어보고자 한다.
이 문제는 인접행렬을 이용해서 푼다.
그래프가 주어진 문제는 인접행렬을 만들어야 하며 만약에 그 범위가 너무 크면
메모리를 잡아먹기 때문에 그럴 때는 인접리스트를 이용해야 한다.
그러나 이 문제는 컴퓨터의 대수가 100이하이기 때문에 2차원 배열의 경우 10000이하의 범위를 가지게 된다. 따라서 행렬을 이용한다.
import java.util.*;
import java.io.*;
public class Main{
static int[] ch;
static int[][] graph;
public static int BFS(int L){
Queue<Integer> Q = new LinkedList<>();
Q.offer(L);
ch[L]=1;
int cnt =0;
while(!Q.isEmpty()){
int n= Q.poll();
//System.out.println("n:"+n);
for(int i=1;i<=graph.length-1;i++) {
if (graph[n][i]==1&& ch[i] == 0){
int input = i;
Q.offer(input);
ch[i]=1;
cnt++;
}
}
}
return cnt;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st = null;
int C = Integer.parseInt(br.readLine());
int V = Integer.parseInt(br.readLine());
ch= new int[C+1];
graph = new int[C+1][C+1];
for(int i=1;i<=V;i++){
st= new StringTokenizer(br.readLine()," ");
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
graph[a][b]=graph[b][a]=1;
}
int result = BFS(1);
bw.write(Integer.toString(result));
bw.flush();
bw.close();
br.close();
}
}
는 인프런 알고리즘 강의에 올라와 있는 문제이다.
1부터 시작하여 각각의 노드에 최소횟수를 도착하는 그 횟수를 각각 하는 것이다.
이 같은 경우에서 주의해야할 점은
들렸던 노드에 대해서는 꼭 체크를 해줘야 한다.
이미 들려서 최단거리를 알고 있는 마당에 중간에 다시 들릴 필요가 없기 때문이다.
또한 레벨이 같은 경우 같은 횟수를 갖는다.
이를 어떻게 구현할 것인가 생각해야 한다.
나는 그 이전 레벨에 더하기를 하는 방식으로 구현했다.
result[i]=result[ver]+1;
이 부분의 코드이다.
import java.io.*;
import java.util.*;
public class Main{
static int[][] arr;
static int[] result;
static int[] ch;
static void BFS(int L, int N){
Queue<Integer> Q = new LinkedList<>();
Q.offer(L);
boolean tag=false;
while(!Q.isEmpty()){
int ver = Q.poll();
for(int i=1;i<=N;i++){
if(arr[ver][i]==1 && ch[i]==0){
result[i]=result[ver]+1;
ch[i]=1;
Q.offer(i);
}
}
}
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st = new StringTokenizer(br.readLine()," ");
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
arr= new int[N+1][N+1];
ch = new int[N+1];
result = new int[N+1];
for(int i=0;i<M;i++){
st= new StringTokenizer(br.readLine()," ");
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
arr[a][b]=1;
}
ch[1]=1;
BFS(1,N);
for(int i=2;i<=N;i++){
bw.write(Integer.toString(i)+":"+Integer.toString(result[i])+"\n");
}
bw.flush();
bw.close();
br.close();
}
}
이 문제는 BFS로 더해가면서 나아가는 문제이다.
말 그대로 너비로 하나씩 탐색하다가
거슬러줄 돈에 맞아지면 멈추고 그 레벨을 반환하는 것이다.
레벨 = 거슬러줄 동전의 수
가 된다.
import java.util.*;
import java.io.*;
public class Main{
static int N =0;
static int M=0;
static int[] coin ;
static int result=0;
static void BFS(int L){
Queue<Integer> Q = new LinkedList<>();
int[] ch = new int[501];
int[] level = new int[501];
Q.offer(L);
while(!Q.isEmpty()){
int n = Q.poll();
if(n==M ) result = level[n];
for(int i=1;i<=N;i++){
if(coin[i]+n<=M && ch[coin[i]+n]==0) {
Q.offer(coin[i]+n);
level[coin[i]+n]=level[n]+1;
ch[coin[i]+n]=1;
}
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
N = Integer.parseInt(br.readLine());
coin = new int[N+1];
StringTokenizer st =new StringTokenizer(br.readLine()," ");
for(int i=1;i<=N;i++){
coin[i]= Integer.parseInt(st.nextToken());
}
M= Integer.parseInt(br.readLine());
BFS(0);
bw.write(Integer.toString(result));
bw.flush();
bw.close();
br.close();
}
}
을 풀어보았다.
이 문제의 핵심은 현재 위치에서 -1만큼 뒤로 갈 수 있으며
따라서 동생의 위치가 수빈이의 위치보다 뒤에 있을 수도 있다.
(수빈이가 5라면 동생은 3의 위치에 있을 수도 있다.)
따라서 여기서는 상태 행령의 범위를 넘지 않도록 조건문에 명시해줘야 한다.
기존에 풀던 문제와 좀 다르다. 기존의 문제는 무조건 앞으로만 이동했다. 따라서 상태행렬의 범위를 넘지 않도록 하는 조건문이 필요하지 않았다. 만약에 수빈이 위치 x이고 동생의 위치가 M이라면
if(x+1<=M && ch[x+1]==0)이런식으로 표현이 가능해서 상태행렬인 ch가 범위를 넘지 않았으나 이 문제는 x+1<=M 표현이 불가능하다.
import java.io.*;
import java.util.*;
public class Main{
static int M;
static int[] ch = new int[100001];
static int[] Level = new int[100001];
static void BFS(int L){
Queue<Integer> Q = new LinkedList<>();
Q.offer(L);
ch[L]=1;
while(!Q.isEmpty()){
int x = Q.poll();
if(x==M){
break;
}
if(x+1<=100000 && ch[x+1]==0){
Q.offer(x+1);
ch[x+1]=1;
Level[x+1] =Level[x]+1;
}
if(x-1>=0 && ch[x-1]==0){
Q.offer(x-1);
ch[x-1]=1;
Level[x-1] =Level[x]+1;
}
if(2*x<=100000 &&x>0 && ch[2*x]==0){
Q.offer(2*x);
ch[2*x]=1;
Level[2*x] =Level[x]+1;
}
}
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st =new StringTokenizer(br.readLine()," ");
int N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
BFS(N);
bw.write(Integer.toString(Level[M]));
bw.flush();
bw.close();
br.close();
}
}
을 풀어보자
토마토가 보관되어져 있는데 0은 아직 익지 않은 토마토, 1은 익은 토마토, -1은 토마토가 들어있지 않다.
나는 아래와 같은 방법으로 BFS 통해서 접근해보려고 한다.
1. 익은 토마토들은 다같이 간다. 그래서 Q에 동시에 다같이 들어간다. 같은 레벨로 전진한다.
2. 따라서 Queue에 같은 레벨로 동시에 넣어줄 ArrayList가 필요하다.
3. ArrayList에는 토마토가 들어있는 row,col이 같이 들어가 있어야 하므로 함께 들어갈 수 있도록 내부 클래스를 통해서 객체를 하나 만들어준다.
왼쪽, 오른쪽, 아래, 위로 한칸씩 접근하기 위해서 조건문이 필요하다.
익은 토마토의 수 == 전체 상자칸의 수 - 토마토가 들어있지 않은 칸의 수
라면 일수를 출력한다.
따라서 풀이는 아래와 같다.
import java.util.*;
import java.io.*;
public class Main{
static int[][] arr;
static ArrayList<XY_to> list = new ArrayList<>();
static int N;
static int M;
static int Day=-1;
static int tomato;
static class XY_to{
int x ;
int y ;
public XY_to (int x, int y){
this.x= x;
this.y =y;
}
}
static void BFS(){
Queue<XY_to> Q = new LinkedList<>();
for(XY_to t : list){
Q.offer(t);
}
while(!Q.isEmpty()){
int size = Q.size();
Day++;
for(int i=0;i<size;i++){
XY_to xy =Q.poll();
int x = xy.x;
int y = xy.y;
if(x-1>=0 && arr[x-1][y]==0 && arr[x-1][y]!=-1){
arr[x-1][y]=1;
Q.offer(new XY_to(x-1,y));
tomato++;
}
if(x+1<N && arr[x+1][y]==0&& arr[x+1][y]!=-1){
arr[x+1][y]=1;
Q.offer(new XY_to(x+1,y));
tomato++;
}
if(y-1>=0 && arr[x][y-1]==0&& arr[x][y-1]!=-1){
arr[x][y-1]=1;
Q.offer(new XY_to(x,y-1));
tomato++;
}
if(y+1<M && arr[x][y+1]==0&& arr[x][y+1]!=-1){
arr[x][y+1]=1;
Q.offer(new XY_to(x,y+1));
tomato++;
}
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st = new StringTokenizer(br.readLine()," ");
M = Integer.parseInt(st.nextToken());//col
N = Integer.parseInt(st.nextToken());//row
arr= new int[N][M];
int result = M*N;
for(int i=0;i<N;i++){
st=new StringTokenizer(br.readLine()," ");
for(int j=0;j<M;j++){
int box = Integer.parseInt(st.nextToken());
arr[i][j]=box;
if(box==1) {
list.add(new XY_to(i,j));
tomato++;
} else if(box ==-1) result--;
}
}
BFS();
if(tomato == result) bw.write(Integer.toString(Day));
else bw.write("-1");
bw.flush();
bw.close();
br.close();
}
}
마지막으로 주의해야할 부분이 있다.
Q에서 값을 꺼낼때마다 size가 변하기 때문에 저런 경우에 절대로 for(int i=0;i<Q.size();i++)
로 선언하지 않도록 하자.
정리하자면 리스트나 배열,Q와 같이 거기 안에서 무언가 꺼내서 삭제하는 작업이 for에서 이루어지는 경우에는 절대로 for문의 범위 제한 부분에 사이즈를 넣지 않도록 주의하자!
을 풀어보자
기존 토마토 문제에서 높이까지 생긴 문제이다
기존 토마토는 왼쪽, 오른쪽, 앞, 뒤 + 위,아래가 추가 되었다.
따라서 몇까지 내용이 추가된다.
static int[] cx = {0,0,-1,1};
static int[] cy= {1,-1,0,0};
static int[] cz= {-1,1};//위, 아래
static class XYZ {
int x;
int y;
int z;
public XYZ(int x, int y, int z){
this.x=x;
this.y=y;
this.z=z;
}
}
for(int z=0;z<H;z++){
for(int x=0;x<N;x++){
st = new StringTokenizer(br.readLine()," ");
for(int y=0;y<M;y++){
int index = Integer.parseInt(st.nextToken());
arr[z][x][y]=index;
if(index==1) {
list.add(new XYZ(x,y,z));
to_ripe++;
}
if(index==-1) cnt++;
}
}
}
따라서 풀이는 아래와 같다.
import java.util.*;
import java.io.*;
public class Main{
static ArrayList<XYZ> list;
static int[][][] arr;
static int[] cx = {0,0,-1,1};
static int[] cy= {1,-1,0,0};
static int[] cz= {-1,1};
static int M ;
static int N ;
static int H ;
static int to_day=-1;
static int to_ripe=0;
static class XYZ {
int x;
int y;
int z;
public XYZ(int x, int y, int z){
this.x=x;
this.y=y;
this.z=z;
}
}
static void BFS(){
Queue<XYZ> Q = new LinkedList<>();
for(XYZ x : list){
Q.offer(x);
}
while(!Q.isEmpty()){
int size = Q.size();
to_day++;
for(int i=0;i<size;i++){
XYZ tomato = Q.poll();
for(int xy=0;xy<4;xy++){
int xx = tomato.x+cx[xy];
int yy = tomato.y+cy[xy];
if(xx>=0&&xx<N&&yy>=0&&yy<M&&arr[tomato.z][xx][yy]==0){
Q.offer(new XYZ(xx,yy,tomato.z));
arr[tomato.z][xx][yy]=1;
to_ripe++;
}
}
for(int z=0;z<2;z++){
int zz = tomato.z+cz[z];
if(zz>=0&&zz<H&&arr[zz][tomato.x][tomato.y]==0){
Q.offer(new XYZ(tomato.x,tomato.y,zz));
arr[zz][tomato.x][tomato.y]=1;
to_ripe++;
}
}
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st = new StringTokenizer(br.readLine()," ");
M = Integer.parseInt(st.nextToken());
N = Integer.parseInt(st.nextToken());
H = Integer.parseInt(st.nextToken());
list = new ArrayList<>();
arr = new int[H][N][M];
int cnt=0;
for(int z=0;z<H;z++){
for(int x=0;x<N;x++){
st = new StringTokenizer(br.readLine()," ");
for(int y=0;y<M;y++){
int index = Integer.parseInt(st.nextToken());
arr[z][x][y]=index;
if(index==1) {
list.add(new XYZ(x,y,z));
to_ripe++;
}
if(index==-1) cnt++;
}
}
}
BFS();
if(N*M*H-cnt==to_ripe) bw.write(Integer.toString(to_day)+"\n");
else bw.write("-1\n");
bw.flush();
bw.close();
br.close();
}
}
을 풀어보자
이 문제는 조건을 생각하는게 중요하다.
static int[] dice ={1,2,3,4,5,6};
static int[] ridl = new int[101];
static int[] snk = new int[101];
static int[] ch = new int[101];
static int cnt=-1;
....
//BFS 안 조건식
for(int j=0;j<6;j++){
int res = x+dice[j];
if(res<=100){
if(ridl[res]>0 && snk[res]==0&&ch[res]==0){ // 사다리로 갈 수 있는
Q.offer(ridl[res]);
ch[ridl[res]]=1;
}else if(snk[res]==0&&ch[res]==0){ // 사다리로 못가고 주사위로
Q.offer(res);
ch[res]=1;
}else if(snk[res]>0 && ch[res]==0){ // 뱀으로밖에는 방법이 없는 경우
Q.offer(snk[res]);
ch[snk[res]]=1;
}
}
따라서 전체 풀이는 아래와 같다.
import java.util.*;
import java.io.*;
public class Main{
static int[] dice ={1,2,3,4,5,6};
static int[] ridl = new int[101];
static int[] snk = new int[101];
static int[] ch = new int[101];
static int cnt=-1;
static void BFS() {
Queue<Integer> Q = new LinkedList<>();
Q.offer(1);
boolean tag = false;
while(!Q.isEmpty()){
int size = Q.size();
cnt++;
for(int i=0;i<size;i++){
int x = Q.poll();
if(x==100){
tag=true;
break;
}
for(int j=0;j<6;j++){
int res = x+dice[j];
if(res<=100){
if(ridl[res]>0 && snk[res]==0&&ch[res]==0){ // 사다리로 갈 수 있는
Q.offer(ridl[res]);
ch[ridl[res]]=1;
}else if(snk[res]==0&&ch[res]==0){ // 사다리로 못가고 주사위로
Q.offer(res);
ch[res]=1;
}else if(snk[res]>0 && ch[res]==0){ // 뱀으로밖에는 방법이 없는 경우
Q.offer(snk[res]);
ch[snk[res]]=1;
}
}
}
}
if(tag==true) break;
}
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st =new StringTokenizer(br.readLine()," ");
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
for(int i=0;i<N;i++){
st=new StringTokenizer(br.readLine()," ");
int x = Integer.parseInt(st.nextToken());
int y =Integer.parseInt(st.nextToken());
ridl[x]=y;
}
for(int i=0;i<M;i++){
st=new StringTokenizer(br.readLine()," ");
int x = Integer.parseInt(st.nextToken());
int y =Integer.parseInt(st.nextToken());
snk[x]=y;
}
BFS();
bw.write(Integer.toString(cnt));
bw.flush();
bw.close();
br.close();
}
}
를 풀어보았다.
계속 실패한다.
import java.util.*;
import java.io.*;
public class Main{
static int[] cx = {-1,1,0,0};
static int[] cy = {0,0,1,-1};
static int[][] arr;
static int N;
static int time=-1;
static int fish_size=2;
static int[] fishes = new int[8];
static class XY{
int x;
int y;
public XY(int x, int y){
this.x =x;
this.y =y;
}
}
static void BFS(int x, int y){
if(remain_sm_f(fish_size)==0) {
time=0;
return;
}
Queue<XY> Q = new LinkedList<>();
PriorityQueue<XY> net = new PriorityQueue<>((x1,x2)->{
if(x1.x==x2.x) return x1.y-x2.y;
else return x1.x-x2.x;
});
int[][] ch = new int[N][N];
ch[x][y]=1;
arr[x][y]=0;
int eat_f =0;
Q.offer(new XY(x,y));
while(!Q.isEmpty()){
int size = Q.size();
time++;
System.out.println("위에 time:"+time);
boolean tag = false;
for(int i=0;i<size;i++){
XY fish = Q.poll();
if(arr[fish.x][fish.y]>0 && arr[fish.x][fish.y]<fish_size){
net.add(fish);
tag= true;
continue;
} if(tag==true) break;
for(int j=0;j<4;j++){
int fx = fish.x + cx[j];
int fy = fish.y + cy[j];
if(fx>=N||fx<0||fy>=N||fy<0)
continue;
if(arr[fx][fy]<=fish_size && ch[fx][fy]==0){
Q.offer(new XY(fx,fy));
ch[fx][fy]=1;
}
}//상하좌우
}//Q안에 들은 것들
if(tag==true){
XY sm_f =net.poll();
net=new PriorityQueue<>((x1,x2)->{
if(x1.x==x2.x) return x1.y-x2.y;
else return x1.x-x2.x;
});
Q.clear();
Q.offer(sm_f);
ch=new int[N][N];
fishes[arr[sm_f.x][sm_f.y]]--;
ch[sm_f.x][sm_f.y]=1;
arr[sm_f.x][sm_f.y]=0;
eat_f++;
if(eat_f == fish_size){
fish_size++;
eat_f=0;
}
System.out.println("남은 물고기:"+remain_sm_f(fish_size));
System.out.println("time:"+time);
System.out.println("지금 먹은 물고기:"+sm_f.x+":"+sm_f.y);
System.out.println("아기 상어 크기:"+fish_size);
if(remain_sm_f(fish_size)==0) break;
else{
time--;
System.out.println("minus_time:"+time);
}
}//tag
}//while
}
static int remain_sm_f(int shark_size){
int answer =0;
if(shark_size>=7) shark_size=7;
for(int i=1;i<shark_size;i++){
answer+=fishes[i];
}
return answer;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
N = Integer.parseInt(br.readLine());
arr= new int[N][N];
StringTokenizer st = null;
int x = 0;
int y = 0;
for(int i=0;i<N;i++){
st = new StringTokenizer(br.readLine()," ");
for(int j=0;j<N;j++){
int f = Integer.parseInt(st.nextToken());
arr[i][j]=f;
if(f==9){
x=i;
y=j;
}else fishes[f]++;
}
}
BFS(x,y);
bw.write(Integer.toString(time));
bw.flush();
bw.close();
br.close();
}
}