|  관련 강의 영상

https://www.youtube.com/watch?v=UcjK_k5PLHI 

- KMP나 Robin-Karp를 사용하면 O(n)의 시간복잡도로 문자열에서 패턴 문자열을 찾을 수 있다.

- KMP는 어려워서 일단 Robin-karp 부분만 찾아서 적용해보았다.

 

|  Robin-Karp 알고리즘 사용해보기

https://joomn11.tistory.com/111?category=900637 

 

[알고리즘] 라빈 카프(Rabin-Karp)(문자열 탐색)(Rolling Hash)

문자열 탐색 알고리즘 Rabin-Karp는 Hashing을 이용하여 문자열에서 특정 패턴과 일치하는지 찾는 알고리즘이다. 문자열에서 찾고자 하는 패턴의 길이만큼 hash값으로 변환하여, 패턴과 hash값이 같은

joomn11.tistory.com

public static int robinKarp(String parent, String pattern, int parentSize, int patternSize){

    int parentHash = 0, patternHash = 0, power = 1;
    int result = 0;

    // 슬라이딩 윈도우 방식으로 하나씩 증가
    for (int i = 0; i <= parentSize - patternSize; i++) {
        // 처음에는, 패턴 문자열 길이만큼 해시값을 구한다.
        if (i == 0) {
            for (int j = 0; j < patternSize; j++) {
                parentHash += parent.charAt(j) * power;
                patternHash += pattern.charAt(j) * power;
            }

            if (i < patternSize - 1) {
                power *= 2;
            }
        } 
        // 처음이 아닌 경우, 가장 앞의 문자를 빼고, 새로운 문자를 추가한다.
        else {
            parentHash = (parentHash - parent.charAt(i - 1) * power) * 2
                    + parent.charAt(i + patternSize - 1);
        }

        // 윈도우 위치에서 부모와 패턴이 일치하는 경우
        if (parentHash == patternHash) {
            result++;
        }
    }

    return result;
}

|  알고리즘 속도 구하는 방법

- 아래와 같은 방법으로 알고리즘 속도를 구할 수 있다.

- 예전에 알고 있던 방법이었는데, 금방 잊어버렸기에 생각날 때 노트해두었다.

Long start = System.currentTimeMillis();

// 알고리즘 돌리고

Long end = System.currentTimeMillis();

System.out.prinln(end - start + " ms");

 

|  최소신장트리

- Mininum Spanning Tree

- 그래프의 모든 노드를 연결할 때 가중치 합이 최소가 나오는 트리

- 특징 : 

(1) 사이클 제외 -- 가중치의 합은 사이클이 포함될 때 최소가 될 수 없다. 

(2) N개의 노드에 대한 최소신장트리 간선 수는 늘 N - 1개이다.

 

|  종류

  크루스칼(Kruskal) 프림(Prim)
핵심 - 최소값의 간선 우선 연결
- 사이클 발생 시 다른 간선 선택
- 임의의 노드에서 시작
- 연결된 간선 중 낮은 가중치 선택
언제? - 간선 수가 비교적 적을 때 - 간선 수가 많을 때 유리
메모리 에지 리스트(or 배열)
유니온 파인드 배열
인접 리스트(or 배열)
방문 배열
속도 O(ElogE) O(ElogV)  - 우선순위 큐 사용 시

*O(v2) - 정렬되지 않은 배열을 사용할 때

 

|  크루스칼 구현

- 구현 순서

1 에지 리스트(그래프) & 유니온 파인드 배열 초기화
2 에지 리스트를 가중치 기준으로 정렬
3 가중치가 낮은 에지부터 연결하기 반복

 

[1] 에지 리스트(그래프) 정렬 & 유니온 파인드 배열 초기화

- 수업 시간의 경우, 아예 외부에서 입력값으로 미정렬된 간선 배열을 받는 것을 가정으로 했다.

- 이 경우에는 위의 [1][2]를 묶어 간선 배열을 정렬하면 된다.

// 전역변수로 유니온-파인드 배열 지정
static int[] parents;

// data : 간선배열, v : 노드 수, e : 간선 수
public static int kruskal(int[][] data, int v, int e) {
    int weightSum = 0; // 전체 가중치
    
    // 1. 그래프 및 유니온 파인드 배열 초기화
    // 1-1. 그래프 정렬
    Arrays.sort(data);
    
    // 1-2. 유니온 파인트 배열 초기화
    parents = new int[v + 1];
    
    for (int i = 1; i < v + 1; i++){
    	parents[i] = i; // 처음에는 자기 자신
    }
    
    return weightSum;
}

 

[2] 가중치가 낮은 에지부터 연결하기 x 반복

- 가중치가 낮은 에지부터 연결을 하려면 먼저 아래와 같은 메소드가 필요하다.

- 참고로 수업시간에 받은 간선 배열은 무방향 간선들로, 모두 (작은 숫자 노드 -> 큰 숫자 노드)로 연결되어 있었다.

 

2-1. find(int a) :: int 

public static int find(int a){
	// 자기 자신과 연결된 경우
	if (parents[a] == a){
    	return a;
    }
    // 자기 자신과 연결되지 않은 경우
    return parents[a] = find(parents[a]);
}

 

2-2. union(int a, int b) :: void

public static void union(int a, int b){
    int aP = parents[a];
    int bP = parents[b];
    
    if (aP != bP){ // 여기서는 크기비교x (필요할 경우 추가)
    	parents[aP] = bP;
    }
}

 

2-3. 최소값 우선으로 간선 연결

