안녕하세요, 여행벌입니다.

오늘은 그래프 탐색 기법 중 너비 우선 탐색(BFS)에 대해서 포스팅해보겠습니다.


그래프(graph)

 그래프(graph)는 연결되어 있는 객체 간의 관계를 표현할 수 있는 자료 구조입니다. 수학자 오일러에 의해 처음 창안되었으며, 정점(vertex)와 간선(edge)로 그래프를 표현합니다. 정점은 노드(node)라고도 불리며, 간선은 링크(link) 라고도 불립니다. 아래 그림과 같이 지도를 나타낼 때 그래프가 많이 이용됩니다.

그래프 예시

너비 우선 탐색(BFS)

 너비 우선 탐색은 시작 정점으로부터 가까운 정점을 먼저 방문하고 멀리 떨어져 있는 정점을 나중에 방문하는 순회 방법입니다. 너비 우선 탐색을 위해서는 방문한 정점들을 차례로 저장한 후 꺼낼 수 있는 자료 구조 큐(queue)가 필요하고, 방문한 정점들을 체크하는 bool 배열이 필요합니다.

 예시를 통해서 BFS 탐색 순서를 익혀보겠습니다.

 BFS 탐색은 다음과 같은 순서로 진행됩니다.

1)  큐의 가장 앞에 있는 노드(현재 방문 중인 노드)와 연결된 노드 중, 방문하지 않은 노드가 있다면 큐에 삽입하고 방문 표시를 한다.

2) 1)의 과정을 큐에 더 이상 노드가 없을 때까지 반복한다.

코드 구현 ( C )

 인접 행렬로 표현된 그래프에 대한 BFS

void bfs_mat(GraphType* g, int v) {
	QueueType q;
	init(&q);
	visited[v] = TRUE; // v정점에서부터 탐색 시장
	enqueue(&q, v);
	while (!is_empty(&q)) { // 큐에 남은 정점이 없을 때까지 진행
		v = dequeue(&q);
		printf("%d 정점을 방문\n", v);
		for (int w = 0; w < g->n; w++) {
			if (g->adj_mat[v][w] && !visited[w]) { // 간선이 존재하고 방문하지 않은 정점
				visited[w] = TRUE;
				enqueue(&q, w);
			}
		}
	}
}

 인접 리스트로 표현된 그래프에 대한 BFS

void bfs_list(GraphType* g, int v) {
	GraphNode* w;
	QueueType q;
	init(&q);
	visited[v] = TRUE;
	enqueue(&q, v);
	while (!is_empty(&q)) {
		v = dequeue(&q);
		printf("%d 정점을 방문\n", v);
		for (w = g->adj_list[v]; w; w = w->link) {
			if (!visited[w->vertex]) {
				visited[w->vertex] = TRUE;
				enqueue(&q, w->vertex);
			}
		}
	}
}

코드 구현 ( C++ )

 인접 행렬로 표현된 그래프에 대한 BFS

void bfs_mat(int v) { // 정점의 수 N개 graph[x][y] 는 x에서 y로 가는 간선을 의미.
	queue<int> q;
	bool visited[N] = {};
	q.push(v);
	visited[v] = 1;
	while (!q.empty()) {
		int cur = q.front();
		cout << cur << "노드를 방문 중\n";
		q.pop();
		for(int i = 0; i < N; i++)
			if (graph[cur][i] && !visited[i]) {
				visited[i] = true;
				q.push(i);
			}
	}
}

 벡터로 표현된 그래프에 대한 BFS

void bfs_mat(int v) { // 정점의 수 N개 graph[x][y] 는 x에서 y로 가는 간선을 의미.
	queue<int> q;
	bool visited[N] = {};
	q.push(v);
	visited[v] = 1;
	while (!q.empty()) {
		int cur = q.front();
		cout << cur << "노드를 방문 중\n";
		q.pop();
		for(int i = 0; i < graph[cur].size() ; i++)
			if (graph[cur][i] && !visited[i]) {
				visited[i] = true;
				q.push(i);
			}
	}
}

너비 우선 탐색의 복잡도

● 인접 행렬로 표현된 경우 N개의 노드에 대해서 N 개의 다른 노드와 연결되어있고, 방문되지 않은 노드를 체크해야되므로 O(N^2)의 시간 복잡도를 가집니다.

