가장 빠르게 도달 하는 방법

그리디 알고리즘 및 다이나믹 알고리즘의 한 유형

가장 짧은 경로를 찾는 알고리즘

예 )

  • 한 지점에서 다른 특정 지점까지의 최단 경로를 구해야하는 경우
  • 모든 지점에서 다른 모든 지점까지의 최단 경로를 모두 구해야하는 경우

종류

  • 다익스트라 최단경로 알고리즘
  • 플로이드 워셜
  • 벨만 포드 알고리즘

다익스트라 최단 경로 알고리즘

각 노드에서 현재까지의 최단거리 정보를 항상 1 차원 리스트에 저장하며 리스트를 계속 갱신

  • 그래프에서 여러 개의 노드가 있을 때, 특정 노드에서 출발해 다른 노드로 가는 각각의 최단 경로 구해주는 알고리즘
  • 음의 간선이 없을 때 사용 가능
  • **가장 비용이 적은 노드**를 선택해 임의의 과정 반복
  • 원리
    1. 출발 노드를 정함
    2. 최단 거리 테이블 초기화
    3. 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택
    4. 해당 노드를 거쳐 다른 노드로 가는 비용을 계산하여 최단 거리 테이블 갱신
    5. 위 과정에서 3,4 반복

다익스트라 알고리즘 구현방법

  1. 구현하기 쉽지만 느림
  2. 구현하기 조금 까다롭지만 빠름

1.
시간 복잡도 - O(V^2)

#include <stdio.h>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <deque>
#include <stack>
#include <vector>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
#define MAX 10001
#define INF 1e9 

using namespace std;

int N, cnt, start;
vector<pair<int, int>> v[MAX];
bool visit[MAX];
int ans[MAX];
int findSmallN() {
    int tmpN = INF; // 가장 작은 수
    int index; // 가장 작은 수 index
    for (int i = 1; i <= N; i++) {
        if (ans[i] < tmpN && !visit[i]) {
            tmpN = ans[i];
            index = i;
        }
    }
    return index;
}
void func(int n) {
    visit[n] = true;
    // 배열 초기화
    for (int i = 0; i < v[n].size(); i++) {
        ans[v[n][i].first] = v[n][i].second;
    }
    // 노드들 중에서 가장 작은수 선택
    int smallN = findSmallN();

    for (int i = 0; i < N - 1; i++) {
        visit[smallN] = true;
        for (int j = 0; j < v[smallN].size(); j++) {
            ans[v[smallN][j].first] = min(ans[v[smallN][j].first], ans[smallN] + v[smallN][j].second);
        }
        smallN = findSmallN();
    }
}
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    // 배열 가장 큰수로 초기화
    fill_n(ans, MAX, INF);

    cin >> N >> cnt >> start;
    ans[start] = 0;

    for (int i = 0; i < cnt; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        v[a].push_back({ b,c });
    }

    func(start);
    for (int i = 1; i <= N; i++) {
        cout << ans[i]<<"\n;
    }

}

2.

시간복잡도 - O(ElogV)

(E - 간선 갯수, V - 노드 갯수)

힙 자료구조 이용

#include <stdio.h>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <deque>
#include <stack>
#include <vector>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
#define MAX 100001
#define INF 1e9 // 무한을 의미하는 값으로 10억을 설정

using namespace std;

// 노드의 개수(N), 간선의 개수(M), 시작 노드 번호(Start)
// 노드의 개수는 최대 100,000개라고 가정
int n, m, start;
// 각 노드에 연결되어 있는 노드에 대한 정보를 담는 배열
vector<pair<int, int> > v[100001];
// 최단 거리 테이블 만들기
int ans[100001];

void func(int n) {
    priority_queue < pair<int, int>, vector<pair<int,int>>> pq;
    pq.push({ 0,n });
    ans[n] = 0;
    while (!pq.empty()) {
        int node = pq.top().first;
        int now = pq.top().second;
        pq.pop();

        if (ans[now] < node) continue; //이미 연결한 적이 있는경우
        for (int j = 0; j < v[now].size(); j++) {
            int tmp = node + v[now][j].second;
            if (tmp < ans[v[now][j].first]) {
                ans[v[now][j].first] = tmp;
                pq.push({ tmp, v[now][j].first });
            }
        }

    }
}
int main(void) {
    cin >> n >> m;
    cin >> start;
    for (int i = 0; i < m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        v[a].push_back({ b,c });

    }
    fill(ans, ans+ 100001, INF);

    func(start);

    for (int i = 1; i <= n; i++) {
        if (ans[i] == MAX) { cout << "INF" << "\n"; }
        else { cout << ans[i] << "\n"; }
    }
}

