플로이드 알고리즘: 모든 쌍의 최단 경로를 구하는 알고리즘

c.f. 벨만 포드/다익스트라 알고리즘: 시작점이 1개일 때

 

이 문제는 플로이드 알고리즘을 써서 풀 수 있다.

그런데, 도시 i에서 도시 j로 가는 최소 비용에 포함되어 있는 도시의 개수 K를 출력해야 하기때문에

그동안 지나왔던 경로도 저장해야 한다.

 

import java.util.*;
import java.io.*;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int m = in.nextInt();
		int mat[][] = new int[n + 1][n + 1];
		for (int i = 0; i <= n; i++) {
			Arrays.fill(mat[i], 1000000);
			mat[i][i] = 0;
		}
		for (int i = 0; i < m; i++) {
			int start = in.nextInt();
			int end = in.nextInt();
			int cost = in.nextInt();
			mat[start][end] = Math.min(mat[start][end], cost);
		}

		int route[][] = new int[n + 1][n + 1];
		for (int k = 1; k <= n; k++) {
			for (int i = 1; i <= n; i++) {
				for (int j = 1; j <= n; j++) {
					if (mat[i][j] > mat[i][k] + mat[k][j]) {
						route[i][j] = k; // i~j를 갈 때 k를 거친다
						mat[i][j] = mat[i][k] + mat[k][j];
					}
				}
			}
		}

		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				System.out.print(mat[i][j] + " ");
			}
			System.out.println();
		}

		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				if (i == j) {
					System.out.println("0");
				} else {
					LinkedList<Integer> list = new LinkedList<Integer>();
					routeList(i, j, route, list);
					System.out.print(list.size() + " ");
					for (int k = 0; k < list.size(); k++) {
						System.out.print(list.get(k) + " ");
					}
					System.out.println();
				}
			}
		}
	}

	public static void routeList(int start, int end, int route[][], LinkedList<Integer> list) {
		if (route[start][end] == 0) { // i~j를 갈 때 거치는 점이 없을
			list.add(start);
			if (start != end) {
				list.add(end);
			}
		} else {
			int middle = route[start][end]; // i~j를 갈 때 경유
			routeList(start, middle, route, list);
			list.removeLast(); // 중복 제거
			routeList(middle, end, route, list);
		}
	}
}

그동안 지나왔던 경로를 저장할 때에는 route[i][j]에 i와 j를 지날 때 경유한 점을 저장하도록 한다.

즉, route[i][j]에는 i->j 간선이 원래 어디를 가르켰는지 저장한다.

경유했던 점들의 리스트를 가져올 때에는 routeList에서 list에 하나씩 넣는데

routeList(start, middle, route, list)와 routeList(middle, end, route, list)에서 middle 부분이 list에 두번 중복되어 들어가므로 한 번 제거해줘야한다.

 

'알고리즘 공부 > boj' 카테고리의 다른 글

boj 1956: 운동  (0) 2020.07.10
boj 1507: 궁금한 민호  (0) 2020.07.10
boj 1806: 부분합  (0) 2020.07.09
boj 1753: 최단경로  (0) 2020.07.08
boj 1504: 특정한 최단 경로  (0) 2020.07.08

연속된 수들 중 부분합이 S 이상인 최소 길이를 구하면 되므로 투 포인터를 활용하여 풀어본다

 

부분 합이 sum보다 작을 때에는 오른쪽 인덱스를 가리키는 포인터를 오른쪽으로,

sum보다 클 때에는 왼쪽 인덱스를 가리키는 포인터를 오른쪽으로 옮긴다.

 

부분합의 가장 작은 길이를 구해야 하므로 while문을 돌면서 min 값을 업데이트 해준다.

 

import java.util.*;
import java.io.*;

public class Main {
	public static int n;
	public static long sum;
	public static int arr[];

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		sum = in.nextInt();
		arr = new int[n];
		for (int i = 0; i < n; i++) {
			arr[i] = in.nextInt();
		}

