DFS 란 단어 뜻 그대로 깊이 우선 탐색이라는 뜻이다.

 

 

 

그래서 구현은?

 

우선 DFS는 두 가지 정보를 우리가 알아야한다.

 

위의 그림에서 D를 방문하고 H를 방문했다고 하자.

다음에는 어디로 가야 할까?

 

우리는 당연히 안다.

다시 3번으로 올라왔다가 5번으로 가야 되는걸.   이걸 코드로 실행하려면 다음의 두 정보가 필요하다.

 

전에 방문한 정점(vertex)을 알아야 한다. 따라서 각 정점(vertex)의 방문 여부를 저장하는 자료구조가 필요핟.

- 한번 방문한 정점(vertex)이라면 들릴 필요가 없다.

 

 더이상 방문 할 정점이 없으면 이전의 정점으로 돌아가야 합니다. ( 이전 정점의 정보를 알고 있어야 한다. )

- 위 DFS 예시에서 나오는거 처럼 H 의 자식노드가 없어 더이상 갈곳이 없다면 그 전 노드로 돌아가 방문하지 않은 i 노드를 방문해야한다.

 

 

이 두 가지를 충족시키는 방법은  Stack 자료구조를 사용하거나 재귀함수 를 이용하는 것이다.

 


참고로 그래프를 코드로 표현하는 방법에는 두 가지가 있다.

 

< 1. Adjacency Matrix>

1. 그래프의 정점(vertex)들을 행렬(Matrix)로 표시해서 인접하면 1, 아니면 0으로 표시하여 2차원 행렬로 표현.

 

< 2. Adjacency list>

2. 배열에 정점(vertex)들을 나열하고 각 배열에 저장되어 있는 정점(vertex) 옆으로 리스트 형식으로 서로 인접한 정점들을 표시( 순서 상관 X ) - 간혹 문제에 크기(숫자)가 작은 정점부터 방문하라고 한다면 Sort 해주면 된다.


[ Recursion 이용한 DFS 구현 ]

#include<iostream>
#include<vector>

using std::endl;
using std::cout;

const int adjacent2_size=7;  // 주어진 그래프의 vertex 개수
std::vector<std::vector<int>> adjacent = { 7 ,std::vector<int>(7,0)} ;  // 인접행렬의 표현 (V x V) 행렬
std::vector<bool> visited(adjacent2_size, false);
// 0 번 인덱스도 사용.

//  ==========================================================
//  < 1. Adjacency Matrix > 로 그래프를 표현했을때 DFS 
void DFS(int here) {     

	cout << "visit " << here << '\n';
	visited[here] = true;

	for (int i = 0; i < adjacent2_size; i++) {
		if (adjacent[here][i] != 0 && !visited[i]) {

			DFS(i);
		}
	}
}

//  ==========================================================
//   < 2. Adjacency list > 로 그래프를 표현했을때 DFS 

// 0 index는 안쓰고 1번 부터 쓸거기 때문에 +1 할당할때 +1을 하여 할당해준다.
std::vector<std::vector<int>> adjacent2(9); // 행이 9행인 2차원 vector 생성 / 열은 가변적.
std::vector<bool> visited2(adjacent2.size(), false);


void DFS2(int here) {

	visited2[here] = true;
	cout << "visit2 : " << here << '\n';

	for (int i = 0; i < adjacent2[here].size(); i++) {
		int next = adjacent2[here][i];
		if (!visited2[next]) {
			DFS2(next);
		}
	}
}

 

여기서 주의할 점은

 

한번의 DFS로 모든 노드가 순회 된다는 보장은 없기 때문에 

모든 노드를 방문하여야 된다면, DFS를 한번 돌고 방문하지 않은 노드를 찾아서 DFS를 다시 실행 시켜줘야 된다.

 

for (int i = 0; i < n; i++) {
		if (visited[i] == false) {
            //  인접 행렬로 표현했을때 행에서 1이 1개라면 자기 자신 말고는 인접한 노드가 없는 것이다.
			if (std::count(adjacent[i].begin(), adjacent[i].end(), 1) > 1) {  	
				Dfs(i);
			}
		}
	}