cf ) 힙이란?

  • 우선순위 큐 - 우선순위가 가장 높은 데이터를 가장 먼저 삭제

  • 데이터를 우선순위에 따라 처리하고 싶을 때 사용

  • 다익스트라 최단 경로 알고리즘에서는 비용이 적은 노드를 우선시함 (최소힙)

최소 힙을 최대 힙 처럼 사용하기
일부러 우선순위에 해당하는 값에 음수 부호(-)를 붙여서 넣었다가, 나중에 우선순위 큐에서 꺼낸 다음에 음수 부호(-) 를 붙여서 원래의 값으로 돌려놓는 방식
  • C++ 최소힙/ 최대힙

      priority_queue<int, vector<int>, greater<int>> q; // 최소힙
      priority_queue<int, vector<int>> q;   // 최대힙

자료구조 


플로이드 워셜 알고리즘

모든 지점에서 다른 모든 지점까지의 최단 경로를 모두 구해야 하는 경우

다익스트라 알고리즘과 비교하면 구현 과정에서 어려움을 겪지 않을 것이지만핵심아이디어가 중요하다

시간 복잡도 - O(N^3)

#include <stdio.h>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <deque>
#include <stack>
#include <vector>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<map>
#define MAX 100001
#define INF 1e9 // 무한을 의미하는 값으로 10억을 설정

using namespace std;

// 노드의 개수(N), 간선의 개수(M), 시작 노드 번호(Start)
// 노드의 개수는 최대 100,000개라고 가정
int n, m, start;
// 각 노드에 연결되어 있는 노드에 대한 정보를 담는 배열
vector<pair<int, int> > v[100001];
// 최단 거리 테이블 만들기
int ans[100001];

void func(int n) {
    priority_queue < pair<int, int>, vector<pair<int,int>>> pq;
    pq.push({ 0,n });
    ans[n] = 0;
    while (!pq.empty()) {
        int node = pq.top().first;
        int now = pq.top().second;
        pq.pop();

        if (ans[now] < node) continue; //이미 연결한 적이 있는경우
        for (int j = 0; j < v[now].size(); j++) {
            int tmp = node + v[now][j].second;
            if (tmp < ans[v[now][j].first]) {
                ans[v[now][j].first] = tmp;
                pq.push({ tmp, v[now][j].first });
            }
        }

    }
}
int main(void) {
    cin >> n >> m;
    cin >> start;
    for (int i = 0; i < m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        v[a].push_back({ b,c });

    }
    fill(ans, ans+ 100001, INF);

    func(start);

    for (int i = 1; i <= n; i++) {
        if (ans[i] == MAX) { cout << "INF" << "\n"; }
        else { cout << ans[i] << "\n"; }
    }
}

'Algorithm > C++' 카테고리의 다른 글

[개념] 다이나믹 프로그래밍(동적계획법)  (0) 2020.10.11
[개념] DFS/BFS  (0) 2020.10.09
[개념] 이진탐색  (0) 2020.10.09
[개념] 정렬 알고리즘  (0) 2020.10.01

연산 속도와 메모리 공간을 최대한으로 활용할 수 있는 효율적인 알고리즘

다이나믹 프로그래밍 사용할 수 있는 경우

  1. 큰 문제를 작은 문제로 나눌 수 있다
  2. 작은 문제에서 구한 정답은 그것을 포함하는 큰 문제에서도 동일하다

메모이제이션 (캐싱)

  • 다이나믹 프로그래밍을 구현하는 방법 중 한 종류로, 한 번 구한 결과를 메모리 공간에 메모해두고 같은 식을 다시 호출하면 메모한 결과를 그대로 가져오는 기법
  • 한 번 구한 정보를 리스트에 저장하는 것

탑다운(Top-Down) / 보텀업(Bottom-Up) 방식

탑 다운 방식 (메모이제이션)

  • 하향식
  • 재귀 함수를 이용해 다이나믹 프로그래밍 소스코드를 작성하는 방법
  • 큰 문제를 해결하기 위해 작은 문제를 호출 하는 방법

