알고리즘(코딩테스트)

그래프 (재귀, 비재귀 DFS)

gbleem 2025. 3. 10. 15:29

1. 그래프의 특징과 종류


방향이 있는 그래프

 

가중치가 있는 그래프

 

순환이 있는 그래프

 

 

2. 그래프 구현


2 - 1. 인접 행렬을 이용한 그래프

  • 배열의 인덱스는 노드
    • 세로 방향 출발 노드
    • 가로 방향 도착 노드
  • 배열의 값은 노드의 가중치

 

2 - 2. 인접 리스트를 이용한 그래프

 

 

결론

  • 인접 행렬
    • 크기가 고정되어 있기 때문에 노드가 많고 연결이 적은 그래프에서 낭비가 심한 단점이 있다.
    • 간선의 정보를 O(1)에 얻을 수 있는 장점이 존재
  • 인접 리스트
    • 대부분의 경우 인접 행렬에 비해 메모리를 절약할 수 있다.
    • 평균적으로 간선의 정보를 얻는데 시간이 좀 더 걸림

 

 

3. 그래프 탐색


3 - 1. BFS

  • 최단 경로를 보장하는 탐색 방식
  • queue를 사용한다.
  • 방문 처리를 queue에 push할때 진행한다.
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;

vector<char> answer;
vector<char> board[30];
unordered_map<char, int> vis;

void bfs(char cur)
{
	queue<char> q;
	q.push(cur);
	vis.insert({ cur, 1 });
	answer.push_back(cur);

	while (!q.empty())
	{
		auto cur = q.front();
		q.pop();

		for (auto nxt : board[cur])
		{
			if (vis.find(nxt) != vis.end())
				continue;
			q.push(nxt);
			vis.insert({ nxt, 1 });
			answer.push_back(nxt);
		}
	}
}

vector<char> solution(vector<pair<char, char>> graph, char start)
{
	for (int i = 0; i < graph.size(); ++i)
	{
		board[graph[i].first].push_back(graph[i].second);
	}

	bfs(start);

	return answer;
}


int main()
{
	vector<pair<char, char>> graph = { {'a','b'}, {'a','c'},{'b','d'},{'b','e'}, { 'c','f' },{'c','g'},{'d','h'}, { 'e','h' },{'f','i'},{'g','i'} };
	vector<char> ans = solution(graph, 'a');

	for (auto a : ans)
		cout << a << " ";
}

 

3 - 2. DFS

  • 코딩 테스트에서 최단 경로 문제가 아닌 경우 처음으로 시도해보면 좋은 방식
  • stack을 사용한다. or 재귀
  • 방문 처리를 stack에서 pop 한 후 진행한다. (재귀가 아닌 방식에서!!)

dfs를 재귀 버전과 재귀가 아닌 버전 두가지로 구현할 수 있다.

  • 재귀
    • 갈 수 있는 곳이 여러 곳이라면 번호가 낮은 순서부터 방문
    • 실제 방문을 할 때 방문 표시를 남긴다!!!
    • 우리가 개념적으로 알고있는 DFS의 순서로 진행한다. (가장 깊은곳으로 먼저 들어가는 방식)
  • 비 재귀
    • 단순히 bfs 코드에서 queue만 stack으로 바꾼 경우 우리가 원하는 대로 동작하지 않는다!!
    • 비 재귀 방식을 이렇게 구현하면, 방문하기 전에 방문하지 않은 곳을 발견하면, 방문 표시를 남겨버리는 특징 때문이다.
    • 이 경우, 개념적으로 알고있는 DFS와 순서가 다르기 때문에 아래와 같이 구현하면 된다.
      • 처음 들어온 값에 대해서도 방문처리를 하지 않고,
      • stack에서 pop 한 후 방문처리를 해주면 된다.

주의점) 

  • 실제로 정확한 방문 순서는 다르지만, 깊이를 우선적으로 탐색하는 탐색 방향성은 같다.
  • 또한 그래프에 방향성이 있는 경우는 adj 라는 배열을 구성할 때 아래와 같이 구성하면 되지만
  • 방향성이 없는 경우 아래와 같이 처리해 주어야 한다.
    • adj[vec[i][0]].push_back(vec[i][1]);
    • adj[vec[i][1]].push_back(vec[i][0]);
#include <iostream>
#include <vector>
#include <stack>
using namespace std;

vector<int> adj[10];
bool vis[10];
vector<int> answer;

void dfs(int cur)
{
	stack<int> st;
	st.push(cur);

	while (!st.empty())
	{
		int cur = st.top();
		st.pop();

		if (vis[cur])
			continue;
		vis[cur] = 1;
		answer.push_back(cur);

		for (int nxt : adj[cur])
		{
			if (vis[nxt])
				continue;
			st.push(nxt);
		}
	}
}

void dfs_recursive(int cur)
{
	vis[cur] = 1;
	answer.push_back(cur);

	for (int nxt : adj[cur])
	{
		if (vis[nxt])
			continue;
		dfs(nxt);
	}
}

int main()
{
	vector<vector<int>> vec = { {1,2},{1,3},{2,4},{2,5},{3,6} };

	for (int i = 0; i < vec.size(); ++i)
	{
		adj[vec[i][0]].push_back(vec[i][1]);
	}

	dfs_recursive(1);
	//dfs(1);

	for (auto a : answer)
		cout << a << " ";
}