		int leftIndex = 0;
		int rightIndex = leftIndex;
		int ans = n + 1;
		long subSum = arr[leftIndex];
		while (leftIndex <= rightIndex) {
			if (subSum < sum) {
				rightIndex++;

				if (rightIndex == n) {
					break;
				}

				subSum += arr[rightIndex];
			} else if (subSum == sum) {
				ans = Math.min(ans, rightIndex - leftIndex + 1);

				rightIndex++;

				if (rightIndex == n) {
					break;
				}

				subSum += arr[rightIndex];
			} else {
				ans = Math.min(ans, rightIndex - leftIndex + 1);

				subSum -= arr[leftIndex];
				leftIndex++;
			}
		}

		if (ans > n) {
			ans = 0;
		}
		System.out.println(ans);

	}
}

'알고리즘 공부 > boj' 카테고리의 다른 글

boj 1507: 궁금한 민호  (0) 2020.07.10
boj 11780: 플로이드2  (0) 2020.07.09
boj 1753: 최단경로  (0) 2020.07.08
boj 1504: 특정한 최단 경로  (0) 2020.07.08
boj 11779: 최소비용 구하기 2  (0) 2020.07.07

아래와 같이 다른 다익스트라 문제와 동일하게 풀었는데 메모리 초과 및 시간초과가 나왔다.

인접 행렬로 풀면 V==20000일 때 인접행렬은 4억개이고 정수형이니 각 크기가 8B면 3200 MB가 되어서 그렇다고 함,,,

import java.util.*;

public class Main {
	public static long MAX = 20000 * 300000 * 10 + 1;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int e = in.nextInt();
		int startPoint = in.nextInt();
		long mat[][] = new long[n + 1][n + 1];
		for (int i = 0; i <= n; i++) {
			Arrays.fill(mat[i], MAX);
		}
		for (int i = 0; i < e; i++) {
			int start = in.nextInt();
			int end = in.nextInt();
			long weight = in.nextLong();

			mat[start][end] = Math.min(mat[start][end], weight);
		}

		long dist[] = dijkstra(startPoint, n, mat);
		for (int i = 1; i <= n; i++) {
			if (dist[i] >= MAX) {
				System.out.println("INF");
			} else {
				System.out.println(dist[i]);
			}
		}
	}

	public static long[] dijkstra(int startPoint, int n, long mat[][]) {
		long[] dist = new long[n + 1];
		boolean[] isVisited = new boolean[n + 1];

		Arrays.fill(dist, MAX);

		for (int i = 1; i <= n; i++) {
			dist[i] = mat[startPoint][i];
		}
		dist[startPoint] = 0;
		isVisited[startPoint] = true;

		for (int i = 1; i < n; i++) {
			int index = -1;
			long min = MAX;
			for (int j = 1; j <= n; j++) {
				if (min > dist[j] && !isVisited[j]) {
					min = dist[j];
					index = j;
				}
			}

			if (index == -1) {
				break;
			}
			isVisited[index] = true;

			for (int j = 1; j <= n; j++) {
				if (dist[j] > dist[index] + mat[index][j]) {
					dist[j] = dist[index] + mat[index][j];
				}
			}

		}

		return dist;
	}
}

 

그리고 V<=20000이기 때문에 시간복잡도 O(V^2)로는 4억(약 4초)가 걸린다.

다익스트라 알고리즘을 구하는 순서를 다시 보면 아래와 같다.

  1. 체크되어 있지 않은 정점 중에서 D의 값이 가장 작은 정점 index 를 선택한다. : O(V)
  2. index틑 체크한다.
  3. index와 연결된 모든 정점에 대해 최단거리 갱신: 인접행렬의 경우 O(V^2), 인접리스트의 경우 O(V+E)

여기서 시간을 줄일 수 있는 부분은 1번이다.

(정점 번호, Dist)값을 heap이나 priorityQueue에 넣어서 가장 작은 정점을 선택할 수 있도록 한다.

이떄 시간복잡도는 모든 간선이 들어가는 것이나 마찬가지이다.

왜냐면 dist배열이 업데이트 될때마다 들어가는데, 업데이트 된다는 것의 의미는

어떤 정점에 연결되어 있는 간선에 대해 비교를 한 것과 같다.