보텀업 방식 (DP)

  • 다이나믹 프로그래밍의 전형적인 형태
  • 상향식
  • 단순히 반복문을 이용해 소스코드를 작성하는 경우 작은 문제부터 차근차근 답을 도출하는 방법

대표 문제 : 피보나치 수열

#include <iostream>
#include <stdlib.h>
#include<cstdio>
#include<algorithm>
using namespace std;

int fibo(int x) {
    if (x == 0 || x == 1) {
        return 1;
    }
    return fibo(x - 1) + fibo(x - 2);
}

int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);

    int num[6];
    memset(num,sizeof(num), 0);

    cout<<fibo(5);
}

'Algorithm > C++' 카테고리의 다른 글

[개념] 최단 경로 구하기  (0) 2020.10.13
[개념] DFS/BFS  (0) 2020.10.09
[개념] 이진탐색  (0) 2020.10.09
[개념] 정렬 알고리즘  (0) 2020.10.01

스택

  • 박스 쌓기에 비유
  • 선입후출 구조 혹은 후입 선출 구조

  • 놀이공원 줄서기 비유
  • 선입선출 구조

queue 보다는 deque 구조 사용
스택과 큐의 장점을 택한것이고 데이터를 넣고 뺴는 속도가 리스트 자료형에 디배 효율적이며 queue라이브러리를 사용하는 것 보다 간단하다

재귀함수

  • 자기자신을 호출하는 함수
  • 재귀함수 사용시 스택 자료 구조 이용
  • 함수를 계속 호출했을 때 가장 마지막에 호출함 함수가 먼저 수행을 끝내야 그 앞의 함수 호출이 종료됨

재귀 함수 사용시 재귀함수가 언제 끝나고, 종료할지 꼭 명시

 


팩토리얼 예제

#include <stdio.h>
#include <string>
#include <iostream>
#include <stdlib.h>
using namespace std;
int answer = 0;
int factorial(int n) {
    // n = 1 인경우 재귀함수 멈춤
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

int main (){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);

    int num;
    cin >> num;

    cout<<factorial(num);

}

DFS 스택 자료구조

  1. 탐색 시작 노드를 스택에 삽입하고 방문 처리
  2. 스택의 최상단 노드를 방문하지 않은 인접 노드가 있으면 그 인접 노드를 스택에 넣고 방문 처리,
    방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드를 꺼냄
  3. 2번 과정을 더이상 수행할 수 없을 때까지 반복

cf ) 방문 처리 - 스택에 한 번 삽입되어 처리된 노드가 다시 삽입되지 않게 체크하는 것

BFS

  • 너비 우선 탐색
  • 가까운 노드부터 탐색하는 알고리즘
  • 선입선출 방식인 큐 자료구조 사용
  • 인접한 노드를 반복적으로 큐에 놓도록 알고리즘 작성하면 자연스럽게 먼저 들어온 것이 먼저 나가게됨
  1. 탐색 시작 노드를 큐에 삽입하고 방문 처리함
  2. 큐에서 노드를 꺼내 해당 노드의 인접노드 중 방문하지 않은 노드를 모두 큐에 삽입 후 방문처리
  3. 2번 과정을 더이상 수행할 수 없을 때까지 반복

너비 우선 탐색은 DFS보다 수행시간이 좋음

 

dfs / bfs 비교

'Algorithm > C++' 카테고리의 다른 글

[개념] 최단 경로 구하기  (0) 2020.10.13
[개념] 다이나믹 프로그래밍(동적계획법)  (0) 2020.10.11
[개념] 이진탐색  (0) 2020.10.09
[개념] 정렬 알고리즘  (0) 2020.10.01

순차 탐색

최악의 시간 복잡도: O(N)

리스트 안에 있는 특정한 데이터를 찾기 위해 앞에서부터 데이터를 하나씩 차례대로 확인하는 방법

보통 정렬되지 않은 리스트에서 데이터를 찾을 때 사용

#include <stdio.h>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <vector>
using namespace std;

int main (){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    int num;
    vector<string> str;
    string target;
    int ans;
    cin >> num >> target;

    for (int i = 0; i < num; i++) {
        string strTmp;
        cin >> strTmp;
        str.push_back(strTmp);
    }
    for (ans = 0; ans < num; ans++) {
        if (str[ans] == target) {
            break;
        }
    }
    cout << ans+1;
}

