MST - 최소 신장 트리

MST

Spanning Tree

  • 그래프 내의 모든 정점을 포함하는 트리이다.

Minimum Spanning Tree

  • 그래프의 최소 연결 부분 그래프이다.
  • n개의 정점을 가지고 있다면, 최소 간선의 수는 (n-1)개이다.

Prim’s Algorithm

특징

  1. 정점 선택을 기반으로 하는 알고리즘
  2. 이전 단계에서 만들어진 신장 트리를 확장하는 방법

구현

배열

W : G의 인접 행렬

nearest[] : vi에 가장 가까운 정점 Y의 인덱스

distance[] : nearest[i] 의 정점과 v 사이의 가중치

구현 - java

package prim;

public class prim {
    static int inf = Integer.MAX_VALUE;
    public static class vertex{
        int a,b;
        vertex(int a, int b)
        {
            this.a = a;
            this.b = b;
        }
    }
    public static void main(String[] args)
    {
        int W[][] = {{},{0,0,1,3,inf,inf},{0,1,0,3,6,inf},{0,3,3,0,4,2},{0,inf,6,4,0,5},{0,inf,inf,2,5,0}};
        vertex F[] = new vertex[W.length];

        F = prim(5,W,F);
        System.out.println(1);
    }
    /*
        n : vertex의 개수
        F : edge의 집합
    */
    public static vertex[] prim(int n, int W[][], vertex[] F)
    {
        int i, vnear=0, min, edge;
        int[] nearest = new int[n+1];
        int[] distance = new int[n+1];
        int IF = 0;
				/* 1. 초기화 */
        for(i=2;i<=n;i++)
        {
            nearest[i] = 1;
            distance[i] = W[1][i];
        }
				/* 2 */
        for(int j=1;j<n;j++)
        {
            min = inf;
            for(i=2;i<=n;i++)
            {
                if(0<=distance[i] && distance[i]<min)
                {
                    min = distance[i];
                    vnear = i;
                }
            }
            F[IF++] = new vertex(vnear, nearest[vnear]);
            distance[vnear] =  -1;
            for(i=2;i<=n;i++)
            {
                if(W[i][vnear] < distance[i]){
                    distance[i] = W[i][vnear];
                    nearest[i] = vnear;
                }
            }
        }
        return F;
    }
}

1. nearest, distance 배열 초기화

  1 2 3 4 5
nearest[]   1 1 1 1
distance[]   1 3 inf inf

2. 간선은 n-1 개 만들어지므로, n-1번 반복을 한다.

  1. min 변수를 inf로 초기화 한다.
  2. 인접한 꼭짓점을 찾기위해 distance[] 에서 0보다 크고 min보다 작은 값을 찾는다.
    1. min을 inf로 설정을 해두었기 때문에, 방문하지 못하는 곳은 걸러진다.
    2. 찾은 값을 min으로 해두고, vnear 값에 index를 저장한다.
  3. 찾은 vnear 값과, nearest[v]의 값을 F에 저장해준다.
  4. distance 배열의 vnear 값을 음수로 변경하여, e 의 비교과정을 스킵되게 한다.
    1. W[i][vnear]이 distance[i]보다 작다면, distance 값을 갱신해주고, nearest[i] 값을 vnear로 변경해준다.

Kruskal’s Algorithm

그래프 간선들을 가중치의 오름차순으로 정렬해 놓은 뒤, 사이클을 형성하지 않는 선에서 정렬된 순서대로 간선을 선택한다.

  1. 그래프 간선을 오름차순 순서대 정렬한다.
  2. 가장 작은 간선을 선택한다.
  3. 다음 간선을 선택한다
    1. 만약 다음 간선을 선택했을 때, 간선 간 사이클이 형성이 된다면, 선택하지 않는다.
     🔥 union-find를 통해 사이클을 확인한다.
  4. 선택된 간선의 개수가 정점의 개수 n 보다 1 작게되면, 종료한다.
package kruskal;

import java.util.List;
import java.util.Scanner;