따라서 O(ElogE), 즉 (정점 번호, Dist)쌍이 E번 들어갔다가 나오는 시간이 걸린다.

 

위 풀이를 적용하여 다시 문제를 풀어볼 예정

 

 

'알고리즘 공부 > boj' 카테고리의 다른 글

boj 11780: 플로이드2  (0) 2020.07.09
boj 1806: 부분합  (0) 2020.07.09
boj 1504: 특정한 최단 경로  (0) 2020.07.08
boj 11779: 최소비용 구하기 2  (0) 2020.07.07
boj 1916: 최소비용 구하기  (0) 2020.07.07

가능한 경로 2가지

  • 1 -> v1 -> v2 -> N
  • 1 -> v2 -> v1 -> N

다익스트라 알고리즘을 시작점 1, v1, v2로 하여 각각 구한뒤 더했을 때 최솟값을 구하도록 한다

 

import java.util.*;

public class Main {
	public static long MAX = 800 * 200000 * 1000 + 1;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int e = in.nextInt();
		long mat[][] = new long[n + 1][n + 1];
		for (int i = 0; i <= n; i++) {
			Arrays.fill(mat[i], MAX);
		}
		for (int i = 0; i < e; i++) {
			int start = in.nextInt();
			int end = in.nextInt();
			long weight = in.nextLong();

			mat[start][end] = Math.min(mat[start][end], weight);
			mat[end][start] = Math.min(mat[end][start], weight);
		}

		int v1 = in.nextInt();
		int v2 = in.nextInt();

		long distFrom1[] = dijkstra(1, n, mat);
		long distFromV1[] = dijkstra(v1, n, mat);
		long distFromV2[] = dijkstra(v2, n, mat);

		long ans = -1;
		// 갈 수 없는 경우 체크
		if (distFrom1[v1] < MAX && distFromV1[v2] < MAX && distFromV2[n] < MAX) {
			ans = distFrom1[v1] + distFromV1[v2] + distFromV2[n];
		}
		if (distFrom1[v2] < MAX && distFromV2[v1] < MAX && distFromV1[n] < MAX) {
			ans = Math.min(ans, distFrom1[v2] + distFromV2[v1] + distFromV1[n]);
		}
		System.out.println(ans);
	}

	public static long[] dijkstra(int startPoint, int n, long mat[][]) {
		long[] dist = new long[n + 1];
		boolean[] isVisited = new boolean[n + 1];

		Arrays.fill(dist, MAX);

		for (int i = 1; i <= n; i++) {
			dist[i] = mat[startPoint][i];
		}
		dist[startPoint] = 0;
		isVisited[startPoint] = true;

		for (int i = 1; i < n; i++) {
			int index = -1;
			long min = MAX;
			for (int j = 1; j <= n; j++) {
				if (min > dist[j] && !isVisited[j]) {
					min = dist[j];
					index = j;
				}
			}

			if (index == -1) {
				break;
			}
			isVisited[index] = true;

			for (int j = 1; j <= n; j++) {
				if (dist[j] > dist[index] + mat[index][j]) {
					dist[j] = dist[index] + mat[index][j];
				}
			}

		}
		
		return dist;
	}
}

 

'알고리즘 공부 > boj' 카테고리의 다른 글

boj 1806: 부분합  (0) 2020.07.09
boj 1753: 최단경로  (0) 2020.07.08
boj 11779: 최소비용 구하기 2  (0) 2020.07.07
boj 1916: 최소비용 구하기  (0) 2020.07.07
boj 1865: 웜홀  (0) 2020.07.07

화면에 도형을 그리는 어플리케이션을 만들어 사용자에게 배포하였다고 가정하자. 이미 도형에 대한 클래스 모델링이 잘 되어 있기 때문에 Shape이라는 인터페이스는 이미 배포되어 있다. 그리고 몇몇 도형들 역시 배포된 상태다. 소프트웨어 내부에서는 이미 배포된 jar 파일을 통해 도형들을 생성해서 사용하고 있다.