이진탐색 : 반으로 쪼개면서 탐색하기

시간복잡도: O(logN)

배열 내부가 정렬되어있어야만 사용가능

#include <iostream>
#include <stdlib.h>
#include <vector>
#include<cstdio>
#include<algorithm>
using namespace std;

int cnt;
vector<int> num;
int target;

int func(int left, int right) {
    if (left > right) return -1;
    int mid = (left + right) / 2;
    if (num[mid] == target) {
        return mid;
    }
    else if (num[mid] > target) {
        return func(left, mid - 1);
    }
    else {
        return func(mid + 1, right);
    }

}

int main (){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    cin >> cnt >> target;
    for (int i = 0; i < cnt; i++) {
        int tmp;
        cin >> tmp;
        num.push_back(tmp);
    }
    sort(num.begin(), num.end());
    int ans = func(0, cnt-1);
    if (ans == -1) {
        cout << "원소가 존재하지 않습니다.";
        return 0;
    }
    cout << ans+1;

}

Parametric Search (파라메트릭 서치)

최적화 문제를 결정하는 문제로 바꿔서 해결하는 기법

⇒ 결정하는 문제 = '예' '아니오'로 대답

ex ) 범위 내에서 조건을 만족하는 가장 큰 값을 찾으려는 최적화 문제라면 이진 탐색으로 결정 문제를 해결하면서 범위를 좁힐 수 있다

떡볶이 떡 자르기 문제

  • '떡볶이 떡 자르기' 문제를 예로 하면 적절한 높이를 찾을 때까지 절단기의 높이 H를 반복적으로 조정한다
  • 현재 이 높이로 자르면 조건을 만족할 수 있는가 → 예 vs 아니오
  • 범위로 좁힐 때는 이진 탐색 원리를 이용해 절반찍 좁혀나간다
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <vector>
#include<cstdio>
#include<algorithm>
using namespace std;
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);

    int cnt;
    int etc;
    vector<int> rice;
    cin >> cnt >> etc;
    for (int i = 0; i < cnt; i++) {
        int tmp;
        cin >> tmp;
        rice.push_back(tmp);
    }
    int left, right;
    sort(rice.begin(), rice.end());
    left = 0;
    right = rice.back();
    int ans, mid;

    while (left < right) {
        ans = 0;
        mid = (left + right) / 2;
        for (int i = 0; i < cnt; i++) {
            int tmp = rice[i] - mid < 0 ? 0 : rice[i] - mid;
            ans += tmp;
        }
        if (ans == etc)  break;
        else if (ans < etc) {
            right = mid - 1;
        }
        else {
            // 적어도 M만큼의 떡을 얻기 위해
            result = mid;
            left = mid + 1;
        }
    }
    cout << result;

}

'Algorithm > C++' 카테고리의 다른 글

[개념] 최단 경로 구하기  (0) 2020.10.13
[개념] 다이나믹 프로그래밍(동적계획법)  (0) 2020.10.11
[개념] DFS/BFS  (0) 2020.10.09
[개념] 정렬 알고리즘  (0) 2020.10.01

정렬

  • 데이터를 특정한 기준에 따라 순서대로 나열한 것

선택정렬

  • 시간복잡도 - $O(N^2)$
  • 가장 작은 데이터를 선택해 맨 앞에 있는 데이터와 바꾸고, 그다음 작은 데이터를 선택해 앞에서 두번째 데이터와 바꾸는 과정을 반복
  • 가장 작은 것을 선택
#include <iostream>
#include <stdlib.h>
#include<cstdio>
#include<algorithm>
using namespace std;

