Algorithm/DataStructure

[자료구조] 깊이우선탐색(DFS)

은 딩 2022. 7. 6. 22:17

DFS란?

  • Depth-Find Search깊이 우선 탐색 알고리즘
  • 다차원 배열에서 각 칸을 방문할 때 깊이를 우선으로 방문하는 알고리즘
  • 그래프의 모든 Vertex와 Edge를 방문한다.
  • Time Complexity는 O(n+m)
    => 모든 Vertex, Edge 통해 이동하고 노드와 인접한 노드가 visited인지 체크  * edge 개수에 비례
  • 드라마로 예를 들었을 때, DFS는 한 드라마만 골라서 주구장창 본다고 하면, BFS는 여러 드라마를 한 번에 보는 느낌
  • DFS에는 Back edge, BFS에는 Cross edge가 있다.
  • DFS에선 Stack(또는 재귀함수) BFS에서는 Queue를 이용한다.

Properties of DFS

Property 1

    DFS(G, v)는 모든 Vertex, Edge를 v의 connected component 내에서 방문한다.

Property 2

    DFS(G, v)에서 형성된 discovery edge는 spanning tree Ts를 형성한다.

       +) spanning Tree : 모든 vertex를 지나는 tree의 subgraph


동작과정

1. 탐색 시작 노드를 스택에 삽입하고 방문 처리

2. 스택의 최상단 노드에 방문하지 않은 인접한 노드가 하나라도 있으면 그 노드를 스택에 넣고 방문 처리한다.

    방문하지 않은 인접노드가 없으면 스택에서 최상단 노드를 꺼낸다.

3. 더 이상 2번의 과정을 수행할 수 없을 때까지 반복


Analysis of DFS

  • vertex/edge label을 Setting/getting 하는데 O(1) time이 걸린다. (여기서 label은 상태 ex) visited, discovery, cross )
  • 각 vertex는 2번 label이 지정된다.(UNEXPLORED / VISITED)
  • 각 edge는 2번 label이 지정된다. (UNEXPLORED /  DISCOVERY or BACK)
  • Method incidentEdges는 각 vertex에서 한 번 호출된다.
  • n = 정점 수(vertex), m = 간선 수(edge)일 때, 그래프가 Adjacency List structure로 표현될 경우 O(n+m)
  • n = 정점 수(vertex)일 때, 그래프가 Adjacency Matrix로 표현될 경우 O(n^2)

PseudoCode

1) Path Finding

Algorithm pathDFS(G,v,z):
	setLabel(v, VISITED)
    S.push(v)
    if v == z
    	return S.elements()
    for all e in G.incidentEdges(v)
    	if getLabel(e) = UNEXPLORED
        	w = opposite(v, e)
            if getLabel(w) = UNEXPLORED
            	setLabel(e, DISCOVERY)
                S.push(e)
                pathDFS(G,w,z)
                S.pop(e)
            else  // 이미 방문
            	setLabel(e, BACK)
    S.pop(v)

 

2) Cycle Finding

Algorithm cycleDFS(G,v,z):
	setLabel(v, VISITED)
    S.push(v)
    for all e in G.incidentEdges(v)
    	if getLabel(e) = UNEXPLORED
        	w = opposite(v,e)
            S.push(e)
            if getLabel(w) = UNEXPLORED
            	setLabel(e, DISCOVERY)
                cycleDFS(G,w,z)
                S.pop(e)
            else
                T = new empty stack
                while o != w
                	o = S.pop()
                    T.push(o)
                return T.elements()
         S.pop(v)

Python Implementation

def DFS(g,u,discovered): # Graph g, starting at Vertex u
	for e in g.incident_edges(u):
    	v = e.opposite(u)
        if v not in discovered:
        discovered[v] = e
        DFS(g,v,discovered)
# DFS 메서드 정의
def dfs(graph, v, visited):
    # 현재 노드를 방문 처리
    visited[v] = True
    print(v, end=' ')
    # 현재 노드와 연결된 다른 노드를 재귀적으로 방문
    for i in graph[v]:
        if not visited[i]:
            dfs(graph, i, visited)


# 각 노드가 연결된 정보를 표현(2차원 리스트)
graph = [
    [], # 0번째는 비우기
    [2, 3, 8], # 1번 노드랑 연결된 노드
    [1, 7], # 2번 노드랑 연결된 노드
    [1, 4, 5], # 3번 노드랑 연결된 노드
    [3, 5], # 4번 노드랑 연결된 노드
    [3, 4], # 5번 노드랑 연결된 노드
    [7], # 6번 노드랑 연결된 노드
    [2, 6, 8], # 7번 노드랑 연결된 노드
    [1, 7] # 8번 노드랑 연결된 노드
]

# 각 노드가 방문된 정보를 표현(1차원 리스트)
visited = [False] * 9

# 정의된 DFS 함수 호출
dfs(graph, 1, visited)

C++ Implementation

#include <iostream>
#include <utility>
#include <stack>
using namespace std;

#define X first
#define Y second

int board[502][502] = { // 1이 blue, 0이 red
 {1,1,1,0,1,0,0,0,0,0},
 {1,0,0,0,1,0,0,0,0,0},
 {1,1,1,0,1,0,0,0,0,0},
 {1,1,0,0,1,0,0,0,0,0},
 {0,1,0,0,0,0,0,0,0,0},
 {0,0,0,0,0,0,0,0,0,0},
 {0,0,0,0,0,0,0,0,0,0} };
bool vis[502][502];
int n = 7, m = 10;
int dx[4] = { 1, 0, -1, 0 };
int dy[4] = { 0, 1, 0, -1 };

int main() {
	stack<pair<int, int> > S;
	vis[0][0] = 1;
	S.push({ 0, 0 });

	while (!S.empty()) {
		pair<int, int> cur = S.top();
		S.pop();
		cout << '(' << cur.X << ", " << cur.Y << ") ->";
		for (int dir = 0; dir < 4; dir++) {
			int nx = cur.X + dx[dir];
			int ny = cur.Y + dy[dir];
			if (nx < 0 || nx >= n || ny < 0 || ny >= m)
				continue;
			if (vis[nx][ny] || board[nx][ny] != 1)
				continue;
			vis[nx][ny] = 1;
			S.push({ nx,ny });
		}
	}
}

 


Q : DFS는 Shortest paths를 찾는데 왜 자주 쓰이지 않을까?

A : DFS로 경로를 검색할 경우 처음으로 발견한 경로가 최단거리가 아닐 수 있다. 만약, 트리라는 확신이 있다면 노드간 경로가 하나만 존재하기 때문에 최단경로일 수 밖에 없다. 반면에 BFS는 현재노드에서 가까운 곳부터 찾기 때문에 경로 탐색시 먼저 찾아지는 경로가 최단 경로이다.

 

 


참고 : 이것이 취업을 위한 코딩 테스트다 with 파이썬, 바킹독 알고리즘