본문 바로가기
자료구조,알고리즘 개념정리

[그래프] 그래프란 뭘까요?

by Meaning_ 2022. 7. 10.
728x90
반응형

그래프

정점들의 집합 v와 간선들의 집합 e로 구성된 자료구조

정점의 위치정보나 간선의 순서등은 그래프의 정의에 포함되지 않음

🥑 그래프의 종류

방향그래프 간선이 ‘방향’이라는 새로운 속성을 가짐

무향그래프 간선에 방향이 없는 그래프
가중치 그래프 간선에 ‘가중치’라는 실수 속성을 부여
다중 그래프 두 정점 사이에 두개 이상의 간선이 있을 수 있는 그래프
단순 그래프 두 정점 사이에 최대 한개의 간선만 있는 그래프
트리 혹은 루트없는 트리 부모 자식 관계가 없을 뿐 간선들의 연결 관계는 무향 그래프
이분 그래프 그래프의 정점들을 겹치지 않는 두개의 그룹으로 나눠서 서로 다른 그룹에 속한 정점 간에만 간선이 존재하도록 만들 수 있는 그래프

🥑 그래프의 경로

단순경로 : 경로 중 한 정점을 최대 한 번만 지나는 경로

사이클/회로 : 시작한 점에서 끝나는 경로

🥑 그래프 사용 예

철도망의 안정성 분석 절단점 찾기 알고리즘

소셜 네트워크 분석 BFS
인터넷 전송속도 계산 최소 스패닝 트리
한붓 그리기 오일러 경로 → DFS
외환 거래 최단거리(가중치)

🥑 인접 행렬과 인접 리스트

인접 행렬 2차원 배열을 활용해 그래프이 간선 정보 저장. adjacent[i,j]는 정점 i에서 정점 j로 가는 간선이 있는지 나타냄. 가중치가 있다면 간선의 정보에 정수나 실수 변수를 두면 됨.

인접 리스트 각 정점 마다 해당 정점에서 나가는 간선의 목록을 저장해서 그래프 표현

인접행렬의 시간복잡도는 V*V 배열을 사용하기에 O(V^2)

인접리스트의 시간복잡도는 V개의 연결리스트에 실제 간선 수만큼 원소가 들어있기에 O(V+E) 공간만큼 사용

📌 그럼 언제 인접 행렬을 쓰고 언제 인접 리스트를 쓸까요?

간선의 수가 V^2에 비해 훨씬 적은 그래프를 희소 그래프, 간선의 수가 V^2에 비례하는 그래프를 밀집 그래프라고 함.

희소 그래프 → 인접리스트 , 밀집 그래프 → 인접행렬

🥑 오일러 서킷

  • 그래프의 모든 간선을 정확히 한번씩 지나서 시작점으로 돌아오는 경로를 찾는 문제
  • 한붓그리기
  • 무향그래프, 방향그래프에서 모두 해결 가능.
  • 중복정점 허용 (모든 정점의 차수는 짝수)
  • 어떤 그래프 G가 오일러 순회를 가지기 위한 필요충분조건은 G가 연결 그래프이고, 모든 정점들이 짝수 개의 차수를 가져야 한다.

무향 그래프 방향 그래프

• 모든 정점이 짝수개여야 함. (들어가는 횟수와 나가는 횟수가 같아야 하기 때문)  
• 모든 간선이 하나의 컴포넌트여야 함.  
• 각 정점에 인접한 간선이 짝수개여야 함. (짝수점) • 각 간선은 둘 중 한 방향만 쓸 수 있기 때문에 각 정점에 들어오는 간선의 수와 나가는 간선의 수가 같아야만 한다.
• 모든 정점이 짝수개여야 함. (들어가는 횟수와 나가는 횟수가 같아야 하기 때문)  
• 모든 간선이 하나의 컴포넌트여야 함.  

+) 한 정점에 인접한 간선의 수를 해당 정점의 “차수”라고 함. 차수가 짝수인 정점을 “짝수점”, 차수가 홀수인 정점을 “홀수점”이라고 함.

🥑 오일러 트레일 (오일러 경로)

  • 모든 간선을 정확히 한번씩 지나지만 시작점과 끝점이 다른 경로
  • 시작점과 끝점을 제외한 모든 점이 짝수점이고, 시작점과 끝점은 홀수점이여야 한다.
  • 어떤 그래프 G가 오일러 경로(트레일)를 가지기 위한 필요충분조건은 G가 연결 그래프이고, 홀수 차수의 개수가 0 또는 2여야 함.

오일러 회로와 오일러 경로 정리

