충남대학교 컴퓨터공학과 이영석 교수님의 "알고리즘" 강의 실습 내용입니다.
- 실습 문제들의 코드 위주로 구성되어 있습니다.
문제 1: 얼음장수
#include <cstdlib>
#include <iostream>
#include <vector>
#include <array>
#include <cmath>
#include <queue>
using namespace std;
#define ROW 0
#define COL 1
#define BEG -1
#define END -2
int dr[4] = { -1, 0, 1, 0 };
int dc[4] = { 0, 1, 0, -1 };
array<int, 2> start, endd;
// 좌표의 정보를 담는 구조체
struct Cor {
int row, col, G, H;
// 좌표 객체를 생성하면 Heuristic값이 자동으로 계산됨
Cor(const int& r, const int& c, const int& g): row(r), col(c), G(g) {
int dr = r - endd[ROW];
int dc = c - endd[COL];
H = sqrt(dr*dr + dc*dc);
}
// 좌표의 대소비교는 G와 Heuristic를 더한 값을 기준으로 수행됨
bool operator<(const Cor& c) const { return this->G + this->H < c.G + c.H; }
bool operator>(const Cor& c) const { return this->G + this->H > c.G + c.H; }
bool operator==(const Cor& c) const { return this->G + this->H == c.G + c.H; }
};
int main() {
int R, C, ice;
cin >> R >> C >> ice;
// 입력값으로 좌표평면을 초기화하고 방문체크용 배열도 초기화함
vector<vector<int>> matrix(R, vector<int>(C));
vector<vector<bool>> visited(R, vector<bool>(C, false));
for(int r = 0; r < R; r++) {
string line;
cin >> line;
for(int c = 0; c < C; c++) {
switch(line[c]) {
case 'S' : matrix[r][c] = BEG; start[ROW] = r; start[COL] = c; visited[r][c] = true; break;
case 'E' : matrix[r][c] = END; endd[ROW] = r; endd[COL] = c; break;
default: matrix[r][c] = line[c] - '0';
}
}
}
// 우선순위 큐를 생성하여 G + Heuristic값이 작은 좌표를 선택하도록 함
priority_queue<Cor, vector<Cor>, greater<Cor>> pq;
// 시작지점을 우선순위 큐에 넣어줌
pq.push(Cor(start[ROW], start[COL], 0));
while(!pq.empty()) {
auto h = pq.top();
pq.pop();
// 현재 좌표가 종료지점이면 녹은 얼음의 양을 초기 얼음의 양에서 빼준 뒤 반복문을 빠져나옴
if(matrix[h.row][h.col] == END) {
ice -= h.G - END;
break;
}
for(int d = 0; d < 4; d++) {
int nr = h.row + dr[d];
int nc = h.col + dc[d];
if(0 <= nr && nr < R && 0 <= nc && nc < C) {
// 현재좌표와 인접한 좌표가 방문하지 않은 좌표라면 우선순위 큐에 넣어주고 방문체크함
if(!visited[nr][nc]) {
// G의 값으로 지금까지 녹은 얼음의 양과 해당 좌표로 갈 경우 녹는 얼음의 양의 합을 설정함
pq.push(Cor(nr, nc, h.G + matrix[nr][nc]));
visited[nr][nc] = true;
}
}
}
}
// 결과 출력
if(ice < 0) {
cout << "FAIL" << endl;
} else {
cout << ice << endl;
}
return 0;
}문제 2: 위상정렬
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <queue>
using namespace std;
#define umap unordered_map
#define uset unordered_set
int main() {
int N, E;
cin >> N >> E;
// 한 노드에서 갈 수 있는 다음 노드들을 저장
umap<string, vector<string>> nextIs;
// 해당 노드를 방문하기 위해 먼저 방문해야 되는 노드들을 저장
umap<string, vector<string>> beforeIs;
// 선행노드가 없는 노드가 시작지점이기 때문에 그러한 노드를 저장하기 위한 set
uset<string> noDepend;
// 각 자료구조들을 초기화함
for(int i = 0; i < N; i++) {
string node;
cin >> node;
nextIs[node] = {};
beforeIs[node] = {};
noDepend.insert(node);
}
// 후행노드, 선행노드 연결관계를 입력받고
// 선행노드가 있는 노드를 시작지점 집합에서 삭제함
for(int i = 0; i < E; i++) {
string s, d;
cin >> s >> d;
nextIs[s].push_back(d);
beforeIs[d].push_back(s);
noDepend.erase(d);
}
// 방문한 노드를 체크하기 위함
uset<string> completed;
// 정렬된 결과를 담음
vector<string> seq;
seq.reserve(N);
// 큐를 생성하고 시작지점 집합에 들어있는 노드들을 넣어줌
queue<string> q;
for(auto it = noDepend.begin(); it != noDepend.end(); it++) {
q.push(*it);
}
while(!q.empty()) {
auto cur = q.front();
q.pop();
// 큐의 head를 방문처리해주고 정렬결과에 담음
completed.insert(cur);
seq.push_back(cur);
// 현재 노드의 모든 후행노드에 대해서
for(const auto& next : nextIs[cur]) {
// 해당 후행 노드의 선행 노드가 모두 완료되었는지 체크
bool dependCompl = true;
for(const auto& depend : beforeIs[next]) {
if(completed.count(depend) == 0) {
dependCompl = false;
}
}
// 선행노드가 모두 완료되었다면 큐에 넣어줌
if(dependCompl) {
q.push(next);
}
}
}
// 결과 출력
for(int i = 0; i < N - 1; i++) {
cout << seq[i] << ' ';
}
cout << seq.back() << endl;
}문제 3: 네비게이션
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
#define MAXINT 2147483647
int main() {
int N;
cin >> N;
// 간선의 용량을 저장
vector<vector<int>> capp(N, vector<int>(N, 0));
// 간선에 흐르는 유량을 저장
vector<vector<int>> flow(N, vector<int>(N, 0));
// 노드 연결 관계 저장
vector<vector<int>> conn(N);
// 자료구조 초기화
for(int i = 0; i < N; i++) {
for(int j = 0; j < N; j++) {
int c;
cin >> c;
if(c > 0) {
capp[i][j] += c;
conn[i].push_back(j);
conn[j].push_back(i);
}
}
}
int tot = 0;
const int beg = 0, endd = N - 1;
while(true) {
// 시작지점에서 종료지점까지의 경로를 하나 선택함
// 어떤 노드를 방문하기 바로 직전에 방문했던 노드를 저장하는 방식으로 경로를 저장함
vector<int> prev(N, -1);
// 시작지점부터 BFS로 탐색
queue<int> q;
q.push(beg);
// prev[endd] == -1이라면 아직 목적지까지 도달한것이 아니므로
while(!q.empty() && prev[endd] == -1) {
auto curr = q.front();
q.pop();
for(auto& next : conn[curr]) {
// 현재 노드와 연결된 다음 노드가 방문한 적이 없어야 하고
if(prev[next] == -1) {
// 현재 노드에서 다음 노드로 가는 간선의 용량이 유량보다 많아야 됨
if(capp[curr][next] > flow[curr][next]) {
prev[next] = curr;
// 다음 노드가 목적지일 경우 break를 걸어 제일 먼저 찾은 경로를 검토하게 함
if(next == endd) { break; }
q.push(next);
}
}
}
}
// 경로상의 모든 간선에 대해 유량이 용량보다 작은 경로를 찾지 못한 경우
if(prev[endd] == -1) { break; }
// 찾은 경로에서 추가적으로 흘려보낼 수 있는 유량의 양을 계산함
int nextFlow = MAXINT;
for(auto it = endd; it != beg; it = prev[it]) {
// 경로상의 (용량 - 유량)의 최솟값을 구하면 그것이 모든 간선을 통과할 수 있는 유량이 됨
nextFlow = min(nextFlow, capp[prev[it]][it] - flow[prev[it]][it]);
}
// 경로상의 모든 간선에 유량을 추가함
for(auto it = endd; it != beg; it = prev[it]) {
flow[prev[it]][it] += nextFlow;
// 한방향으로 유체가 흐른다는 것은 반대방향으로 음의 유량이 흐른다고도 해석할 수 있으므로
// 경로의 간선에 대해 반대방향으로 음의 유량을 더해줌
flow[it][prev[it]] -= nextFlow;
}
// 총 흐르고 있는 유량의 크기를 더해줌
tot += nextFlow;
}
// 결과 출력
cout << tot << endl;
return 0;
}