● 자료구조 vector 혹은 연결 리스트로 표현된 경우 N개의 노드에 대해서 연결된 M개의 간선만큼만 탐색을 진행하므로 O(N + M)의 시간 복잡도를 가집니다.


이상으로 유명한 그래프 탐색 방법 중 하나 인 너비 우선 탐색(BFS) 포스팅을 마치겠습니다.

안녕하세요, 여행벌입니다.

이번 포스팅에서는 사용자 입력을 받는 <input> 태그에 대해서 다뤄보겠습니다.


input 태그

 웹에서의 폼은 크게 사용자가 입력하는 부분과 입력한 내용을 서버로 보내는 버튼 부분으로 나눌 수 있습니다. 이때, 사용자가 내용을 입력하는 부분은 한 줄짜리 텍스트나 비밀번호 같은 부분으로 <input> 태그를 이용해 만들 수 있습니다. 또, 체크 박스나 로그인 버튼처럼 사용자가 클릭하는 버튼도 <input> 태그를 이용합니다.

<input type="type속성값"> </input>

 기본 형태는 위와 같습니다. type에 따라 사용자가 입력할 수 있는 형태가 달라지고 input 태그는 굉장히 많은 type 유형이 있습니다.

type 속성

 input 태그의 type 유형은 굉장히 많습니다. 상황에 맞춰 적절한 type 속성 값을 적용해줘야합니다. 물론, 이 모든 type 유형을 외우는 건 불가능합니다! 이러이러한 유형들이 존재한다는 것만 알아두시고 필요할 때마다 구글링 할 수 있는 정도만 알아두시면 될 것 같습니다.

id 속성

 폼을 만들다 보면 똑같은 폼 요소가 여러 번 사용됩니다. 예를 들어 회원가입 폼을 만들 경우, 이름 입력 항목이나 주소 입력 항목 모두 <input type="text"> 를 이용하게 됩니다. 이렇게 여러 번 사용된 폼 요소를 구분하기 위해 사용하는 것이 id 속성입니다. id를 지정해 놓으면 <label> 태그를 이용해 캡션을 붙일 수도 있고 나중에 배울 CSS를 이용해 각 요소마다 다른 형태로 꾸밀 수도 있습니다.

<input type="type속성값" id="id값"> </input>

autofocus 속성 - 입력 커서 표시하기

 autofocus 속성을 이용하면 페이지를 불러오자마자 폼의 요소 중에서 원하는 요소에 마우스 커서를 표시할 수 있습니다. 아래 그림처럼 웹 페이지를 불러오자마자 입력할 수 있게 마우스 커서가 표시됩니다.

<input type="text" autofocus></input>

placeholder 속성 - 힌트 표시하기

 사용자가 텍스트를 입력할 때 도움이 되도록 입력란에 힌트를 표시할 때 사용하는 속성입니다.

<input type="text" placeholder="ex)010-****-****"></input>

readonly 속성 - 읽기 전용 필드 만들기

 readonly 속성은 해당 필드를 읽기 전용으로 바꿉니다. 필드 안에 내용은 있지만 사용자에게 내용을 보여주기만 하고 입력은 할 수 없게 합니다.

<input type="text" value="읽기만 가능합니다." readonly></input>

required 속성 - 필수 필드 지정하기

 내용을 폼에 입력한 후 submit 버튼을 클릭하면 폼을 서버로 넘겨주는데, 이때 꼭 입력받아야 하는 필드들이 모두 채워졌는지 검사해야 합니다. required 속성은 해당 필드를 입력해야만 제출이 가능하게 해줍니다.

<input type="text" required></input>

min, max, step 속성

 min 속성과 max 속성은 각각 해당 필드의 최솟값과 최댓값을 지정합니다. step 속성은 허용된 범위 내의 숫자의 일정한 간격을 가리킵니다.

● type이 date / datetime / datetime-local / month / week / time / number / range 인 경우 사용 가능.

<input type="number" min="10" max="100" step="10">