[ Stack을 이용한 DFS 구현 ]

( 작은 노드 부터 실행하기 위해 sort 를 해주었다. )

std::vector<int> adj_list[10];
bool visited[10] = { false, };
std::stack<int> st;

// 인접 리스트로 구하기.
void DFS(int start) {
	st.push(start);
	visited[start] = true;
	std::cout << start << " ";

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

		st.pop();

		std::sort(adj_list[cur].begin(), adj_list[cur].end());

		for (size_t i = 0; i<adj_list[cur].size(); i++) {

			int next = adj_list[cur][i];

			if (!visited[next]) {
				st.push(cur);  < 위에서 pop()을 해줬기 때문에 cur를 다시 push 해준다.
				st.push(next);
				std::cout << next << " ";
				visited[next] = true;
				break;
			}
		}
	}
}

 

주의 해야 할점은

st.push(cur);  //  위에서 pop()을 해줬기 때문에 cur를 다시 push 해준다.
st.push(next);

 

push를 두번 해준다는 점이다.

이것이 스택을 사용하는 이유이다.

 

DFS에서 스택을 사용 하는 명확한 이유는

 

어떠한 노드 에서 더 이상 인접한 노드가 없거나 전부 방문한 노드들 일때 그 이전 노드로 돌아가 이전 노드의 다른 인접한 노드를 방문해야하고 스택은 그것을 가능하게 하는 자료구조 이기 때문이다.

 

스택에는 당연히 그 이전 노드를 가지고 있어야한다.

 

위와 같이 push를 두번 해주지 않고, 한번 해주게 되면 이전 노드의 값이 스택에 없어지게되고 인접한 노드가 없거나 방문한 노드만 있을때 뒤로 가는 제 기능을 발휘하지 못하게된다.

 

가끔 DFS 코드를 구글링 해보다 보면 next 만 push 해주는 코드를 볼 수 있는데 인접한 노드가 2개 이상인 그래프를 만나면 오류가 나는것을 확인 할 수 있을것이다. ( 모든 노드가 인접한 노드를 1개씩만 가지고 있다면 상관없다. )

 

다시 돌아와서 무튼 이러한 이유로 push 두번 해주어야한다.


 

근데 나는 위 코드가 뭔가 별로였다.

 

왜 헷갈리게 스택에서 뺐다가 넣었다가 하는지...

 

다른 방법이 있는지 골머리 굴려보다가 안되서 포기하고 구글 검색을 하던 도중에 내가 정확히 찾던 코드 발견!!

 

 

void DFS(int start) {
	st.push(start);
	visited[start] = true;
	std::cout << start << " ";

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

		std::sort(adj_list[cur].begin(), adj_list[cur].end());

		size_t i;    //  for 문 밖에서다가 선언.
		for (i = 0; i<adj_list[cur].size(); i++) {

			int next = adj_list[cur][i];

			if (!visited[next]) {
				st.push(next);
				std::cout << next << " ";
				visited[next] = true;
				break;
			}
		}
		if (i == adj_list[cur].size()) {  // 연결된 노드가 없거나 모두 방문한 경우
			st.pop();
		}
	}
}

 

if (i == adj_list[cur].size()) {  // 연결된 노드가 없거나 모두 방문한 경우
			st.pop();
	}

 

i == adj_list[cur].size() <== adj_list[cur].size() 가 만약 0 이라면 for문 에서 i가 0이 되고 바로 빠져 나오니깐 둘의 값은 같고.

i 의 인접한 노드 전부를 이미 방문한 경우 for 문에서 i가 adj_list[cur].size()랑 같아져서 나오기 때문에

 

결과적으로는 둘의 값이 같으면 연결된 노드가 없거나 모두 방문한 경우가 된다

 

'CS > Algorithm' 카테고리의 다른 글

최단 경로 ② ( Floyd - Warshall )  (0) 2021.11.17
최단 경로 & 그래프 문제 유형  (0) 2021.11.17
Kruskal ( Union - Find )  (0) 2021.11.12
MST ( Minimum Spanning Tree ) - Kruskal , Prim  (0) 2021.11.11
BFS ( Breadth First Search )  (0) 2021.11.10

+ Recent posts