이 상황에서 고객이 새로운 도형을 만들어 달라고 요청해 왔다. 필요한 도형은 Circle과 Triangle이다. 이 파일들은 개발자에 의해서 개발되었고 사용자에게 배포되었다. 이 때 이미 동작 중인 어플리케이션은 종료할 수 없다. 이런 경우에 어플리케이션을 중단시키지 않아도 클래스들을 로드할 수 있는 동적 로더가 활용될 수 있다.

 

Shape 인터페이스는 이미 배포된 상태이다. 여기서 사용자가 원하는 Circle과 Triangle을 각각의 이름으로 된 jar 파일을 배포하였다.

여기서 사용자가 어플리케이션을 종료하지 않고 이들 jar 파일들을 로드하려면 동적 Jar 로드 기능이 탑재되어 있어야 한다. 이를 구현한 것이 DynamicJarLoader 클래스이다.

 


DynamicJarLoader 클래스는 객체 생성자를 통해서 Jar 파일이 들어 있는 폴더 경로(String jarPath)를 입력 받는다.

이후 load(jarFileName : String) 메소드를 통해서 Jar 파일 이름을 입력해주면 라이브러리가 로드된다.

특히 DynamicJarLoader 클래스는 내부에 있는 loaderMap을 통해서 여러 이름의 라이브러리를 동시에 로드할 수 있다. 따라서 이 객체 하나 만으로도 여러 라이브러리 파일을 로드하고, 로드된 라이브러리들을 통해서 객체를 생성할 수 있다. 리턴되는 boolean 값은 라이브러리 로드가 성공했는지 여부를 알려준다.

 

unload(jarFileName : String) 메소드는 이미 로드된 라이브러리를 메모리에서 내리거나, 새로운 버전의 라이브러리를 열기 위해서 기존에 이미 열려진 라이브러리를 닫아야 할 경우에 호출하는 메소드이다.

 

이미 라이브러리가 로드되어 있다면 열린 라이브러리를 통해서 객체를 새로 생성할 수 있어야 한다. newInstance() 메소드는 클래스 이름(className)을 통해서 객체를 로드할 수 있도록 구현된 메소드이다. newInstance() 메소드는 두 개가 있는데, 하나는 className만으로 객체를 생성하도록 하고, 나머지 하나는 jar 파일 이름까지 입력해서 보다 정확한 객체를 생성하도록 한다.

 

public class DynamicJarLoader {

    private String jarPath;
    private Map<String, URLClassLoader> loaderMap = new HashMap<String, URLClassLoader>();

    public DynamicJarLoader(String jarPath){
        this.jarPath = jarPath;
        this.jarPath.replaceAll("\\\\", "/");
        if(this.jarPath.endsWith("/") == false) this.jarPath = this.jarPath + "/";
    }

    public boolean load(String jarFileName){
        if(loaderMap.containsKey(jarFileName) == true) unload(jarFileName);

        String jarFilePath = jarPath + jarFileName;
        File jarFile = new File(jarFilePath);

        try {
            URL classURL = new URL("jar:" + jarFile.toURI().toURL() + "!/");
            URLClassLoader classLoader = new URLClassLoader(new URL [] {classURL});
            loaderMap.put(jarFileName, classLoader);
            return true;
        } catch (MalformedURLException e) {
            return false;
        }
    }

    public boolean unload(String jarFileName){
        URLClassLoader loader = loaderMap.get(jarFileName);
        if(loader == null) return true;

        try {
            loader.close();
            return true;
        } catch (IOException e) {
            return false;
        }
        finally{
            loaderMap.remove(jarFileName);
        }
    }

    public Object newInstance(String jarFileName, String className){
        URLClassLoader loader = loaderMap.get(jarFileName);
        if(loader == null) return true;

        try {
            Class<?> clazz = loader.loadClass(className);
            return clazz.newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            return null;
        }
    }

    public Object newInstance(String className){
        for(String each : loaderMap.keySet()){
            Object object = newInstance(each, className);
            if(object != null) return object;
        }

        return null;
    }
}

'Java (+ Spring)' 카테고리의 다른 글

