Skip to content

Commit 8afd94e

Browse files
committed
complete find_word_chains additional_task
1 parent 07019ff commit 8afd94e

File tree

7 files changed

+366
-1
lines changed

7 files changed

+366
-1
lines changed

additional_tasks/chem_experiments_chain/src/chem_experiments_chain.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ void Solution(std::istream& is = std::cin, std::ostream& os = std::cout) {
66
size_t experiments_amount;
77
is >> experiments_amount;
88

9-
Graph<std::string, long> graph;
9+
Graph<std::string> graph;
1010
graph.MakeDirected();
1111

1212
for (size_t i = 0; i < experiments_amount; i++) {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
4+
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
5+
project(${PROJECT_NAME} LANGUAGES CXX)
6+
7+
set(CMAKE_CXX_STANDARD 23)
8+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
9+
10+
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
11+
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
12+
set(TEST_SOURCE_LIST ${SOURCE_LIST})
13+
file(GLOB_RECURSE test_list "src/*test.cpp")
14+
15+
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
16+
list(REMOVE_ITEM SOURCE_LIST ${test_list})
17+
18+
include_directories(${PROJECT_NAME} PUBLIC src)
19+
20+
find_library(Utils ../)
21+
22+
add_executable(${PROJECT_NAME} ${SOURCE_LIST})
23+
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)
24+
25+
# Locate GTest
26+
enable_testing()
27+
find_package(GTest REQUIRED)
28+
include_directories(${GTEST_INCLUDE_DIRS})
29+
30+
# Link runTests with what we want to test and the GTest and pthread library
31+
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
32+
target_link_libraries(
33+
${PROJECT_NAME}_tests
34+
GTest::gtest_main
35+
Utils
36+
)
37+
38+
include(GoogleTest)
39+
gtest_discover_tests(${PROJECT_NAME}_tests)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Задача: можно ли объединить список слов в цепочку
2+
3+
Дан набор слов. Требуется определить, можно ли расположить эти слова в таком порядке, чтобы последняя буква каждого слова совпадала с первой буквой следующего слова, образуя замкнутую цепочку.
4+
5+
Например, слова “for”, “rig”, “geek”, “kaf” можно соединить в цепочку (for -> rig -> geek -> kaf -> for), в то время как для слов “aab”, “abb” это невозможно.
6+
7+
## Входные данные:
8+
9+
* Первая строка содержит целое число `N` (`N` > 0) — количество слов.
10+
* Следующая строка содержит `N` строковых литералов, описывающих набор **слов**.
11+
12+
## Выходные данные:
13+
14+
* Выходные данные представляют собой строку, содержащую `Yes`, если слова образуют цепочку и `No`, если нет.
15+
16+
## Решение:
17+
18+
Для решения задачи используется подход, основанный на теории графов. Набор слов преобразуется в ориентированный граф следующим образом:
19+
20+
1. **Построение графа:** Каждая буква английского алфавита (a-z) представляет собой вершину графа. Для каждого слова создаётся ориентированное ребро, соединяющее первую букву слова с его последней буквой. Например, для слова "for" будет создано ребро от вершины 'f' к вершине 'r'.
21+
22+
2. **Проверка на Эйлеров цикл:** Задача сводится к проверке существования Эйлерова цикла в построенном графе. Эйлеров цикл — это циклический путь, проходящий через каждое ребро графа ровно один раз. Для существования эйлерова цикла необходимо и достаточно выполнение следующих условий:
23+
24+
* **Степень вершин:** В ориентированном графе для каждой вершины количество входящих ребер должно быть равно количеству исходящих ребер.
25+
* **Связность:** Граф должен быть сильно связным, то есть из любой вершины должна быть достижима любая другая вершина, двигаясь по ребрам.
26+
27+
3. **Алгоритм проверки:** Алгоритм реализует проверку условий существования эйлерова цикла:
28+
29+
* **Подсчет степеней:** Для каждой вершины подсчитываются входящая и исходящая степени. Если хотя бы для одной вершины эти степени не равны, эйлеров цикл невозможен.
30+
* **Проверка на связность:** Используется алгоритм поиска в глубину (Depth-First Search, DFS). Начинается обход с произвольной вершины, имеющей исходящее ребро. Если после DFS не все вершины с ненулевой степенью были посещены, значит граф не сильно связный, а значит, и эйлерова цикла нет.
31+
32+
Если оба условия выполнены, алгоритм возвращает "Yes", в противном случае — "No".
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#pragma once
2+
3+
#include "graph.hpp"
4+
5+
namespace {
6+
7+
/**
8+
* @brief
9+
*
10+
* @tparam vert_t
11+
* @tparam weight_t
12+
* @param v
13+
* @param graph
14+
* @param visited
15+
*/
16+
17+
/**
18+
* @brief Вспомогательная рекурсивная функция для обхода графа в глубину (DFS).
19+
*
20+
* @details Проверяет связность графа, начиная с вершины `v`. Эта функция
21+
* используется внутри `HasEulerPath` для проверки связности после проверки
22+
* количества вершин с нечётной степенью.
23+
*
24+
* @tparam vert_t: тип вершин графа.
25+
* @tparam weight_t: тип весов рёбер графа.
26+
* @param v: начальная вершина для DFS.
27+
* @param graph: граф, в котором выполняется обход.
28+
* @param visited: посещенные вершины.
29+
*/
30+
template <AllowedVertType vert_t, AllowedWeightType weight_t>
31+
inline void HasEulerPathStep(const vert_t& v,
32+
const Graph<vert_t, weight_t>& graph,
33+
std::unordered_map<vert_t, bool>& visited) {
34+
visited[v] = true;
35+
36+
// получаем соседнюю вершину (откуда идёт ребро), если соседняя вершина ещё не
37+
// посещена, рекурсивно вызываем DFS для неё.
38+
39+
for (const auto& edge_tuple : graph.Edges()) {
40+
if (StartVertFromTuple(edge_tuple) == v) {
41+
vert_t neighbor = EndVertFromTuple(edge_tuple);
42+
43+
if (!visited[neighbor]) HasEulerPathStep(neighbor, graph, visited);
44+
}
45+
46+
if (EndVertFromTuple(edge_tuple) == v && !graph.IsDirected()) {
47+
vert_t neighbor = StartVertFromTuple(edge_tuple);
48+
49+
if (!visited[neighbor]) HasEulerPathStep(neighbor, graph, visited);
50+
}
51+
}
52+
}
53+
54+
/**
55+
* @brief Вычисляет исходящую степень вершины в графе.
56+
*
57+
* @tparam vert_t: тип вершин графа.
58+
* @tparam weight_t: тип весов рёбер графа.
59+
* @param graph: граф, в котором вычисляется исходящая степень.
60+
* @param v: вершина, для которой вычисляется исходящая степень.
61+
*
62+
* @return size_t: исходящая степень вершины (количество исходящих ребер).
63+
*/
64+
template <AllowedVertType vert_t, AllowedWeightType weight_t>
65+
inline size_t OutDeg(const Graph<vert_t, weight_t>& graph, const vert_t& v) {
66+
return graph.GetAdjList()[v].size();
67+
}
68+
69+
/**
70+
* @brief Вычисляет входящую степень вершины в графе.
71+
*
72+
* @tparam vert_t: тип вершин графа.
73+
* @tparam weight_t: тип весов рёбер графа.
74+
* @param graph: граф, в котором вычисляется входящая степень.
75+
* @param v: вершина, для которой вычисляется входящая степень.
76+
*
77+
* @return size_t: входящая степень вершины (количество входящих ребер).
78+
*/
79+
template <AllowedVertType vert_t, AllowedWeightType weight_t>
80+
inline size_t InDeg(const Graph<vert_t, weight_t>& graph, const vert_t& v) {
81+
// для неориентированных графов входящая степень равна исходящей
82+
if (!graph.IsDirected()) return OutDeg(graph, v);
83+
84+
size_t res = 0;
85+
86+
auto adj_list = graph.GetAdjList();
87+
88+
for (const auto& u : graph.Verts())
89+
if (Contains(adj_list[u], v)) res++;
90+
91+
return res;
92+
}
93+
94+
} // namespace
95+
96+
/**
97+
* @brief Проверяет, существует ли в графе эйлеров путь.
98+
*
99+
* @details Эйлеров путь — это путь в графе, который проходит через каждое ребро
100+
* ровно один раз. Функция сначала проверяет необходимое, но недостаточное
101+
* условие существования эйлерова пути: количество вершин с нечетной степенью
102+
* должно быть не больше двух. Затем она проверяет связность графа с помощью
103+
* DFS.
104+
*
105+
* @tparam vert_t: тип вершин графа.
106+
* @tparam weight_t: тип весов рёбер графа.
107+
* @param graph: граф, для которого проверяется наличие эйлерова пути.
108+
*
109+
* @return `true`, если эйлеров путь существует
110+
* @return `false` в противном случае.
111+
*/
112+
template <AllowedVertType vert_t, AllowedWeightType weight_t>
113+
inline bool HasEulerPath(const Graph<vert_t, weight_t>& graph) {
114+
// считаем количество вершин с нечетной степенью
115+
size_t odd_vert_count = 0;
116+
117+
for (const auto& v : graph.Verts())
118+
if (OutDeg(graph, v) != InDeg(graph, v))
119+
odd_vert_count++;
120+
121+
else if (!graph.IsDirected() && OutDeg(graph, v) % 2 != 0)
122+
odd_vert_count++;
123+
124+
// если вершин с нечетной степенью больше двух, то эйлерова пути нет
125+
if (odd_vert_count > 2) return false;
126+
127+
std::unordered_map<vert_t, bool> visited;
128+
for (const auto& vert : graph.Verts()) visited[vert] = false;
129+
130+
std::unordered_map<vert_t, size_t> degree;
131+
for (const auto& vert : graph.Verts())
132+
degree[vert] = graph.IsDirected() ? InDeg(graph, vert) + OutDeg(graph, vert)
133+
: OutDeg(graph, vert);
134+
135+
// находим первую вершину с ненулевой степенью и начинаем DFS
136+
for (const auto& v : graph.Verts())
137+
if (degree[v] > 0) {
138+
HasEulerPathStep(v, graph, visited);
139+
break;
140+
}
141+
142+
// проверяем, все ли вершины с ненулевой степенью были посещены.
143+
for (const auto& v : graph.Verts())
144+
if (degree[v] > 0 && !visited[v]) return false;
145+
146+
// все вершины с ненулевой степенью были посещены, эйлеров путь существует
147+
return true;
148+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#pragma once
2+
3+
#include "euler_path.hpp"
4+
5+
/**
6+
* @brief Проверяет, можно ли составить цепочку из заданных слов так, чтобы
7+
* последняя буква каждого слова совпадала с первой буквой следующего.
8+
*
9+
* @param words вектор строк (слов), которые нужно проверить.
10+
*
11+
* @return `true`: слова можно объединить в цепочку
12+
* @return `false`: в противном случае
13+
*/
14+
bool CanBeChained(const std::vector<std::string>& words) {
15+
if (words.empty()) return true;
16+
17+
Graph<char> chars_graph;
18+
19+
for (const std::string& s : words) {
20+
if (s.empty()) continue; // пропускаем пустые строки
21+
22+
chars_graph.AddEdge({s[0], s.back()});
23+
}
24+
25+
// дополнительная проверка на связность:
26+
if (chars_graph.VertsAmount() > 0 && chars_graph.EdgesAmount() == 0)
27+
return false; // есть изолированная вершина
28+
29+
// проверка на Эйлеров цикл
30+
return HasEulerPath(chars_graph);
31+
}
32+
33+
/// @brief Решает задачу: ""
34+
void Solution(std::istream& is = std::cin, std::ostream& os = std::cout) {
35+
size_t words_amount;
36+
is >> words_amount;
37+
38+
std::vector<std::string> words(words_amount);
39+
for (auto& word : words) is >> word;
40+
41+
if (CanBeChained(words))
42+
os << "Yes";
43+
else
44+
os << "No";
45+
46+
os << std::endl;
47+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include "find_word_chains.hpp"
2+
3+
int main() {
4+
Solution();
5+
return 0;
6+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#include <gtest/gtest.h>
2+
3+
#include "find_word_chains.hpp"
4+
5+
TEST(CanBeChainedTest, EmptyInput) {
6+
std::vector<std::string> words = {};
7+
ASSERT_TRUE(CanBeChained(words));
8+
}
9+
10+
TEST(CanBeChainedTest, SingleWord) {
11+
std::vector<std::string> words = {"abc"};
12+
ASSERT_TRUE(CanBeChained(words));
13+
}
14+
15+
TEST(CanBeChainedTest, SimpleChain) {
16+
std::vector<std::string> words = {"for", "rig", "geek", "kaf"};
17+
ASSERT_TRUE(CanBeChained(words));
18+
}
19+
20+
TEST(CanBeChainedTest, SimpleChain2) {
21+
std::vector<std::string> words = {"abc", "cba"};
22+
ASSERT_TRUE(CanBeChained(words));
23+
}
24+
25+
TEST(CanBeChainedTest, NoChain2) {
26+
std::vector<std::string> words = {"abc", "def"};
27+
ASSERT_FALSE(CanBeChained(words));
28+
}
29+
30+
TEST(CanBeChainedTest, SameStartEnd) {
31+
std::vector<std::string> words = {"aba", "cba", "acc"};
32+
ASSERT_TRUE(CanBeChained(words));
33+
}
34+
35+
TEST(CanBeChainedTest, MultipleSameWords) {
36+
std::vector<std::string> words = {"aba", "aba", "aca"};
37+
ASSERT_TRUE(CanBeChained(words));
38+
}
39+
40+
TEST(CanBeChainedTest, WordsWithSameStartEnd) {
41+
std::vector<std::string> words = {"aaa", "bbb", "ccc"};
42+
ASSERT_FALSE(CanBeChained(words));
43+
}
44+
45+
TEST(CanBeChainedTest, MultipleWordsWithSameStartEnd) {
46+
std::vector<std::string> words = {"aaa", "aba", "aca"};
47+
ASSERT_TRUE(CanBeChained(words));
48+
}
49+
50+
TEST(CanBeChainedTest, MultipleWordsWithSameStartEnd2) {
51+
std::vector<std::string> words = {"aaa", "abb", "aba"};
52+
ASSERT_TRUE(CanBeChained(words));
53+
}
54+
55+
TEST(CanBeChainedTest, ComplexChain) {
56+
std::vector<std::string> words = {"aba", "bab", "bba"};
57+
ASSERT_FALSE(CanBeChained(words));
58+
}
59+
60+
TEST(CanBeChainedTest, ComplexChain2) {
61+
std::vector<std::string> words = {"abc", "cde", "efg", "gba"};
62+
ASSERT_TRUE(CanBeChained(words));
63+
}
64+
65+
TEST(CanBeChainedTest, ComplexNoChain) {
66+
std::vector<std::string> words = {"abc", "cde", "efg", "hbl"};
67+
ASSERT_FALSE(CanBeChained(words));
68+
}
69+
70+
TEST(CanBeChainedTest, OneWordMultipleSameLetters) {
71+
std::vector<std::string> words = {"aaaa"};
72+
ASSERT_TRUE(CanBeChained(words));
73+
}
74+
75+
TEST(CanBeChainedTest, TwoWordsSameStartEnd) {
76+
std::vector<std::string> words = {"aaa", "bbb"};
77+
ASSERT_FALSE(CanBeChained(words));
78+
}
79+
80+
TEST(CanBeChainedTest, EmptyWords) {
81+
std::vector<std::string> words = {"abc", "", "def"};
82+
ASSERT_FALSE(CanBeChained(words));
83+
}
84+
85+
TEST(CanBeChainedTest, EmptyWordsOnly) {
86+
std::vector<std::string> words = {"", "", ""};
87+
ASSERT_TRUE(CanBeChained(words));
88+
}
89+
90+
TEST(CanBeChainedTest, LongChain) {
91+
std::vector<std::string> words = {"a", "ab", "bc", "cd", "de", "ef", "fa"};
92+
ASSERT_TRUE(CanBeChained(words));
93+
}

0 commit comments

Comments
 (0)