-
Notifications
You must be signed in to change notification settings - Fork 23
additional tasks by Krivoruchko Dmitry #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9cfcf59
39f8a8f
aa962ae
6e59658
b5d47d1
ae45494
6032727
bcd54e5
5230994
cf04922
4c939f8
ff9397f
1ccac8e
64008e7
3976ff9
04c762f
b85a801
ffa9925
4e8ea35
b9d5975
c47cb73
4fa263b
8691cba
b224e5b
4ddb11c
6bfef24
ffd686d
fb1cbba
0919619
f737732
05774f8
60b581a
6672c40
60f10e7
80637ba
a530c1f
12dc12a
4000c47
b553558
729a22e
664a7e5
cfc14c9
f1d3404
6cb4ae5
f01684b
dd17ff5
19cffc6
f4321ff
01b22c8
5dee1f6
650045a
d40c1bd
903be8b
16f71c5
1f3015b
8cdba58
e252cdf
2382a0d
ce2993f
609b741
7a14ed6
380efe7
c87ebc8
c2b29aa
b7fda02
5a1efed
5d4385a
b600b27
a67d809
cbe81d7
dfe0bfe
e5fa95a
1097fec
fc5f0d2
6d560f1
5761e70
efa50c7
fc3e316
6e411db
07019ff
8afd94e
2db05e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,21 @@ | ||
cmake_minimum_required(VERSION 3.10) | ||
cmake_minimum_required(VERSION 3.20) | ||
|
||
project(homeworks) | ||
project(homeworks LANGUAGES CXX) | ||
|
||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib) | ||
set(CMAKE_CXX_STANDARD 23) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic -std=c++23 -O2") | ||
|
||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib) | ||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sandbox) | ||
|
||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/additional_tasks) | ||
|
||
file(GLOB_RECURSE tasks_dirs LIST_DIRECTORIES true ".") | ||
file(GLOB_RECURSE SUBFOLDERS LIST_DIRECTORIES true ".") | ||
|
||
foreach(dir ${tasks_dirs}) | ||
IF(IS_DIRECTORY ${dir}) | ||
IF(${dir} MATCHES "task_0[0-9]$" AND NOT ${dir} MATCHES "build") | ||
add_subdirectory(${dir}) | ||
ENDIF() | ||
ELSE() | ||
CONTINUE() | ||
ENDIF() | ||
foreach(FOLDER ${SUBFOLDERS}) | ||
if(IS_DIRECTORY ${FOLDER}) | ||
if(${FOLDER} MATCHES "task_0[0-9]$" AND NOT ${FOLDER} MATCHES "build") | ||
add_subdirectory(${FOLDER}) | ||
endif() | ||
endif() | ||
endforeach() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
# Домашнее задание для 2 семестра алгоритмов и структур данных | ||
## (Homework for second semester algorithms and data structures on MIPT DAFE/RSE) | ||
|
||
### Для удобства можно пользоваться папкой lib, все файлы из этой папки будут подключаться к любой задаче | ||
|
||
### Можно получить дополнительные баллы, если добавить интересные текстовые задачи. Необходимы текст задачи, решение и тесты. Каждая задача отдельный ПР, полчуть дополнительные баллы можно только если пулл реквест замержен в основную ветку. | ||
Можно получить дополнительные баллы, если добавить интересные текстовые задачи. Необходимы текст задачи, решение и тесты. Каждая задача отдельный - Pull Request, получить дополнительные баллы можно только если PR замерджен в основную ветку. | ||
|
||
### Можно получить дополнительные баллы, если добавить теорию в папку doc. Делается в отдельном ПР, полчуть дополнительные баллы можно только если пулл реквест замержен в основную ветку. | ||
Можно получить дополнительные баллы, если добавить теорию в папку doc. Делается в отдельном PR, только если PR замерджен в основную ветку. | ||
|
||
### Код должен быть отформатирован clang-format'ом со стилем Google | ||
Код должен быть отформатирован clang-format'ом со стилем Google. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,13 @@ | ||
cmake_minimum_required(VERSION 3.10) | ||
cmake_minimum_required(VERSION 3.20) | ||
|
||
project(additional_tasks) | ||
project(additional_tasks LANGUAGES CXX) | ||
|
||
file(GLOB_RECURSE tasks_dirs LIST_DIRECTORIES true ".") | ||
file(GLOB_RECURSE SUBFOLDERS LIST_DIRECTORIES true ".") | ||
|
||
foreach(dir ${tasks_dirs}) | ||
IF(IS_DIRECTORY ${dir}) | ||
IF(NOT ${dir} MATCHES ".*src.*") | ||
add_subdirectory(${dir}) | ||
ENDIF() | ||
ELSE() | ||
CONTINUE() | ||
ENDIF() | ||
foreach(FOLDER ${SUBFOLDERS}) | ||
if(IS_DIRECTORY ${FOLDER}) | ||
if(NOT ${FOLDER} MATCHES ".*src.*") | ||
add_subdirectory(${FOLDER}) | ||
endif() | ||
endif() | ||
endforeach() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
cmake_minimum_required(VERSION 3.20) | ||
|
||
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) | ||
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME}) | ||
project(${PROJECT_NAME} LANGUAGES CXX) | ||
|
||
set(CMAKE_CXX_STANDARD 23) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
|
||
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp") | ||
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp") | ||
set(TEST_SOURCE_LIST ${SOURCE_LIST}) | ||
file(GLOB_RECURSE test_list "src/*test.cpp") | ||
|
||
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST}) | ||
list(REMOVE_ITEM SOURCE_LIST ${test_list}) | ||
|
||
include_directories(${PROJECT_NAME} PUBLIC src) | ||
|
||
find_library(Utils ../) | ||
|
||
add_executable(${PROJECT_NAME} ${SOURCE_LIST}) | ||
target_link_libraries(${PROJECT_NAME} PUBLIC Utils) | ||
|
||
# Locate GTest | ||
enable_testing() | ||
find_package(GTest REQUIRED) | ||
include_directories(${GTEST_INCLUDE_DIRS}) | ||
|
||
# Link runTests with what we want to test and the GTest and pthread library | ||
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST}) | ||
target_link_libraries( | ||
${PROJECT_NAME}_tests | ||
GTest::gtest_main | ||
Utils | ||
) | ||
|
||
include(GoogleTest) | ||
gtest_discover_tests(${PROJECT_NAME}_tests) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
# Задача: охранники речных мостов | ||
|
||
Река имеет `N` контрольно-пропускных пунктов на левом берегу и `M` контрольно-пропускных пунктов на правом берегу. | ||
|
||
`P` мостов построены, соединяя контрольно-пропускные пункты через реку. На контрольно-пропускных пунктах необходимо разместить охрану, и охрана может защищать все мосты, на которых присутствует этот КПП. | ||
|
||
Для защиты одного моста может быть более одного охранника. | ||
|
||
Найдите минимальное количество охранников, необходимое для защиты всех мостов через реку. | ||
|
||
## Входные данные: | ||
|
||
* Первая строка ввода состоит из 2 целых чисел, разделенных пробелами `N` | ||
и `M` − количество КПП на левом и правом берегу реки соответственно (1 ≤ `N`, `M` ≤ 100) | ||
* Вторая строка ввода состоит из одного целого числа `P` − общее количество мостов через реку (1 ≤ `P` ≤ 100). | ||
* Следующие `P` строк, каждая из которых состоит из 2 целых чисел, разделенных пробелами `u`, `v`, обозначающих, что между КПП `u` на левом берегу и КПП `v` на правом берегу есть мост (1 ≤ `u` ≤ `N`) (1 ≤ `v` ≤ `M`). | ||
|
||
## Выходные данные: | ||
|
||
* Одно целое число − минимальное количество охранников, необходимое для защиты всех мостов через реку. | ||
|
||
## Пример: | ||
|
||
### Вход: | ||
``` | ||
4 3 | ||
4 | ||
1 3 | ||
1 2 | ||
2 2 | ||
4 1 | ||
``` | ||
|
||
### Выход: | ||
``` | ||
3 | ||
``` | ||
|
||
### Решение: | ||
|
||
#### Введение и моделирование графом: | ||
|
||
Для решения задачи о часовых на реке, смоделируем ситуацию с помощью двудольного графа. | ||
Одна доля графа представляла пункты наблюдения на левом берегу, а другая – на правом берегу. | ||
|
||
Ребра между долями обозначали мосты. Целью было найти минимальный набор вершин (пунктов), которые ‘покрывают’ все ребра (мосты). Это классическая задача поиска минимального вершинного покрытия. | ||
|
||
Известно, что в общем случае задача поиска минимального вершинного покрытия является NP-трудной, то есть не существует быстрого алгоритма для произвольных графов. Однако, для двудольных графов эту задачу можно решить за полиномиальное время, применив теорему Кёнига. | ||
|
||
**Теорема Кёнига** утверждает, что в двудольном графе размер минимального вершинного покрытия равен размеру максимального паросочетания. | ||
|
||
*Паросочетание* – это набор ребер, которые не имеют общих вершин, то есть ни одна вершина не является концом более чем одного ребра в паросочетании. | ||
|
||
*Максимальное паросочетание* – это паросочетание с наибольшим возможным количеством ребер. | ||
|
||
#### Максимальное паросочетание и алгоритм поиска максимального потока: | ||
|
||
Для нахождения максимального паросочетания используем алгоритм поиска максимального потока. | ||
|
||
Чтобы применить этот алгоритм, преобразуем двудольный граф в сеть, добавив искусственный источник (`s`) и сток (`t = s + 1`). | ||
|
||
Источник (`s`) имел ребра единичной пропускной способности ко всем вершинам, представляющим пункты на левом берегу. Все вершины, представляющие пункты на правом берегу, имели ребра единичной пропускной способности к стоку (`t`). Рёбра между пунктами левого и правого берегов (представляющие мосты) также имели единичную пропускную способность. | ||
|
||
Это преобразование позволило свести задачу поиска максимального паросочетания к задаче поиска максимального потока в созданной сети. | ||
|
||
Для поиска максимального потока используем модификацию алгоритма Эдмондса-Карпа, реализуя обход в ширину (BFS) для поиска увеличивающего пути. | ||
|
||
Функция `BFS` реализована следующим образом: | ||
```C++ | ||
inline size_t BFS(size_t s, std::vector<ssize_t>& parent, | ||
std::vector<std::vector<size_t>>& adj_list, | ||
std::vector<std::vector<size_t>>& capacity) { | ||
parent[s] = -2; | ||
size_t n = parent.size(); | ||
|
||
std::vector<bool> visited(n, false); | ||
visited[s] = true; | ||
|
||
std::queue<std::pair<size_t, size_t>> nodes; | ||
|
||
nodes.push({s, LLONG_MAX}); | ||
|
||
while (!nodes.empty()) { | ||
std::pair<size_t, size_t> node_flow = nodes.front(); | ||
size_t node = node_flow.first; | ||
size_t flow = node_flow.second; | ||
|
||
nodes.pop(); | ||
|
||
for (size_t i = 0; i < adj_list[node].size(); i++) { | ||
size_t next = adj_list[node][i]; | ||
|
||
if (visited[next] || capacity[node][i] == 0) continue; | ||
|
||
visited[next] = true; | ||
|
||
parent[next] = node; | ||
|
||
size_t new_flow = std::min(flow, capacity[node][i]); | ||
|
||
if (next == s + 1) return new_flow; | ||
nodes.push({next, new_flow}); | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
``` | ||
|
||
В этой функции `parent` используется для отслеживания пути, `adj_list` - список смежности, `capacity` - матрица пропускных способностей. Функция возвращает пропускную способность найденного увеличивающего пути, или 0, если путь не найден. | ||
|
||
#### Реализация алгоритма максимального потока: | ||
|
||
Алгоритм максимального потока реализован в функции `MaxFlow`: | ||
```C++ | ||
inline size_t MaxFlow(size_t s, std::vector<std::vector<size_t>>& adj_list, | ||
std::vector<std::vector<size_t>>& capacity) { | ||
size_t flow = 0; | ||
std::vector<ssize_t> parent(adj_list.size(), -1); | ||
|
||
size_t new_flow = 0; | ||
|
||
while ((new_flow = BFS(s, parent, adj_list, capacity))) { | ||
flow += new_flow; | ||
size_t curr = s + 1; | ||
|
||
while (curr != s) { | ||
size_t prev = parent[curr]; | ||
size_t idx = (find(adj_list[prev].begin(), adj_list[prev].end(), curr) - | ||
adj_list[prev].begin()); | ||
capacity[prev][idx] -= new_flow; | ||
|
||
idx = (find(adj_list[curr].begin(), adj_list[curr].end(), prev) - | ||
adj_list[curr].begin()); | ||
capacity[curr][idx] += new_flow; | ||
|
||
curr = prev; | ||
} | ||
} | ||
|
||
return flow; | ||
} | ||
|
||
``` | ||
|
||
Функция `MaxFlow` инициализирует поток в 0, и пока находит увеличивающие пути с помощью `BFS`, наращивает поток и обновляет пропускные способности остаточных ребер. Возвращает величину максимального потока. | ||
|
||
#### Преобразование двудольного графа в сеть и нахождение максимального паросочетания | ||
|
||
Преобразование двудольного графа в сеть и использование алгоритма максимального потока для нахождения максимального паросочетания реализовано в функции `MaximumBipartiteMatching`: | ||
```C++ | ||
inline size_t MaximumBipartiteMatching(size_t n, size_t m, | ||
Graph<size_t>& bipartite_graph) { | ||
std::vector<std::vector<std::pair<size_t, size_t>>> bipartite_edges_stack( | ||
n + m + 3); | ||
std::vector<std::vector<size_t>> adj_list(n + m + 3); | ||
std::vector<std::vector<size_t>> capacity(n + m + 3); | ||
|
||
for (size_t i = 0; i < bipartite_graph.EdgesAmount(); i++) { | ||
size_t u = StartVertFromTuple(bipartite_graph.Edges()[i]); | ||
size_t v = EndVertFromTuple(bipartite_graph.Edges()[i]); | ||
|
||
v += n; | ||
|
||
bipartite_edges_stack[u].push_back({v, 1}); | ||
bipartite_edges_stack[v].push_back({u, 0}); | ||
} | ||
|
||
for (size_t i = 1; i <= n; i++) { | ||
bipartite_edges_stack[n + m + 1].push_back({i, 1}); | ||
bipartite_edges_stack[i].push_back({n + m + 1, 0}); | ||
} | ||
for (size_t i = 1; i <= m; i++) { | ||
bipartite_edges_stack[i + n].push_back({n + m + 2, 1}); | ||
bipartite_edges_stack[n + m + 2].push_back({i + n, 0}); | ||
} | ||
|
||
for (size_t i = 1; i <= n + m + 2; i++) | ||
sort(bipartite_edges_stack[i].begin(), bipartite_edges_stack[i].end()); | ||
|
||
for (size_t i = 1; i <= n + m + 2; i++) | ||
for (size_t j = 0; j < bipartite_edges_stack[i].size(); j++) { | ||
adj_list[i].push_back(bipartite_edges_stack[i][j].first); | ||
capacity[i].push_back(bipartite_edges_stack[i][j].second); | ||
} | ||
|
||
return MaxFlow(n + m + 1, adj_list, capacity); | ||
} | ||
|
||
``` | ||
|
||
В этой функции двудольный граф `bipartite_graph` преобразуется в сеть с помощью `bipartite_edges_stack`, добавляется источник (`n + m + 1`) и сток (`n + m + 2`), затем строится матрица смежности `adj_list` и матрица пропускных способностей `capacity` и вызывается функция MaxFlow для вычисления максимального потока. | ||
|
||
#### Вызов функции решения и заключение: | ||
|
||
Функция `Solution` считывает входные данные и выводит результат: | ||
|
||
```C++ | ||
inline void Solution(std::istream& is = std::cin, | ||
std::ostream& os = std::cout) { | ||
size_t n, m; | ||
is >> n >> m; | ||
|
||
size_t p; | ||
is >> p; | ||
|
||
Graph<size_t> bipartite_graph; | ||
|
||
for (size_t i = 0; i < p; i++) { | ||
size_t u, v; | ||
is >> u >> v; | ||
|
||
bipartite_graph.AddEdge({u, v}); | ||
} | ||
|
||
os << MaximumBipartiteMatching(n, m, bipartite_graph) << std::endl; | ||
return; | ||
} | ||
``` | ||
|
||
В результате, величина максимального потока, возвращаемая функцией `MaximumBipartiteMatching` и являющаяся размером максимального паросочетания, дает размер минимального вершинного покрытия, что и являлось ответом к задаче – минимальное количество необходимых часовых. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
#include <climits> | ||
#include <queue> | ||
|
||
#include "graph.hpp" | ||
|
||
/** | ||
* @brief Функция BFS (поиск в ширину) для нахождения увеличивающего пути в | ||
* сети. | ||
* | ||
* @param s: начальная вершина (источник) в сети. | ||
* @param parent: массив, хранящий родительские вершины для восстановления пути. | ||
* @param adj_list: список смежности графа. | ||
* @param capacity: матрица пропускных способностей ребер. | ||
* | ||
* @return `size_t`: пропускная способность найденного увеличивающего пути, или | ||
* 0, если путь не найден. | ||
*/ | ||
inline size_t BFS(size_t s, std::vector<ssize_t>& parent, | ||
std::vector<std::vector<size_t>>& adj_list, | ||
std::vector<std::vector<size_t>>& capacity) { | ||
// инициализация родительского массива и массива посещенных вершин | ||
|
||
parent[s] = -2; // отмечаем начальную вершину как посещенную, значение -2 | ||
// используется для обозначения источника | ||
size_t n = parent.size(); | ||
|
||
std::vector<bool> visited(n, false); | ||
visited[s] = true; | ||
|
||
// используем очередь для обхода графа в ширину | ||
std::queue<std::pair<size_t, size_t>> nodes; | ||
|
||
// добавляем начальную вершину с максимальным потоком | ||
nodes.push({s, LLONG_MAX}); | ||
|
||
// обходим граф до тех пор, пока очередь не пуста | ||
while (!nodes.empty()) { | ||
// извлекаем вершину и её текущий поток из очереди | ||
std::pair<size_t, size_t> node_flow = nodes.front(); | ||
size_t node = node_flow.first; | ||
size_t flow = node_flow.second; | ||
|
||
nodes.pop(); | ||
|
||
// проверяем все ребра, исходящие из текущей вершины | ||
for (size_t i = 0; i < adj_list[node].size(); i++) { | ||
size_t next = adj_list[node][i]; // следующая вершина | ||
|
||
// если следующая вершина уже посещена или пропускная способность ребра | ||
// равна 0, то пропускаем ребро | ||
if (visited[next] || capacity[node][i] == 0) continue; | ||
|
||
// отмечаем следующую вершину как посещенную | ||
visited[next] = true; | ||
|
||
// записываем родительскую вершину | ||
parent[next] = node; | ||
|
||
// вычисляем новый поток как min из текущего потока и пропускной | ||
// способности ребра | ||
size_t new_flow = std::min(flow, capacity[node][i]); | ||
|
||
// если достигли стока, то возвращаем найденный поток | ||
if (next == s + 1) return new_flow; | ||
// иначе добавляем следующую вершину в очередь с новым потоком | ||
nodes.push({next, new_flow}); | ||
} | ||
} | ||
|
||
// увеличивающий путь не найден, возвращаем 0 | ||
return 0; | ||
} | ||
|
||
/** | ||
* @brief Функция для вычисления максимального потока в сети. | ||
* | ||
* @param s: начальная вершина (источник) в сети. | ||
* @param adj_list: список смежности графа. | ||
* @param capacity: матрица пропускных способностей ребер. | ||
* | ||
* @return `size_t`: величина максимального потока в сети. | ||
*/ | ||
inline size_t MaxFlow(size_t s, std::vector<std::vector<size_t>>& adj_list, | ||
std::vector<std::vector<size_t>>& capacity) { | ||
size_t flow = 0; // инициализируем максимальный поток нулем | ||
std::vector<ssize_t> parent(adj_list.size(), -1); // родительский массив | ||
|
||
size_t new_flow = 0; | ||
|
||
// повторяем поиск увеличивающих путей до тех пор, пока они находятся | ||
while ((new_flow = BFS(s, parent, adj_list, capacity))) { | ||
// добавляем найденный поток к максимальному потоку | ||
flow += new_flow; | ||
|
||
size_t curr = s + 1; // начинаем с стока | ||
|
||
// обновляем пропускные способности ребер по найденному пути | ||
while (curr != s) { | ||
size_t prev = parent[curr]; | ||
size_t idx = (find(adj_list[prev].begin(), adj_list[prev].end(), curr) - | ||
adj_list[prev].begin()); | ||
capacity[prev][idx] -= new_flow; | ||
|
||
idx = (find(adj_list[curr].begin(), adj_list[curr].end(), prev) - | ||
adj_list[curr].begin()); | ||
capacity[curr][idx] += new_flow; | ||
|
||
curr = prev; | ||
} | ||
} | ||
|
||
return flow; // максимальный поток | ||
} | ||
|
||
/** | ||
* @brief Функция для вычисления максимального паросочетания в двудольном графе. | ||
* | ||
* @param n: количество вершин в первой доле графа. | ||
* @param m: количество вершин во второй доле графа. | ||
* @param bipartite_graph: объект, представляющий двудольный граф. | ||
* | ||
* @return `size_t`: размер максимального паросочетания. | ||
*/ | ||
inline size_t MaximumBipartiteMatching(size_t n, size_t m, | ||
Graph<size_t>& bipartite_graph) { | ||
// вспомогательные структуры данных для алгоритма Форда-Фалкерсона | ||
std::vector<std::vector<std::pair<size_t, size_t>>> bipartite_edges_stack( | ||
n + m + 3); | ||
std::vector<std::vector<size_t>> adj_list(n + m + 3); | ||
std::vector<std::vector<size_t>> capacity(n + m + 3); | ||
|
||
// преобразуем двудольный граф в сеть | ||
|
||
for (size_t i = 0; i < bipartite_graph.EdgesAmount(); i++) { | ||
size_t u = StartVertFromTuple(bipartite_graph.Edges()[i]); | ||
size_t v = EndVertFromTuple(bipartite_graph.Edges()[i]); | ||
|
||
v += n; // смещаем номера вершин второй доли | ||
|
||
bipartite_edges_stack[u].push_back({v, 1}); | ||
bipartite_edges_stack[v].push_back( | ||
{u, 0}); // обратное ребро с пропускной способностью 0 | ||
} | ||
|
||
// добавляем источник и сток | ||
for (size_t i = 1; i <= n; i++) { | ||
bipartite_edges_stack[n + m + 1].push_back({i, 1}); | ||
bipartite_edges_stack[i].push_back({n + m + 1, 0}); | ||
} | ||
for (size_t i = 1; i <= m; i++) { | ||
bipartite_edges_stack[i + n].push_back({n + m + 2, 1}); | ||
bipartite_edges_stack[n + m + 2].push_back({i + n, 0}); | ||
} | ||
|
||
// сортируем ребра для удобства работы | ||
for (size_t i = 1; i <= n + m + 2; i++) | ||
sort(bipartite_edges_stack[i].begin(), bipartite_edges_stack[i].end()); | ||
|
||
// списки смежности и пропускных способностей | ||
for (size_t i = 1; i <= n + m + 2; i++) | ||
for (size_t j = 0; j < bipartite_edges_stack[i].size(); j++) { | ||
adj_list[i].push_back(bipartite_edges_stack[i][j].first); | ||
capacity[i].push_back(bipartite_edges_stack[i][j].second); | ||
} | ||
|
||
// максимальный поток, который равен максимальному паросочетанию | ||
return MaxFlow(n + m + 1, adj_list, capacity); | ||
} | ||
|
||
inline void Solution(std::istream& is = std::cin, | ||
std::ostream& os = std::cout) { | ||
size_t n, m; | ||
is >> n >> m; | ||
|
||
size_t p; | ||
is >> p; | ||
|
||
Graph<size_t> bipartite_graph; | ||
|
||
for (size_t i = 0; i < p; i++) { | ||
size_t u, v; | ||
is >> u >> v; | ||
|
||
bipartite_graph.AddEdge({u, v}); | ||
} | ||
|
||
os << MaximumBipartiteMatching(n, m, bipartite_graph) << std::endl; | ||
return; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#include "bridge_guards.hpp" | ||
|
||
int main() { | ||
Solution(); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
#include <gtest/gtest.h> | ||
|
||
#include "bridge_guards.hpp" | ||
|
||
TEST(BridgeGuardsTest, SimpleTest) { | ||
std::stringstream ss; | ||
ss << "1 1\n" | ||
<< "1\n" | ||
<< "1 1\n"; | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "1\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, Test_1) { | ||
std::stringstream ss; | ||
ss << "4 3\n" | ||
<< "4\n" | ||
<< "1 3\n" | ||
<< "1 2\n" | ||
<< "2 1\n" | ||
<< "2 3\n"; | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "2\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, Test_2) { | ||
std::stringstream ss; | ||
ss << "4 3\n" | ||
<< "4\n" | ||
<< "1 3\n" | ||
<< "1 2\n" | ||
<< "2 2\n" | ||
<< "4 1\n"; | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "3\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, Test_3) { | ||
std::stringstream ss; | ||
ss << "4 4\n" | ||
<< "4\n" | ||
<< "1 1\n" | ||
<< "2 2\n" | ||
<< "3 3\n" | ||
<< "4 4\n"; | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "4\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, Test_4) { | ||
std::stringstream ss; | ||
ss << "5 2\n" | ||
<< "5\n" | ||
<< "1 1\n" | ||
<< "2 1\n" | ||
<< "3 1\n" | ||
<< "4 2\n" | ||
<< "5 2\n"; | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "2\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, HardTest) { | ||
std::stringstream ss; | ||
ss << "10 6\n" | ||
<< "12\n" | ||
<< "1 1\n" | ||
<< "2 1\n" | ||
<< "3 1\n" | ||
<< "4 3\n" | ||
<< "5 3\n" | ||
<< "6 2\n" | ||
<< "7 3\n" | ||
<< "8 3\n" | ||
<< "9 4\n" | ||
<< "9 5\n" | ||
<< "10 5\n" | ||
<< "10 6\n"; | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "5\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, EmptyGraph) { | ||
std::stringstream ss; | ||
ss << "0 0\n" // 0 вершин в обеих долях | ||
<< "0\n"; // 0 мостов | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "0\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, NoEdges) { | ||
std::stringstream ss; | ||
ss << "5 3\n" // вершин в первой доле, 3 во второй | ||
<< "0\n"; // 0 мостов | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "0\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, OneSidedGraph) { | ||
std::stringstream ss; | ||
ss << "5 0\n" // 5 вершин в первой доле, 0 во второй | ||
<< "0\n"; // 0 мостов | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "0\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, OneSidedGraph2) { | ||
std::stringstream ss; | ||
ss << "0 3\n" // 0 вершин в первой доле, 3 во второй | ||
<< "0\n"; // 0 мостов | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "0\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, LargeGraphSmallEdges) { | ||
std::stringstream ss; | ||
ss << "100 100\n" // 100 вершин в каждой доле | ||
<< "5\n" // 5 мостов | ||
<< "1 1\n" | ||
<< "2 2\n" | ||
<< "3 3\n" | ||
<< "4 4\n" | ||
<< "5 5\n"; | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "5\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, CompleteBipartiteGraph) { | ||
std::stringstream ss; | ||
ss << "3 3\n" | ||
<< "9\n" | ||
<< "1 1\n" | ||
<< "1 2\n" | ||
<< "1 3\n" | ||
<< "2 1\n" | ||
<< "2 2\n" | ||
<< "2 3\n" | ||
<< "3 1\n" | ||
<< "3 2\n" | ||
<< "3 3\n"; | ||
|
||
std::stringstream output; | ||
|
||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "3\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, StarShapedGraph) { | ||
std::stringstream ss; | ||
ss << "5 1\n" | ||
<< "5\n" | ||
<< "1 1\n" | ||
<< "2 1\n" | ||
<< "3 1\n" | ||
<< "4 1\n" | ||
<< "5 1\n"; | ||
|
||
std::stringstream output; | ||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "1\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, StarShapedGraph2) { | ||
std::stringstream ss; | ||
ss << "1 5\n" | ||
<< "5\n" | ||
<< "1 1\n" | ||
<< "1 2\n" | ||
<< "1 3\n" | ||
<< "1 4\n" | ||
<< "1 5\n"; | ||
|
||
std::stringstream output; | ||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "1\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, DisconnectedGraph) { | ||
std::stringstream ss; | ||
ss << "6 6\n" | ||
<< "4\n" | ||
<< "1 1\n" | ||
<< "2 2\n" | ||
<< "3 3\n" | ||
<< "4 4\n"; | ||
|
||
std::stringstream output; | ||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "4\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, Test_5) { | ||
std::stringstream ss; | ||
ss << "3 2\n" | ||
<< "4\n" | ||
<< "1 1\n" | ||
<< "1 2\n" | ||
<< "2 1\n" | ||
<< "3 1\n"; | ||
|
||
std::stringstream output; | ||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "2\n"); | ||
} | ||
|
||
TEST(BridgeGuardsTest, Test_6) { | ||
std::stringstream ss; | ||
ss << "2 3\n" | ||
<< "4\n" | ||
<< "1 1\n" | ||
<< "1 2\n" | ||
<< "2 2\n" | ||
<< "2 3\n"; | ||
|
||
std::stringstream output; | ||
Solution(ss, output); | ||
EXPECT_EQ(output.str(), "2\n"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
cmake_minimum_required(VERSION 3.20) | ||
|
||
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) | ||
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME}) | ||
project(${PROJECT_NAME} LANGUAGES CXX) | ||
|
||
set(CMAKE_CXX_STANDARD 23) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
|
||
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp") | ||
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp") | ||
set(TEST_SOURCE_LIST ${SOURCE_LIST}) | ||
file(GLOB_RECURSE test_list "src/*test.cpp") | ||
|
||
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST}) | ||
list(REMOVE_ITEM SOURCE_LIST ${test_list}) | ||
|
||
include_directories(${PROJECT_NAME} PUBLIC src) | ||
|
||
find_library(Utils ../) | ||
|
||
add_executable(${PROJECT_NAME} ${SOURCE_LIST}) | ||
target_link_libraries(${PROJECT_NAME} PUBLIC Utils) | ||
|
||
# Locate GTest | ||
enable_testing() | ||
find_package(GTest REQUIRED) | ||
include_directories(${GTEST_INCLUDE_DIRS}) | ||
|
||
# Link runTests with what we want to test and the GTest and pthread library | ||
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST}) | ||
target_link_libraries( | ||
${PROJECT_NAME}_tests | ||
GTest::gtest_main | ||
Utils | ||
) | ||
|
||
include(GoogleTest) | ||
gtest_discover_tests(${PROJECT_NAME}_tests) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Задача: обратимые состояния в цепочке химических экспериментов | ||
|
||
В прогрессивной Московской химической лаборатории используют новые методы описания цепочки экспериментов: лаборанты изображают их в виде графов, вершинами которого являются состояния вещества, а эксперименты - ребрами. | ||
|
||
Таким образом компании, разрабатывающей программное обеспечение, используя методы хемоинформатики, очень удобно использовать эксперименты этой лаборатории в своих наработках. В одном из проектов им потребовалось найти исключительно полезные состояния, обладающие свойством обратимости в цепочке. | ||
|
||
К обратимым экспериментам относят те, результирующее состояние вещества которых можно вернуть к исходному, через другие процессы в той же цепочке; в таком случае результирующее состояния вещества также называют обратимым. | ||
|
||
Найдите по известной цепочке экспериментов, оформленной в виде графа, эти обратимые состояния. | ||
|
||
## Входные данные: | ||
|
||
* Первая строка содержит целое число `N` (`N` > 0) — количество экспериментов в графе. | ||
* Следующие `N` строк содержат по два поля, разделенных пробелом: `A` `B`, где `A` и `B` — это строковые имена вершин (состояния веществ). Ребро идет от вершины `A` к вершине `B`. Имена вершин могут содержать любые символы, кроме пробела, переноса строки и пр. | ||
|
||
## Выходные данные: | ||
|
||
* Выходные данные представляют собой строку, содержащую через пробел имена всех обратимых состояний (порядок вывода обратимых состояний не важен, состояния выводятся без повторений) | ||
* Если обратимых состояний нет, то вывод должен быть пустым. | ||
|
||
# Используемый алгоритм: нахождение компонент сильной связности алгоритмом Тарьяна | ||
|
||
## Как работает алгоритм Тарьяна: | ||
|
||
Алгоритм Тарьяна - это подход к решению задачи поиска компонент сильной связности (СС) в ориентированном графе. Он работает за линейное время (`O(|V|+|E|)`, где `V` - количество вершин, а `E` - количество ребер) и использует рекурсию для эффективного обхода графа. | ||
|
||
Алгоритм Тарьяна основывается на том, что вершина `v` является корнем СС тогда и только тогда, когда ее “низкая связь” (`low_links`) равна ее “времени входа” (`indexes`). “Низкая связь” - это минимальное “время входа” всех вершин, которые можно достичь из `v` по пути рекурсии. | ||
|
||
Иначе говоря, вершины рассматриваются в обратном топологическом порядке, поэтому в конце рекурсивной функции для исходной вершины не будет встречено ни одной вершины из той же компоненты сильной связности, так как все вершины, достижимые из исходной, уже обработаны, и обратные связи в дереве дают второй путь из одной вершины в другую и связывают компоненты сильной связности в одну. | ||
|
||
## Объяснение кода: | ||
|
||
```C++ | ||
template <typename vert_t> | ||
static void StronglyConnectedComponentsStep( | ||
const vert_t& v, size_t& curr_index, std::stack<vert_t>& verts_stack, | ||
std::unordered_map<vert_t, size_t>& indexes, | ||
std::unordered_map<vert_t, size_t>& low_links, | ||
std::unordered_map<vert_t, bool>& is_on_stack, | ||
std::unordered_map<vert_t, std::vector<vert_t>>& adj_list, | ||
std::vector<std::vector<vert_t>>& strongly_connected_components); | ||
``` | ||
С помощью функции выше выполняется рекурсивный обход. | ||
Для каждой вершины `v`, которая еще не была посещена, выполняется рекурсивный обход. При первом посещении вершины `v`, `indexes[v]` устанавливается равным `curr_index`, `low_links[v]` также устанавливается равным `curr_index`. `curr_index` увеличивается на 1. Вершина `v` помещается в стек `verts_stack`. | ||
Для каждой смежной вершины `w` вершине `v` выполняется следующее: | ||
1. Если w не была посещена (```indexes[w] == 0```): выполняется рекурсивный вызов StronglyConnectedComponentsStep для вершины `w`, `low_links[v]` обновляется минимальным значением между текущим значением и `low_links[w]`. | ||
2. Если w уже находится в стеке (`is_on_stack[w]`): | ||
`low_links[v]` обновляется минимальным значением между текущим значением и `low_links[w]`. | ||
После обработки всех смежных вершин, если ```low_links[v] == indexes[v]```, это означает, что `v` является корнем СС. | ||
В этом случае все вершины, которые находятся в стеке от v до вершины, которая была обработана ранее, также принадлежат этой СС. | ||
Вершины удаляются из стека, пока v не будет удален. Эти удаленные вершины образуют СС, которая добавляется в список СС. | ||
```C++ | ||
/** | ||
* @brief Поиск компонент сильной связности в ориентированного графа по | ||
* алгоритму Тарьяна | ||
* @tparam vert_t: тип вершин | ||
* @tparam weight_t: тип весов | ||
* @param graph: исходный граф | ||
* @throw std::invalid_argument("StronglyConnectedComponents: graph is not | ||
* directed."); | ||
* @return std::vector<std::vector<vert_t>>: компоненты сильной связности | ||
*/ | ||
template <AllowedVertType vert_t, AllowedWeightType weight_t> | ||
std::vector<std::vector<vert_t>> StronglyConnectedComponents( | ||
Graph<vert_t, weight_t> graph); | ||
``` | ||
|
||
Основная функция выше вызывает ```StronglyConnectedComponentsStep(...)``` для каждой вершины графа и возвращает список компонент. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#include "tarjan_algorithm.hpp" | ||
|
||
/// @brief Решает задачу: "обратимые состояния в цепочке химических | ||
/// экспериментов" | ||
void Solution(std::istream& is = std::cin, std::ostream& os = std::cout) { | ||
size_t experiments_amount; | ||
is >> experiments_amount; | ||
|
||
Graph<std::string> graph; | ||
graph.MakeDirected(); | ||
|
||
for (size_t i = 0; i < experiments_amount; i++) { | ||
std::string u, v; | ||
is >> u >> v; | ||
|
||
graph.AddEdge({u, v}); | ||
graph.AddVert(u); | ||
graph.AddVert(v); | ||
} | ||
|
||
for (const auto& component : StronglyConnectedComponents(graph)) | ||
if (component.size() != 1 && !component.empty()) | ||
for (const auto& elem : component) os << elem << " "; | ||
|
||
os << std::endl; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#include "chem_experiments_chain.hpp" | ||
|
||
int main() { | ||
Solution(); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
#pragma once | ||
|
||
#include <algorithm> | ||
#include <stack> | ||
#include <stdexcept> | ||
|
||
#include "graph.hpp" | ||
|
||
namespace { | ||
|
||
template <AllowedVertType vert_t> | ||
inline void StronglyConnectedComponentsStep( | ||
const vert_t& vert, size_t& curr_index, std::stack<vert_t>& verts_stack, | ||
std::unordered_map<vert_t, size_t>& indexes, | ||
std::unordered_map<vert_t, size_t>& low_links, | ||
std::unordered_map<vert_t, bool>& is_on_stack, | ||
std::unordered_map<vert_t, std::vector<vert_t>>& adj_list, | ||
std::vector<std::vector<vert_t>>& strongly_connected_components) { | ||
// в curr_index храним количество ранее обработанных вершин, | ||
// indexes[vert] - это "время входа" в вершину vert | ||
indexes[vert] = low_links[vert] = curr_index++; | ||
|
||
verts_stack.push(vert); | ||
|
||
// is_on_stack нужно, чтобы проверять принадлежность вершины стеку за O(1) | ||
is_on_stack[vert] = true; | ||
|
||
// перебираем рёбра, исходящие из vert | ||
for (auto& u_vert : adj_list[vert]) { | ||
if (indexes[u_vert] == 0) { | ||
// вершина u_vert ранее не посещалась; запускаемся из неё рекурсивно | ||
StronglyConnectedComponentsStep(u_vert, curr_index, verts_stack, indexes, | ||
low_links, is_on_stack, adj_list, | ||
strongly_connected_components); | ||
|
||
low_links[vert] = std::min(low_links[vert], low_links[u_vert]); | ||
} else if (is_on_stack[u_vert]) | ||
// вершина u_vert находится в стеке, значит, принадлежит той же компоненте | ||
// сильной связности, что и vert | ||
|
||
// если u_vert не в стеке, значит, ребро (vert, u_vert) ведёт в ранее | ||
// обработанную компоненту сильной связности и должна быть проигнорирована | ||
low_links[vert] = std::min(low_links[vert], low_links[u_vert]); | ||
} | ||
|
||
// вершина vert - корень текущей компоненты сильной связности, | ||
// все вершины в стеке от vert и выше образуют эту компоненту | ||
if (low_links[vert] == indexes[vert]) { | ||
vert_t u_vert; | ||
std::vector<vert_t> strongly_connected_component; | ||
|
||
do { | ||
u_vert = verts_stack.top(); | ||
verts_stack.pop(); | ||
|
||
is_on_stack[u_vert] = false; | ||
strongly_connected_component.push_back(u_vert); | ||
} while (u_vert != vert); | ||
|
||
strongly_connected_components.push_back(strongly_connected_component); | ||
} | ||
} | ||
|
||
} // namespace | ||
|
||
/** | ||
* @brief Поиск компонент сильной связности в ориентированного графа по | ||
* алгоритму Тарьяна. | ||
* @tparam vert_t: тип вершин | ||
* @tparam weight_t: тип весов | ||
* @param graph: исходный граф | ||
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @throw `std::invalid_argument("StronglyConnectedComponents: graph is not | ||
* directed.")`. | ||
* @return `std::vector<std::vector<vert_t>>`: компоненты сильной связности | ||
*/ | ||
template <AllowedVertType vert_t, AllowedWeightType weight_t> | ||
std::vector<std::vector<vert_t>> StronglyConnectedComponents( | ||
const Graph<vert_t, weight_t>& graph) { | ||
if (!graph.IsDirected()) | ||
throw std::invalid_argument( | ||
"StronglyConnectedComponents: graph is not directed."); | ||
|
||
if (graph.Verts().empty()) return {}; | ||
|
||
std::vector<std::vector<vert_t>> strongly_connected_component; | ||
|
||
std::stack<vert_t> verts_stack; | ||
size_t curr_index = 0; | ||
|
||
std::unordered_map<vert_t, std::vector<vert_t>> adj_list = graph.GetAdjList(); | ||
|
||
std::unordered_map<vert_t, size_t> indexes; | ||
std::unordered_map<vert_t, size_t> low_links; | ||
std::unordered_map<vert_t, bool> is_on_stack; | ||
|
||
for (const auto& vert : graph.Verts()) { | ||
indexes[vert] = low_links[vert] = 0; | ||
is_on_stack[vert] = false; | ||
} | ||
|
||
for (const auto& vert : graph.Verts()) | ||
if (indexes[vert] == 0) | ||
StronglyConnectedComponentsStep(vert, curr_index, verts_stack, indexes, | ||
low_links, is_on_stack, adj_list, | ||
strongly_connected_component); | ||
|
||
return strongly_connected_component; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
#include <gtest/gtest.h> | ||
|
||
#include "tarjan_algorithm.hpp" | ||
|
||
TEST(SCCSTA_Test, Simple) { | ||
std::unordered_map<std::string, std::vector<std::string>> adj_list_dict = { | ||
{"A", {"B"}}, {"B", {"C", "D"}}, {"C", {"A", "D"}}, {"D", {"E"}}, | ||
{"E", {"D"}}, {"F", {"E", "G"}}, {"G", {"F", "H"}}, {"H", {"E", "G"}}}; | ||
|
||
auto graph = Graph<std::string, long>::GraphFromAdjList(adj_list_dict); | ||
|
||
std::vector<std::vector<std::string>> answer = { | ||
{"D", "E"}, {"H", "G", "F"}, {"A", "C", "B"}}; | ||
|
||
ASSERT_EQ(StronglyConnectedComponents(graph), answer); | ||
} | ||
|
||
TEST(SCCSTA_Test, EmptyGraph) { | ||
Graph<int, long> graph; | ||
auto components = StronglyConnectedComponents(graph); | ||
|
||
ASSERT_EQ(components.size(), 0); | ||
} | ||
|
||
TEST(SCCSTA_Test, SingleVertex) { | ||
Graph<int, long> graph; | ||
graph.AddVert(1); | ||
|
||
auto components = StronglyConnectedComponents(graph); | ||
|
||
ASSERT_EQ(components.size(), 1); | ||
|
||
ASSERT_EQ(components[0], std::vector<int>({1})); | ||
} | ||
|
||
TEST(SCCSTA_Test, TwoVerticesNoEdges) { | ||
Graph<int, long> graph; | ||
graph.AddVert(1); | ||
graph.AddVert(2); | ||
|
||
auto components = StronglyConnectedComponents(graph); | ||
ASSERT_EQ(components.size(), 2); | ||
|
||
ASSERT_EQ(components[0], std::vector<int>({1})); | ||
ASSERT_EQ(components[1], std::vector<int>({2})); | ||
} | ||
|
||
TEST(SCCSTA_Test, SimpleCycle) { | ||
Graph<int, long> graph; | ||
graph.AddVert(1); | ||
graph.AddVert(2); | ||
graph.AddEdge({1, 2}); | ||
graph.AddEdge({2, 1}); | ||
auto components = StronglyConnectedComponents(graph); | ||
ASSERT_EQ(components.size(), 1); | ||
ASSERT_EQ(components[0], std::vector<int>({1, 2})); | ||
} | ||
|
||
TEST(SCCSTA_Test, MultipleCycles) { | ||
Graph<int, long> graph; | ||
graph.AddVert(1); | ||
graph.AddVert(2); | ||
graph.AddVert(3); | ||
graph.AddVert(4); | ||
graph.AddEdge({1, 2}); | ||
graph.AddEdge({2, 1}); | ||
graph.AddEdge({3, 4}); | ||
graph.AddEdge({4, 3}); | ||
|
||
auto components = StronglyConnectedComponents(graph); | ||
|
||
ASSERT_EQ(components.size(), 2); | ||
|
||
std::sort(components[0].begin(), components[0].end()); | ||
std::sort(components[1].begin(), components[1].end()); | ||
|
||
ASSERT_EQ(components[0], std::vector<int>({1, 2})); | ||
ASSERT_EQ(components[1], std::vector<int>({3, 4})); | ||
} | ||
|
||
TEST(SCCSTA_Test, ComplexGraph) { | ||
Graph<int, long> graph; | ||
graph.AddVert(1); | ||
graph.AddVert(2); | ||
graph.AddVert(3); | ||
graph.AddVert(4); | ||
graph.AddVert(5); | ||
graph.AddVert(6); | ||
graph.AddEdge({1, 2}); | ||
graph.AddEdge({2, 3}); | ||
graph.AddEdge({3, 1}); | ||
graph.AddEdge({4, 5}); | ||
graph.AddEdge({5, 6}); | ||
graph.AddEdge({6, 4}); | ||
graph.AddEdge({2, 4}); | ||
|
||
auto components = StronglyConnectedComponents(graph); | ||
|
||
ASSERT_EQ(components.size(), 2); | ||
|
||
std::sort(components.begin(), components.end()); | ||
|
||
std::sort(components[0].begin(), components[0].end()); | ||
std::sort(components[1].begin(), components[1].end()); | ||
|
||
ASSERT_EQ(components[0], std::vector<int>({1, 2, 3})); | ||
ASSERT_EQ(components[1], std::vector<int>({4, 5, 6})); | ||
} | ||
|
||
TEST(SCCSTA_Test, UndirectedGraph) { | ||
Graph<int, long> graph; | ||
graph.AddVert(1); | ||
graph.AddVert(2); | ||
graph.AddEdge({1, 2}); | ||
|
||
graph.MakeUndirected(); | ||
|
||
ASSERT_THROW(StronglyConnectedComponents(graph), std::invalid_argument); | ||
} | ||
|
||
TEST(SCCSTA_Test, SelfLoop) { | ||
Graph<int, long> graph; | ||
graph.AddVert(1); | ||
graph.AddEdge({1, 1}); | ||
|
||
auto components = StronglyConnectedComponents(graph); | ||
|
||
ASSERT_EQ(components.size(), 2); | ||
|
||
ASSERT_EQ(components[0], std::vector<int>({1})); | ||
ASSERT_EQ(components[1], std::vector<int>({1})); | ||
} | ||
|
||
TEST(SCCSTA_Test, MultipleSelfLoops) { | ||
Graph<int, long> graph; | ||
graph.AddVert(1); | ||
graph.AddVert(2); | ||
graph.AddEdge({1, 1}); | ||
graph.AddEdge({2, 2}); | ||
|
||
auto components = StronglyConnectedComponents(graph); | ||
|
||
ASSERT_EQ(components.size(), 3); | ||
|
||
ASSERT_EQ(components[0], std::vector<int>({1})); | ||
ASSERT_EQ(components[2], std::vector<int>({2})); | ||
} | ||
|
||
TEST(SCCSTA_Test, ConnectedComponents) { | ||
Graph<int, long> graph; | ||
graph.AddVert(1); | ||
graph.AddVert(2); | ||
graph.AddVert(3); | ||
graph.AddVert(4); | ||
graph.AddEdge({1, 2}); | ||
graph.AddEdge({2, 1}); | ||
graph.AddEdge({3, 4}); | ||
graph.AddEdge({4, 3}); | ||
graph.AddEdge({1, 3}); | ||
|
||
auto components = StronglyConnectedComponents(graph); | ||
|
||
ASSERT_EQ(components.size(), 2); | ||
|
||
std::sort(components.begin(), components.end()); | ||
|
||
std::sort(components[0].begin(), components[0].end()); | ||
std::sort(components[1].begin(), components[1].end()); | ||
|
||
ASSERT_EQ(components[0], std::vector<int>({1, 2})); | ||
ASSERT_EQ(components[1], std::vector<int>({3, 4})); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
cmake_minimum_required(VERSION 3.20) | ||
|
||
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) | ||
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME}) | ||
project(${PROJECT_NAME} LANGUAGES CXX) | ||
|
||
set(CMAKE_CXX_STANDARD 23) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
|
||
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp") | ||
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp") | ||
set(TEST_SOURCE_LIST ${SOURCE_LIST}) | ||
file(GLOB_RECURSE test_list "src/*test.cpp") | ||
|
||
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST}) | ||
list(REMOVE_ITEM SOURCE_LIST ${test_list}) | ||
|
||
include_directories(${PROJECT_NAME} PUBLIC src) | ||
|
||
find_library(Utils ../) | ||
|
||
add_executable(${PROJECT_NAME} ${SOURCE_LIST}) | ||
target_link_libraries(${PROJECT_NAME} PUBLIC Utils) | ||
|
||
# Locate GTest | ||
enable_testing() | ||
find_package(GTest REQUIRED) | ||
include_directories(${GTEST_INCLUDE_DIRS}) | ||
|
||
# Link runTests with what we want to test and the GTest and pthread library | ||
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST}) | ||
target_link_libraries( | ||
${PROJECT_NAME}_tests | ||
GTest::gtest_main | ||
Utils | ||
) | ||
|
||
include(GoogleTest) | ||
gtest_discover_tests(${PROJECT_NAME}_tests) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Задача: можно ли объединить список слов в цепочку | ||
|
||
Дан набор слов. Требуется определить, можно ли расположить эти слова в таком порядке, чтобы последняя буква каждого слова совпадала с первой буквой следующего слова, образуя замкнутую цепочку. | ||
|
||
Например, слова “for”, “rig”, “geek”, “kaf” можно соединить в цепочку (for -> rig -> geek -> kaf -> for), в то время как для слов “aab”, “abb” это невозможно. | ||
|
||
## Входные данные: | ||
|
||
* Первая строка содержит целое число `N` (`N` > 0) — количество слов. | ||
* Следующая строка содержит `N` строковых литералов, описывающих набор **слов**. | ||
|
||
## Выходные данные: | ||
|
||
* Выходные данные представляют собой строку, содержащую `Yes`, если слова образуют цепочку и `No`, если нет. | ||
|
||
## Решение: | ||
|
||
Для решения задачи используется подход, основанный на теории графов. Набор слов преобразуется в ориентированный граф следующим образом: | ||
|
||
1. **Построение графа:** Каждая буква английского алфавита (a-z) представляет собой вершину графа. Для каждого слова создаётся ориентированное ребро, соединяющее первую букву слова с его последней буквой. Например, для слова "for" будет создано ребро от вершины 'f' к вершине 'r'. | ||
|
||
2. **Проверка на Эйлеров цикл:** Задача сводится к проверке существования Эйлерова цикла в построенном графе. Эйлеров цикл — это циклический путь, проходящий через каждое ребро графа ровно один раз. Для существования эйлерова цикла необходимо и достаточно выполнение следующих условий: | ||
|
||
* **Степень вершин:** В ориентированном графе для каждой вершины количество входящих ребер должно быть равно количеству исходящих ребер. | ||
* **Связность:** Граф должен быть сильно связным, то есть из любой вершины должна быть достижима любая другая вершина, двигаясь по ребрам. | ||
|
||
3. **Алгоритм проверки:** Алгоритм реализует проверку условий существования эйлерова цикла: | ||
|
||
* **Подсчет степеней:** Для каждой вершины подсчитываются входящая и исходящая степени. Если хотя бы для одной вершины эти степени не равны, эйлеров цикл невозможен. | ||
* **Проверка на связность:** Используется алгоритм поиска в глубину (Depth-First Search, DFS). Начинается обход с произвольной вершины, имеющей исходящее ребро. Если после DFS не все вершины с ненулевой степенью были посещены, значит граф не сильно связный, а значит, и эйлерова цикла нет. | ||
|
||
Если оба условия выполнены, алгоритм возвращает "Yes", в противном случае — "No". |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
#pragma once | ||
|
||
#include "graph.hpp" | ||
|
||
namespace { | ||
|
||
/** | ||
* @brief | ||
* | ||
* @tparam vert_t | ||
* @tparam weight_t | ||
* @param v | ||
* @param graph | ||
* @param visited | ||
*/ | ||
|
||
/** | ||
* @brief Вспомогательная рекурсивная функция для обхода графа в глубину (DFS). | ||
* | ||
* @details Проверяет связность графа, начиная с вершины `v`. Эта функция | ||
* используется внутри `HasEulerPath` для проверки связности после проверки | ||
* количества вершин с нечётной степенью. | ||
* | ||
* @tparam vert_t: тип вершин графа. | ||
* @tparam weight_t: тип весов рёбер графа. | ||
* @param v: начальная вершина для DFS. | ||
* @param graph: граф, в котором выполняется обход. | ||
* @param visited: посещенные вершины. | ||
*/ | ||
template <AllowedVertType vert_t, AllowedWeightType weight_t> | ||
inline void HasEulerPathStep(const vert_t& v, | ||
const Graph<vert_t, weight_t>& graph, | ||
std::unordered_map<vert_t, bool>& visited) { | ||
visited[v] = true; | ||
|
||
// получаем соседнюю вершину (откуда идёт ребро), если соседняя вершина ещё не | ||
// посещена, рекурсивно вызываем DFS для неё. | ||
|
||
for (const auto& edge_tuple : graph.Edges()) { | ||
if (StartVertFromTuple(edge_tuple) == v) { | ||
vert_t neighbor = EndVertFromTuple(edge_tuple); | ||
|
||
if (!visited[neighbor]) HasEulerPathStep(neighbor, graph, visited); | ||
} | ||
|
||
if (EndVertFromTuple(edge_tuple) == v && !graph.IsDirected()) { | ||
vert_t neighbor = StartVertFromTuple(edge_tuple); | ||
|
||
if (!visited[neighbor]) HasEulerPathStep(neighbor, graph, visited); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @brief Вычисляет исходящую степень вершины в графе. | ||
* | ||
* @tparam vert_t: тип вершин графа. | ||
* @tparam weight_t: тип весов рёбер графа. | ||
* @param graph: граф, в котором вычисляется исходящая степень. | ||
* @param v: вершина, для которой вычисляется исходящая степень. | ||
* | ||
* @return size_t: исходящая степень вершины (количество исходящих ребер). | ||
*/ | ||
template <AllowedVertType vert_t, AllowedWeightType weight_t> | ||
inline size_t OutDeg(const Graph<vert_t, weight_t>& graph, const vert_t& v) { | ||
return graph.GetAdjList()[v].size(); | ||
} | ||
|
||
/** | ||
* @brief Вычисляет входящую степень вершины в графе. | ||
* | ||
* @tparam vert_t: тип вершин графа. | ||
* @tparam weight_t: тип весов рёбер графа. | ||
* @param graph: граф, в котором вычисляется входящая степень. | ||
* @param v: вершина, для которой вычисляется входящая степень. | ||
* | ||
* @return size_t: входящая степень вершины (количество входящих ребер). | ||
*/ | ||
template <AllowedVertType vert_t, AllowedWeightType weight_t> | ||
inline size_t InDeg(const Graph<vert_t, weight_t>& graph, const vert_t& v) { | ||
// для неориентированных графов входящая степень равна исходящей | ||
if (!graph.IsDirected()) return OutDeg(graph, v); | ||
|
||
size_t res = 0; | ||
|
||
auto adj_list = graph.GetAdjList(); | ||
|
||
for (const auto& u : graph.Verts()) | ||
if (Contains(adj_list[u], v)) res++; | ||
|
||
return res; | ||
} | ||
|
||
} // namespace | ||
|
||
/** | ||
* @brief Проверяет, существует ли в графе эйлеров путь. | ||
* | ||
* @details Эйлеров путь — это путь в графе, который проходит через каждое ребро | ||
* ровно один раз. Функция сначала проверяет необходимое, но недостаточное | ||
* условие существования эйлерова пути: количество вершин с нечетной степенью | ||
* должно быть не больше двух. Затем она проверяет связность графа с помощью | ||
* DFS. | ||
* | ||
* @tparam vert_t: тип вершин графа. | ||
* @tparam weight_t: тип весов рёбер графа. | ||
* @param graph: граф, для которого проверяется наличие эйлерова пути. | ||
* | ||
* @return `true`, если эйлеров путь существует | ||
* @return `false` в противном случае. | ||
*/ | ||
template <AllowedVertType vert_t, AllowedWeightType weight_t> | ||
inline bool HasEulerPath(const Graph<vert_t, weight_t>& graph) { | ||
// считаем количество вершин с нечетной степенью | ||
size_t odd_vert_count = 0; | ||
|
||
for (const auto& v : graph.Verts()) | ||
if (OutDeg(graph, v) != InDeg(graph, v)) | ||
odd_vert_count++; | ||
|
||
else if (!graph.IsDirected() && OutDeg(graph, v) % 2 != 0) | ||
odd_vert_count++; | ||
|
||
// если вершин с нечетной степенью больше двух, то эйлерова пути нет | ||
if (odd_vert_count > 2) return false; | ||
|
||
std::unordered_map<vert_t, bool> visited; | ||
for (const auto& vert : graph.Verts()) visited[vert] = false; | ||
|
||
std::unordered_map<vert_t, size_t> degree; | ||
for (const auto& vert : graph.Verts()) | ||
degree[vert] = graph.IsDirected() ? InDeg(graph, vert) + OutDeg(graph, vert) | ||
: OutDeg(graph, vert); | ||
|
||
// находим первую вершину с ненулевой степенью и начинаем DFS | ||
for (const auto& v : graph.Verts()) | ||
if (degree[v] > 0) { | ||
HasEulerPathStep(v, graph, visited); | ||
break; | ||
} | ||
|
||
// проверяем, все ли вершины с ненулевой степенью были посещены. | ||
for (const auto& v : graph.Verts()) | ||
if (degree[v] > 0 && !visited[v]) return false; | ||
|
||
// все вершины с ненулевой степенью были посещены, эйлеров путь существует | ||
return true; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
#pragma once | ||
|
||
#include "euler_path.hpp" | ||
|
||
/** | ||
* @brief Проверяет, можно ли составить цепочку из заданных слов так, чтобы | ||
* последняя буква каждого слова совпадала с первой буквой следующего. | ||
* | ||
* @param words вектор строк (слов), которые нужно проверить. | ||
* | ||
* @return `true`: слова можно объединить в цепочку | ||
* @return `false`: в противном случае | ||
*/ | ||
bool CanBeChained(const std::vector<std::string>& words) { | ||
if (words.empty()) return true; | ||
|
||
Graph<char> chars_graph; | ||
|
||
for (const std::string& s : words) { | ||
if (s.empty()) continue; // пропускаем пустые строки | ||
|
||
chars_graph.AddEdge({s[0], s.back()}); | ||
} | ||
|
||
// дополнительная проверка на связность: | ||
if (chars_graph.VertsAmount() > 0 && chars_graph.EdgesAmount() == 0) | ||
return false; // есть изолированная вершина | ||
|
||
// проверка на Эйлеров цикл | ||
return HasEulerPath(chars_graph); | ||
} | ||
|
||
/// @brief Решает задачу: "" | ||
void Solution(std::istream& is = std::cin, std::ostream& os = std::cout) { | ||
size_t words_amount; | ||
is >> words_amount; | ||
|
||
std::vector<std::string> words(words_amount); | ||
for (auto& word : words) is >> word; | ||
|
||
if (CanBeChained(words)) | ||
os << "Yes"; | ||
else | ||
os << "No"; | ||
|
||
os << std::endl; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#include "find_word_chains.hpp" | ||
|
||
int main() { | ||
Solution(); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
#include <gtest/gtest.h> | ||
|
||
#include "find_word_chains.hpp" | ||
|
||
TEST(CanBeChainedTest, EmptyInput) { | ||
std::vector<std::string> words = {}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, SingleWord) { | ||
std::vector<std::string> words = {"abc"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, SimpleChain) { | ||
std::vector<std::string> words = {"for", "rig", "geek", "kaf"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, SimpleChain2) { | ||
std::vector<std::string> words = {"abc", "cba"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, NoChain2) { | ||
std::vector<std::string> words = {"abc", "def"}; | ||
ASSERT_FALSE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, SameStartEnd) { | ||
std::vector<std::string> words = {"aba", "cba", "acc"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, MultipleSameWords) { | ||
std::vector<std::string> words = {"aba", "aba", "aca"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, WordsWithSameStartEnd) { | ||
std::vector<std::string> words = {"aaa", "bbb", "ccc"}; | ||
ASSERT_FALSE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, MultipleWordsWithSameStartEnd) { | ||
std::vector<std::string> words = {"aaa", "aba", "aca"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, MultipleWordsWithSameStartEnd2) { | ||
std::vector<std::string> words = {"aaa", "abb", "aba"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, ComplexChain) { | ||
std::vector<std::string> words = {"aba", "bab", "bba"}; | ||
ASSERT_FALSE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, ComplexChain2) { | ||
std::vector<std::string> words = {"abc", "cde", "efg", "gba"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, ComplexNoChain) { | ||
std::vector<std::string> words = {"abc", "cde", "efg", "hbl"}; | ||
ASSERT_FALSE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, OneWordMultipleSameLetters) { | ||
std::vector<std::string> words = {"aaaa"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, TwoWordsSameStartEnd) { | ||
std::vector<std::string> words = {"aaa", "bbb"}; | ||
ASSERT_FALSE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, EmptyWords) { | ||
std::vector<std::string> words = {"abc", "", "def"}; | ||
ASSERT_FALSE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, EmptyWordsOnly) { | ||
std::vector<std::string> words = {"", "", ""}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} | ||
|
||
TEST(CanBeChainedTest, LongChain) { | ||
std::vector<std::string> words = {"a", "ab", "bc", "cd", "de", "ef", "fa"}; | ||
ASSERT_TRUE(CanBeChained(words)); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
cmake_minimum_required(VERSION 3.20) | ||
|
||
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) | ||
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME}) | ||
project(${PROJECT_NAME} LANGUAGES CXX) | ||
|
||
set(CMAKE_CXX_STANDARD 23) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
|
||
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp") | ||
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp") | ||
set(TEST_SOURCE_LIST ${SOURCE_LIST}) | ||
file(GLOB_RECURSE test_list "src/*test.cpp") | ||
|
||
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST}) | ||
list(REMOVE_ITEM SOURCE_LIST ${test_list}) | ||
|
||
include_directories(${PROJECT_NAME} PUBLIC src) | ||
|
||
find_library(Utils ../) | ||
|
||
add_executable(${PROJECT_NAME} ${SOURCE_LIST}) | ||
target_link_libraries(${PROJECT_NAME} PUBLIC Utils) | ||
|
||
# Locate GTest | ||
enable_testing() | ||
find_package(GTest REQUIRED) | ||
include_directories(${GTEST_INCLUDE_DIRS}) | ||
|
||
# Link runTests with what we want to test and the GTest and pthread library | ||
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST}) | ||
target_link_libraries( | ||
${PROJECT_NAME}_tests | ||
GTest::gtest_main | ||
Utils | ||
) | ||
|
||
include(GoogleTest) | ||
gtest_discover_tests(${PROJECT_NAME}_tests) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Класс `Graph` от Криворучко Дмитрия Игоревича | ||
|
||
Ненормальный класс `Graph`, содержащий класс `Edge` в качестве приватного поля, но не имеющий отдельного класса Vertice (допускается использование только `std::string` и целочисленных типов (`int`, `short`, `std::size_t`) в качестве названия вершин, подставляемых шаблонами) с восемью статическими методами — псевдоконструкторами, различными вспомогательными методами для удаления и поиска ребра или вершины. | ||
|
||
Может использоваться для решения специфических задач, где граф надо хранить не как список смежности, а как списки вершин и рёбер. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
#include <iostream> | ||
|
||
#include "graph.hpp" | ||
|
||
template <typename V, typename W> | ||
void PrintGraph(const Graph<V, W>& graph) { | ||
std::cout << std::endl << graph << std::endl << std::endl; | ||
graph.PrintAdjList(); | ||
std::cout << std::endl << graph.GetAdjList() << std::endl; | ||
} | ||
|
||
int main() { | ||
// ------------------------- Graph from pairs ------------------------- | ||
|
||
std::cout << std::endl | ||
<< "------------------------------" | ||
" Graph from pairs " | ||
"------------------------------" | ||
<< std::endl | ||
<< std::endl; | ||
|
||
const std::vector<std::pair<size_t, size_t>> edges_pairs = { | ||
{0, 1}, {1, 2}, {2, 0}}; | ||
|
||
auto graph = Graph<size_t, long>::GraphNonWeighted(edges_pairs); | ||
|
||
std::cout << "Pairs: " << edges_pairs << std::endl; | ||
|
||
PrintGraph(graph); | ||
|
||
// ------------------- Graph from pairs and weights ------------------- | ||
|
||
std::cout << std::endl | ||
<< "------------------------------" | ||
" Graph from pairs and weights " | ||
"------------------------------" | ||
<< std::endl | ||
<< std::endl; | ||
|
||
const std::vector<long> weights = {1, 2, 3}; | ||
|
||
graph = Graph<size_t, long>::GraphWeighted(edges_pairs, weights); | ||
|
||
std::cout << "Pairs: " << edges_pairs << std::endl | ||
<< "Weights: " << weights << std::endl; | ||
|
||
PrintGraph(graph); | ||
|
||
// ------------------------- Graph from tuples ------------------------ | ||
|
||
std::cout << std::endl | ||
<< "------------------------------" | ||
" Graph from tuples " | ||
"------------------------------" | ||
<< std::endl | ||
<< std::endl; | ||
|
||
const std::vector<std::tuple<size_t, size_t, long>> edges_tuples = { | ||
{0, 1, 5}, {1, 2, 10}, {2, 0, 3}}; | ||
|
||
graph = Graph<size_t, long>::GraphWeighted(edges_tuples); | ||
|
||
std::cout << "Tuples: " << edges_tuples << std::endl; | ||
|
||
PrintGraph(graph); | ||
|
||
// ------------------------ Graph from strings ------------------------ | ||
|
||
std::cout << std::endl | ||
<< "------------------------------" | ||
" Graph from strings " | ||
"------------------------------" | ||
<< std::endl | ||
<< std::endl; | ||
|
||
const std::vector<std::string> edges_strs = {"0->1", "2->1", "3->2", "1->3"}; | ||
|
||
graph = Graph<size_t, long>::GraphFromStrs(edges_strs); | ||
|
||
std::cout << "Strings: " << edges_strs << std::endl; | ||
|
||
PrintGraph(graph); | ||
|
||
// -------------------------- Graph from dict ------------------------- | ||
|
||
std::cout << std::endl | ||
<< "------------------------------" | ||
" Graph from dict " | ||
"------------------------------" | ||
<< std::endl | ||
<< std::endl; | ||
|
||
const std::unordered_map<std::string, long> edges_dict = { | ||
{"0->1", 5}, {"2->1", 1}, {"3->2", 2}, {"1->3", 3}}; | ||
|
||
graph = Graph<size_t, long>::GraphFromMap(edges_dict); | ||
|
||
std::cout << "Dicts: " << edges_dict << std::endl; | ||
|
||
PrintGraph(graph); | ||
|
||
// ----------------------- Graph from AdjMatrix ----------------------- | ||
|
||
std::cout << std::endl | ||
<< "------------------------------" | ||
" Graph from AdjMatrix " | ||
"------------------------------" | ||
<< std::endl | ||
<< std::endl; | ||
|
||
std::vector<std::vector<long>> adj_matrix = {{0, 1, 0}, {1, 0, 1}, {0, 1, 0}}; | ||
|
||
graph = Graph<size_t, long>::GraphFromAdjMatrix(adj_matrix); | ||
|
||
std::cout << "AdjMatrix: " << adj_matrix << std::endl; | ||
|
||
PrintGraph(graph); | ||
|
||
std::cout << std::endl << "WEIGHTED VERSION:" << std::endl << std::endl; | ||
|
||
adj_matrix = {{0, 1, 0}, {1, 0, 5}, {0, 7, 0}}; | ||
graph = Graph<size_t, long>::GraphFromAdjMatrix(adj_matrix, true); | ||
|
||
std::cout << "AdjMatrix: " << adj_matrix << std::endl; | ||
|
||
PrintGraph(graph); | ||
|
||
// ------------------------ Graph from AdjList ------------------------ | ||
|
||
std::cout << std::endl | ||
<< "------------------------------" | ||
" Graph from AdjList " | ||
"------------------------------" | ||
<< std::endl | ||
<< std::endl; | ||
|
||
const std::vector<std::vector<size_t>> adj_list = {{1}, {0, 2}, {1}}; | ||
|
||
graph = Graph<size_t, long>::GraphFromAdjList(adj_list); | ||
|
||
std::cout << "AdjList: " << adj_list << std::endl; | ||
|
||
PrintGraph(graph); | ||
|
||
// ------------------- Graph from AdjList with keys ------------------- | ||
|
||
std::cout << std::endl | ||
<< "------------------------------" | ||
" Graph from AdjList with keys " | ||
"------------------------------" | ||
<< std::endl | ||
<< std::endl; | ||
|
||
const std::unordered_map<size_t, std::vector<size_t>> adj_list_dict = { | ||
{0, {1}}, {1, {0, 2}}, {2, {1}}}; | ||
|
||
graph = Graph<size_t, long>::GraphFromAdjList(adj_list_dict); | ||
|
||
std::cout << "AdjList: " << adj_list_dict << std::endl; | ||
|
||
PrintGraph(graph); | ||
|
||
std::cout << std::endl << "STRING VERSION:" << std::endl << std::endl; | ||
|
||
std::unordered_map<std::string, std::vector<std::string>> adj_list_dict_2 = { | ||
{"A", {"B"}}, {"B", {"A", "C"}}, {"C", {"B"}}}; | ||
|
||
auto graph_2 = Graph<std::string, long>::GraphFromAdjList(adj_list_dict_2); | ||
|
||
std::cout << "AdjList: " << adj_list_dict_2 << std::endl; | ||
|
||
PrintGraph(graph_2); | ||
|
||
return 0; | ||
} |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
cmake_minimum_required(VERSION 3.20) | ||
|
||
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) | ||
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME}) | ||
project(${PROJECT_NAME} LANGUAGES CXX) | ||
|
||
set(CMAKE_CXX_STANDARD 23) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
|
||
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp") | ||
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp") | ||
set(TEST_SOURCE_LIST ${SOURCE_LIST}) | ||
file(GLOB_RECURSE test_list "src/*test.cpp") | ||
|
||
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST}) | ||
list(REMOVE_ITEM SOURCE_LIST ${test_list}) | ||
|
||
include_directories(${PROJECT_NAME} PUBLIC src) | ||
|
||
find_library(Utils ../) | ||
|
||
add_executable(${PROJECT_NAME} ${SOURCE_LIST}) | ||
target_link_libraries(${PROJECT_NAME} PUBLIC Utils) | ||
|
||
# Locate GTest | ||
enable_testing() | ||
find_package(GTest REQUIRED) | ||
include_directories(${GTEST_INCLUDE_DIRS}) | ||
|
||
# Link runTests with what we want to test and the GTest and pthread library | ||
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST}) | ||
target_link_libraries( | ||
${PROJECT_NAME}_tests | ||
GTest::gtest_main | ||
Utils | ||
) | ||
|
||
include(GoogleTest) | ||
gtest_discover_tests(${PROJECT_NAME}_tests) |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
#pragma once | ||
|
||
#include <functional> | ||
#include <limits> | ||
#include <queue> | ||
#include <set> | ||
|
||
#include "graph.hpp" | ||
|
||
/** | ||
* @brief Вычисляет кратчайший путь между двумя вершинами с помощью A*. | ||
* @tparam vert_t: тип вершины в графе | ||
* @tparam weight_t: тип веса в графе | ||
* @param start: начальная вершина. | ||
* @param goal: конечная вершина. | ||
* @param graph: граф, для которого необходимо вычислить кратчайшие пути. | ||
* @param heuristic_range: функция эвристической оценки расстояния от | ||
* произвольной вершины до конечной вершины. Должна принимать две вершины в | ||
* качестве аргументов и возвращать вес (оценку расстояния). | ||
* @throw `std::invalid_argument("AStar: there is no such start vertice in | ||
* graph.")`. | ||
* @throw `std::invalid_argument("AStar: there is no such goal vertice in | ||
* graph.")`. | ||
* @return `weight_t`: стоимость кратчайшего пути от `start` до `goal` | ||
* (если до вершины нет пути, то значение будет равно | ||
* `std::numeric_limits<weight_t>::max()`). | ||
*/ | ||
template <AllowedVertType vert_t, AllowedWeightType weight_t> | ||
weight_t AStar( | ||
const vert_t& start, const vert_t& goal, | ||
const Graph<vert_t, weight_t>& graph, | ||
std::function<weight_t(const vert_t&, const vert_t&)> heuristic_range) { | ||
if (!graph.ContainsVert(start)) | ||
throw std::invalid_argument( | ||
"AStar: there is no such start vertice in graph."); | ||
|
||
if (!graph.ContainsVert(goal)) | ||
throw std::invalid_argument( | ||
"AStar: there is no such goal vertice in graph."); | ||
|
||
std::priority_queue<std::pair<weight_t, vert_t>, | ||
std::vector<std::pair<weight_t, vert_t>>, | ||
std::greater<std::pair<weight_t, vert_t>>> | ||
/** | ||
* @brief приоритетная очередь для вершин, которые нужно посетить | ||
* @details пары (стоимость, вершина), упорядоченные по стоимости. | ||
*/ | ||
visited_verts; | ||
|
||
// @brief стоимости пути от стартовой вершины до каждой вершины | ||
std::unordered_map<vert_t, weight_t> cost_from_start; | ||
|
||
// @brief суммы стоимостей пути от стартовой вершины и эвристики до конечной | ||
std::unordered_map<vert_t, weight_t> range_plus_cost; | ||
|
||
for (const auto& v : graph.Verts()) | ||
cost_from_start[v] = std::numeric_limits<weight_t>::max(); | ||
|
||
// начальные значения | ||
cost_from_start[start] = 0; | ||
range_plus_cost[start] = | ||
cost_from_start[start] + heuristic_range(start, goal); | ||
visited_verts.push({range_plus_cost[start], start}); | ||
|
||
while (!visited_verts.empty()) { | ||
// посещенная вершина с минимальным значением range_plus_cost | ||
const vert_t current = visited_verts.top().second; | ||
visited_verts.pop(); | ||
|
||
// достигнута конечная вершина, уже нашли путь до нужной вершины | ||
if (current == goal) return cost_from_start[goal]; | ||
|
||
const auto neighbors = graph.GetAdjList()[current]; | ||
for (const auto& neighbor : neighbors) { | ||
// @brief предполагаемая стоимость пути до соседа | ||
weight_t tentative_score = | ||
cost_from_start[current] + | ||
((graph.IsWeighted()) ? graph.GetEdgeWeight({current, neighbor}) : 1); | ||
|
||
// предполагаемая стоимость меньше текущей, | ||
// обновляем стоимость и добавляем соседа в очередь | ||
if (tentative_score < cost_from_start[neighbor]) { | ||
cost_from_start[neighbor] = tentative_score; | ||
|
||
// range_plus_cost для новой вершины (другого соседа) | ||
range_plus_cost[neighbor] = | ||
cost_from_start[neighbor] + heuristic_range(neighbor, goal); | ||
visited_verts.push({range_plus_cost[neighbor], neighbor}); | ||
} | ||
} | ||
} | ||
|
||
// путь не был найден | ||
return std::numeric_limits<weight_t>::max(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
#pragma once | ||
|
||
#include <limits> | ||
|
||
#include "topological_sort.hpp" | ||
|
||
/** | ||
* @brief Вычисляет кратчайшие пути от заданной начальной вершины до всех других | ||
* вершин в направленном ациклическом графе (DAG) с помощью релаксации. | ||
* @tparam vert_t: тип вершины в графе. | ||
* @tparam weight_t: тип веса в графе. | ||
* @param graph: граф, для которого необходимо вычислить кратчайшие пути. | ||
* @param start: начальная вершина, от которой вычисляются расстояния. | ||
* @throw `std::invalid_argument("DAGRelaxation: there is no such start | ||
* vertice.")`. | ||
* @throw `std::invalid_argument("DAGRelaxation: graph is not directed.")`. | ||
* @return `std::unordered_map<vert_t, weight_t>`: словарь, где ключ - вершина, | ||
* а значение - кратчайшее расстояние от start до этой вершины (если до вершины | ||
* нет пути, то значение будет равно `std::numeric_limits<weight_t>::max()`). | ||
*/ | ||
template <AllowedVertType vert_t, AllowedWeightType weight_t> | ||
std::unordered_map<vert_t, weight_t> DAGRelaxation( | ||
const Graph<vert_t, weight_t>& graph, const vert_t& start) { | ||
if (!graph.ContainsVert(start)) | ||
throw std::invalid_argument( | ||
"DAGRelaxation: there is no such start vertice in graph."); | ||
|
||
if (!graph.IsDirected()) | ||
throw std::invalid_argument("DAGRelaxation: graph is not directed."); | ||
|
||
/// @brief хеш-таблица расстояний от start до каждой вершины | ||
std::unordered_map<vert_t, weight_t> dists; | ||
|
||
// инициализация расстояний от start до каждой вершины бесконечностями | ||
for (const auto& vert : graph.Verts()) | ||
dists[vert] = std::numeric_limits<weight_t>::max(); | ||
|
||
// расстояние от начальной вершины до самой себя равно 0 | ||
dists[start] = 0; | ||
|
||
std::vector<vert_t> sorted_verts = TopologicalSort(graph); | ||
|
||
for (const auto& u_vert : sorted_verts) | ||
for (size_t i = 0; i < graph.GetAdjList()[u_vert].size(); i++) { | ||
auto vert = graph.GetAdjList()[u_vert][i]; | ||
|
||
// (нас не интересует бесконечное расстояние от start до u_vert) | ||
if (dists[u_vert] == std::numeric_limits<weight_t>::max()) continue; | ||
|
||
// если граф не взвешен, то задаем расстояние единицей | ||
weight_t u_v_dist = 1; | ||
if (graph.IsWeighted()) u_v_dist = graph.GetEdgeWeight({u_vert, vert}); | ||
|
||
// релаксируем ребро, если текущее расстояние до vert больше, чем | ||
// расстояние до u_vert + расстояние между вершинами (вес их ребра) | ||
if (dists[vert] > dists[u_vert] + u_v_dist) | ||
dists[vert] = dists[u_vert] + u_v_dist; | ||
} | ||
|
||
return dists; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#include "petya_and_vasya_labyrinth.hpp" | ||
|
||
int main() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: an exception may be thrown in function 'main' which should not throw exceptions [bugprone-exception-escape] int main() {
^
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// ComplicatedBadSolution(); | ||
GoodSolution(); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
#include <algorithm> | ||
#include <fstream> | ||
#include <sstream> | ||
#include <stack> | ||
|
||
#include "a_star.hpp" | ||
#include "dag_relaxation.hpp" | ||
|
||
namespace { | ||
|
||
/** | ||
* @brief Находит ячейку в заданном векторе строк, соответствующую определенному | ||
* суффиксу. | ||
* @param strings: вектор строк для поиска. | ||
* @param i: индекс. | ||
* @param j: индекс. | ||
* @return std::string: строка, содержащая суффикс, или пустая строка, если | ||
* совпадений не найдено. | ||
*/ | ||
inline std::string FindCell(const std::vector<std::string>& strings, size_t i, | ||
size_t j) { | ||
std::string suffix = "_" + std::to_string(i) + "_" + std::to_string(j); | ||
|
||
auto it = | ||
std::find_if(strings.begin(), strings.end(), [&](const std::string& str) { | ||
return str.find(suffix) != std::string::npos; | ||
}); | ||
|
||
return it == strings.end() ? "" : *it; | ||
} | ||
|
||
/** | ||
* @brief Добавляет ребра в граф на основе смежности ячеек. | ||
* @param graph: граф, в который нужно добавить ребра (НЕНАПРАВЛЕННЫЙ). | ||
*/ | ||
inline void AddEdgesNonDirected(Graph<std::string, long>& graph) { | ||
for (const auto& vert : graph.Verts()) { | ||
size_t j = std::stoll(vert.substr(vert.rfind('_') + 1)); | ||
size_t i = std::stoll(vert.substr( | ||
vert.rfind('_', vert.rfind('_') - 1) + 1, | ||
vert.rfind('_') - vert.rfind('_', vert.rfind('_') - 1) - 1)); | ||
|
||
std::string up_vert = "", down_vert = ""; | ||
auto right_vert = FindCell(graph.Verts(), i + 1, j); | ||
auto left_vert = FindCell(graph.Verts(), i, j + 1); | ||
|
||
if (i != 0 && j != 0) { | ||
up_vert = FindCell(graph.Verts(), i - 1, j); | ||
down_vert = FindCell(graph.Verts(), i, j - 1); | ||
} | ||
|
||
for (const auto& another_vert : {up_vert, down_vert, right_vert, left_vert}) | ||
if (!another_vert.empty() && !graph.ContainsEdge({another_vert, vert})) | ||
graph.AddEdge({vert, another_vert}); | ||
} | ||
} | ||
|
||
/** | ||
* @brief Итеративно добавляет ребра в граф на основе смежности ячеек. | ||
* @param graph: граф, в который нужно добавить ребра (НАПРАВЛЕННЫЙ). | ||
* @param vert: начальная вершина для обхода. | ||
*/ | ||
inline void AddEdgesIterative(Graph<std::string, long>& graph, | ||
const std::string& vert) { | ||
std::stack<std::string> s; | ||
s.push(vert); | ||
|
||
while (!s.empty()) { | ||
std::string current_vert = s.top(); | ||
s.pop(); | ||
|
||
size_t j = std::stoll(current_vert.substr(current_vert.rfind('_') + 1)); | ||
size_t i = std::stoll(current_vert.substr( | ||
current_vert.rfind('_', current_vert.rfind('_') - 1) + 1, | ||
current_vert.rfind('_') - | ||
current_vert.rfind('_', current_vert.rfind('_') - 1) - 1)); | ||
|
||
std::string up_vert = "", down_vert = ""; | ||
auto right_vert = FindCell(graph.Verts(), i + 1, j); | ||
auto left_vert = FindCell(graph.Verts(), i, j + 1); | ||
|
||
if (i != 0 && j != 0) { | ||
up_vert = FindCell(graph.Verts(), i - 1, j); | ||
down_vert = FindCell(graph.Verts(), i, j - 1); | ||
} | ||
|
||
for (const auto& another_vert : {up_vert, down_vert, right_vert, left_vert}) | ||
if (!another_vert.empty() && | ||
!graph.ContainsEdge({another_vert, current_vert})) { | ||
graph.AddEdge({current_vert, another_vert}); | ||
if (another_vert[0] != 'E') s.push(another_vert); | ||
} | ||
} | ||
} | ||
|
||
} // namespace | ||
|
||
struct PetyaAndVasyaGraphs { | ||
struct { | ||
Graph<std::string, long> graph; | ||
std::string vert; | ||
std::vector<std::string> exits; | ||
} petya, vasya; | ||
}; | ||
|
||
enum class SolutionType { ComplicatedBad, Good }; | ||
|
||
/** | ||
* @brief Разбирает входные данные лабиринта из стандартного ввода и создает | ||
* направленный граф. | ||
* @return `PetyaAndVasyaGraphs`, содержащий графы для обоих игроков. | ||
*/ | ||
inline PetyaAndVasyaGraphs ParseMaze( | ||
std::istream& is = std::cin, | ||
SolutionType sol_type = SolutionType::ComplicatedBad) { | ||
Graph<std::string, long> graph; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: enum 'SolutionType' uses a larger base type ('int', size: 4 bytes) than necessary for its value set, consider using 'std::uint8_t' (1 byte) as the base type to reduce its size [performance-enum-size] a;
^
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
UmbrellaLeaf5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
std::vector<std::string> console_maze; | ||
std::string line; | ||
|
||
PetyaAndVasyaGraphs maze; | ||
size_t petya_start_i = -1, petya_start_j = -1, vasya_start_i = -1, | ||
vasya_start_j = -1; | ||
|
||
// считывание лабиринта | ||
while (std::getline(is, line) && !line.empty()) console_maze.push_back(line); | ||
|
||
// добавление вершин | ||
for (size_t i = 0; i < console_maze.size(); i++) { | ||
const auto& line = console_maze[i]; | ||
|
||
for (size_t j = 0; j < line.size(); j++) { | ||
const auto& cell = line[j]; | ||
std::stringstream cell_name_stream; | ||
|
||
if (cell == 'P') { | ||
petya_start_i = i, petya_start_j = j; | ||
maze.petya.vert = "E_" + std::to_string(i) + "_" + std::to_string(j); | ||
maze.vasya.exits.push_back(maze.petya.vert); | ||
|
||
cell_name_stream << 'E'; | ||
|
||
} else if (cell == 'V') { | ||
vasya_start_i = i, vasya_start_j = j; | ||
maze.vasya.vert = "E_" + std::to_string(i) + "_" + std::to_string(j); | ||
maze.petya.exits.push_back(maze.vasya.vert); | ||
|
||
cell_name_stream << 'E'; | ||
} | ||
|
||
else if (cell == '0') { | ||
if (j == 0 || i == 0 || j == (line.size() - 1) || | ||
i == (console_maze.size() - 1)) { | ||
auto vert = "E_" + std::to_string(i) + "_" + std::to_string(j); | ||
|
||
maze.vasya.exits.push_back(vert); | ||
maze.petya.exits.push_back(vert); | ||
|
||
cell_name_stream << 'E'; | ||
} | ||
|
||
else | ||
cell_name_stream << 'C'; | ||
} | ||
|
||
if (!cell_name_stream.str().empty()) { | ||
cell_name_stream << "_" << i << "_" << j; | ||
|
||
graph.AddVert(cell_name_stream.str()); | ||
} | ||
} | ||
} | ||
|
||
// добавление ребер | ||
maze.petya.graph = graph; | ||
maze.vasya.graph = graph; | ||
|
||
switch (sol_type) { | ||
case SolutionType::ComplicatedBad: { | ||
AddEdgesIterative(maze.petya.graph, | ||
FindCell(graph.Verts(), petya_start_i, petya_start_j)); | ||
|
||
AddEdgesIterative(maze.vasya.graph, | ||
FindCell(graph.Verts(), vasya_start_i, vasya_start_j)); | ||
|
||
maze.petya.graph.MakeDirected(); | ||
maze.vasya.graph.MakeDirected(); | ||
break; | ||
} | ||
|
||
case SolutionType::Good: { | ||
AddEdgesNonDirected(maze.petya.graph); | ||
maze.petya.graph.MakeUndirected(); | ||
|
||
maze.vasya.graph = maze.petya.graph; | ||
break; | ||
} | ||
} | ||
|
||
return maze; | ||
} | ||
|
||
/// @brief Хорошо решает задачу: "Петя и Вася в страшном лабиринте". | ||
void GoodSolution(std::istream& is = std::cin, std::ostream& os = std::cout) { | ||
try { | ||
auto maze = ParseMaze(is, SolutionType::Good); | ||
|
||
long petya_min = std::numeric_limits<long>::max(); | ||
long vasya_min = std::numeric_limits<long>::max(); | ||
|
||
auto manhattan_heuristic = [](const std::string& vert_1, | ||
const std::string& vert_2) { | ||
auto extract_coords = [](const std::string& vert) { | ||
size_t j = std::stoll(vert.substr(vert.rfind('_') + 1)); | ||
size_t i = std::stoll(vert.substr( | ||
vert.rfind('_', vert.rfind('_') - 1) + 1, | ||
vert.rfind('_') - vert.rfind('_', vert.rfind('_') - 1) - 1)); | ||
|
||
return std::make_pair(i, j); | ||
}; | ||
|
||
// в матрице (лабиринте) координаты положительные, модули не нужны | ||
return extract_coords(vert_1).first - extract_coords(vert_2).first + | ||
extract_coords(vert_1).second - extract_coords(vert_2).second; | ||
}; | ||
|
||
// (размеры будут одинаковые) | ||
for (size_t i = 0; i < maze.petya.exits.size(); i++) { | ||
petya_min = std::min( | ||
petya_min, | ||
AStar(maze.petya.vert, maze.petya.exits[i], maze.petya.graph, | ||
std::function<long(const std::string&, const std::string&)>( | ||
manhattan_heuristic))); | ||
|
||
vasya_min = std::min( | ||
vasya_min, | ||
AStar(maze.vasya.vert, maze.vasya.exits[i], maze.vasya.graph, | ||
std::function<long(const std::string&, const std::string&)>( | ||
manhattan_heuristic))); | ||
} | ||
|
||
if (vasya_min < petya_min) | ||
os << "Vasya! with " << vasya_min << std::endl; | ||
|
||
else if (vasya_min == petya_min) | ||
if (vasya_min != std::numeric_limits<long>::max()) | ||
os << "Draw! with " << vasya_min << std::endl; | ||
else | ||
os << "Deadlock! Valery!" << std::endl; | ||
|
||
else | ||
os << "Petya! with " << petya_min << std::endl; | ||
|
||
} catch (const std::exception&) { | ||
throw std::invalid_argument("Invalid enter format!"); | ||
} | ||
} | ||
|
||
/// @brief Сложно и плохо решает задачу: "Петя и Вася в страшном лабиринте". | ||
void ComplicatedBadSolution(std::istream& is = std::cin, | ||
std::ostream& os = std::cout) { | ||
try { | ||
auto maze = ParseMaze(is, SolutionType::ComplicatedBad); | ||
|
||
auto petya_relax = DAGRelaxation(maze.petya.graph, maze.petya.vert); | ||
auto vasya_relax = DAGRelaxation(maze.vasya.graph, maze.vasya.vert); | ||
|
||
long petya_min = std::numeric_limits<long>::max(); | ||
long vasya_min = std::numeric_limits<long>::max(); | ||
|
||
// (размеры будут одинаковые) | ||
for (size_t i = 0; i < maze.petya.exits.size(); i++) { | ||
petya_min = std::min(petya_min, petya_relax[maze.petya.exits[i]]); | ||
vasya_min = std::min(vasya_min, vasya_relax[maze.vasya.exits[i]]); | ||
} | ||
|
||
if (vasya_min < petya_min) | ||
os << "Vasya! with " << vasya_min << std::endl; | ||
|
||
else if (vasya_min == petya_min) | ||
if (vasya_min != std::numeric_limits<long>::max()) | ||
os << "Draw! with " << vasya_min << std::endl; | ||
else | ||
os << "Deadlock! Valery!" << std::endl; | ||
|
||
else | ||
os << "Petya! with " << petya_min << std::endl; | ||
} catch (const std::exception&) { | ||
throw std::invalid_argument("Invalid enter format!"); | ||
} | ||
} |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
#pragma once | ||
|
||
#include "graph.hpp" | ||
|
||
namespace { | ||
|
||
template <AllowedVertType vert_t> | ||
void TopologicalSortStep( | ||
const vert_t& u_vert, std::unordered_map<vert_t, bool>& visited, | ||
std::unordered_map<vert_t, std::vector<vert_t>>& adj_list, | ||
std::vector<vert_t>& topological_order) { | ||
visited[u_vert] = true; | ||
|
||
for (const auto& vert : adj_list[u_vert]) | ||
if (!visited[vert]) | ||
TopologicalSortStep(vert, visited, adj_list, topological_order); | ||
|
||
topological_order.push_back(u_vert); | ||
} | ||
|
||
} // namespace | ||
|
||
/** | ||
* @brief Производит топологическую сортировку вершин на основе обхода в глубину | ||
* (DFS) | ||
* @tparam vert_t: тип вершины в графе | ||
* @tparam weight_t: тип веса в графе | ||
* @param graph: сортируемый граф | ||
* @throw `std::invalid_argument("TopologicalSort: graph is not directed.")`. | ||
* @return `std::vector<vert_t>`: список отсортированных вершин | ||
*/ | ||
template <AllowedVertType vert_t, AllowedWeightType weight_t> | ||
std::vector<vert_t> TopologicalSort(const Graph<vert_t, weight_t>& graph) { | ||
if (!graph.IsDirected()) | ||
throw std::invalid_argument("TopologicalSort: graph is not directed."); | ||
if (graph.Verts().empty()) return {}; | ||
|
||
std::vector<vert_t> topological_order; | ||
|
||
std::unordered_map<vert_t, bool> visited; | ||
for (const auto& vert : graph.Verts()) visited[vert] = false; | ||
|
||
std::unordered_map<vert_t, std::vector<vert_t>> adj_list = graph.GetAdjList(); | ||
|
||
for (const auto& vert : graph.Verts()) | ||
if (!visited[vert]) | ||
TopologicalSortStep(vert, visited, adj_list, topological_order); | ||
|
||
std::reverse(topological_order.begin(), topological_order.end()); | ||
|
||
return topological_order; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
cmake_minimum_required(VERSION 3.10) | ||
project(Utils) | ||
cmake_minimum_required(VERSION 3.20) | ||
|
||
project(Utils LANGUAGES CXX) | ||
|
||
set(CMAKE_CXX_STANDARD 23) | ||
|
||
file(GLOB_RECURSE lib_source_list "src/*.cpp" "src/*.hpp") | ||
|
||
add_library(${PROJECT_NAME} ${lib_source_list}) | ||
|
||
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) | ||
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) |
Large diffs are not rendered by default.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
#pragma once | ||
|
||
// std libs: | ||
#include <algorithm> | ||
#include <iostream> | ||
#include <numeric> | ||
#include <stdexcept> | ||
#include <string> | ||
#include <unordered_map> | ||
#include <unordered_set> | ||
#include <utility> | ||
#include <variant> | ||
#include <vector> | ||
|
||
using std::size_t; | ||
|
||
/** | ||
* @brief Выводит все элементы пары в поток | ||
* @tparam Type1: тип, возможный к выводу в консоль | ||
* @tparam Type2: тип, возможный к выводу в консоль | ||
* @param os: ссылка на поток, в который надо вывести (мод.) | ||
* @param pair: пара элементов произвольного типа | ||
* @return std::ostream&: ссылка на поток, в который вывели | ||
*/ | ||
template <typename Type1, typename Type2> | ||
inline std::ostream& operator<<(std::ostream& os, | ||
const std::pair<Type1, Type2>& pair) { | ||
return os << "{" << pair.first << "; " << pair.second << "}"; | ||
} | ||
|
||
/** | ||
* @brief Выводит все элементы std::tuple в поток | ||
* @tparam I: текущий индекс, обрабатываемый в кортеже | ||
* @tparam Ts: типы элементов в кортеже | ||
* @param os: выходной поток, в который будут записаны элементы кортежа | ||
* @param t: кортеж, который нужно распечатать | ||
* @return std::ostream&: модифицированный выходной поток | ||
*/ | ||
template <std::size_t I = 0, typename... Ts> | ||
static std::ostream& PrintTuple(std::ostream& os, const std::tuple<Ts...>& t) { | ||
if constexpr (I < sizeof...(Ts)) { | ||
if (I != 0) os << "; "; | ||
|
||
os << std::get<I>(t); | ||
return PrintTuple<I + 1, Ts...>(os, t); | ||
} else | ||
return os; | ||
} | ||
|
||
/** | ||
* @brief Выводит все элементы std::tuple в поток | ||
* @tparam Ts: типы элементов в кортеже. | ||
* @param os: выходной поток, в который будет записан кортеж. | ||
* @param t: кортеж, который нужно распечатать. | ||
* @return std::ostream&: модифицированный выходной поток | ||
*/ | ||
template <typename... Ts> | ||
std::ostream& operator<<(std::ostream& os, const std::tuple<Ts...>& t) { | ||
os << "{"; | ||
PrintTuple(os, t); | ||
return os << "}"; | ||
} | ||
|
||
/** | ||
* @brief Выводит все элементы вектора в поток | ||
* @tparam Type: тип, возможный к выводу в консоль | ||
* @param os: ссылка на поток, в который надо вывести (мод.) | ||
* @param vec: вектор элементов произвольного типа | ||
* @return std::ostream&: ссылка на поток, в который вывели | ||
*/ | ||
template <typename Type> | ||
inline std::ostream& operator<<(std::ostream& os, | ||
const std::vector<Type>& vec) { | ||
os << "{"; | ||
|
||
for (std::size_t i = 0; i < vec.size(); i++) { | ||
os << vec[i]; | ||
if (i != vec.size() - 1) os << "; "; | ||
} | ||
|
||
return os << "}"; | ||
} | ||
|
||
/** | ||
* @brief Выводит все элементы std::unordered_map в выходной поток | ||
* @tparam K: тип ключей в неупорядоченной карте | ||
* @tparam V: тип значений в неупорядоченной карте | ||
* @param os: выходной поток, в который будет записан словарь | ||
* @param map: словарь, который нужно распечатать | ||
* @return std::ostream&: модифицированный выходной поток | ||
*/ | ||
template <typename K, typename V> | ||
std::ostream& operator<<(std::ostream& os, | ||
const std::unordered_map<K, V>& map) { | ||
os << "{"; | ||
|
||
bool first = true; | ||
for (const auto& [key, value] : map) { | ||
if (!first) os << "; "; | ||
|
||
os << key << ": " << value; | ||
first = false; | ||
} | ||
|
||
return os << "}"; | ||
} | ||
|
||
/** | ||
* @brief функция, которая обрезает незнач. нули float при преобр. в строку | ||
* @param number: число типа float | ||
* @return std::string: итоговое число, записанное в строку | ||
*/ | ||
inline std::string ErasedZerosStr(float number) { | ||
std::string str = std::to_string(number); | ||
|
||
// удаляем незначащие нули | ||
str.erase(str.find_last_not_of('0') + 1, std::string::npos); | ||
|
||
// если последний символ - десятичная точка, удаляем | ||
if (str.back() == '.') str.pop_back(); | ||
|
||
return str; | ||
} | ||
|
||
/** | ||
* @brief перегрузка, которая вводит все элементы вектора из потока | ||
* (работает исключительно с консолью, так как | ||
* (вывод о текущем состоянии происходит туда) | ||
* @tparam Type: тип, возможный к выводу в консоль | ||
* @param is: ссылка на поток, из которого надо ввести (мод.) | ||
* @param vec: вектор элементов типа Type (мод.) | ||
* @return std::istream&: ссылка на поток, из которого ввели | ||
*/ | ||
template <typename Type> | ||
inline std::istream& operator>>(std::istream& is, std::vector<Type>& vec) { | ||
// @brief размер вектора | ||
long size = 0; | ||
|
||
std::cout << "Enter array size: "; | ||
while (size <= 0) { | ||
is >> size; | ||
if (!is) { | ||
std::cerr << "Invalid size input." << std::endl; | ||
return is; | ||
} | ||
|
||
if (size <= 0) std::cout << "Invalid size input. Try again: "; | ||
} | ||
|
||
// @brief текущий элемент | ||
Type curr; | ||
|
||
vec.clear(); // (для перезаписи нужна отчистка) | ||
std::cout << "Enter array elements: "; | ||
for (std::size_t i = 0; i < std::size_t(size); i++) { | ||
is >> curr; | ||
if (!is) { | ||
std::cerr << "Invalid array input. The entry is incorrect." << std::endl; | ||
return is; | ||
} | ||
|
||
vec.push_back(curr); | ||
} | ||
|
||
return is; | ||
} | ||
|
||
/** | ||
* @brief Проверяет наличие элемента в векторе | ||
* @tparam T: тип элемента | ||
* @param vec: исходный вектор | ||
* @param value: искомое значение | ||
* @return true: элемент найден | ||
* @return false: элемент не найден | ||
*/ | ||
template <typename T> | ||
inline bool Contains(const std::vector<T>& vec, const T& value) { | ||
return std::find(vec.begin(), vec.end(), value) != vec.end(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,13 @@ | ||
cmake_minimum_required(VERSION 3.10) | ||
cmake_minimum_required(VERSION 3.20) | ||
|
||
project(sendbox) | ||
project(sandbox LANGUAGES CXX) | ||
|
||
file(GLOB_RECURSE tasks_dirs LIST_DIRECTORIES true ".") | ||
file(GLOB_RECURSE SUBFOLDERS LIST_DIRECTORIES true ".") | ||
|
||
foreach(dir ${tasks_dirs}) | ||
IF(IS_DIRECTORY ${dir}) | ||
IF(NOT ${dir} MATCHES ".*src.*") | ||
add_subdirectory(${dir}) | ||
ENDIF() | ||
ELSE() | ||
CONTINUE() | ||
ENDIF() | ||
foreach(FOLDER ${SUBFOLDERS}) | ||
if(IS_DIRECTORY ${FOLDER}) | ||
if(NOT ${FOLDER} MATCHES ".*src.*") | ||
add_subdirectory(${FOLDER}) | ||
endif() | ||
endif() | ||
endforeach() |
Uh oh!
There was an error while loading. Please reload this page.