int main (){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    int num[] = { 7,5,9,0,3,1,6,2,4,8 };
    // 선택 정렬
    for (int i = 0; i <10; i++) {
        int idx = i; // 가장 작은 원소 인덱스
        for (int j = i; j < 10; j++) {
            if (num[j] < num[idx]) {
                idx = j;
            }
        }
        swap(num[idx], num[i]);
    }
    for (int i = 0; i < 10; i++) {
        cout << num[i] << " ";
    }

삽입 정렬

알고리즘 문제 풀이에 사용하기엔 느림
But 정렬이되어 있는 상태로 입력이 주어지는 문제라면 다른 알고리즘을 사용하는 것보다 효율적

  • 시간 복잡도 - 최선 $O(N)$ , 최악 $O(N^2)$
  • 필요할 때만 위치를 바꿔서 '데이터가 거의 정렬 되어 있을 때' 효율적
  • 선택 정렬은 현재 데이터의 상태와 상관없이 무조건 모든 원소를 비교하고 위치를 바꾸는 반면 삽입 정렬은 그렇지 않다
#include <iostream>
#include <stdlib.h>
#include<algorithm>
using namespace std;

int main (){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    int num[] = { 7,5,9,0,3,1,6,2,4,8 };
    // 삽입 정렬
    for (int i = 1; i < 10; i++) {
        for (int j = i; j>0; j--) {
            if (num[j] < num[j - 1]) {
                swap(num[j], num[j - 1]);
            }
            else {
                break;
            }
        }
    }
    for (int i = 0; i < 10; i++) {
        cout << num[i] << " ";
    }
}

퀵 정렬

c++ STL의 algorithm 헤더에 있는 sort() 함수 ⇒ 퀵정렬

  • 시간 복잡도 - $O(NlongN)$
  • 기준을 설정한 다음 큰 수와 작은 숫자를 교환한 후 리스트를 반으로 나누는 방식
  • 피봇 : 큰 수와 작은 수를 교환 할 때, 교환하기 위한 기준을 명칭
#include <iostream>
#include <stdlib.h>
#include<cstdio>
#include<algorithm>
using namespace std;
void quickSort(int* arr, int start, int end) {
    if (start >= end) return; // 원소가 1개인 경우 종료
    int pivot = start; // 피벗은 첫 번째 원소
    int left = start + 1;
    int right = end;
    while (left <= right) {
        // 피벗보다 큰 데이터를 찾을 때까지 반복
        while (left <= end && arr[left] <= arr[pivot]) left++;
        // 피벗보다 작은 데이터를 찾을 때까지 반복
        while (right > start && arr[right] >= arr[pivot]) right--;
        // 엇갈렸다면 작은 데이터와 피벗을 교체
        if (left > right) swap(arr[pivot], arr[right]);
        // 엇갈리지 않았다면 작은 데이터와 큰 데이터를 교체
        else swap(arr[left], arr[right]);
    }
    // 분할 이후 왼쪽 부분과 오른쪽 부분에서 각각 정렬 수행
    quickSort(arr, start, right - 1);
    quickSort(arr, right + 1, end);
}

int main (){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    int num[] = { 7,5,9,0,3,1,6,2,4,8 };
    // 퀵 정렬
    // 라이브러리 이용
    // sort(num, num + 10);
    // 일반 구현
    quickSort(num, 0, 9);
    for (int i = 0; i < 10; i++) {
        cout << num[i] << " ";
    }

}

계수 정렬

  • 시간복잡도 - $O(N+K)$
  • 특정한 조건이 부합할 때만 사용할 수 있지만 매우 빠른 정렬 알고리즘
  • 데이터의 크기 범위가 제한되어 정수로 표현할 수있을 때만 사용 가능
  • 가장 큰 데이터와 작은 데이터 크기가 작아야함
#include <iostream>
#include <stdlib.h>
#include<cstdio>
#include<algorithm>
using namespace std;

int main (){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    int num[] = { 7,5,9,0,3,1,6,2,4,8,0,5,2};
    int ans[13] = {0,};
    // 계수 정렬
    for (int i = 0; i < 13; i++) {
        ans[num[i]]++;
    }
    for (int i = 0; i < 10; i++) {
        if (ans[i] > 0) {
            while (ans[i]--) {
                cout << i << " ";
            }
        }
    }
}

C++ sort STL

오름차순 (default)

  • sort()는 algorithm 헤더 파일에 속함
  • 퀵 정렬이용
  • 평균 시간복잡도 - $O(NlongN)$

사용법

sort(arr, arr+1);
sort(v.begin(), v.end());
sort(v.begin(), v.end(), compare); // compare : 사용자 정의 함수
sort(v.begin(), v.end(), greater<자료형>()); // 내림차순
sort(v.begin(), v.end(), less<자료형>()); // default - 오름차순

'Algorithm > C++' 카테고리의 다른 글

[개념] 최단 경로 구하기  (0) 2020.10.13
[개념] 다이나믹 프로그래밍(동적계획법)  (0) 2020.10.11
[개념] DFS/BFS  (0) 2020.10.09
[개념] 이진탐색  (0) 2020.10.09

+ Recent posts