2022. 7. 2. 01:03ㆍTIL💡/Algorithms
Union-Find
- 처음에는 parent[i] = i로 초기화한다.
- find로 조상의 루트로 갱신함과 동시에 리턴한다. 이를 통해 트리의 레벨을 최소화해 시간복잡도를 낮춘다. → 경로 압축
- 그냥 타고타고 올라가서 조상의 번호를 리턴하면 최대 시간복잡도는 $O(N)$이가 되기 때문이다.
- 트리의 랭크는 높이와 같은 의미를 갖지만 경로 압축을 사용하면 높이와 다른 값을 가질 수도 있다. 따라서 높이 대신 랭크라는 표현을 사용한다.
- 랭크가 작은 것을 랭크가 높은 것의 자식으로 만든다.
💚 2606 바이러스
- 컴퓨터가 연결된 상태가 주어진다.
- 1번 컴퓨터가 웜 바이러스에 걸렸을 때 웜바이러스에 걸리게 되는 컴퓨터의 수를 구하는 문제
DFS를 통해 모든 컴퓨터 연결 상태를 탐색할 수 있다. 하지만 유니온 파인드로도 풀 수 있다.
#include <iostream>
using namespace std;
int parent[101];
int Find(int x) {
if(x == parent[x]) {
return x;
}
return parent[x] = Find(parent[x]);
}
void Union(int x, int y) {
x = Find(x);
y = Find(y);
if(x != y) {
parent[y] = x;
}
}
int main() {
int n, m;
cin >> n >> m;
for(int i = 1;i <= n;i++) {
parent[i] = i;
}
while(m--) {
int x, y;
cin >> x >> y;
Union(x, y);
}
int ans = 0;
for(int i = 2;i <= n;i++) {
if(Find(1) == Find(i)) {
ans += 1;
}
}
cout << ans << '\n';
return 0;
}
Heap
최소/최대값을 찾을 때나 제거할 때 유용하다.
힙은 이진 트리(자식의 수가 최대2)
최대 힙
- 부모 노드는 자식 노드에 들어있는 값보다 크다.
최대 힙 삽입
- 가장 마지막 위치에 새로운 수를 넣는다.
- 그 수와 부모를 계속해서 비교해가며 루트 < 자식이면 두 수를 바꿔준다.
최대 힙 제거
- 자식과 비교하면서 아래로 내려감
- 루트 > 자식이 만족하도록 두 수를 바꿔가며 내려간다.
최소 힙
- 부모 노드는 자식 노드에 들어있는 값보다 작다.
힙은 완전 이진 트리(Complete Binary Tree)이다. → 높이는 $logN$
C++에서는 priority_queue를 사용하면 된다.
최소 힙을 위해서는 priority_queue<int, vector<int>, greater<int>> q
로 사용 가능하다.
💚 1655 가운데를 말해요
N개의 수가 주어졌을 때
각각의 수가 주어졌을 때마다 지금까지 나온 수의 중간값을 구하는 문제
N이 10만이라는 점을 고려해야한다.
매번 정렬하고 중간값 구하면 $O(N^2logN)$이라는 큰 시간복잡도가 생긴다.
링크드 리스트에 넣어도 매번 정렬할 필요가 없긴 하지만 여전히 시간복잡도는 $O(N^2)$으로 크다.
1) 왼쪽에 있는 수보다 오른쪽에 있는 수가 항상 크도록
2) N / 2씩 왼쪽, 오른쪽으로 나눈다. → 왼쪽은 최대힙으로, 오른쪽은 최소힙으로 구현
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
int n;
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
// 최대 힙
priority_queue<int> l;
// 최소 힙
priority_queue<int, vector<int>, greater<int>> r;
while(n--) {
int x;
cin >> x;
if(l.empty() || r.empty()) {
l.push(x);
}
else {
if(x <= l.top()) {
l.push(x);
}
else {
if(x <= l.top()) {
l.push(x);
}
else if(x >= r.top()) {
r.push(x);
}
else {
// 왼쪽보다도 크고 오른쪽보다도 작으면
l.push(x);
}
}
}
// 동일하거나 r이 l보다 1만큼 큰 게 아니라면
while(!(l.size() == r.size() || l.size() == r.size() + 1)) {
if(l.size() > r.size()) {
r.push(l.top());
l.pop();
}
else {
l.push(r.top());
r.pop();
}
}
cout << l.top() << '\n';
}
return 0;
}
Binary Search Tree(BST, 이진 검색 트리)
- 이진 트리
- 현재 노드의 왼쪽 서브 트리에는 항상 현재 노드의 값보다 작은 값이 들어있고
- 현재 노드의 오른쪽 서브 트리에는 항상 현재 노드의 값보다 큰 값이 들어있다.
특징
- 트리를 inorder 순회를 하면 BST에 저장된 값을 오름차순으로 구할 수 있다.
삭제의 경우에는 총 3가지 경우의 수가 있다.
1. 자식이 0개인 경우 - 그냥 지운다.
2. 자식이 1개인 경우 - 노드를 지우고 자식을 이어 붙인다.
3. 자식이 2개인 경우- 지우려는 노드의 값을 Inorder successor 노드의 값으로 바꿔준 다음 inorder successor 노드를 지운다.
Node *inorder_successor(Node *node) {
Node *p = node;
while(p -> left != NULL) {
p = p -> left;
}
return p;
}
Node *remove(Node *node, int data) {
if(node == NULL) {
return node;
}
if(data < node -> data) {
node -> left = remove(node -> left, data);
} else if(data > node -> data) {
node -> right = remove(node -> right, data);
} else {
if(node -> left == NULL) {
Node *temp = node -> right;
node = NULL;
return temp;
} else if(node -> right == NULL){
Node *temp = node -> left;
node = NULL;
return temp;
}
// 2개인 경우
Node *temp = inorder_successor(node -> right);
node -> data = temp -> data;
node -> right = remove(node -> right, temp -> data);
}
return node;
}
이진 검색트리의 경우 같은 데이터도 어떤 순서로 넣냐에 따라 트리의 형태가 달라지게 된다.
BST의 삽입/삭제/검색은 시간복잡도가 $O(h)$가 된다.
높이의 최대값은 N이 될 수 있기 때문에, $O(N)$이 될 수 있다.
따라서 균형이 맞춰진 트리를 사용하는 것이 좋다.(Balanced BST)
- AVL 트리
- Red-black Tree
- Splay Tree
- Treap
그런데 이건 구현하는 데 오래 걸린다.
그래서 구현하지 않고 있는 걸 쓰자!
💥C++의 경우에는 set, map을 사용한다. 정렬된 상태가 필요없다면 C++은 unordered_set, unordered_map을 사용한다.
✨Java의 경우에는 TreeSet, TreeMap을 사용하고, 정렬된 상태가 필요없으면 HashSet, HashMap을 이용할 수 있다.
'TIL💡 > Algorithms' 카테고리의 다른 글
[프로그래머스] 폰켓몬 (0) | 2022.07.17 |
---|---|
[AtCoder] C - Robot Takahashi (0) | 2022.07.05 |
[AtCoder] Filling 3x3 array (0) | 2022.06.30 |
[AtCoder] Batters (0) | 2022.06.30 |
[백준] 2240: 자두나무 (0) | 2022.06.30 |