DFS
이는 깊이 우선 탐색이라고도 하며, 루트 노드 ( 혹은 다른 임의의 노드)에서 시작해서 다음 분기로 넘어가기 전에 해당 분기를 deep하게 탐색하는 방법입니다.
- 미로를 탐색할 때 한 방향으로 갈 수 있을 때까지 계속 가다가 더 이상 갈 수 없게 되면 다시 가장 가까운 갈림길로 돌아와서 이곳부터 다른 방향으로 다시 탐색을 진행하는 방법과 유사합니다.
- 즉, 넓게(wide)탐색하기 전에 깊게(deep)탐색 하는 것입니다.
- 사용하는 경우 : 모든 노드를 방문 하고자 하는 경우에 이 방법을 선택합니다.
BFS
이는 너비 우선 탐색이라고도 하며, 루트 노드(혹은 다른 임의의 노드)에서 시작해서 인접한 노드를 먼저 탐색하는 방법입니다.
- 시작 정점으로부터 가까운 정점을 먼저 방문하고 멀리 떨어져 있는 정점을 나중에 방문하는 순회 방법입니다.
- 즉, 깊게(deep 탐색하기 전에 넓게(wide)탐색하는 것입니다.
- 만약에 두 노드 사이의 최단 경로 혹은 임의의 경로를 찾고 싶을 때 이 방법을 사용합니다.
- Ex) 지구상에 존재하는 모든 친구 관걔를 그래프로 표현한 후 Ash와 Vanessa사이에 존재하는 경로를 찾는 경우
- 깊이 우선 탐색의 경우 - 모든 친구 관계를 다 살펴봐야 할지도 모릅니다.
- 너비 우선 탐색의 경우 - Ash와 가까운 관계부터 탐색해야 합니다.
- 또한 너비 탐색이 깊이 우선 탐색보다 좀 더 복잡하게 됩니다.
/DFSBFS.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_VERTEX 100
#define true 1
#define false 0
typedef struct node_
{
int vertex;
struct node_ *link;
} node, *nodePointer;
// queue를 위해 front와 rear를 선언해주어야 합니다.
nodePointer front, rear;
nodePointer queue[MAX_VERTEX];
// adjacent list
nodePointer adjList[MAX_VERTEX];
short int visited[MAX_VERTEX];
void initNode(nodePointer *node, int vertex)
{
*node = malloc(sizeof(nodePointer));
(*node)->vertex = vertex;
(*node)->link = NULL;
}
/**
* @brief DFS를 시작 노드 v부터 시작한다.
*
*/
void dfs(int v)
{
nodePointer w;
visited[v] = true;
printf(" %d ", v);
w = adjList[v];
while (w != NULL)
{
// 방문하지 않았을 때만 dfs과정을 진행해 주는 것이 좋아
if (!visited[w->vertex])
dfs(w->vertex);
w = w->link;
}
}
void addq(int item)
{
nodePointer temp;
initNode(&temp, item);
if (!rear || !front)
{
front = rear = temp;
}
else
{
rear->link = temp;
rear = rear->link;
}
}
int deleteq()
{
int temp = front->vertex;
front = front->link;
return temp;
}
// bfs는 dfs와는 다르게 큐를 이용하여야 한다.
void bfs(int v)
{
nodePointer w;
front = rear = NULL;
printf(" %d ", v);
visited[v] = true;
addq(v);
while (front != NULL)
{
v = deleteq();
w = adjList[v];
while (w != NULL)
{
if (!visited[w->vertex])
{
printf(" %d ", w->vertex);
addq(w->vertex);
visited[w->vertex] = true;
}
w = w->link;
}
}
}
int main(void)
{
FILE *fp_read = fopen("DFSBFS.txt", "r");
if (fp_read == NULL)
{
fprintf(stderr, "File Open Error!");
exit(EXIT_FAILURE);
}
int vertCount, data, check = false;
fscanf(fp_read, "%d", &vertCount);
// tempNode는 가장 뒤를 가리키게 하는 변수
nodePointer newNode, tempNode;
// adjacent list를 구현해야 한다.
for (int i = 0; i < vertCount; i++)
{
check = false;
for (int j = 0; j < vertCount; j++)
{
fscanf(fp_read, "%d", &data);
if (data == 1)
{
// data가 있는 곳이라면
initNode(&newNode, j);
if (check == false)
{
// 일단 처음에 아무것도 안들어가 있다면
adjList[i] = newNode;
tempNode = newNode;
check = true;
}
else
{
tempNode->link = newNode;
tempNode = newNode;
}
}
}
}
for (int i = 0; i < vertCount; i++)
{
printf("Vertex %d: ", i);
tempNode = adjList[i];
while (tempNode)
{
printf(" %d ", tempNode->vertex);
tempNode = tempNode->link;
}
printf("\n");
}
printf("DFS: ");
dfs(0);
printf("\n");
memset(visited, 0, sizeof(short int) * MAX_VERTEX);
printf("BFS: ");
bfs(0);
printf("\n");
return 0;
}
/DFSBFS.txt
8
0 1 1 0 0 0 0 0
1 0 0 1 1 0 0 0
1 0 0 0 0 1 1 0
0 1 0 0 0 0 0 1
0 1 0 0 0 0 0 1
0 0 1 0 0 0 0 1
0 0 1 0 0 0 0 1
0 0 0 1 1 1 1 0
시간 복잡도
dfs(x)는 x에 방문하는 함수이므로 정점의 개수, 즉 차수인 V번만큼 dfs(x)가 호출됩니다. 따라서 dfs(x) * 시간복잡도 * V가 전체 dfs알고리즘의 시간복잡도가 됩니다. 구현 방식에 따라 어떻게 다른지 구체적으로 알아보도록 합시다.
- 인접행렬에서의 시간복잡도
- 모든 정점을 다 찾아봐야 하기 때문에 dfs(x)의 시간복잡도는 O(V)
- dfs(x)가 V번 호출되어야 하므로 dfs알고리즘의 시간복잡도는 O(V * V) = O(V^2)
- 인접리스트에서의 시간복잡도
- 인접행렬과 마찬가지로 dfs(x)는 V번 호출
- dfs(x)의 시간복잡도는 O(V + E)
- 인접리스트에서 dfs가 호출되는 방법은 모든 정점을 다 찾는 인접행렬의 탐색방법과 많이 다릅니다.
- 또한 a[x].size()는 전체 간선의 개수가 아니라, 한 정점과 연결된 간선의 개수이기 때문에 dfs(x)의 시간복잡도는 O(E)가 아님을 확인해야 합니다.
'School > DataStructure' 카테고리의 다른 글
Data Structure - Tree ( Forests ) (0) | 2022.05.31 |
---|---|
Data Structure - Tree ( Selection Tree ) (0) | 2022.05.31 |
Data Structure - Tree ( BST ) (0) | 2022.05.31 |
Data Structure - Tree ( Heaps ) (0) | 2022.05.31 |
Data Structure - Hashing ( Dynamic Hashing ) (0) | 2022.05.30 |