public static int kruskal(int[][] data, int v, int e){
    // 생략
    
    // 2. 최소값 우선 간선 연결
    for (int i = 0; i < e; i++){
    	// 서로 연결된 적이 없었던 경우
        if (find(data[i][0]) != find(data[i][1]){
        	// 연결
            union(data[i][0], data[i][1]);
            // 전체 가중치 값 갱신
            weightSum += data[i][2];
        }
    }
}

 

- 전체 코드

 

static int[] parents;
public static int kruskal(int[][] data, int v, int e) {
    int weightSum = 0;

    // 1. 그래프 및 유니온 파인드 배열 초기화
    // edge graph
    Arrays.sort(data, (x, y) -> x[2] - y[2]);

    // union-find
    parents = new int[v + 1];
    for (int i = 1; i < v + 1; i++) {
        parents[i] = i; // 최초에는 자기자신
    }

    // 2. 최소값 우선으로 간선 연결
    for (int i = 0; i < e; i++) {
        if (find(data[i][0]) != find(data[i][1])) {
            union(data[i][0], data[i][1]);
            weightSum += data[i][2];
        }
    }

    return weightSum;
}

public static void union(int a, int b) {
    int aP = find(a);
    int bP = find(b);

    if (aP != bP) {
        parents[aP] = bP;
    }
}

public static int find(int a) {
    if (a == parents[a]) {
        return a;
    }
    return parents[a] = find(parents[a]);
}

 

|  프림 구현

- 구현 순서

1 인접 리스트 및 방문 배열 초기화
2 우선 순위 큐를 통해 자동 정렬
3 임의의 노드(ex. 1)를 선택한 후 낮은 가중치의 간선 연결 x 노드 수만큼 반복

 

- 원리

입력받은 간선 데이터
int[][] graph = 
{{1, 3, 1}, {1, 2, 9}, {1, 6, 8}, 
{2, 4, 13}, {2, 5, 2}, {2, 6, 7}, 
{3, 4, 12}, 
{4, 7, 17}, {5, 6, 5}, {5, 7, 20}};

 

(1) 방문 배열 : 총 노드의 수(v)만큼 방문배열이 입력된다.

  1 2 3 4 5 6 7
1 t            
2 t   t        
3 t   t     t  
4 t   t   t t  
5 t t t   t t  
6 t t t t t t  
7 t t t t t t t

 

(2) 우선순위 큐

- 가중치의 크기를 기준으로 오름차순 자동 정렬

- 가장 작은 가중치의 노드를 꺼내, 방문 배열을 체크하고, 전체 가중치(weightSum)를 +하는 형태

  * 방문했던 노드는 다시 방문하지 않는다면, 결과적으로는 1-3-6-5-2-4-7 과 같이 노드가 연결될 것

- 타입 : Node (도착노드, 가중치)

흐름 우선순위 큐
초기 N(1, 0)        
1 N(3, 1) N(6, 8) N(2, 9)    
2 N(6, 8) N(2, 9) N(4, 12)    
3 N(5, 5) N(2, 7) N(2, 9) N(4, 12)  
4 N(2, 5) N(6, 5) N(2, 7) N(2, 9) N(4, 12)
5 N(6, 5) N(2, 7) N(2, 9) N(4, 12) N(4, 13)
6 N(4, 13) N(7, 17)      

 

- 구현

 

[1] 인접 리스트 및 방문 배열 초기화

static class Node{
    int to;
    int weight;
    
    Node(int to, int weight;){
    	this.to = to;
        this.weight = weight;
    }
}

public static int prim(int[][] data, int v, int e){
    // 인접리스트
    ArrayList<ArrayList<Node>> adjList = new ArrayList();
    for (int i = 0; i < v + 1; i++){
    	adjList.add(new ArrayList());
    }
    
    for (int i = 0; i < e; i++){
    	// 양방향으로 저장
    	adjList.get(data[i][0]).add(new Node(data[i][1], data[i][2]));
        adjList.get(data[i][1]).add(new Node(data[i][0], data[i][2]));
    }
    
    // 방문 배열
    boolean[] visited = new boolean[v + 1];
}

 

[2] 우선 순위 큐를 통해 자동 정렬

public static int prim(int[][] data, int v, int e){
    // 생략
    
    // 우선순위 큐
    PriorityQueue<Node> pq = new PriorityQueue((x, y) -> x.weight - y.weight);
}

 

[3] 임의의 노드(ex. 1)를 선택한 후 낮은 가중치의 간선 연결 x 노드 수만큼 반복

public static int prim(int[][] data, int v, int e){
    // 생략
    
    // 우선순위 큐
    PriorityQueue<Node> pq = new PriorityQueue((x, y) -> x.weight - y.weight);
    pq.add(new Node(1, 0)); // 임의의 노드를 시작점으로 지정
    
    int cnt = 0; // 노드만큼만 반복
    while (!pq.isEmpty()){
    	Node cur = pq.poll();
        cnt++;
        
        // 방문 여부확인
        if (visited[cur.to]){
        	continue;
        }
        visited[cur.to] = true; // 방문 체크
        weightSum += cur.weight; // 전체 가중치 갱신
        
        // 반복 수 확인(마지막 뽑는 노드)
        if (cnt == v) {
        	break;
        }
        
        // 인접한 정점을 작은 가중치 순으로 offer
        for (int i = 0; adjList.get(cur.to).size(); i++){
        	Node adjNode = adjList.get(cur.to).get(i);
            
            if (visited[adjNode.to]) {
            	continue;
            }
            
            pq.offer(adjNode);
        }
        
    }
    
    return weightSum;
}

 

- 전체 코드

static class Node {
    int to;
    int weight;

    public Node(int to, int weight) {
        this.to = to;
        this.weight = weight;
    }
}

public static int prim(int[][] data, int v, int e) {
    int weightSum = 0;

    ArrayList<ArrayList<Node>> adjList = new ArrayList();
    for (int i = 0; i < v + 1; i++) {
        adjList.add(new ArrayList<>());
    }

    for (int i = 0; i < e; i++) {
        // 양방향으로 연결
        adjList.get(data[i][0]).add(new Node(data[i][1], data[i][2]));
        adjList.get(data[i][1]).add(new Node(data[i][0], data[i][2]));
    }

    boolean[] visited = new boolean[v + 1];

    PriorityQueue<Node> pq = new PriorityQueue<>((x, y) -> x.weight - y.weight);
    pq.add(new Node(1, 0));

    int cnt = 0;
    while (!pq.isEmpty()) {
        Node cur = pq.poll();
        cnt++;

        if (visited[cur.to]) {
            continue;
        }
        visited[cur.to] = true;
        weightSum += cur.weight;

        if (cnt == v) {
            return weightSum;
        }

        // 인접한 정점을 작은 가중치 순으로 offer
        for (int i = 0; i < adjList.get(cur.to).size(); i++) {
            Node adjNode = adjList.get(cur.to).get(i);

            if (visited[adjNode.to]) {
                continue;
            }

            pq.offer(adjNode);
        }
    }

    return weightSum;
}

 

 

 

[ 참고 및 출처 ]

- 부트캠트 수업 내용 정리

- [Do it! 알고리즘 코딩 테스트 자바 편]

|  플로이드-워셜 알고리즘

- 모든 노드의 최단 경로를 구하는 알고리즘으로 전체 간선을 인접 행렬로 표현하고 dp를 통해 최단 경로를 구하는 방식

핵심 : 전체 경로의 최단 경로 = 부분 경로의 최단 경로의 조합

- 기능 : 모든 노드 간에 최단 경로 탐색

- 특징 : 음수 가중치 Ok, 동적 계획법의 원리를 사용

- 시간복잡도 : O(V3)

 

|  플로이드-워셜 구현하기

1 배열을 선언하고 초기화
2 노드의 각 인접 노드 경로 입력
3 점화식으로 배열 업데이트
4 음수사이클 확인

 

[1] 배열을 선언하고 초기화

- 각각의 노드를 Start지점으로 두고 모든 Start지점에 대한 최단 거리를 구할 예정

  1 2 3 4 5
1 0
2 0
3 0
4 0
5 0
static int[][] distDP; // 행 : 출발점, 열 : 도착점
static final int INF = 100_000_000; // overflow방지 큰 값 지정

public static void floydWarshall(int V, int E, int[][] data, int start) {

    // 1. 배열을 선언하고 초기화
    distDP = new int[V + 1][V + 1];
    for (int i = 1; i < V + 1; i++) {
        for (int j = 1; j < V + 1; j++) {
            if (i != j) {
                distDP[i][j] = INF;
            }
        }
    }
        
}

 

[2] 노드의 각 인접 노드 경로 입력

- 먼저 dp에 인접 행렬를 입력하는 것

- 인접 행렬을 기준으로 각각의 경유지에 대한 최단 거리를 업데이트할 예정

// 2. 노드의 각 인접 노드 경로 입력
for (int i = 0; i < E; i++) {
    distDP[data[i][0]][data[i][1]] = data[i][2];
}

 

[3] 점화식으로 배열 업데이트

- 각각의 경유지에 관해서, 어떤 출발지~경유지 + 경유지~어떤 도착지가 최소일 경우를 구한다.

for 경유지 K에 관해
   for 출발지 S에 관해
    	for 도착지 E에 관해
          D[S][E] = Math.min(D[S][E], D[S][K] + D[K][E]);
// 3. 점화식으로 배열 업데이트
// :  어떤 경유지를 거쳐 가는 것이 가장 빠른 가를 파악
for (int k = 1; k < V + 1; k++) { // 경유지
     for (int s = 1; s < V + 1; s++) { // 출발지
        for (int e = 1; e < V + 1; e++) { // 도착지
            // 경유지를 출발지 또는 도착지로 두는 거리가 입력된 바 없으면 무시
            if (distDP[s][k] != INF && distDP[k][e] != INF) {
                distDP[s][e] = Math.min(distDP[s][e], distDP[s][k] + distDP[k][e]);
            }
        }
    }
}

 

[4] 음수 사이클의 경우

- 음수사이클의 경우, 위 알고리즘에서 자기자신의 dp지점이 음수로 나타나게 된다.

 

** 최종 코드

static int[][] distDP; // 행 : 출발점, 열 : 도착점
static final int INF = 100_000_000; // overflow방지 큰 값 지정

public static void floydWarshall(int V, int E, int[][] data, int start) {

    // 1. 배열을 선언하고 초기화
    distDP = new int[V + 1][V + 1];
    for (int i = 1; i < V + 1; i++) {
        for (int j = 1; j < V + 1; j++) {
            if (i != j) {
                distDP[i][j] = INF;
            }
        }
    }

    // 2. 노드의 각 인접 노드 경로 입력
    for (int i = 0; i < E; i++) {
        distDP[data[i][0]][data[i][1]] = data[i][2];
    }

    // 3. 점화식으로 배열 업데이트
    // :  어떤 경유지를 거쳐 가는 것이 가장 빠른 가를 파악
    for (int k = 1; k < V + 1; k++) { // 경유지
         for (int s = 1; s < V + 1; s++) { // 출발지
            for (int e = 1; e < V + 1; e++) { // 도착지
                // 경유지를 출발지 또는 도착지로 두는 거리가 입력된 바 없으면 무시
                if (distDP[s][k] != INF && distDP[k][e] != INF) {
                    distDP[s][e] = Math.min(distDP[s][e], distDP[s][k] + distDP[k][e]);
                }
            }
        }
    }

    // 출력
    for (int i = 1; i < V + 1; i++) {
        for (int j = 1; j < V + 1; j++) {
            if (distDP[i][j] >= INF) {
                System.out.printf("%5s ", "INF");
            } else {
                System.out.printf("%5d ", distDP[i][j]);
            }
        }
        System.out.println();
    }

}

 

 

[ 참고 및 출처 ]

부트캠프

Do it 알고리즘 코딩 테스트

|  벨만-포드

- 가중치가 양수만 있을 때에 다익스트라를 사용했다면,

- 가중치에 음수가 있을 때엔 벨만-포드를 사용한다.

- 기능 : 특정 출발 노드에서 도착 노드까지의 최단 경로 탐색

- 특징 : 가중치 음수 OK,  음수 사이클 여부 판단

- 시간 복잡도 : O(VE)

 

|  벨만-포드 구현하기

- 지난번에 구현했던 다익스트라의 우선순위 큐 버전으로 쓰면 음수도 커버가 가능한데

- 벨만-포드를 사용하게 되면 음수 사이클 여부까지 커버할 수 있다.

- 구현의 순서

1 에지 배열 & 최단 경로 dp 배열 초기화
2 모든 에지를 하나씩 확인하여 최단 경로 업데이트
3 음수 사이클 여부 확인

 

[1] 에지 배열(또는 리스트) & 최단 경로 dp 배열 초기화

- 이 부분은 다익스트라의 노드 클래스가 간선 클래스로 바뀐 것 밖에 없다.

- Edge라는 클래스안에 출발지점, 도착지점, 가중치를 넣어 배열에 저장한다.(또는 리스트에 저장)

edge 1 2 3 4 5
출발          
종료          
가중          

- 최단 경로 dp 배열

dp 1 2 3 4 5
최단          
class Edge{
    int from;
    int to;
    int weight;
    
    public Edge(int from, int to, int weight) {
        this.from = from;
        this.to = to;
        this.weight = weight;
    }
}

public void Bellmanford(int v, int e, int[][] data, int start){
    // 1. 간선 정보 및 최단 경로 배열 초기화
    // 1-1. 간선 정보를 배열로 저장
    Edge[] edge = new Edge[e];
    for (int i = 0; i < edge.length; i++) {
        edge[i] = new Edge(data[i][0], data[i][1], data[i][2]);
    }

    // 1-2. 최단 경로 배열
    int[] distDP = new int[v + 1];
    for (int i = 1; i < v + 1; i++) {
        distDP[i] = Integer.MAX_VALUE;
    }
    distDP[start] = 0;
}

 

[2] 모든 에지를 하나씩 확인하여 최단 경로 업데이트

* D는 dp, S는 출발노드, e는 도착노드
D[s] != ∞ 이며, D[e] > D[s] + w 일 때, D[e] = D[s] + w

- 음수 사이클이 없을 때 최대 에지 사용 횟수는 V - 1이다.

* 만약 노드의 개수가 5개이고, 음수사이클이 없다면 아래와 같이 4번 반복하여 최단거리를 확인한다.

dp 1 2 3 4 5
초기 0
1          
2          
3          
4          
// 2. 모든 간선을 확인해 정답 배열 업데이트 하기
for (int i = 1; i < v + 1; i++) { // 간선 횟수 + 1만큼 (음수사이클 확인)
    for (int j = 0; j < e; j++) { // 각 간선에 대하여
        Edge cur = edge[j]; 

        if (distDP[cur.from] == Integer.MAX_VALUE) { // 초기화되지x간선 무시(간선X)
            continue;
        }

        // 기존 거리 > 새로운 거리 : 현재 출발지 + 가중치  
        if (distDP[cur.to] > distDP[cur.from] + cur.weight) {
            distDP[cur.to] = distDP[cur.from] + cur.weight;
        }
    }
}

 

[3] 음수 사이클 여부 확인

- 만약에 음수 사이클이 없다면, 최대 에지 사용 횟수는 V 이상이다.

- 바깥 if문 안으로 들어오는 경우가 v번째이상인 경우는 음수사이클이 발생하므로 아래와 같이 작성해주면 완료된다.

if (distDP[cur.to] > distDP[cur.from] + cur.weight) {
    distDP[cur.to] = distDP[cur.from] + cur.weight;

    // 3. 음수 사이클 유무 확인하기
    if (i == v) {
        isMinusCycle = true;
    }
}

* 최종 코드 

public void bellmanFord(int v, int e, int[][] data, int start) {
    // 1. 간선 정보 및 최단 경로 배열 초기화
    // 1-1. 간선 정보를 배열로 저장
    Edge[] edge = new Edge[e];
    for (int i = 0; i < edge.length; i++) {
        edge[i] = new Edge(data[i][0], data[i][1], data[i][2]);
    }

    // 1-2. 최단 경로 배열
    int[] distDP = new int[v + 1];
    for (int i = 1; i < v + 1; i++) {
        distDP[i] = Integer.MAX_VALUE;
    }
    distDP[start] = 0;


    boolean isMinusCycle = false;
    // 2. 모든 간선을 확인해 정답 배열 업데이트 하기
    for (int i = 0; i < v + 1; i++) {
        for (int j = 0; j < e; j++) {
            Edge cur = edge[j];

            if (distDP[cur.from] == Integer.MAX_VALUE) {
                continue;
            }

            if (distDP[cur.to] > distDP[cur.from] + cur.weight) {
                distDP[cur.to] = distDP[cur.from] + cur.weight;

                // 3. 음수 사이클 유무 확인하기
                if (i == v) {
                    isMinusCycle = true;
                }
            }

        }
    }

    System.out.println("음수 사이클 발생 : " + isMinusCycle);
    for (int i = 1; i < v + 1; i++) {
        if (distDP[i] == Integer.MAX_VALUE) {
            System.out.print("INF" + " ");
        } else {
            System.out.print(distDP[i] + " ");
        }
    }
    System.out.println();

}

 

|  백준문제풀기

11657 타임머신  
1219 세일즈맨  

 

 

 

[ 출처 및 참조 ]

부트캠프 수업 내용 정리

 doit 알고리즘 코딩 테스트 책

|  다익스트라

- 가중치가 있는 그래프에서, 최단 경로를 구하는 알고리즘

- 핵심 : 그래프의 인접리스트와 DP를 이용

- 기능 : 출발 노드와 모든 노드 간의 최단 거리 탐색

- 특징 : 간선은 모두 양수

- 시간 복잡도 : O(ElogV) * E : 간선수, V : 정점수

 

|  다익스트라 구현

- 배열을 사용하는 것도 좋지만 배열 크기가 너무 클 경우를 대비해 인접 리스트를 사용

- (1),(2) 모두 기본적으로 동일하게 아래를 세팅

(1) 간선 정보 리스트 : 인접 리스트에 Node(연결된 노드, 가중치) 타입으로 저장

(2) 임시 거리 배열 : 출발지부터 노드 X까지의 현재 최소 거리를 업데이트할 배열

ㄴ start 위치는 자기 자신이므로 0으로 세팅한다.

class Node{
    int to;
    int weight;
    
    Node (int to, int weight){
    	this.to = to;
        this.weight = weight;
    }
}
// v : 간선수, data : 각 노드의 간선과 가중치 정보, start : 시작점
public void dijkstra(int v, int[][] data, int start){
    // (1) 간선 정보 업데이트
    ArrayList<ArrayList<Node>> adjList = new ArrayList();
    for (int i = 0; i < v + 1; i++){
    	adjList.add(new ArrayList());
    }
    
    for (int i = 0; i < data.length; i++){
    	adjList.get(data[i][0]).add(new Node(data[i][1], data[i][2]));
    }  
}
// v : 간선수, data : 각 노드의 간선과 가중치 정보, start : 시작점
public void dijkstra(int v, int[][] data, int start){
	// (1) 간선 정보 업데이트
    ArrayList<ArrayList<Node>> adjList = new ArrayList();
    for (int i = 0; i < v + 1; i++){
    	adjList.add(new ArrayList());
    }
    
    for (int i = 0; i < data.length; i++){
    	adjList.get(data[i][0]).add(new Node(data[i][1], data[i][2]));
    }  
    
    // (2) 현재까지 start부터 각 노드들까지 최소거리 dp 배열
    // : 초기값을 무한대(Integer.MAX_VALUE)로 설정한 후, 최소값을 갱신
    int distDP = new int[v + 1];
    for (int i = 1; i < v + 1; i++){
    	distDP = Integer.MAX_VALUE:
    }
    distDP[start] = 0; // start를 0으로 세팅
}

 

1. 방문 배열과 반복문 사용하기

(1) 먼저 방문 배열을 만들고, 첫번째 노드부터 끝 노드까지 현재 지점을 짚는다

// v : 간선수, data : 각 노드의 간선과 가중치 정보, start : 시작점
public void dijkstra(int v, int[][] data, int start){
    // (3) 방문 배열 + 반복문으로 다익스트라 구현
    
    // 방문배열
    boolean[] visited = new boolean[v + 1];
    
    // 반복문
    // start부터 각 노드들까지
    for (int i = 1; i < v + 1; i++){
        int minDistance = Integer.MAX_VALUE; // 최초 거리 : 무한대
        int curIdx = 0; // 최단 거리 노드
        
        // 1부터 V 중에 현재 노드
        for (int j = 1; j < v + 1; j++){
            if (!visited[j] && distDP[j] < minDistance){
            	minDistance = distDP[j];
                curIdx = j;
            }
        }
        
        visited[curIdx] = true;
        
        // 현재 노드에서 
        for (int j = 0; j < adjList.get(curIdx).size(); j++){
            // 연결된 노드들에 대하여
            Node adjNode = adjList.get(curIdx).get(j);
            
            if (distDP[adjNode.to] > distDP[curIdx] + adjNode.weight){
            	distDP[adjNode.to] = distDP[curIdx] + adjNode.weight;
            }
        }
    }
}

- 처음 Start부터 각 노드까지의 최선거리 DP는 모두 무한대(Integer.MAX_VALUE)로 2^32인 2147483647인 상태였다.

1 2 3 4 5
무한대 무한대 무한대 무한대 무한대

- 여기에 앞에서 기본 세팅해줄 때에 Start지점을 0으로 세팅했으므로 현재 상태는 아래와 같다.

1 2 3 4 5
0 무한대 무한대 무한대 무한대

- 위의 for문에서 아래 내용의 distDP[j] > minDistance는 따라서 가장 처음엔 Start값인 1이다.

// 1부터 V의 노드들 중에
for (int j = 1; j < v + 1; j++){
    // 현재 노드 짚기
    if (!visited[j] && distDP[j] < minDistance){
    	minDistance = distDP[j]; // minDistance = distDP[1];
        curIdx = j; // curIdx = 1;
    }
}

- 1 다음부터는 무한대 값인 녀석들을 처음에 curIdx로 짚어주고, 

(2) 현재노드부터 연결된 노드까지의 거리 중 최단 거리를 찾는다.

  (1) 현재까지  X노드까지 거리 : distDP[adjNode.to]

  (2) 새롭게 찾은 X노드까지 거리 : distDP[curIdx] + adjNode.weight 

  이 둘 중에 새롭게 찾은 (2) 가 더 작은 경우 갱신하라는 의미이다.

// 현재 노드에서 
for (int j = 0; j < adjList.get(curIdx).size(); j++){
    // 다시 파생되는 노드들에 대하여
    Node adjNode = adjList.get(curIdx).get(j);

    if (distDP[adjNode.to] > distDP[curIdx] + adjNode.weight){
        distDP[adjNode.to] = distDP[curIdx] + adjNode.weight;
    }
}
  curId 1 2 3 4 5
초기 - 0
1 1 0 2 3
2 2 0 2 3 7
3 3 0 2 3 7
4 4 0 2 3 7
5 5 0 2 3 7

 

2. 우선순위 큐 사용하기

- 우선순위 큐는 원리는 비슷한데, 

- 공간이나 시간 복잡도 면에서 조금 더 개선된 코드이다.

- 하나씩 해보는 것이 필요한 코드..

boolean[] visited;

public void dijkstra(int v, int[][] data, int start){
    // (3) 방문 배열 초기화
    visited = new boolean[v + 1];

    // (4) 우선순위 큐를 통한 다익스트라 구현
    // -- 가중치 기준 오름차순으로 자동 정렬
    PriorityQueue<Node> pq = new PriorityQueue((x, y) -> x.weight - y.weight);
    pq.offer(new Node(start, 0)); // start지점을 0으로 갱신
    
    while (!pq.isEmpty()){
    	Node curNode = pq.poll(); // 현재 지점
        
        if (visited[curNode.to]) { // 방문 했던 곳은 X
            continue;
        }
        visited[curNode.to] = true; 
        
        for (int i = 0; i < adjList.get(curNode.to).size(); i++){ // 최단거리 갱신
            Node adjNode = adjList.get(curNode.to).get(i);
            
            if (!visited[curNode.to] && distDP[adjNode.to] > curNode.weight + adjNode.weight){
            	distDP[adjNode.to] = curNode.weight + adjNode.weight;
                pq.offer(new Node(adjNode.to, distDP[adjNode.to]);
            }
        } 
    }
}
  curNode pq 1 2 3 4 5
초기 - (1, 0) 0
1 (1, 0) (2, 2) (3, 3) 0 2 3
2 (2, 2) (3, 3) (4, 7) 0 2 3 7
3 (3, 3) (4, 7)  0 2 3 7
4 (4, 7)    0 2 3 7

 

|  백준 문제 풀이

최단 경로 구하기 https://www.acmicpc.net/problem/1753 😦
최소 비용 구하기 https://www.acmicpc.net/problem/1916 🙃
K번째 최단 경로 찾기    

 

 

[ 출처 및 참조 ]

부트캠프 수업 후 정리

Do it! 알고리즘 코딩 테스트

|  백트래킹

- 모든 가능한 경우의 수 중에서 유망하지 않은 쪽은 제외하고 최적해를 구하는 방법

- 주로 재귀를 사용해서 구현한다.

유망(Promising) 가능성 있다.
가지치기(Pruning) 가능성 없는 것 제외하기
백트래킹(Backtracking) 유망하지 않은 쪽으로는 가지 않고 Back한다.

 

|  N-Queen 을 통한 백트래킹 이해

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

 

9663번: N-Queen

N-Queen 문제는 크기가 N × N인 체스판 위에 퀸 N개를 서로 공격할 수 없게 놓는 문제이다. N이 주어졌을 때, 퀸을 놓는 방법의 수를 구하는 프로그램을 작성하시오.

www.acmicpc.net

- N x N의 체스판에 N개의 퀸을 서로 공격할 수 없도록 배치한다고 할 때

- 메모리 공간 : 

(1) 각 행의 열을 짚어줄 배열

(2) 카운팅 변수

- Flow

(1) i번째 행의 각 열에 퀸을 배치한다.

(2) 유망여부 : 이전 행의 퀸 열의 상하좌우, 대각선에 해당되는가? 

- 2-1 ) YES : 다음 행으로 이동 

- 2-2 ) NO : 가지치기

