이분 그래프
2 초 | 256 MB | 56362 | 14581 | 8622 | 23.264% |
문제
그래프의 정점의 집합을 둘로 분할하여, 각 집합에 속한 정점끼리는 서로 인접하지 않도록 분할할 수 있을 때, 그러한 그래프를 특별히 이분 그래프 (Bipartite Graph) 라 부른다.
그래프가 입력으로 주어졌을 때, 이 그래프가 이분 그래프인지 아닌지 판별하는 프로그램을 작성하시오.
입력
입력은 여러 개의 테스트 케이스로 구성되어 있는데, 첫째 줄에 테스트 케이스의 개수 K가 주어진다. 각 테스트 케이스의 첫째 줄에는 그래프의 정점의 개수 V와 간선의 개수 E가 빈 칸을 사이에 두고 순서대로 주어진다. 각 정점에는 1부터 V까지 차례로 번호가 붙어 있다. 이어서 둘째 줄부터 E개의 줄에 걸쳐 간선에 대한 정보가 주어지는데, 각 줄에 인접한 두 정점의 번호 u, v (u ≠ v)가 빈 칸을 사이에 두고 주어진다.
출력
K개의 줄에 걸쳐 입력으로 주어진 그래프가 이분 그래프이면 YES, 아니면 NO를 순서대로 출력한다.
제한
- 2 ≤ K ≤ 5
- 1 ≤ V ≤ 20,000
- 1 ≤ E ≤ 200,000
풀이1 실패: 입력하면서 분류하기
배열 한 개를 사용해서 처리할 수는 없는가 고민했다.
0은 입력하지 않음, 1과 -1은 각각 어느 집합에 분류되어있는지 표시하는 것이다.
import java.io.*;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
int[] A;
boolean judge=true;
int K = Integer.parseInt(br.readLine());
while(K-->0) {
st=new StringTokenizer(br.readLine()," ");
int V = Integer.parseInt(st.nextToken());
A=new int[200001];
int E = Integer.parseInt(st.nextToken());
for(int i=0;i<E;i++) {
st=new StringTokenizer(br.readLine()," ");
int num = Integer.parseInt(st.nextToken());
int num2 = Integer.parseInt(st.nextToken());
int arr1 = A[num];
int arr2 = A[num2];
if(arr1==0 & arr2==0) {
A[num]=1;
A[num2]=-1;
}else if(arr1==1 && arr2==0) {
A[num2]=-1;
}else if(arr1==-1 && arr2==0) {
A[num2]=1;
}else if(arr1==0 && arr2==1){
A[num]=-1;
}else if(arr1==0 && arr2==-1){
A[num]=1;
}else if(arr1!=0 && arr2!=0 && arr1==arr2) {
judge=false;
break;
}
}
System.out.println(judge?"YES":"NO");
}
}}
입력하면서 바로 분류 했기 때문에 이후 자료와 출동이 생겼을 것이라 예상했다.
풀이2실패: BFS의 사용, 메모리 부족
그래프의 연결관계를 그려보면 삼각형으로 연결되거나 5각형으로 연결되었을 때 두 집합으로 나눌 수 없게 된다.
두 가지 방법을 생각했다.
1. 사이클의 크기가 0을 포함한 짝수여야만 한다. 사이클의 크기를 구하는 메소드를 만든다
2. 현재 위치에서 2번 이동한 정점은 현재 위치와 같은 집합에 분류 되어야 한다. 1번 이동한 정점은 현재와 다른 집합에 분류되어야 한다.
편의상 2를 사용하기로 했다.
값을 기록
boolean arr[][] 배열에 연결관계를 저장한다. 방향이 없는 무향 그래프기 때문에 1 4를 입력 받으면 배열에는 4 1 도 같이 입력해준다.
방문 기록
이동시 boolean check[][] 배열에 방문 표시를 해준다. 방문표시를 하고 현재 노드가 어디에 속한 집합인지 확인해야한다.
Integer[] 배열을 만들고 기본값을 NULL값으로 설정한다.
이전 노드가 집합1 속했다면 현재 노드의 집합은 집합0에 있다고 표시해준다.
메소드1 : 찾기
입력 받은 모든 정점을 Integer[] 배열에 0으로 넣어준다.
for문을 통해서 값이 0인 index을 발견하면 탐색메소드를 실행한다
메소드2 : 탐색
처음 index값을 입력 받고 연결관계를 통해 그와 연결된 모든 노드들에 집합 위치를 부여한다.
import java.util.*;
public class Main {
static int V;
static Integer[] check;
static boolean[][] arr;
static boolean judge;
static Queue<int[]> queue = new LinkedList<int[]>();
static void bfs() {
for(int j=1;j<=V;j++) {
if(check[j]==0) {
queue.add(new int[] {j, 1});
check[j]=1;
while(!queue.isEmpty()) {
int[] now = queue.poll();
int N = now[0];
int team = now[1];
for(int i=1;i<=V;i++) {
if(arr[N][i] && check[i]!=null) {
if(check[i]==0) {
check[i]=team*-1;
queue.add(new int[] {i,team*-1});
}else if(check[i]==team) {
judge=false;
break;
}
}
}
}
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int K = sc.nextInt();
while(K-->0) {
V = sc.nextInt();
arr = new boolean[V+1][V+1];
check = new Integer[V+1];
judge = true;
int E = sc.nextInt();
while(E-->0) {
int start = sc.nextInt();
int last = sc.nextInt();
arr[start][last]=true;
arr[last][start]=true;
check[start]=0;
check[last]=0;
}
bfs();
System.out.println(judge?"YES":"NO");
}
}}
2차원 배열을 계속 서칭하기 때문에 공간의 낭비가 발생한다.
풀이3: BFS의 사용 + LinkedList의 사용
위 풀이 2번과 완전 똑같은 논리지만 배열이 아닌 LinkedList를 사용해 풀이했다.
LinkedList 배열 안에 LinkedList를 집어넣어 graph를 만든다.
LinkedList<Integer>[] graph = new LinkedList[V+1];
for(int i=1;i<=V;i++) {
graph[i]=new LinkedList<>();
}
LinkedList와 그래프의 시너지
위 문제처럼 노드의 개수가 10만개가 넘어가면 탐색하는데 있어서 굳이 필요없는 자료들을 살펴보다 메모리를 낭비하게 된다.
즉, 그래프에서 연결이 되어있다고 판단된 자료만 탐색해야하는데 그것을 가능하게 하는 것이 LinkedList 이다.
서로가 연결된 곳만 탐색할 수 있어 graph에서 노드가 많아지는 경우 LinkedList의 효율은 더 높아진다.
BFS + LinkedList
import java.util.*;
public class Main {
static int V;
static int[] check;
static boolean judge;
static Queue<Integer> queue = new LinkedList<Integer>();
static LinkedList<Integer>[] graph;
static void bfs() {
for(int j=1;j<=V;j++) {
if(check[j]==0) {
queue.add(j);
check[j]=1;
while(!queue.isEmpty()) {
int now = queue.poll();
for(Integer next:graph[now]) {
if(check[next]==0) {
queue.offer(next);
check[next]=check[now]*-1;
} else if(check[next]==check[now]) {
judge=false;
break;
}
}
}
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int K = sc.nextInt();
while(K-->0) {
V = sc.nextInt();
int E = sc.nextInt();
judge=true;
check = new int[V+1];
graph = new LinkedList[V+1];
for(int i=1;i<=V;i++) {
graph[i]=new LinkedList<>();
}
while(E-->0) {
int a = sc.nextInt();
int b = sc.nextInt();
graph[a].add(b);
graph[b].add(a);
}
bfs();
System.out.println(judge?"YES":"NO");
}
}}
'Study > Baekjoon' 카테고리의 다른 글
Baekjoon1991: 트리 순회 (0) | 2022.01.19 |
---|---|
Baekjoon11725: 트리의 부모, 숏코딩 (0) | 2022.01.18 |
Baekjoon2206: 벽 부수고 이동하기 (0) | 2022.01.16 |
Baekjoon1697: 숨바꼭질 (0) | 2022.01.15 |
Baekjoon7562: 나이트의 이동 (0) | 2022.01.14 |