그래프

노드와 노드사이에 연결된 간선의 정보를 가지고 있는 자료구조

ex ) 여러개의 도시가 연결되어 있다

 

 

그래프 구현 방법

  1. 인접 행렬 : 2차원 배열을 사용
  2. 인접 리스트 : 리스트를 사용

두가지 방식 특징 ( 노드의 갯수 V, 간선의 갯수 E인 그래프)

인접 행렬 이용

  • 장점 - 특정한 노드 A에서 다른 특정한 노드 B로 이어진 간선의 비용을 $O(1)$의 시간으로 즉시 알수 있음
  • 단점 - 간선 정보를 저장하기 위해 $O(V^2)$ 만큼의 메모리 공간이 필요

인접 리스트 이용

  • 장점 - $O(V)$만큼의 시간 소요
  • 단점 - 간선의 개수만큼 $O(E)$만큼의 메모리 공간 필요

서로소 집합 (유니온 파인드 - Union find)

공통 원소가 없는 두 집합

union - 합집합 연산 - 2개의 원소가 포함된 집합을 하나의 집합으로 합침

find - 찾기 연산 - 특정 원소가 속한 집합이 어떤 집합인지 알려줌

트리 자료 구조 이용

  1. union(합집합) 연산을 확인하여, 서로 연결된 두 노두 A,B 확인
    1. A와 B의 루트 노드 A', B'를 찾음
    2. A'를 B'의 부모 노드로 설정
  2. 모든 union(합집합) 연산을 처리할 때까지 1번 과정 반복

(A', B' 중 보통 더 작은 숫자가 부모가 됨)

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

using namespace std;

int parent[MAX];
int V, E;
int findParent(int num) {
    if (num == parent[num]) return num;
    findParent(parent[num]);
}
void func(int a, int b) {
    a = findParent(a);
    b = findParent(b);
    if (a > b) parent[a] = b;
    else parent[b] = a;
}
int main(void) {
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);

    cin >> V >> E;
    for (int i = 1; i <= V; i++) {
        parent[i] = i;
    }
    for (int i = 0; i < E; i++) {
        int a, b;
        cin >> a >> b;
        func(a, b);
    }
    // 각원소가 속한 최상위 집합
    int cnt = 0;
    for (int i = 1; i <= V; i++) {
        cout << findParent(i) << " ";
    }
    cout << "\n";
    // 각원소의 부모 테이블
    for (int i = 1; i <= V; i++) {
        cout << parent[i] << " ";
    }

}

⇒ 해당 코드는 만약 하나의 그래프로 쭉~연결되어 있다면 최대 O(V)시간이 걸릴 수 있다

노드개수가 V이고 find 혹은 union연산 개수가 M개인 경우 전체 시간복잡도는 O(VM)이 되어 비효율적임

find 과정을 최적화 하는 법

  • 경로 압축기법 - 재귀적 호출한 뒤 부모 테이블값 갱신
int findParent(int num) {
    if (num == parent[num]) return num;
    return parent[num] = findParent(parent[num]);
}

서로소 집합을 활용한 사이클 판별

무방향 그래프 내에서의 사이클 판별할 때 사용
  1. 각 간선을 확인하며 두 노드의 루트 노드를 확인
    1. 루트 노드가 서로 다르면 두 노드에 대해 union연산 수행
    2. 루트 노드가 서로 같다면 사이클 발생
  2. 그래프에 포함되어 있는 모든 간선에 대해 1번 과정 반복
#include <stdio.h>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <deque>
#include <stack>
#include <vector>
#include<cstdio>
#include<algorithm>
#include<memory.h>
#include<queue>
#include<map>
#define MAX 100001
#define INF 1e9 // 무한을 의미하는 값으로 10억을 설정

using namespace std;

int parent[MAX];
int V, E;
int findParent(int num) {
	if (num == parent[num]) return num;
	return parent[num] = findParent(parent[num]);
}
void func(int a, int b) {
	a = findParent(a);
	b = findParent(b);
	if (a > b) parent[a] = b;
	else parent[b] = a;
}
int main(void) {
	ios_base::sync_with_stdio(0);
	cin.tie(NULL);
	cout.tie(NULL);

	cin >> V >> E;
	bool check;
	for (int i = 1; i <= V; i++) {
		parent[i] = i;
	}
	for (int i = 0; i < E; i++) {
		int a, b;
		cin >> a >> b;
		if (findParent(a) == findParent(b)) {
			check = false;
		}
		else {
		func(a, b);
		}
	}
	if (check) {
		cout << "사이클 없음";
	}
	else {
		cout << "사이클 있음";
	}

}

 

 

가장 빠르게 도달 하는 방법

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

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

예 )

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

종류

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

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

각 노드에서 현재까지의 최단거리 정보를 항상 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

문제

[링크] 프로그래머스 이중우선순위큐

아이디어

  • 처음에는 priority_queue로 풀려고 했는데 생각해보니 priority_queue는 index를 직접 접근을 못하는걸로 알고 있다

  • 그래서 앞뒤로 삽입,삭제 가능한 deque를 사용하기로함

  • string 이 값이므로 일단 D인지 I인지를 구문해야함

  • 구분한후 뒤에 숫자는 문자이기에 string 헤더를 넣어서 stoi(문자열을 숫자로 바꿔주는 함수) 사용

