일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 회고
- kotlin강좌
- 커스텀상태
- 자바
- theming
- Gradle
- mockito
- 안드로이드강좌
- Coroutine
- Compose
- android
- 글또
- 테스트
- 코틀린
- 병렬프로그래밍
- Kotlin
- ReactiveProgramming
- g 단위테스트
- 디자인패턴
- viewmodel
- 안드로이드
- 스레드
- 알고리즘
- Rxjava
- 책
- 코루틴
- 안드로이드스튜디오
- 병럴프로그래밍
- 알게되는
- k8s
- Today
- Total
선생님, 개발을 잘하고 싶어요.
자료구조 정리 C++ 본문
내장된 라이브러리가 존재
선형 자료 구조
# 정적 배열 (array)
- 인덱스를 통해 자료에 접근
# 동적 배열 (std::vector)
- 정적 배열과 같으나, 배열의 크기를 런타임에 바꿀 수 있다.
정렬 - algorithm 내의 sort, partial_sort, stable_sort 를 사용 => 정렬을 하기위해 비교 함수를 명시해야 한다. => 비교 함수 사용법 알아둘 필요가 있다.
검색 - 정렬후 이진탐색 (lower_bound, upper_bound, binary_search)
# 연결 리스트 (std::list)
- 임의의 위치에 새로운 문자를 삽입 할 때 효율적 (그 외에는 배열을 사용하도록 하자)
# 스택 (std::stack)
- 후입선출 (LIFO)
- push, pop
# 큐 (std::queue)
- BFS(너비 우산 팀섹)와 같은 알고리즘에 사용된다.
- 선입선출 (FIFO)
# 데크 (std::deque)
- 배열과 큐와 비슷하지만 앞과 뒤에서 빠르게 삽입 삭제가 가능하다.
비선형 자료 구조
# 균형 잡힌 이진 검색 트리 (std::map, std::set)
- AVL 트리, 레드-블랙 트리로 균형 잡힌 이진 트리를 구현해 놓은 라이브러리 (보통 레드-블랙 트리로 구현)
- 삽입, 검색, 삭제 작업이 O(logN)
- map 은 키->데이터 페어를, set 은 키만을 저장한다
# 힙 (std::priority_queue : 최대힙 기본)
- 완전 이진 트리 (x를 루트로 하는 서브트리의 각각은 x보다 작아야 한다 : 우선순위 큐)
- 최대 값 추출, 새로운 원소 추가를 O(logN) 에 할 수 있고, 최대 값을 알아내는 것은 O(1) 시간에 할 수 있다.
# 해시 테이블 (std::unordered_map)
- 대회에선 map을 사용해도 충분하다!
자체적인 라이브러리가 필요한 자료 구조
# 그래프
- 정점의 집합과 간선의 집합으로 구성된다.
A) 인접 행렬 (2차원 배열 형태 / int adjMat[V][V])
- 정점의 개수 V에 대해 O(V^2)의 공간 복잡도를 가진다.
- 작은 크기의 밀집 그래프(dense graph)에 속한 두 정점 사이의 연결 관계 확인 (O(1))에 확인가능) 시 유리
- 큰 크기의 희소 그래프(sparse graph)는 비효율적
- V가 1000보다 크면 보통 사용하기 힘들다.
- 정점 v와 연결된 이웃을 나열할 때 O(V)만큼의 시간이 든다(실제로 이웃이 몇 안되도 무조건 최악의 알고리즘이 된다).
B) 인접 리스트 (vector<vii> adjList / using ii = pair<int,int>; using vii = vector<ii>)
- 각 정점 u의 이웃 목록 + 간선 정보(주로 가중치) 를 저장
- 공간 복잡도는 O(V+E) 이고, 보통 E는 O(V^2) 수보다 훨씬 작다.
- 정점 v의 이웃 수가 k 일대, 이웃 나열 작업 시간 복잡도는 O(k)가 된다.
C) 간선 리스트 (vector<pair<int, ii> > edgeList)
- 간선만큼 저장하기 때문에 공간 복잡도는 O(E) 이다.
- 간선의 가중치 목록을 정렬할때 괜찮게 사용 (크루스칼 알고리즘)
- 묵시적 그래프
# 유니온-파인드 상호 배타적 집합 (Union-Find Disjoint Set, UFDS)
- 상호 배타적인 집합들의 모임
- 어떤 원소가 어느 집합에 속해 있는지 판별 (거의 O(1))
- 두 집합을 더 큰 하나의 집합으로 합치는 작업 (거의 O(1)))
- 각 원소마다 부모 원소의 번호 / 각 트리의 높이를 저장 (vi p, vi rank)
1. 모든 rank 를 0으로 / 모든 p 를 자기 자신으로 초기화 한다.
2. union(i,j) 작업은 i와 j가 동일한 대표 원소를 갖도록 만든다. (랭크가 더 높은 집합의 대표 원소를 -> 랭크가 더 낮은 집합의 새 부모로 삼으면 "랭크 최소화" 가능)
3. 경로 압축을 통해 모든 원소가 "부모 노드로 루트를 직접 가리키도록" 만든다. (find(i) 는 단순히 p[i]를 참조하는 것과 비슷하므로 매우 빠를것!)
C++ 구현 코드
(
#include <vector>
#include <set>
#include <algorithm>
using vi = std::vector<int>;
using si = std::set<int>;
class UnionFind {
private: vi p, rank, size;
public:
UnionFind(int N) {
rank.assign(N,0); p.assign(N,0); for (int i=0;i<N;i++) p[i] = i; size.assign(N,1);
}
int findSet(int i) {
return (p[i] == i) ? i : (p[i] = findSet(p[i]));
}
bool isSameSet(int i, int j) {
return findSet(i) == findSet(j);
}
void unionSet(int i, int j) {
if (!isSameSet(i, j)) { // 다른 집합일때만
int x = findSet(i), y = findSet(j);
if (rank[x] > rank[y]) swap(x,y); // 랭크가 더 높은 집합의 대표 원소를 랭크가 더 낮은 집합의 새 부모로 결정한다.
if (rank[x] == rank[y]) rank[y]++; // 랭크가 같으면 랭크를 하나 증가시킨다.
p[x] = y; size[y] = size[x] + size[y];
}
}
// 여기부터는 내가 직접 만들어보는 유용한 함수들
int numDisjointSets() { // 현재 구조에 존재하는 상호 배타적 집합의 개수를 반환 O(NlogN)
si s(p.begin(), p.end()); return s.size();
}
int sizeOfSet(int i) { // 현재 원소 i가 속해 있는 집합의 크기를 반환 O(1)
return size[findSet(i)];
}
};
)
# 구간 트리
- 동적인 구간 질의에 효율적으로 답하기 위한 자료 구조 (배열 [0..n] 중에 [i..j] 중에 가장 ~한 것은?)
- 구간 트리를 만드는데 O(n)
- 구간 트리를 만들면 구간 질의엔 O(nlogn)
@ 구간이 정적이라면 O(1)에 해결하는 DP 풀이가 있다.
- 원소 갱신에 O(logn) -> 이것 때문에 동적인 구간 질의에서 사용!
부분합 구간 트리 구현해본거
class ST {
private:
vi A;
vll st;
int n;
int left(int p) { return p << 1; }
int right(int p) { return (p << 1) + 1; }
void build(int p, int L, int R) { // O(n)
if (L == R)
st[p] = A[L];
else {
build(left(p), L, (L + R) / 2);
build(right(p), (L + R) / 2 + 1, R);
st[p] = st[left(p)] + st[right(p)];
}
}
long long rsq(int p, int L, int R, int i, int j) { // O(logn)
if (i > R || j < L) return 0; // 질의 구간의 바깥 쪽
if (L >= i && R <= j) return st[p]; // 질의 구간의 안쪽
long long s1 = rsq(left(p), L, (L + R) / 2, i, j);
long long s2 = rsq(right(p), (L + R) / 2 + 1, R, i, j);
return s1 + s2;
}
void update(int p, int L, int R, int i, long long diff) {
if (i < L || i > R) return;
st[p] += diff;
if (L != R) {
update(left(p), L, (L + R) / 2, i, diff);
update(right(p), (L + R) / 2 + 1,R, i, diff);
}
}
public:
ST(const vi &_A) {
A = _A; n = (int)A.size();
st.assign(4 * n, 0);
build(1, 0, n - 1);
}
long long rsq(int i, int j) { return rsq(1, 0, n - 1, i, j); }
void update(int i, long long diff) {
update(1, 0, n - 1, i, diff);
}
};
# 펜윅 트리 (이진 인덱스 트리)
- [1..n] 범위의 정수 키 m 개가 주어졌을 때
- 질의 및 갱신 O(n) 공간 & O(nlogn) 시간에 처리
@ 정적인 구간에선 O(n)의 사전작업 후 O(1)에 질의 가능
using vi = std::vector<int>;
class FenwickTree {
private: vi ft;
public: FenwickTree(int n) { ft.assign(n+1, 0); } // [1..n] 범위를 사용하고 0은 무시
int rsq(int b) {
int sum = 0; for (; b; b -= LSOne(b)) sum += ft[b]; // LSOne 은 제일 오른쪽 켜진 비트를 반환한다. (ex 110 (6) 은 010 (2) 를 반환)
return sum;
}
int rsq(int a, int b) {
return rsq(b) - (a == 1 ? 0 : rsq(a-1));
}
void adjust(int k, int v) { // k번째 원소의 값을 v만큼 조정한다 (v가 양수이면 증가, v가 음수이면 감소)
for(; k < (int)ft.size(); k += LSOne(k)) ft[k] += v;
}
};
'개발 > 알고리즘 공부' 카테고리의 다른 글
백준 2156번 포도주 시식 (0) | 2019.04.22 |
---|---|
백준 15954번 문제 풀기 (0) | 2018.09.27 |