size, minlength, maxlength 속성 - 길이, 최소 길이, 최대 길이 속성

 min, max, step 속성과 다르게 size, minlength, maxlength 속성은 텍스트 길이에 조건을 걸어줍니다. size 속성은 텍스트 필드와 비밀번호 필드, 검색 필드 등 한 줄짜리 텍스트와 관련된 필드에서 화면에 몇 글자까지 보이게 할지 지정하고, minlength, maxlength 속성은 사용자가 최소, 최대 몇 글자까지 입력할 수 있는지 지정합니다.

<input type="text" size="10" minlength="4" maxlength="15">

value 속성 - 초기값 설정

 value 속성은 필드에 초기값을 설정해줍니다. 그리고 사용자에게는 보이지 않지만 서버로 정보를 넘길 때도 사용합니다. 하지만 placeholder 속성과 다르게 사용자가 지우고 입력을 해야 한다는 불편함이 있습니다.

<input type="" value="서버로넘기고싶은정보 혹은 초기값"></input>


이상으로 input 태그에 대한 포스팅을 마치겠습니다.

다양한 type과 다양한 속성들이 있는데 input 태그는 자주 쓰이니 익혀두시는 게 좋을 것 같습니다 :))

문제 : https://www.acmicpc.net/problem/1005


위상정렬

위상정렬 문제로 endNode 까지 도착하는데 가장 긴 경로를 구하면 된다.

자세한 풀이는 BOJ 1516 게임 개발 풀이를 참고해주시면 감사합니다. 

(https://travelbeeee.tistory.com/375)

#include<iostream>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);

	int t;
	cin >> t;
	while(t--){
		int N, K, endGame, time[1001] = {}, inDegree[1001] = {}, ans[1001] = {};
		vector<int> graph[1001];
		stack<int> s;

		cin >> N >> K;
		for (int i = 1; i <= N; i++)
			cin >> time[i];
		for (int i = 0, x, y; i < K; i++) {
			cin >> x >> y;
			graph[x].push_back(y);
			inDegree[y]++;
		}
		cin >> endGame;

		for (int i = 1; i <= N; i++)
			if (inDegree[i] == 0){
				s.push(i);
				ans[i] = time[i];
			}

		while (!s.empty()) {
			int cur = s.top();
			s.pop();
			if (cur == endGame) {
				cout << ans[cur] << '\n';
				break;
			}
			for (int i = 0; i < graph[cur].size(); i++) {
				inDegree[graph[cur][i]]--;
				ans[graph[cur][i]] = max(ans[graph[cur][i]], ans[cur] + time[graph[cur][i]]);
				if (inDegree[graph[cur][i]] == 0) {
					s.push(graph[cur][i]);
				}
			}
		}
	}
	return 0;
}

 

문제 : https://www.acmicpc.net/problem/1516


위상 정렬

● 건물을 동시에 지을 수 있으므로, 각 건물별로 필요한 시간은 그래프 경로 중 가장 긴 경로가 된다.

ans[i] := 건물 순서를 고려해 i 번 째 건물을 짓는데 필요한 최대 시간.

● time[i] := 단순히 i번 째 건물만 짓는데 걸리는 시간.

위상 정렬을 이용해 inDegree가 0인 노드부터 탐색을 시작해, 현재 탐색중인 노드에서 연결된 노드들의 ans[i] 를 계속 갱신을 해주면 된다. 예시를 통해 다뤄보겠다.

#include<iostream>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);

	int N, M, inDegree[501] = {}, time[501] = {}, ans[501] = {};
	vector<int> graph[501] = {};
	stack<int> s;

	cin >> N;
	for (int i = 1, x; i <= N; i++) {
		cin >> time[i];
		while(1){
			cin >> x;
			if (x == -1)
				break;
			graph[x].push_back(i);
			inDegree[i]++;
		}
	}

	for (int i = 1; i <= N; i++)
		if (inDegree[i] == 0){
			s.push(i);
			ans[i] = time[i];
		}

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

		for (int i = 0; i < graph[cur].size(); i++) {
			inDegree[graph[cur][i]]--;
			ans[graph[cur][i]] = max(ans[graph[cur][i]], ans[cur] + time[graph[cur][i]]);
			if (inDegree[graph[cur][i]] == 0) {
				s.push(graph[cur][i]);
			}
		}
	}

	for (int i = 1; i <= N; i++)
		cout << ans[i] << '\n';

	return 0;
}

 

+ Recent posts