Spring: Spring MVC 설정 하기  (0) 2020.07.26
Android: tutorial page  (0) 2020.07.18
Android: Launch screen  (0) 2020.07.16
Reflection을 활용한 범용 toString() 함수 만들기  (0) 2020.07.08
enum의 활용법 - 계산기  (0) 2020.07.07

기본적으로 Java에서는 모든 클래스가 toString() 함수를 지원한다. 따라서 라이브러리에 선언되어 있는 클래스들은 .toString() 함수만 호출하면 해당 클래스 내부에 있는 값들을 출력해 낼 수 있다. 하지만 사용자가 직접 선언한 클래스는 toString() 함수 호출만 가지고 내부 변수에 가지고 있는 값들을 확인하기가 어렵다.

 

예를 들어 아래와 같은 클래스를 선언하면

class TestClass{
    short s = 1;
    int i = 10;
    long l = 20;
    String name = "nameString";
    Long L = new Long(100);
    List<String> list = new ArrayList<String>();
    Map<String, String> map = new HashMap<String, String>();

    public TestClass(){
        list.add("string1");
        list.add("string2");
        list.add("string3");

        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
    }
}

이후 toString() 함수를 호출해보면

TestClass tc = new TestClass();
System.out.println(tc.toString()); // MyPattern.reflection.TestClass@15db9742

패키지 경로에 따라서 조금씩 다를 수는 있지만 기본적으로는 내부에 있는 변수들의 값이 나오지는 않는다. 그래서 새로 클래스를 정의할 때마다 toString() 함수를 재정의(override) 할 필요가 있다. 하지만 Java 프로그래밍을 하면서 엄청나게 많은 클래스들을 만들텐데 새로 만드는 클래스마다 각 클래스에 필요한 toString() 함수를 재정의 하기는 너무 힘든 일이다.

 

따라서 재정의를 하지 않으면서 범용으로 사용할 toString 클래스를 만들어 보자.

 

public class ToString {  
    public static String toString(Object object){
        Field []fields = object.getClass().getDeclaredFields();

        String str = "{";

        for(Field field : fields){
            field.setAccessible(true);
            try {
                String type =               
                    field.getType().toString().
                    substring(field.getType().toString().lastIndexOf(".") + 1);

                if(field.getType().toString().startsWith("class ") &&           
                   !field.getType().toString().startsWith("class java.lang.")){
                   // primitive 타입이나 라이브러리에서 제공하는 객체가 아닐때 toString을 재귀적 호출
                   str += type + " " + field.getName() + 
                          toString(field.get(object)) + " ";
                }
                else{
                   str += type + " " + field.getName() + ":" + 
                          field.get(object) + " ";   
                }
            } catch (IllegalArgumentException e) {
            } catch (IllegalAccessException e) {
            }
        }
        return str + "} ";
    }
    public static void main(String[] args) {
        AllTypes types = new AllTypes();
        System.out.println(ToString.toString(types));
        /* 실행 결과
        {short s:1 int i:1 long l:1 Short S:0 Integer I:0 Long L:0 InnerClass inner{int a:100 long b:200 String name:innerClass }  List list:[] Map map:{} } 
        */
    }
}

getClass()는 Reflection에 활용되는 클래스 객체를 가지고 오는 함수이다. 즉, object 객체는 .class 객체를 가지고 있는데, 객체의 본래 타입에 따라서 .class 객체는 모두 다르다. getDeclaredFields() 함수는 클래스 객체로부터 선언된 모든 Field 객체들을 배열 형태로 가져오는 함수다. .class 객체가 객체의 본래 타입에 따라 다르기 때문에 getDeclaredFields() 함수를 통해 나오게 되는 Field[]도 모두 다르다.

 

for문 안에 setAccessible(true) 함수 호출되는 부분의 경우는, 만약 Field가 public이 아닌 경우 캡슐화로 인하여 그 값에 접근하는 것이 불가능한데 Reflection은 이 함수를 통해서 접근이 가능하도록 만들 수 있다.(이 부분은 아직도 이슈가 많은 부분이라 프로그래머 마다 찬반 의견이 다르다. 하지만 유용성에 대해서는 아무도 부인하지는 못할 것이다.)

 

  • String getType() : type에 대한 클래스 객체(Class object)를 반환하는 함수  --> 타입을 반환하긴 하는데 primitive 타입이 아닌 경우 타입이 장황하게 출력될 수 있다. 예를 들어 Long 타입의 경우 class java.lang.Long 이라고 출력됨. 따라서 위와 같이 줄이는 부분을 만든다.
  • String getName() : 필드명을 String 객체로 반환하는 함수
  • Object get(object) : Field가 값으로 가지고 있는 객체를 반환하는 함수

