일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 크리티컬섹션
- 언리얼가비지컬렉터
- 데이터애셋
- 강참조
- UML관련
- 자료구조
- 정렬알고리즘
- 알고리즘
- 스마트포인터
- 언리얼엔진구조체
- 선택정렬
- 델리게이트
- 람다
- UE4 커스텀로그
- 정렬
- C++최적화
- BFS
- stl
- 애셋로드
- 프로그래머스
- unorder_map
- 약참조
- map
- moreeffectiveC++
- enumasByue
- UE_LOG
- C++
- dataasset
- UELOG
- 람다사용정렬
- Today
- Total
기억을 위한 기록들
[알고리즘] 그래프 탐색 2가지 - 깊이 우선 탐색(Depth First Search) & 너비우선탐색(Breadth First Search)/ C++ 구현 본문
[알고리즘] 그래프 탐색 2가지 - 깊이 우선 탐색(Depth First Search) & 너비우선탐색(Breadth First Search)/ C++ 구현
에드윈H 2021. 1. 6. 11:58배열이나 링크드리스트에서는 특정 데이터를 찾기위해 순차 탐색, 이진 탐색을 사용. 이진 탐색트리에서도 이진탐색을 사용한다. 근데 그래프에서는?? 이와같은 방법들이 쉽게 통하지 않는다. 대표적인 기법 2가지가 있다. 바로 깊이우선탐색과 너비우선탐색이다.
깊이 우선 탐색(DFS) (길 끝까지 깊이 들어간다.) 재귀활용 (or stack stl를 활용)
설명 : 최상단노드부터 말단 노드까지 빠르게 내려가면서 탐색하는 방법으로 보통 경우의 수를 확인 할 때
(사용 예: 바둑, 장기 등)
1. 시작정점을 방문표시
2. 해당 정점과 이웃하고 있는 정점중 아직 방문표시 없는곳을 선택하고 시작정점으로 삼고 dfs시작 다시 1번.
3. 정점에 더이상 방문하지 않은 인접 정점이 없으면 이전 정점으로 돌아가서 2번.
4. 이전 정점으로 돌아가도 더 이상 방문표시 안한곳이 없으면 종료
장점 : 방문한 표시를 하며 하나의 말단 노드까지 빠르게 진행. 그리고 다시 돌아와 방문하지 않는 노드로 진행
단점 : 그래프가 깊어질 수록 시간이 많이 소요.
너비 우선 탐색(BFS)(근처 주변을 다 살핀다.) queue stl 활용
설명: 시작 정점을 지나면 깊이가 1인 모든 정점 방문, 그 다음 깊이가 2인 정점 모두 방문...반복
1. 시작 정점을 방문표시후 큐에 삽입
2. 큐로부터 맨앞 정점을 제거. 제거한 정점이 이웃하고 있는 인접 정점 중 방문 표시 없는 곳들을 방문표시 후 큐에 삽입.
3. 큐가 비게 되면 탐색 종료됨. 큐가 빌때까지 2번 과정 반복
장점 : 루트노드 부터 특정 노드까지 최단 경로를 찾는데 주로 사용. 상단 노드부터 하나씩 차례대로 탐색하기 때문에 어느시점에 탐색을 종료해도, 그 시점까지 최적의 해를 항상 찾을 수 있다.
단점 : 그래프가 깊어질수록 큐 저장 노드의 개수가 많아짐.
큰 그래프와 시간 제약이 있다면 깊이 우선 탐색(DFS)은 한쪽 방향의 탐색은 마치고 다른쪽은 가보지도 못할듯.
c++ 코드
방문 초기화 함수(SetNewVisited)도 임의로 만들어 놓았다.
실행부분
결과
#include <iostream>
#include <list>
#include <vector>
#include <queue>
#include <stack>
using namespace std;
class Graph {
public:
int visited[100] = { 0 };
vector<int> _adj[100];
void AddEdge(int a, int b); //그래프추가
void SetNewVisited() { //방문기록 초기화
for (int i = 0; i < 100; i++)
visited[i] = 0;
};
void bfs(int start); //큐활용
void dfsR(int start); //재귀활용
void dfs(int start); //스택활용
};
void Graph::AddEdge(int a, int b){
_adj[a].push_back(b);
_adj[b].push_back(a);
}//end of void Graph::AddEdge
void Graph::bfs(int start)
{
queue<int> q;
q.push(start);
visited[start] = true;
while (!q.empty())
{
// 큐에 값이 있을경우 계속 반복 실행 // 큐에 값이 있다. => 아직 방문하지 않은 노드가 존재 한다.
int x = q.front(); q.pop();
cout << x<<" " ;
int temp = _adj[x].size();
for (int i = 0; i < temp; i++)
{
int y = _adj[x][i];
if (!visited[y])// 방문하지 않았다면
{
q.push(y);
visited[y] = true;
}
}
}
}
void Graph::dfsR(int start)
{
if (visited[start]) // 방문한경우 바로 빠져나옴
return;
visited[start] = true;
cout << start << " ";
int temp = _adj[start].size();
for (int i = 0; i < temp; i++)
{
int next_node = _adj[start][i];
if (visited[next_node] == false)
{
dfsR(next_node);
}
}
}//end of void Graph::dfsR
void Graph::dfs(int start)
{
stack<int> s;
s.push(start);
visited[start] = true;
cout << start << " ";
while (!s.empty())
{
int current_node = s.top();s.pop();
int temp=_adj[current_node].size();
for (int i = 0; i < temp; i++)
{
int next_node = _adj[current_node][i];
if (visited[next_node] == false)
{
cout << next_node << " ";
visited[next_node] = true;
s.push(current_node);
s.push(next_node);
break;
}
}
}
}//end of void Graph::dfs
int main() {
Graph FirstG;
FirstG.AddEdge(1, 2);
FirstG.AddEdge(1, 3);
FirstG.AddEdge(2, 4);
FirstG.AddEdge(2, 5);
FirstG.AddEdge(4, 8);
FirstG.AddEdge(5, 9);
FirstG.AddEdge(3, 6);
FirstG.AddEdge(3, 7);
cout << "BFS(큐) 알고리즘 시작 " << endl;
FirstG.bfs(1);
FirstG.SetNewVisited();
cout << endl;
cout << endl;
cout << "DFS(스택) 알고리즘 시작 " << endl;
FirstG.dfs(1);
FirstG.SetNewVisited();
cout << endl;
cout << endl;
cout << "DFS(재귀) 알고리즘 시작 " << endl;
FirstG.dfsR(1);
cout << endl;
}//end of main
'자 & 알 > 알고리즘' 카테고리의 다른 글
[알고리즘] 플러드 필(Flood Fill) 알고리즘? (0) | 2021.01.11 |
---|---|
[알고리즘] 위상정렬(Topological Sort) (0) | 2021.01.06 |
[알고리즘] 탐색/검색에 관하여 (0) | 2021.01.01 |
[알고리즘] Least Recently Used(LRU) 알고리즘 / [1차] 캐시 (0) | 2020.12.19 |
[알고리즘] 정렬알고리즘 비교해보기 (0) | 2020.12.04 |