(2) 모든 행을 다 돌은 경우, 방법의 수 카운팅

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {

    static int[] board; // 각 행의 퀸 열 위치
    static int cnt = 0; // 전체 카운트 수
    static int nQueen(int n, int r){
        // 탈출문
        if (r == n) {
            cnt++;
            //System.out.println(Arrays.toString(board));
            return cnt;
        }

        // 구현문
        // r번째 행에 대하여
        for (int i = 0; i < n; i++) {
            // 열 위치값 지정
            board[r] = i;

            // promising
            if (isPromising(r, i)){
                nQueen(n, r + 1);
            }
        }

        return cnt;
    }

    static boolean isPromising(int r, int c) {
        // 현재 행 전까지 겹치는 부분 확인
        for (int i = 0; i < r; i++) {
            int pre_r = i;
            int pre_c = board[i];

            if (pre_c == c || r - pre_r == Math.abs(c - pre_c)){
                return false;
            }
        }

        return true;
    }

    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());

        board = new int[N];

        System.out.println(nQueen(N, 0));
    }
}

 

|  그 외에 백트래킹을 사용하는 예시

(1) Permutation(순열)을 구하는 알고리즘 

import java.util.Arrays;

public class Practice {

    // 순열
    static int cnt = 0;
    static boolean[] visited;
    static int[] out;
    static int solution(int[] arr, int n, int r) {
        visited = new boolean[n];
        out = new int[r];

        return permutation(arr, n, r, 0);
    }

