일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |
31 |
- graph
- 비트 연산
- Sorting
- 알고리즘
- 배열
- unordered_map
- algorithm
- 이진탐색
- CPP
- 깊이 우선 탐색
- Greedy
- LeetCode
- Interval
- linked list
- C++
- binary search
- bitwise operation
- dynamic programming
- binary
- 메모이제이션
- hash table
- union-find
- 비트 조작
- 탐욕알고리즘
- DFS
- Array
- Sort
- 동적계획법
- Depth-First Search
- bit manipulation
- Today
- Total
공대생 공부노트(Notes for Engineering Studies)
[ leetcode 문제풀이 ] Graph Valid Tree 본문
[ leetcode 문제풀이 ] Graph Valid Tree
301동 노숙자 2022. 2. 26. 20:28
https://leetcode.com/problems/graph-valid-tree/
(Graph Valid Tee 문제는 LeetCode Premium 문제로 위 링크 혹은 LintCode를 통해 풀어보실 수 있습니다.)
< Graph Valid Tee 문제 설명 >
Graph Valid Tree 문제는 다음과 같다.
Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), write a function to check whether these edges make up a valid tree.
Note: you can assume that no duplicate edges will appear in edges. Since all edges are undirected, [0, 1]
is the same as [1, 0] and thus will not appear together in edges.
주어진 0에서 n-1로 이름 붙여진 nodes와 무방향 edges(각 edge는 node쌍으로 구성)의 배열에 대해, 이 edges가 valid tree를 구성하는지 확인하는 함수를 작성하여라.
다음 두 가지 출력 예시가 주어졌다.
< Graph Valid Tee 문제 접근(1) >
Graph가 Valid Tree가 되기 위한 조건은 두 가지이다:
1) 모든 vertices가 연결되어 있다.
2) Graph의 모든 edges는 cycle을 형성하지 않는다.
먼저 graph의 탐색으로 이를 확인할 수 있다. edges로 구성된 그래프가 valid tree라면 임의의 node에서 출발해 DFS를 하면(BFS로도 구현할 수 있음) 모든 nodes를 단 한번만 탐색(최소 1번은 탐색하되 중복해서 탐색하지는 않음) 할 수 있다.
이 DFS를 응용한 알고리즘을 코드로 나타내면 다음과 같다. (Cpp 사용)
#include <algorithm>
#include <list>
struct Node{
vector<list<int>> connected;
vector<bool> visited;
Node(int n) : connected(n), visited(n, false){};
void DFS(int vertexNum, bool& isTree){
visited[vertexNum] = true;
while(!connected[vertexNum].empty()){
int there = connected[vertexNum].front();
if(!visited[there]){
auto it = find(connected[there].begin(), connected[there].end(), vertexNum);
connected[there].erase(it);
DFS(there, isTree);
}
else isTree = false;
if(!isTree) break;
connected[vertexNum].pop_front();
}
} // connected의 삭제가 빈번하므로 list container 사용
};
bool validTree(int n, vector<vector<int>> &edges) {
if(n==1) return true; // n=1인 경우는 무조건 valid tree 임
Node Vertices(n);
// Make Graph
for(auto edge : edges){
Vertices.connected[edge[0]].push_back(edge[1]);
Vertices.connected[edge[1]].push_back(edge[0]);
}
// DFS
bool isTree = true;
Vertices.DFS(0, isTree);
if(!isTree) return false; // 탐색이 중복된다는 것은 cycle이 존재한다는 뜻임
for(int i=0;i<n;i++)
if(!Vertices.visited[i]) // nodes가 두 부분으로 나뉜 경우(forest인 경우)
return false;
return true;
} // runtime : 711 ms, memory usage : 5.67 MB (by LintCode)
complexity analysis: 시간복잡도: O(n) , 공간복잡도: O(n)
→ 0 node에서 시작해 연결된 모든 node를 1번씩 탐색하게 되므로 O(n)의 시간복잡도를 가지고, extra memory usage는 Node.connected와 Node.visited이며 이들은 모두 node의 개수 n 만큼의 크기를 가지므로 공간복잡도는 O(n)이다.
< Graph Valid Tee 문제 접근(2) >
한편 Union-Find data structure으로도 알 수 있다.
Union-Find data structure의 활용(This model can then be used to determine whether two vertices belong to the same component, or whether adding an edge between them would result in a cycle. - from Wikipedia)에서 cycle 판별에 이 자료구조를 적용할 수 있다 하였기 때문..!
union-find를 이용해 작성한 알고리즘은 다음과 같다. (Cpp 사용)
struct Node{
vector<int> parent;
vector<int> rank;
vector<bool> isConnected;
Node(int n) : parent(n), rank(n,0), isConnected(n){
for(int i=0;i<n;i++) parent[i] = i;
}
// this can be decreased to a constant amount memory
// by performing both passes in the same direction
// two passes - once to find the root, once to update pointers.
int Find(int x){
int root = x;
while(parent[root] != root)
root = parent[root];
while(parent[x] != root){
int temp = parent[x];
parent[x] = root;
x = temp;
}
return root;
}
// union by rank - ensure that trees do not become too deep
void Union(int x, int y, bool& isTree){
isConnected[x] = isConnected[y] = true;
x = Find(x);
y = Find(y);
if(x == y){
isTree = false;
return;
}
if(rank[x] < rank[y])
swap(x,y);
parent[y] = x;
if(rank[x] == rank[y])
rank[x]++;
}
};
bool validTree(int n, vector<vector<int>> &edges) {
if(n==1) return true; // n=1인 경우는 무조건 valid tree 임
Node Set(n);
bool isTree = true;
for(auto edge : edges){
Set.Union(edge[0], edge[1], isTree);
if(!isTree) return isTree;
}
int rootNode = Set.Find(0);
for(int i=0;i<n;i++)
if(!Set.isConnected[i] || rootNode != Set.Find(i))
return isTree = false; // vertices가 연결되어 있지 않으면 valid tree 불가
return isTree;
} // runtime : 729 ms, memory usage : 5.52 MB (by LintCode)
일반적인 Node, Union 함수의 구조에서 vertex가 연결되었는지를 판별하는 bool isConnected를 정의하고, Union함수가 불렸을 때 Node x, y는 연결되므로 isConnected를 true로 할당하도록 하였다.
또한, 출력하는 bool값 isTree에 대해 Union함수에서 Node x,y의 root Node가 일치하는 경우 이미 연결되어 있는 두 Node를 연결하는 추가적인 edge가 존재하는 것이므로 이는 곧 cycle을 형성한다는 의미이기에 isTree = false로 할당하였다.
complexity analysis: 시간복잡도: O(n) , 공간복잡도: O(n)
→ edge에 대한 for loop는 cycle을 형성하지 않는 최대 edge의 개수 + 1인 n만큼 반복될 수 있으며 valid tree임을 점검하는 for loop는 node의 개수 n만큼 반복되므로 O(n)의 시간복잡도를 가지며, extra memory usage는 Node.parent, Node.rank, Node.isConnected이며 이들은 모두 node의 개수 n 만큼의 크기를 가지므로 O(n)의 공간복잡도를 갖는다.
'2022 Algorithm Study(by leetcode) > Graph' 카테고리의 다른 글
[ leetcode 문제풀이 ] Number of Connected Components in an Undirected Graph (0) | 2022.02.27 |
---|---|
[ leetcode 문제풀이 ] Number of Islands (0) | 2022.02.17 |
[ leetcode 문제풀이 ] Longest Consecutive Sequence (0) | 2022.02.16 |
[ leetcode 문제풀이 ] Pacific Atlantic Water Flow (0) | 2022.02.12 |
[ leetcode 문제풀이 ] Course Schedule (0) | 2022.02.07 |