public class Kruskal {
    static Edge E[];
    static int[] set;
    static final int INF = Integer.MAX_VALUE;
    static class Edge{
        int a;
        int b;
        int distance;
        Edge(int a, int b, int distance)
        {
            this.a = a;
            this.b = b;
            this.distance = distance;
        }
    }
    static int inf = Integer.MAX_VALUE;
    public static void main(String[] args)
    {
        int N, M;
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt(); M = sc.nextInt();
        E = new Edge[M];
        for(int i=0;i<M;i++)
        {
            E[i] = new Edge(sc.nextInt(), sc.nextInt(), sc.nextInt());
        }
        sortE(E);
        set = new int[N];
        for(int i=0;i<N;i++) set[i] = i;

        int distance_sum = 0;
        for(int i=0; i<M;i++){
            if(!find(E[i].a-1, E[i].b-1)){
                distance_sum += E[i].distance;
                unionParent(E[i].a-1, E[i].b-1);
            }
        }
        System.out.println(1);
    }
    public static void sortE(Edge E[])
    {
        for(int i = 1; i < E.length; i++)
        {
            for(int j = 1; j <= E.length - i; j++)
            {
                if(E[j-1].distance > E[j].distance){
                    swap(j-1,j);
                }
            }
        }
    }
    public static void swap(int i, int j)
    {
        Edge tmp = E[i];
        E[i] = E[j];
        E[j] = tmp;
    }
    public static int getParent(int x)
    {
        if(set[x] == x) return x;
        return set[x] = getParent(set[x]);
    }
    public static void unionParent(int a, int b)
    {
        a = getParent(a);
        b = getParent(b);
        if(a<b) set[b] = a;
        else set[a] = b;
    }
    public static boolean find(int a, int b)
    {
        a = getParent(a);
        b = getParent(b);
        if(a==b) return true;
        else return false;
    }
}

MST

Spanning Tree

  • 그래프 내의 모든 정점을 포함하는 트리이다.

Minimum Spanning Tree

  • 그래프의 최소 연결 부분 그래프이다.
  • **n**개의 정점을 가지고 있다면, 최소 간선의 수는 **(n-1)**개이다.
  • Undirected graph $G = (V,E)$
    • V : set of vertices / $V = \{v_1,v_2,v_3,v_4,v_5\}$
    • E : set of edges / $E = \{(v_1, v_2), (v_1, v_3), (v_2,v_4),(v_3,v_4),(v_3,v_5),(v_4,v_5)\}$
flowchart TD
	v1((v1))
	v2((v2))
	v3((v3))
	v4((v4))
	v5((v5))
	v1---|1|v2
	v2---|3|v3
	v2---|6|v4
	v3---|4|v4
	v5---|2|v3
	v5---|5|v4
	v1---|3|v3

Prim’s Algorithm

특징

  1. 정점 선택을 기반으로 하는 알고리즘
  2. 이전 단계에서 만들어진 신장 트리를 확장하는 방법

구현

배열

W : G의 인접 행렬

$W[i][j] = \begin{cases} weight~on~edge~~~if~there~is~an~edge~from~v_i~to~v_2 \\ \infty ~if~there~is~no~edge~from~v_i~to~v_j\\0~if~i=j \end{cases}$

nearest[] : $v_i$에 가장 가까운 정점 $Y$의 인덱스

distance[] : nearest[i] 의 정점과 v 사이의 가중치

구현 - java

package prim;

public class prim {
    static int inf = Integer.MAX_VALUE;
    public static class vertex{
        int a,b;
        vertex(int a, int b)
        {
            this.a = a;
            this.b = b;
        }
    }
    public static void main(String[] args)
    {
        int W[][] = {{},{0,0,1,3,inf,inf},{0,1,0,3,6,inf},{0,3,3,0,4,2},{0,inf,6,4,0,5},{0,inf,inf,2,5,0}};
        vertex F[] = new vertex[W.length];

        F = prim(5,W,F);
        System.out.println(1);
    }
    /*
        n : vertex의 개수
        F : edge의 집합
    */
    public static vertex[] prim(int n, int W[][], vertex[] F)
    {
        int i, vnear=0, min, edge;
        int[] nearest = new int[n+1];
        int[] distance = new int[n+1];
        int IF = 0;
				/* 1. 초기화 */
        for(i=2;i<=n;i++)
        {
            nearest[i] = 1;
            distance[i] = W[1][i];
        }
				/* 2 */
        for(int j=1;j<n;j++)
        {
            min = inf;
            for(i=2;i<=n;i++)
            {
                if(0<=distance[i] && distance[i]<min)
                {
                    min = distance[i];
                    vnear = i;
                }
            }
            F[IF++] = new vertex(vnear, nearest[vnear]);
            distance[vnear] =  -1;
            for(i=2;i<=n;i++)
            {
                if(W[i][vnear] < distance[i]){
                    distance[i] = W[i][vnear];
                    nearest[i] = vnear;
                }
            }
        }
        return F;
    }
}
  1. nearest, distance 배열 초기화