약간 덧붙이자면, 일단 field가 null인 경우 동작하지 않을 수 있으므로 null 체크도 들어가는게 좋다.

그리고 배열의 경우 위의 경우에서 모두 벗어나기 때문에 제대로 출력이 안될 수 있다. 이 부분은 필요시 직접 처리하기 바란다.

만일 ToString 클래스를 사용해야 하는 것이 불편하고, toString()을 재정의 해서 사용하고 싶다고 할 때에도 이 ToString 클래스는 유용하다. 클래스마다 toString() 함수를 재정의 할 때 다음과 같이 동일하게 해주면 된다.

@Override
public String toString() {
    return ToString.toString(this);
}

 

 

 

'Java (+ Spring)' 카테고리의 다른 글

Spring: Spring MVC 설정 하기  (0) 2020.07.26
Android: tutorial page  (0) 2020.07.18
Android: Launch screen  (0) 2020.07.16
Java 라이브러리(.jar) 동적 로딩: DynamicJarLoader  (0) 2020.07.08
enum의 활용법 - 계산기  (0) 2020.07.07

Java에서의 enum 타입

enum Type{ // abstract class
	ADD, // public static final class ADD extends Type{}
	SUB; // public static final class SUB extends Type{}
}

기본적으로 enum은 추상클래스이고, 그 하위에 선언된 각 열거형 변수는 실제로는 변수가 아니라 enum 의 타입을 상속받은 하위 클래스이다. 이 하위 클래스는 외부에 노출되어 있고 생성할 필요가 없으며 런타임에 교체가 불가능하므로 public static final 타입을 갖는다.

 

enum은 기본적으로 추상 클래스이나, 다른 클래스로부터 상속을 받지 못한다.

하지만 interface는 상속을 받을 수 있다. Enum에 함수가 들어갈 수 있는지 완전 몰랐다,,,,

interface Interface{
    public void api();
}


enum Type implements Interface{
    ADD{
        public void api(){ System.out.println("ADD api"); }
    },
    SUB{
        public void api(){ System.out.println("SUB api"); }
    };
}

이 특성을 이용해서 디자인 패턴에 나오는 수많은 패턴들을 enum을 통해 구현할 수 있다.

enum이 public static final 이라는 점은 Singleton과 유사한 특성을 지니고 있다.

따라서 중복 생성이 안되면서도 일반 클래스 기능을 할 수도 있고, 필요한 때에는 enum의 특성을 활용할 수도 있다.

이 강력한 특성 때문에

Singleton 패턴, Abstract Factory 패턴, Factory Method 패턴, State 패턴, Strategy 패턴, Visitor 등 다양한 패턴의 enum 버전이 있다.

 

이 외에도 enum은 추상 클래스이므로 함수를 선언할 수 있고, 추상 메소드 또는 static 메소드의  선언도 가능하다.

enum Type{
    ADD,
    SUB;
    public void api(){ System.out.println("api()"); }
}
enum Type{
    ADD{
        public void api(){ System.out.println("ADD api"); }
    },
    SUB{
        public void api(){ System.out.println("SUB api"); }
    };
    abstract public void api();
    public static int size(){ return Type.size(); }
}

 

또한 클래스이므로 생성자도 선언할 수 있어야 한다.

enum Type{
    ADD(0),// 생성자에게 필요한 인자는 () 안에 넣는다.
    SUB(1); //
    int value;
    private Type(int value){ this.value = value; }   // 이것이 생성자.
    public int value(){ return value; }
}