관련문제를 풀어보자!

백준 1199

https://www.acmicpc.net/problem/1199

백준 16168

백준 1178

백준 17414

REF

https://hy38.github.io/posts/euilerian-circuit-and-trail/

https://velog.io/@uchang903/이산-수학-오일러-경로와-회로Euler-Paths-and-Circuits-해밀턴-경로와-회로Hamilton-Paths-and-Circuits

https://aren227.me/eulerian-circuit-and-trail/

🥑 해밀토니안 순회

  • 모든 정점을 정확히 한번씩 지나가서 시작점으로 돌아옴(모든 간선을 통과하지 않아도 됨)

🥑 해밀턴 패스 (해밀턴 경로)

  • 시작점과 끝점이 다르지만 모든 정점을 한번씩 방문한다.

REF

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=qbxlvnf11&logNo=221361498110

https://algorfati.tistory.com/140

 

📌 DFS

시간복잡도 : O(n)

재귀적 특징, 백트래킹, 완전탐색(조합,순열) → 하나하나 다 탐색하는 경우

📌 BFS

깊이의 특징을 이용한 문제 (최단경로→ 가중치가 같은 그래프에서 ) , 영역끼리 묶는 경우

시간복잡도 : O(n)

움직여야 하는 최소칸의 개수

움직이는 횟수 배열을 만들어서 누적합으로 구현

cnt배열을 만들어서 cnt[nX+dirX[s]][cntnY+dirY[s]]=cnt[nX][nY]+1;

🐳최소 스패닝 트리

📌 스패닝 트리란 무엇일까?

  • 그래프의 최소 연결부분 그래프이다. (최소연결부분== 간선의 수가 가장 적다)
  • n개의 정점을 가지고 있는 그래프의 최소 간선의 수는 n-1개이고, n-1개의 간선으로 연결되어있으면 필연적으로 트리형태가 되는데, 이것이 스패닝 트리가 된다.
  • 그래프의 일부 간선을 선택해서 만든 트리
  • 하나의 그래프에는 많은 신장트리가 존재할 수 있다.
  • 모든 정점이 연결되어있으며, 사이클을 포함해서는 안된다.

📌 최소 스패닝 트리란?

  • 간선의 가중치의 합이 최소여야 한다.
  • 단순히 적은 간선을 사용한다고 해서 최소비용이 아니다.
  • 간선이 적으면 크루스칼, 간선이 많으면 프림 알고리즘을 사용한다.

🐋크루스칼 알고리즘

유니온파인드의 좋은 사용예이다.

  1. 간선 데이터를 비용에 따라 오름차순으로 정렬한다.
  2. 간선을 하나씩 확인하며 현재의 간선이 사이클을 발생시키는지 확인한다.
    1. 사이클이 발생하지 않는 경우 최소 신장 트리에 포함시킨다.
    2. 사이클이 발생하는 경우 최소 신장 트리에 포함시키지 않는다
  3. 모든 간선에 대하여 2번의 과정을 반복한다.
class Node implements Comparable<Node>{
	int to;
	int from;
	int value;
	
	public Node(int to, int from, int value) {
		this.to = to;
		this.from = from;
		this.value = value;
	}

	@Override
	public int compareTo(Node o) {
		return this.value - o.value;
	}
}

public class Main {
	private static int n; 	
	private static int[] parents;
	public static void main(String[] args) {
		n = 7; 
		int[][] graph = {{1,2,29}, {1,5,75},{2,3,35},{2,6,34}, {3,4,7},{4,6,23},
						{4,7,13}, {5,6,53}, {6,7,25}};
                        
		parents = new int[n + 1];
		for (int i=1; i<n+1; i++) { 
			parents[i] = i; 
		} 
        
		Queue<Node> pq = new PriorityQueue<>();
		for(int i=0; i<graph.length; i++) {
			int to = graph[i][0];
			int from = graph[i][1];
			int value = graph[i][2];
			// 우선순위 큐는 자동으로 간선 비용순(오름차순)으로 정렬된다.
			pq.add(new Node(to, from, value));			
		}
		
		int size = pq.size();
		int total =0;
		// 간선 하나씩 조회 (비용이 작은 간선부터)
		for(int i=0; i< size; i++) {
			Node node = pq.poll();
			int rx = find(node.to);
			int ry = find(node.from);
			 
			 // 사이클이 발생하지 않는 경우에만 집합에 포함 
			 if(!isSameParent(rx, ry)) {
				 total += node.value;
				 union(node.to,node.from);
			 }
		}
		System.out.println(total);
	}
	