1 2 3 4 5

nearest[]   1 1 1 1
distance[]   1 3 inf inf
  1. 간선은 n-1 개 만들어지므로, n-1번 반복을 한다.
    1. min 변수를 inf로 초기화 한다.
    2. 인접한 꼭짓점을 찾기위해 distance[] 에서 0보다 크고 min보다 작은 값을 찾는다.
      1. min을 inf로 설정을 해두었기 때문에, 방문하지 못하는 곳은 걸러진다.
      2. 찾은 값을 min으로 해두고, vnear 값에 index를 저장한다.
    3. 찾은 vnear 값과, nearest[v]의 값을 F에 저장해준다.
    4. distance 배열의 vnear 값을 음수로 변경하여, e 의 비교과정을 스킵되게 한다.
      1. W[i][vnear]이 distance[i]보다 작다면, distance 값을 갱신해주고, nearest[i] 값을 vnear로 변경해준다.

Kruskal’s Algorithm

그래프 간선들을 가중치의 오름차순으로 정렬해 놓은 뒤, 사이클을 형성하지 않는 선에서 정렬된 순서대로 간선을 선택한다.

  1. 그래프 간선을 오름차순 순서대 정렬한다.
  2. 가장 작은 간선을 선택한다.
    1. 만약 다음 간선을 선택했을 때, 간선 간 사이클이 형성이 된다면, 선택하지 않는다.다음 간선을 선택한다
    2. union-find를 통해 사이클을 확인한다.
  3. 선택된 간선의 개수가 정점의 개수 n 보다 1 작게되면, 종료한다.
package kruskal;

import java.util.List;
import java.util.Scanner;

public class Kruskal {
    static Edge E[];
    static int[] set;
    static final int INF = Integer.MAX_VALUE;
    static class Edge{
        int a;
        int b;
        int distance;
        Edge(int a, int b, int distance)
        {
            this.a = a;
            this.b = b;
            this.distance = distance;
        }
    }
    static int inf = Integer.MAX_VALUE;
    public static void main(String[] args)
    {
        int N, M;
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt(); M = sc.nextInt();
        E = new Edge[M];
        for(int i=0;i<M;i++)
        {
            E[i] = new Edge(sc.nextInt(), sc.nextInt(), sc.nextInt());
        }
        sortE(E);
        set = new int[N];
        for(int i=0;i<N;i++) set[i] = i;

        int distance_sum = 0;
        for(int i=0; i<M;i++){
            if(!find(E[i].a-1, E[i].b-1)){
                distance_sum += E[i].distance;
                unionParent(E[i].a-1, E[i].b-1);
            }
        }
        System.out.println(1);
    }
    public static void sortE(Edge E[])
    {
        for(int i = 1; i < E.length; i++)
        {
            for(int j = 1; j <= E.length - i; j++)
            {
                if(E[j-1].distance > E[j].distance){
                    swap(j-1,j);
                }
            }
        }
    }
    public static void swap(int i, int j)
    {
        Edge tmp = E[i];
        E[i] = E[j];
        E[j] = tmp;
    }
    public static int getParent(int x)
    {
        if(set[x] == x) return x;
        return set[x] = getParent(set[x]);
    }
    public static void unionParent(int a, int b)
    {
        a = getParent(a);
        b = getParent(b);
        if(a<b) set[b] = a;
        else set[a] = b;
    }
    public static boolean find(int a, int b)
    {
        a = getParent(a);
        b = getParent(b);
        if(a==b) return true;
        else return false;
    }
}

'알고리즘' 카테고리의 다른 글

String Matching [Navie, DFA, KMP]  (0) 2023.01.24