enum 클래스를 인덱스로 활용할 수 있도록 value 라는 int 형 값을 생성시에 인자로 받도록 했다. Java의 enum은 클래스이기 때문에 C언어에서 처럼 값으로 활용할 수가 없다. 대신에 이처럼 생성자를 통해 인자로 받은 값을 가지고 있다가 값이 필요한 경우 value() 함수를 호출함으로써 값으로도 사용이 가능하다.

이때 생성자가 private인 것에 주목해야 한다.

enum은 외부에서 생성이 불가능하기 때문에 생성자는 항상 private으로 선언해 주어야 한다.

 

enum은 클래스이므로 하위 타입들도 모두 toString() 함수를 가지고 있다. 그 결과 값은 기본적으로 자신의 선언된 이름과 같다. ADD.toString()은 "ADD" 값이 결과값이다. 이러한 특성은 매우 유용한데, 특히 데이터베이스에 저장할 때 그렇다.

DB의 가독성 측면에서 문자열을 활용한 경우에 문자열을 입력받아 타입을 리턴하도록 하면

코드 상에서 문자열 대신 enum을 활용할 수 있으므로 코딩이 편리해진다.

public static Type getTypeByString(String str){
    for(Type each : values()){
        if(each.toString().equals(str)){
            return each;
        }
    }
    return null;
}

 

enum 타입이 제공하는 기본 함수로 enum의 순서를 알 수 있는 함수가 있다.

public int ordinal();

위 ordinal() 이라는 함수인데 이 함수는 선언된 enum의 하위 타입이 몇 번째 순서로 선언되었는지를 알 수 있다.(순서의 시작 값은 0이다.) 가령 위에서 선언한 ADD 타입의 경우 0이 리턴되고, SUB의 경우 1이 리턴된다. 이 특성을 이용하면 enum을 인덱스로도 활용이 가능하다.

 

Java에서의 enum은 열거형의 특성과 클래스의 특성을 함께 가지고 있다는 장점이 있다. toString() 함수를 가지고 있다는 것만으로도 디버깅을 얼마나 쉽게 만들어 주는지 모른다. 그 밖에도 데이터 베이스와의 연동, switch-case 문에 대한 활용, 인터페이스 상속을 활용한 디자인 패턴 등 다양한 곳에 활용할 수 있다.

 


그렇다면 enum 타입을 배열의 인덱스로 활용하는 방법을 알아보자

class Distance{
    private int east;
    private int west;
    private int north;
    private int south;

    public void setEast(int east){ this.east = east; }
    public void setWest(int west){ this.west = west; }
    public void setNorth(int north){ this.north = north; }
    public void setSouth(int south){ this.south = south; }

    public int getEast() { return east; }
    public int getWest() { return west; }
    public int getNorth() { return north; }
    public int getSouth() { return south; }
}

위와 같은 클래스를 보면

모두 같은 타입(int)의 변수를 선언하고 있고, 모두 getter와 setter를 제공하고 있음을 알 수 있다. 그리고 동서남북을 나타내는 변수들이므로 서로 연관성도 있어 보인다. 이런 경우에는 enum 타입을 하나 선언해서 동서남북을 가리키는 서브 타입을 만들고 이것을 인덱스로 활용하는 방법을 사용하면 중복이 제거된다.

enum Direction{
    EAST,
    WEST,
    NORTH,
    SOUTH;
}

class Distance{
    // 각 방향별로 선언되었던 변수를 하나의 배열로 선언
    private int distance[] = new int[Direction.values().length];
    
    public int get(Direction direction){ 
        return distance[direction.ordinal()]; 
    }
    public void set(Direction direction, int value){ 
        distance[direction.ordinal()] = value; 
    }
}

enum 하위 타입의 개수를 알아내는 방법은 보통 Type.values().length 를 이용한다. .values() 함수는 enum 하위 타입들을 배열화하여 리턴해주는 함수이다. .length는 잘 알다시피 배열의 길이를 나타내는 배열 객체의 변수이다. 그리고 이들은 앞서 말한 바와 같이 컴파일 타임에 결정되기 때문에 배열의 개수 초기화에도 사용할 수 있다.