    static int permutation(int[] arr, int n, int r, int depth){
        if (depth == r){
            cnt++;
            System.out.println(Arrays.toString(out));
            return cnt;
        }

        for (int i = 0; i < n; i++) {
            if (visited[i] == false) {
                visited[i] = true;
                out[depth] = arr[i];
                permutation(arr, n, r, depth + 1);
                out[depth] = 0;
                visited[i] = false;
            }
        }

        return cnt;
    }

    public static void main(String[] args) {
        int[] arr = { 1, 2, 3 };
        int n = 3; // n개의 수 중에
        int r = 2; // r개의 수를 고르는 방법
        System.out.println(solution(arr, n, r));
    }
}

(2) N자리수에서 앞자리부터 일의 자리수, 십의 자리수... N의 자리수가 모두 소수인 수를 찾는 알고리즘

- 첫번째 자릿수는 무조건 2,3,5,7중 하나가 들어가야한다.

- 두번재 자릿수부터 0 ~ 9 중에 유망한 수를 구한다.

  > 유망하다 : 2나 5로 나누어떨어지지 않는 수

     > 백트래킹 : 현재까지 자릿수의 숫자(ex.233)가 소수인지 확인

  > 유망하지X : 가지치기 

public class Practice2 {

    static void solution(int n) {
        // 첫번째 자릿수는 무조건 2, 3, 5, 7 중 하나
        int[] primes = {2, 3, 5, 7};

        for (int i = 0; i < primes.length; i++) {
            getPrimes(primes[i], n,1);
        }
    }