	static int find(int x) { 
		if (parents[x] == x) { 
			return x; 
	     } 
		return parents[x] = find(parents[x]);
	} 
	     
	static void union(int x, int y) {
		x = find(x); 
		y = find(y); 
		// 더 find 값으로 부모 노드 설정
	    if (x < y) { 
	    	parents[y] = x; 
	    } 
	    else { 
	    	parents[x] = y; 
	    } 
	}
	
	static boolean isSameParent(int x, int y) {
		if(x == y) return true;
		return false;
	} 
}

🐋 프림 알고리즘

다익스트라 알고리즘과 비슷한 형태를 띄고 있다.

  1. 임의의 간선을 선택한다.
  2. 선택한 간선의 정점으로부터 가장 낮은 가중치를 갖는 정점을 선택한다.
  3. 모든 정점에 대하여 2번의 과정을 반복한다.

++) 양방향 간선을 쓴다.

class Node implements Comparable<Node>{
	int to;
	int value;
	
	public Node(int to, int value) {
		this.to = to;
		this.value = value;
	}

	@Override
	public int compareTo(Node o) {
		return this.value - o.value;
	}
}
public class Main {

	static int total;
	static List<Node>[] list;
	static boolean[] visited;
	public static void main(String[] args){
		int v = 7; 
		int[][] graph = {{1,2,29}, {1,5,75},{2,3,35},{2,6,34}, {3,4,7},{4,6,23},
						{4,7,13}, {5,6,53}, {6,7,25}};	
                        
		list = new ArrayList[v+1];
		visited = new boolean[v+1];
		for(int i=1; i<v+1; i++) {
			list[i] = new ArrayList<>();
		}
		
		for(int i=0; i<graph.length; i++) {
			int a = graph[i][0];
			int b = graph[i][1];
			int w = graph[i][2];
            
			list[a].add(new Node(b,w));
			list[b].add(new Node(a,w));
		}
		
		prim(1);
		System.out.println(total);
	}
	
	static void prim(int start) {
		Queue<Node> pq = new PriorityQueue<>();
		pq.add(new Node(start,0));
        
		while(!pq.isEmpty()) {
			Node p = pq.poll();
			int node = p.to;
			int weight = p.value;
			
			if(visited[node]) continue;
			// 선택한 간선의 정점으로부터 가장 낮은 가중치 갖는 정점 선택 
			visited[node]= true;
			total += weight;
			
			for(Node next : list[node]) {
				if(!visited[next.to]) {
					pq.add(next);
				}
			}
		}
		
	}
}

REF

https://gmlwjd9405.github.io/2018/08/28/algorithm-mst.html

https://loosie.tistory.com/159

  • 0-1 BFS 알고리즘

🐋다익스트라 (그리디)

특정한 노드에서 출발하여 다른 노드로 가는 각각의 최단경로를 구해주는 알고리즘.

→ 우선순위 큐 방법

https://bumbums.tistory.com/4

🐋플로이드 알고리즘 (DP)

모든지점에서 다른 모든 지점까지의 최단경로를 모두 구한다. 모든 노드에 대하여 최단거리를 담아야 하므로 2차원 배열이 필요하고,3중 for문을 쓴다는 특이점을 가진다.

arr[a][b]=Math.min(arr[a][b],arr[a][k]+arr[k][b]);

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

class Main{

    public static int n;
    public static int m;
    public static int arr[][];

    public static int INF=1000000000;

    public static void main(String[]args) throws IOException {
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
        n=Integer.parseInt(br.readLine());
        m=Integer.parseInt(br.readLine());

        arr=new int[n+1][n+1];

        //무한으로 초기화
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                arr[i][j]=INF;
            }
        }
        //자기자신으로 가면 0
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(i==j){
                    arr[i][j]=0;
                }
            }
        }
        
        //입력받기

        for(int i=0;i<m;i++){
            st=new StringTokenizer(br.readLine());
            int a= Integer.parseInt(st.nextToken());
            int b=Integer.parseInt(st.nextToken());
            int c=Integer.parseInt(st.nextToken());

            arr[a][b]=Math.min(arr[a][b],c);//노선이 여러개일 경우 원래있던값이랑 새로운값 중 비교
        }

        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                for(int k=1;k<=n;k++){
                    arr[j][k]=Math.min(arr[j][k],arr[j][i]+arr[i][k]);

                }
            }
        }

        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                if(arr[i][j]==INF){
                    System.out.print("0"+" ");
                }
                else{
                    System.out.print(arr[i][j]+" ");
                }
            }
            System.out.println();
        }
    }
}
728x90
반응형

댓글