get함수에서는 enum 타입인 Direction  하위 타입을 어떻게 인덱스 숫자로 바꾸느냐가 중요한데, JAVA에서는 ordinal() 이라는 함수를 통해 숫자로 바꿀 수 있다. int ordinal() 함수는 해당 서브 enum 타입의 정의 순서에 따른 숫자를 리턴한다. 가령 EAST.ordinal() = 0, WEST.ordinal() = 1......와 같은 방식이다. 이것은 배열의 인덱스 값과 같은 값이다. 따라서 전혀 문제 없이 사용할 수 있다.

 

 

 

https://effectiveprogramming.tistory.com/entry/enum%EC%9D%98-%ED%99%9C%EC%9A%A9%EB%B2%95?category=660010

 

enum의 활용법

C언어에서 enum은 단순히 상수형 변수 역할에 지나지 않았다. 하지만 Java에서는 매우 다른 특성들을 지니고 있다. 이 특성들 중에는 특별한 것들도 있어서 기존과는 다른 여러 방식으로 enum을 활�

effectiveprogramming.tistory.com

 

2020/07/07 - [알고리즘 공부] - boj 1916: 최소비용 구하기

 

boj 1916: 최소비용 구하기

최단경로를 구하는 알고리즘 다익스트라 알고리즘: 모든 간선에 대해 1번만 검사 모든 정점을 선택(V-1)하고, isVisited==false인 값 중에서 가장 가까운 값을 선택하는데 걸리는 시간은 O(V): 총 O(V^2) �

sysgongbu.tistory.com

 

위의 풀이와 똑같지만, 최소 비용을 갖는 경로를 방문하는 도시 순서대로 출력하기 위해서는

경로의 최솟값이 업데이트 될 때의 위치를 저장하도록 하면 된다.

 

마지막에 답을 구할 때에는 이전 위치를 저장하도록 했으므로

도착지점->시작지점 순으로 스택에 넣고 빼면서 프린트 한다.

 

import java.util.*;

public class Main {
	public static int MAX = 1000000000;

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int m = in.nextInt();

		int bus[][] = new int[n + 1][n + 1];
		for (int i = 0; i <= n; i++) {
			Arrays.fill(bus[i], MAX);
		}
		for (int i = 0; i < m; i++) {
			int start = in.nextInt();
			int end = in.nextInt();
			int time = in.nextInt();

			bus[start][end] = Math.min(bus[start][end], time);
		}

		int start = in.nextInt();
		int end = in.nextInt();
		int dist[] = new int[n + 1];
		boolean isVisited[] = new boolean[n + 1];
		int route[] = new int[n + 1];
		Arrays.fill(route, start);
		Arrays.fill(dist, MAX);
		for (int i = 1; i <= n; i++) {
			dist[i] = bus[start][i];
		}
		dist[start] = 0;
		route[start] = 0;
		isVisited[start] = true;

		for (int i = 1; i < n; i++) {

			int index = -1;
			int min = MAX;
			for (int j = 1; j <= n; j++) {
				if (min > dist[j] && !isVisited[j]) {
					min = dist[j];
					index = j;
				}
			}
			if (index == -1) {
				break;
			}
			isVisited[index] = true;
			
			for (int j = 1; j <= n; j++) {
				if (dist[j] > dist[index] + bus[index][j]) {
					dist[j] = dist[index] + bus[index][j];
					route[j] = index;
				}
			}

		}
		
		System.out.println(dist[end]);
		Stack<Integer> answerSet=new Stack<Integer>();
		int now=end;
		while(true) {
			if(now==start) {
				answerSet.add(now);
				break;
			}
			
			answerSet.add(now);
			now=route[now];
		}
		
		System.out.println(answerSet.size());
		while(!answerSet.isEmpty()) {
			System.out.print(answerSet.pop()+" ");
		}
	}
}

'알고리즘 공부 > boj' 카테고리의 다른 글

boj 1753: 최단경로  (0) 2020.07.08
boj 1504: 특정한 최단 경로  (0) 2020.07.08
boj 1916: 최소비용 구하기  (0) 2020.07.07
boj 1865: 웜홀  (0) 2020.07.07
boj 11657: 타임머신  (0) 2020.07.06

+ Recent posts