    static void getPrimes(int prime, int n, int digitLen){
        
        if (digitLen >= n){
            System.out.print(prime + " ");
            return;
        }

        // 두번째 자릿수부터는 0 - 9중에
        for (int i = 0; i < 10; i++) {
            // 가지치기 : 2 또는 5로 나누어떨어지지 않는 수
            if (i % 2 != 0 && i % 5 != 0) {
                int primeCandidate = 10 * prime + i;

                // backtracking : 소수이면
                if (isPrime(primeCandidate)){
                    getPrimes(primeCandidate, n,digitLen + 1);
                }
            }
        }
    }
    
    static boolean isPrime(int n) {

        if (n < 2) {
            return false;
        }

        for (int i = 2; i <= Math.sqrt(n); i++) {
            if (n % i == 0) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        int n = 3;
        solution(n);
    }
}

 

|  백준 문제집

https://www.acmicpc.net/step/34

 

백트래킹 단계

삼성 SW 역량 테스트 기출 문제 1

www.acmicpc.net

 

[ 출처 및 참조 ]

부트캠프 수업 후 정리한 내용입니다.

 

|  동적계획법이란?

- 복잡한 문제를 여러 개의 간단한 문제로 분리하여 해결하는 방법

- 원리 및 구현 방법

1) 큰 문제를 부분적으로 분리하여 분석한다