풀이 순서

  1. 일단 I가 나오는 경우는 삽입 후 소팅 바로 함 (오름차순)
  2. D인경우 중 문제에서 보면 비어있는데 삭제 하려는 경우는 무시하라고 함 -> 여기에서 비어있지 않은 경우 연산을 하도록 조건 걸어줌
    1. 1이면 가장 큰 값 삭제
    2. -1 이면 가장 작은 값 삭제
  3. 큐가 비어있는경우 0,0 강제로 넣음
  4. 큐에 값이 있는 경우 가장 큰 수, 가장 작은 수 순으로 넣음

코드

#include <string>
#include <vector>
#include<queue>
#include<string>
#include<iostream>
#include<cstdio>
#include<functional>
#include<deque>
#include<algorithm>
using namespace std;

vector<int> solution(vector<string> operations) {
    vector<int> answer;
    deque<int> dq;
    for(int i=0;i<operations.size();i++){
        string s=operations.at(i);
        int num = stoi(s.substr(2, s.length()-2));

        if(s[0]=='I'){
            dq.push_back(num);
            sort(dq.begin(),dq.end());
        } else if(!dq.empty()) {
              if (num==1){
                dq.pop_back();
                } else if (num == -1){
                dq.pop_front();
            }
        }
    }
    if(dq.empty()){
        answer.push_back(0);
        answer.push_back(0);
    } else {
        answer.push_back(dq.back());
        answer.push_back(dq.front());
    }
    return answer;
}

정답확인

깃 링크

[링크] 이중우선순위큐-프로그래머스

'Algorithm' 카테고리의 다른 글

[개념] Union-Find 기법  (0) 2020.10.21
[프로그래머스] 디스크컨트롤러 - 힙  (0) 2020.06.24
[프로그래머스-그리디] 체육복  (0) 2020.05.11

문제

문제: 프로그래머스-디스크컨트롤러

주의

  • 문제를 잘 읽어야한다
  • 작업하는 것이 없는 경우 나머지 작업들중 가장 시작이 빠른 작업 진행

문제 해결 아이디어

  • 전제가 아무것도 없는 경우 가장 먼저 작업하는 것을 실행하기에 배열을 sort해야함
    • sort하는 경우 시작 시간이 같으면 시간이 더 오래걸리는 순으로 소팅
  • 우선순위 큐에서 정렬하는 기준은 소요 시간이 가장 긴것을 기준으로 정렬
    • 왜? 그래야 빨리 끝냄 오래된것 먼저 해결~
  • 변수 - 작업 여부관련 변수(idx), 시작 시간 판별 변수(time)
  • idx가 jobs보다 작거나 pq가 비어있지 않은 경우 진행 가능
    • 만약 시작 시간이 현재 시간보다 작거나 같고 (AND) idx가 jobs크기보다 작으면
      • 큐에 삽입 & idx++ 계속 진행
    • 작업 시작 시간이 현재 시간보다 크면 작업 시간이 큰것 부터 실행 단 비어있을때까지
      • 첫번째 값 time+=큐의 첫번째 진행시간
      • answer = answer+ (time + 큐의 진행시간) - 큐의 시작시간
        즉, answer+=time-큐의 시작시간
    • 큐 비어있는데 아직 작업 완료 안함 그리고 아직 시작 시간이 먼 경우
      • time 기준을 현재 idx의 시작 시간으로 변경

코드

#include<cstdio>
#include<queue>
#include<iostream>
#include<algorithm>
#include<vector>
#include<functional>
using namespace std;
// 아무작업도 하고있지 않는 경우 먼저 들어온 요청 부터 작업 수행
// idea 
// 1. 실행순으로 입력값을 sort한다 만약 시작 시간이 같으면 진행시간이 더 짧은걸루
// 2. 우선순위 큐 조건에서 실행시간이 짧은 것 먼저 pop
// 
bool jobscompare(vector<int> a, vector<int> b){
    if(a[0]==b[0]){
        return a[1]<b[1];
    }else {
        return a[0]<b[0];
    }
}
struct compare{
    bool operator()(vector<int> a, vector<int> b){
        return a[1]>b[1];
    }
};
int solution(vector<vector<int>> jobs){
    int answer=0;
    int time=0; // 현재 시간
    int idx =0; // 작업 관련 index

    sort(jobs.begin(), jobs.end(), jobscompare); 
    priority_queue<vector<int>, vector<vector<int>>, compare> pq;
    while(idx<jobs.size()||!pq.empty()){
        if(idx<jobs.size()&&time>=jobs[idx][0]){
            pq.push(jobs[idx]);
            idx++;
            continue;
        }
        if(!pq.empty()){
            vector<int> tmp = pq.top();
            time+=tmp[1];
            answer+=time-tmp[0];
            pq.pop();
        } else {
            // pq가 비어있으면서 다음 값이 있는데 엄청 멀리 있는경우 time을 멀리있는 작업의 시작시간으로 설정 
            time=jobs[idx][0];
        }
    }
    return answer/jobs.size();
} 
int main(){
    vector<vector<int>> jobs{{0,3},{1,9},{2,6}};
    int ans = solution(jobs);
    cout<<ans;
}

인증

배운점

  • 문제를 잘 읽자 이번에는 아무것도 없으면 먼저 시작하는것부터 시작한다를 빼먹어서 삽질좀함
  • priority_queue할때 맨마지막 비교 관련 함수는 구초체로 선언해야한다

'Algorithm' 카테고리의 다른 글

[개념] Union-Find 기법  (0) 2020.10.21
[프로그래머스]이중우선순위큐 - 힙  (0) 2020.06.25
[프로그래머스-그리디] 체육복  (0) 2020.05.11

+ Recent posts