1. 그래프 표현과 정의
1) 그래프의 정의와 종류
- G(V, E)로 정점(Vertex)과 간선(Edge)의 집합으로 구성된 자료구조
- 속성에 따른 분류
방향 그래프(directed graph) / 무향 그래프(undirected graph) / 가중치 그래프(weighted graph)
- 형태에 따른 분류
다중 그래프(multigraph) : 정점들 간에 두 개 이상의 간선이 있을 수 있는 그래프
단순 그래프(simple graph) : 정점간 최대 한 개의 간선만 있는 그래프
트리 혹은 루트 없는 트리(unrooted tree) : 부모자식 관계가 없을 뿐 형태는 무향 그래프
이분 그래프(bipartite graph) : 겹치지 않는 두 개의 그룹으로 나눠서 서로 다른 그룹에 속한 정점들 간에 간선을 갖는 그래프
- 두 가지 이상의 속성을 가질 수 있음
방향 가중치 그래프 / 이분 가중치 그래프 / 사이클이 없는 방향 그래프(Directed Acyclic Graph : DAG) = 의존 그래프
2) 암시적 그래프 구조들
- 그래프 같은 형태를 갖는 구조가 아니라도 그래프를 통해서 표현하면 쉽게 해결할 수 있는 문제
서로 의존 관계를 갖는 그래프에서 순서를 결정하는 위상 정렬, 의존 그래프는 사이클이 없음(DAG)
그래프의 크기는 아주 큰데 실제로는 그 중 일부만 사용 : 15-퍼즐
2^N
3) 그래프의 표현 방법
- 그래프는 트리에 비해 훨씬 정적인 용도로 사용
※ 정적? : 새로운 정점이나 간선을 추가, 삭제 하는 경우가 적은 것
그래프는 정적이므로 구조의 변경이 어렵더라도 메모리를 적게 차지하는 방법으로 구현함
- 간선의 정보를 어떤 식으로 저장하느냐에 따라 두 가지로 나눔
인접 리스트 표현(adjacency list) : 그래프의 각 정점마다 해당 정점에서 나가는 간서의 목록을 저장해서 그래프를 표현
vector<list<int>> adj;
인접 행렬 표현(adjacency matrix) : 2차원 배열을 이용하여 간선의 정보를 저장
vector<vector<bool>> adj; bool adj[v][v];
- 인접 리스트와 인접 행렬 비교
특정 간선을 찾기 위한 탐색 시간 공간
인접 리스트 V * V V+E
인접 행렬 1 V * V
※ 벡터 생성자 : 테케 여러개 돌려야 하는 경우에 초기화를 따로 해줄 필요가 없어서 좋음
vector<int> a; vector<vector<int>> b; a = vector<int>(할당 갯수, 초기값); b = vector<vector<int>>(26, vector<int>(26, 0));
2. DFS
1) DFS ?
- 탐색 알고리즘 중 하나
- 깊이 우선 탐색
- 시간복잡도
인접리스트 : 정점의 수 V + 간선의 수 E = O(|V| + |E|)
인접행렬 : 정점의 수 V * 정점의 수 V = O(|V|^2)
2) dfs의 예제
- 두 정점이 서로 연결되어 있는가 확인
- 연결된 부분집합의 개수 세기
- 위상정렬 : dfs가 종료될 때 저장, 결과를 뒤집으면 위상정렬
문제를 풀때는 사이클이 생성될 가능성이 있는 그래프는 위상정렬된 결과가 사이클을 갖는지 확인해줘야 함.
예제. 문제 : DICTIONARY
https://algospot.com/judge/problem/read/DICTIONARY
1, 2) 문제를 읽고 이해하기 및 재정의
- 어떤 고대사전에 단어가 적혀있는데 순서가 그 시대의 사전순
- 단어 목록이 주어 질 때, 알파벳 간의 상대적 순서를 출력
- 단어의 수 : 1 <= n <= 1,000
- 각 단어의 길이 : 1 <= L <= 20
- 순서에 모순이 있다면 "INVALID HYPOTESIS" 출력
- 없다면 알파벳 순서를 출력
3) 계획 세우기
- 단어에서 알파벳 간의 순서도 즉, 그래프를 만듦
- dfs로 그래프를 돌면서 가장 깊이 들어가는 알파벳이 순서상으로 가장 뒤이므로
dfs가 끝날때 마다 알파벳을 추가
4) 계획 검증하기
- 시간복잡도 : 1000개의 단어 입력 시간 + => 싱크맞추는거 없이 cin 쓰면 오래 걸리지만 시간 초과 나진 않을 것 같음
그래프 생성시간 + => 모든 단어를 다 봐야하므로 NL
위상정렬 시간 + => 각 알파벳을 한번씩만 방문하면 되므로 26
사이클 검사 시간 => 한 알파벳이 사이클을 생성하는지 확인해야 하므로 26 * 26
=> O(NL)
5, 6) 계획 수행하기 및 회고하기
- 처음에 예제출력 결과를 이해하지 못해서 "위상정렬된 알파벳 + 위상정렬에 사용되지 않은 알파벳" 을 출력함.
=> ogklh abcdefijmnpqrstuvwxyz 무조건 이런식으로만 출력해야 되는지 암.
- 알고보니 그럴필요 없이 그래프를 만들고 있는 알파벳 간의 순서만 정렬하면 됨.
zyxwvutsrqp o nmji gklh fedcba 이렇게 그래프를 형성하고 있지 않은 단어들은 순서가 상관없음. ogklh만 순서가 맞으면 됨.
- 그래서 처음에는 그래프를 이루고 있는 알파벳들만 dfs 재귀를 돌림
- 나중에는 그래프를 이루고 있지 않은 알파벳들은 순서가 상관없으므로 모든 알파벳을 dfs 재귀 돌림
- 위상정렬 결과가 사이클을 가지는지 하나씩 확인해봐야 하나???? 으흠...
- dfs를 돌리면서 사이클을 가지는지 확인하기에는 이전 루트를 다 기억하면서 비교해야 하므로 으흠...
- 배열을 하나 만들어서 dfs가 끝나는 시점에 해당 정점이 끝났다는 표시를 해줌.
방문은 했지만, 끝나지 않은 정점을 방문한 경우 역방향 간선 즉, 사이클
bool check[n], end[n];
void dfs(int c){
check[c] = true;
for(int i = 0; i < n; i++){
if(!check[i])
dfs(i);
else if(!end[i])
// cycle
}
end[c] = true;
// 위상정렬 추가
}
소스코드
- 알고리즘 문제 해결전략 2편 중 -
- 저작권 문제시 삭제하겠습니다 -
'먹고살려면 > Algorithm' 카테고리의 다른 글
3-3편 무식하게 풀기 3 (0) | 2017.12.20 |
---|---|
3-2편 무식하게 풀기 2 (0) | 2017.12.18 |
번외편 c/c++ 입력속도 (0) | 2017.12.12 |
3-1편 무식하게 풀기 1 (0) | 2017.12.11 |
2편 알고리즘 시간 복잡도 분석 (0) | 2017.12.07 |