--> 부분적인 문제의 결과값은 항상 동일해야한다.

2) 1)을 통해 알게된 규칙을 통해 점화식을 생성한다.

3) 기존 배열과 dp배열을 어떻게 만들지 구상하고

4) 아래 두 가지 방법 중 적절한 구현 방법에 따라 수도 코드를 작성한다. 

타뷸레이션 = 바텀-업 - for문을 사용하여 구현
메모이제이션 = 탑-다운

* 이미지 출처 : 나무위키


- 재귀를 사용하여 구현

 

|  피보나치를 통해 보는 동적 계획법

- 점화식을 통해 재귀로 푼 피보나치 문제 풀이는 아래와 같았다.

// 피보나치 수열
// 재귀 (일반풀이 방법) - O(n^2), 계산했던 부분도 다시 계산
int fibonachi(int n) {
    if (n < 2) {
        return n;
    } else {
        return fib(n - 2) + fib(n - 1);
    }
}

- 타뷸레이션 방식으로 이를 풀게 되면, 1 1 2 3 5 8.. 순으로 데이터를 넣고 조회한다.

// 타뷸레이션 방식 - O(n)
int fibo2(int n) {
	// 재활용할 메모리 공간
    int[] dp = new int[n + 1];
    dp[0] = 1;
    dp[1] = 1;
    
    for (int i = 1; i <= n; i++){
    	dp[i] = dp[i - 2] + dep[i - 1];
    }
    
    return dp[n];
}

- 메모이제이션을 사용하게 되면, Top-down 방식으로 재귀를 호출하는데,

  첫번째 방식과의 차이는 이미 확인한 데이터는 다시 하위로 깊이를 내려가 풀이 하지 않는다는 것이 다르다.

// 메모이제이션 방식 - O(n)
int[] dp = new int[8];
int fibo2(int n) {

    if (n <= 2) {
    	return 1;
    } 
    
    if (dp[n] != 0) {
    	return dp[n];
    }
    
    dp[n] = fibo2(n - 2) + fibo2(n - 1);
    
    return dp[n];
}

 

|  백준 문제풀이

1 1로 만들기 https://www.acmicpc.net/problem/1463
2 퇴사 https://www.acmicpc.net/problem/14501
3 이친수 구하기 https://www.acmicpc.net/problem/2193
4 2*N 타일 구하기 https://www.acmicpc.net/problem/11726
5 쉬운 계단 수 https://www.acmicpc.net/problem/10844
6 연속된 정수의 합 구하기 https://www.acmicpc.net/problem/13398
7 최장 공통 부분 수열 찾기    
8 가장 큰 정사각형 찾기    
9 빌딩 순서 구하기    
10 DDR을 해보자    
11 행렬 곱 연산 횟수의 최솟값 구하기    
12 외판원의 순회 경로 짜기    
13 가장 길게 증가하는 부분 수열 찾기    
더보기

1. 1로 만들기

- 양의 정수 1~N까지를 아래의 점화식에 따라 풀이한 문제

D[i] = D[i - 1] + 1
if (2의 배수) D[i / 2] + 1이 D[i]보다 작으면 변경
if (3의 배수) D[i / 3] + 1이 D[i]보다 작으면 변경
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Ex1463 {
    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // N : 구하고자 하는 수
        int N = Integer.parseInt(br.readLine());
        int[] dp = new int[N + 1];
        dp[1] = 0;

        // * 규칙이 반복되는 구간은 2부터
        // for (i : 2 ~ N) {
        //    D[i] = D[i - 1] + 1 // 1을 뺀 점화시
        //    if (2의 배수) D[i / 2] + 1이 D[i]보다 작으면 변경
        //    if (3의 배수) D[i / 3] + 1이 D[i]보다 작으면 변경
        // } 
        for (int i = 2; i <= N; i++) {
            dp[i] = dp[i - 1] + 1;

            if (i % 2 == 0) {
                dp[i] = Math.min(dp[i], dp[i / 2] + 1);
            }

            if (i % 3 == 0) {
                dp[i] = Math.min(dp[i], dp[i / 3] + 1);
            }
        }

        // 출력
        System.out.println(dp[N]);
    }
}

 

2. 퇴사

- dp에서 자주 나온다는 배낭 문제와 약간 비슷하나 다른 문제였다.

- 일련의 스케줄이 주어지고 기간 제약을 고려하여 최대 수익을 내는 문제

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Ex14501 {
    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // N 개의 스케줄을 각각의 일차원 배열에 받는다.
        int N = Integer.parseInt(br.readLine());

        // D 점화식 배열
        int[] D = new int[N + 2];

        // T 기간 배열
        int[] T = new int[N + 1];

        // P 수입 배열
        int[] P = new int[N + 1];

        for (int i = 1; i <= N; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());

            T[i] = Integer.parseInt(st.nextToken());
            P[i] = Integer.parseInt(st.nextToken());
        }

        // 터뷸레이션을 사용해서 D를 구한다.
        // D[i] = i날짜부터 n+1(퇴사일)까지 벌수 있는 최대 수익
        // for (i : N ~ 1){
        //    if (i+T[i]가 퇴사일보다 크면) D[i] = D[i + 1]
        //    if (i+T[i]가 퇴사일보다 크지 않으면) D[i] = Math.max(D[i + 1], P[i] + D[i + T[i]])
        // }
        for (int i = N; i >= 1; i--) {
            if (i + T[i] > N + 1) {
                D[i] = D[i + 1];
            } else {
                D[i] = Math.max(D[i + 1], D[i + T[i]] + P[i]);
            }
        }

        // 출력
        // D[1]
        System.out.println(D[1]);
    }
}

 

3. 이친수 구하기

- 욕 같지만 이진수를 만드는 특정 규칙를 지킨 수의 갯수를 구하는 문제였다.

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Ex2193 {
    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());

        // D 배열을 만든다 (2차원 배열)
        // dp[1][0] = 0, dp[1][1] = 1 초기화
        long[][] dp = new long[N + 1][2];
        dp[1][0] = 0;
        dp[1][1] = 1;

        // for (i : 2 ~ N) {
        //    dp[i][0] = dp[i - 1][0] + dp[i - 1][1] // 0은 0이나 1 뒤에 붙일 수 있다.
        //    dp[i][1] = dp[i - 1][0] // 1은 0 뒤에만 붙일 수 있다.
        // }
        for (int i = 2; i <= N; i++) {
            dp[i][0] = dp[i - 1][0] + dp[i - 1][1];
            dp[i][1] = dp[i - 1][0];
        }

        System.out.println(dp[N][0] + dp[N][1]);
    }
}

 

... 나머지는 더 풀이를 하고 모르는 부분만 정리하여 올리려 한다.

위의 내용은 스스로 정리한 부분에 대해 이해할 수 있게 문제풀이한 것을 올린 내용..

 

[ 출처 및 참조 ]

- 부트 캠프 강의를 들은 후 정리한 내용입니다.

- [Do it! 알고리즘 코딩 테스트 - 자바 편] 

'Computer Science > Algorithm' 카테고리의 다른 글

[알고리즘] 최단 경로 구하기 - 1. 다익스트라  (0) 2022.09.01
[알고리즘] 백트래킹  (0) 2022.08.31
모듈러 산술  (0) 2022.08.25
[알고리즘] 분할정복  (0) 2022.08.25
[알고리즘] 그리디  (0) 2022.08.25

|  모듈러 산술

- 모듈러 (=mod = %)는 덧셈, 뺄셈, 곱셈에 있어서 아래와 같은 산술을 할 수 있는 특징이 있다.

- 알고리즘 문제를 풀다보면 애매하게 정수 Max값을 넘을랑 말랑 하는 데이터가 있기도 한데,

  그럴 때 이 모듈러 산술을 고려해서 문제를 풀면 굳이 Long 타입 변수를 쓰지 않아도 Int만으로 충분히 풀이가 가능하다.

(a + b) % C = (a % C + b % C) % C
(a - b) % C = (a % C - b % C) % C
(a * b) % C = (a % C * b % C) % C

 

|  참고할 만한 문제

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

 

11726번: 2×n 타일링

2×n 크기의 직사각형을 1×2, 2×1 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오. 아래 그림은 2×5 크기의 직사각형을 채운 한 가지 방법의 예이다.

www.acmicpc.net

 

 

[ 참고 ]

https://developer-mac.tistory.com/84

https://sskl660.tistory.com/75

'Computer Science > Algorithm' 카테고리의 다른 글

[알고리즘] 백트래킹  (0) 2022.08.31
[알고리즘] DP (동적 계획법)  (0) 2022.08.26
[알고리즘] 분할정복  (0) 2022.08.25
[알고리즘] 그리디  (0) 2022.08.25
[알고리즘] 탐색 - DFS, BFS, 이진 탐색  (0) 2022.08.25

|  분할정복이란?

- 폰노이만 구조를 만든 그 폰노이만에 의해 소개된 알고리즘

- 간략하게 말하면, 큰 문제를 작은 단위로 쪼갠 후 다시 조합하여 문제를 해결하는 방법을 말한다.

분할: 문제를 더이상 분할할 수 없을 때까지 동일한 유형의 여러 하위 문제로 나눈다.
정복: 가장 작은 단위의 하위 문제를 해결하여 정복한다.
조합: 하위 문제에 대한 결과를 원래 문제에 대한 결과로 조합한다

*출처 : 나무위키

 

|  분할정복 과정

1. 문제를 하나 이상의 작은 단위로 분할

2. 작은 단위에서 부분적인 해결

3. 부분들의 해결을 조합해 전체 문제의 해답을 찾는다.

function F(x):
  if F(x)가 간단 then:
    return F(x)를 계산한 값
  else:
    x 를 x1, x2로 분할
    F(x1)과 F(x2)를 호출
    return F(x1), F(x2)로 F(x)를 구한 값
    
* 출처 : 나무위키

 

|  장단점

장점 어려운 문제를 해결할 수 있다
병렬적으로 문제를 해결하는데에 이점이 있다.
단점 메모리 사용량이 높다(재귀 호출을 통한 오버헤드 발생)

 

|  예시

분할정복의 예시로 자주 거론되는 것으로 아래 네 가지가 있다. 

이 중에서 내 단계에서 언급하기 쉬워 보이는 1~3의 알고리즘만 적어보려고 한다.

1. 합병 정렬

2. 퀵소트

3. 카라추바 알고리즘

4. 고속 푸리에 변환

 

1. 합병정렬

- 분할정복과 투포인터의 특성을 사용해서 만들어진 정렬이라 볼 수 있을 것 같다.

- 1) 먼저 배열의 범위를 투 포인터를 통해 분할하여 지정한 후에,

- 2) 작은 범위에서 데이터를 비교하여 정렬을 하고

- 3) 작은 범위를 조합하여 전체 정렬을 이루는 과정 

void mergeSort(int[] arr, int[] tmp, int left, int right){
	if (left < right) {
    	int mid = (left + right) / 2;
        // mid를 기준으로 배열을 분할한다.
        mergeSort(arr, tmp, left, mid);
        mergeSort(arr, tmp, mid + 1, right);
        // mid를 기준으로 배열을 조합한다.
        merge(arr, tmp, left, right, mid);
    }
}

void merge(int[] arr, int[] tmp, int left, int right, int mid){
    // mid를 기준으로 좌/우 포인터, 
    // index포인터를 준비하고
    int l = left;
    int r = mid + 1; 
    int idx = left;
    
    // 좌/우 포인터를 비교하여 tmp에 오름차순으로 저장
    // 적어도 두 포인터 중 하나가 범위 내에 있을 때에
    while (l <= mid || r <= right){
    	// 조건1 : 모든 포인터가 범위내에 있다면
        if (l <= mid && r <= right){
        	// 두 포인터의 데이터를 비교해서 
            // 더 작은 데이터를 tmp에 넣고, 포인터를 하나 증가한다.
            if (arr[l] <= arr[r]){
            	tmp[idx++] = arr[l++];
            } else {
            	tmp[idx++] = arr[r++];
            }
        } 
        // 조건2 : left포인터만 범위내에 있다면
        else if (l <= mid && r > right){
        	tmp[idx++] = arr[l++];
        }
        // 조건3 : right포인터만 범위내에 있다면
        else {
        	tmp[idx++] = arr[r++];
        }
    }
    
    // tmp의 데이터를 arr에 옮긴다.
    for (int i = left; i <= right; i++){
    	arr[i] = tmp[i];
    }
}

 

2. 퀵소트

- 피봇이라는 기준값을 토대로 투포인터와 분할정복을 활용한 정렬 알고리즘이다.

- 1) 먼저 피봇값이라는 기준값을 지정하고 반정렬

- 2) 피봇값을 기준으로 배열을 분할

- 3) 분할된 배열을 부분적으로 정렬

void quickSort(int[] arr, int left, int right){
	// 탈출문
    if (left >= right) {
    	return;
    }
    
    // 피봇값을 지정
    int pivot = partition(arr, left, right);
    
    // 피봇값을 기준으로 배열을 분할
    quickSort(arr, left, pivot - 1);
    quickSort(arr, pivot + 1, right);
}

void int partition(int[] arr, int left, int right){
	// 투 포인터와 피봇값 지정
    int l = left;
    int r = right;
    int pivot = arr[left];
    
    // 피봇값을 기준으로 양 끝 포인터를 올리거나 내려준다
    while(l < r) {
    	while (arr[r] > pivot && l < r) {
        	r--;
        }
        
        while (arr[l] <= pivot && l < r) {
        	l++;
        }
        
        swap(arr, l, r);
    }
    // 기존 피봇값 위치 데이터와 피봇값을 교환
    swap(arr, left, l);
    
    return l;
}

 

3. 카라추바 알고리즘

- 카라추바라는 사람이 1960년에 만든 분할정복 알고리즘으로, 

- 초등학교 곱셈 공식에서 배웠던 각 자릿수를 곱하는 방식을 응용해 만든 알고리즘이다.

- 알고리즘 구현의 단계는 아래와 같다.

1. z0 = a * c
2. z1 = b * d
3. z2 = (a + b)(c + d)
4. z3 = z0 - ac - bd
5. (z0 * 10^n) + (z3 * 10^(n/2)) + z1

- 아래 코드 외에 아예 ArrayList를 사용해서 각 자릿수를 저장하는 방식 또한 있는데, 

  굉장히 복잡하기 때문에 관련된 코드는 검색을 통해 복사하여 사용하는 편이 좋을 것 같다.

** 자바에는 BigInteger라는 큰 수를 다루는 객체가 따로 있다.

  (큰 수의 곱셈은 이 객체의 multiply() 메소드를 쓰는 것이 코드를 가독성 있게 짜기에는 더 좋아 보인다.) 

public static long karatsuba(long x, long y) {
    // 범위가 작은 경우
    if (x <= 50 && y <= 50) {
        return x * y;
    }

    // a와 b의 자릿수
    int numXLen = (int)Math.log10(x);
    int numYLen = (int)Math.log10(y);

    // a와 b의 자릿수 중에 더 큰 자릿수를 선택
    int maxLen = Math.max(numXLen, numYLen); // ex 1234 -> 4

    // 분할한 자릿수
    Integer halfMaxLen = (maxLen / 2) + (maxLen % 2); // ex 1234 -> 2

    // Multiplier : 곱할 10^n
    long halfMaxLenTen = (long)Math.pow(10, halfMaxLen); // ex 1234 -> 10^2

    // 분할한 수들을 구한다.
    long a = x / halfMaxLenTen;
    long b = x % halfMaxLenTen;
    long c = y / halfMaxLenTen;
    long d = y % halfMaxLenTen;

    // 4단계에 따라 각 수들을 곱하고 더한다.
    // 1. a * c
    // 2. b * d
    // 3. (a+b)(c+d)
    // 4. (3)에서 (1), (2)를 뺀다
    // 5. (1) x halfMaxLen + (2) + (4) x (halfMaxLen / 2)
    long z0 = karatsuba(a, c);
    long z1 = karatsuba(b, d);
    long z2 = karatsuba(a + b, c + d);
    long z3 = z2 - z1 - z0;

    long res = (z0 * (long)Math.pow(10, halfMaxLen * 2)) + (z3 * (long)Math.pow(10, halfMaxLen)) + z1;
    return res;
}

 

|  백준 문제

- 백준의 분할 정복에 대한 문제는 사실상 재귀 문제와 별반 다를게 없었다.

- 그만큼 재귀와 분할정복이 서로 땔래야 땔 수 없는 관계라는 것을 의미하는 듯 하다.

https://www.acmicpc.net/workbook/view/798

 

문제집: 분할정복 (cokcjswo)

 

www.acmicpc.net

 

 

[ 출처 및 참조 ]

- 부트 캠프 강의를 들은 후 정리한 내용입니다.

- [Do it! 알고리즘 코딩 테스트 - 자바 편] 

- 나무위키,  https://namu.wiki/w/%EB%B6%84%ED%95%A0%20%EC%A0%95%EB%B3%B5%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98

https://www.geeksforgeeks.org/java-program-to-implement-the-karatsuba-multiplication-algorithm/

'Computer Science > Algorithm' 카테고리의 다른 글

[알고리즘] DP (동적 계획법)  (0) 2022.08.26
모듈러 산술  (0) 2022.08.25
[알고리즘] 그리디  (0) 2022.08.25
[알고리즘] 탐색 - DFS, BFS, 이진 탐색  (0) 2022.08.25
HashSet<int[]>와 Hashset<ArrayList<Integer>>  (0) 2022.08.17

+ Recent posts