diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5e77144..c8c6e2b 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,8 +3,8 @@ { "name": "Mac", "includePath": [ - "${workspaceFolder}/**", // Overall project path - "/opt/homebrew/include" // M1~ Standard path for Mac + "${workspaceFolder}/**", + "/opt/homebrew/include" ], "defines": [], "macFrameworkPath": [ @@ -17,4 +17,4 @@ } ], "version": 4 -} +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c29377f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "C_Cpp.default.compilerPath": "/usr/bin/clang", + "C_Cpp.default.cppStandard": "c++20", + "C_Cpp.default.includePath": [ + "${workspaceFolder}/src" + ], + "C_Cpp.default.intelliSenseMode": "clang-x64" +} diff --git a/src/graph/kruskal.hpp b/src/graph/kruskal.hpp new file mode 100644 index 0000000..305a200 --- /dev/null +++ b/src/graph/kruskal.hpp @@ -0,0 +1,124 @@ +#ifndef KRUSKAL_HPP +#define KRUSKAL_HPP + +#include +#include +#include +#include + +/** + * @brief Edge structure for Kruskal's algorithm + */ +struct KruskalEdge { + int from; + int to; + long long weight; + + // Operator for sorting edges by weight + bool operator<(const KruskalEdge& other) const { + return weight < other.weight; + } +}; + +/** + * @brief Disjoint Set Union (DSU) data structure for Kruskal's algorithm + */ +class DisjointSet { +private: + std::vector parent; + std::vector rank; + +public: + /** + * @brief Initialize a DSU with n elements + * @param n Number of elements + */ + DisjointSet(int n) : parent(n), rank(n, 0) { + for (int i = 0; i < n; i++) { + parent[i] = i; // Each element is its own parent initially + } + } + + /** + * @brief Find the representative of the set containing x + * @param x Element to find + * @return Representative of the set + */ + int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); // Path compression + } + return parent[x]; + } + + /** + * @brief Union of two sets + * @param x First element + * @param y Second element + * @return True if x and y were in different sets, false otherwise + */ + bool unite(int x, int y) { + int rootX = find(x); + int rootY = find(y); + + if (rootX == rootY) { + return false; // Already in the same set + } + + // Union by rank + if (rank[rootX] < rank[rootY]) { + parent[rootX] = rootY; + } else if (rank[rootX] > rank[rootY]) { + parent[rootY] = rootX; + } else { + parent[rootY] = rootX; + rank[rootX]++; + } + + return true; + } +}; + +/** + * @brief Kruskal's algorithm for finding the Minimum Spanning Tree (MST) of a graph + * + * @param n Number of vertices in the graph + * @param edges Vector of edges in the graph + * @return Vector of edges that form the MST and the total weight of the MST + */ +std::pair, long long> kruskal(int n, std::vector& edges) { + // Sort edges by weight + std::sort(edges.begin(), edges.end()); + + DisjointSet dsu(n); + std::vector mst; + long long totalWeight = 0; + + for (const auto& edge : edges) { + if (dsu.unite(edge.from, edge.to)) { + // This edge is part of the MST + mst.push_back(edge); + totalWeight += edge.weight; + + // If we have n-1 edges, we have a complete MST + if (mst.size() == n - 1) { + break; + } + } + } + + return {mst, totalWeight}; +} + +/** + * @brief Check if the graph is connected + * + * @param n Number of vertices + * @param mst The MST edges + * @return True if the graph is connected, false otherwise + */ +bool isConnected(int n, const std::vector& mst) { + return mst.size() == n - 1; +} + +#endif // KRUSKAL_HPP diff --git a/src/search/binary_search.hpp b/src/search/binary_search.hpp new file mode 100644 index 0000000..1c23a0c --- /dev/null +++ b/src/search/binary_search.hpp @@ -0,0 +1,122 @@ +#ifndef BINARY_SEARCH_HPP +#define BINARY_SEARCH_HPP + +#include +#include +#include + +namespace clavis { +namespace search { + +/** + * @brief Binary search algorithm for finding an element in a sorted array + * + * @tparam T Type of elements in the array (must be comparable) + * @param arr Sorted array to search in + * @param target Element to search for + * @return Index of the target element if found, -1 otherwise + */ +template +requires std::totally_ordered +int binary_search(const std::vector& arr, const T& target) { + int left = 0; + int right = arr.size() - 1; + + while (left <= right) { + int mid = left + (right - left) / 2; // Avoid potential overflow + + if (arr[mid] == target) { + return mid; // Found the target + } else if (arr[mid] < target) { + left = mid + 1; // Search in the right half + } else { + right = mid - 1; // Search in the left half + } + } + + return -1; // Target not found +} + +/** + * @brief Lower bound binary search - finds the first element not less than the target + * + * @tparam T Type of elements in the array (must be comparable) + * @param arr Sorted array to search in + * @param target Element to search for + * @return Index of the first element not less than target, or arr.size() if all elements are less than target + */ +template +requires std::totally_ordered +int lower_bound(const std::vector& arr, const T& target) { + int left = 0; + int right = arr.size(); // Note: right is arr.size(), not arr.size() - 1 + + while (left < right) { + int mid = left + (right - left) / 2; + + if (arr[mid] < target) { + left = mid + 1; + } else { + right = mid; + } + } + + return left; // Returns arr.size() if all elements are less than target +} + +/** + * @brief Upper bound binary search - finds the first element greater than the target + * + * @tparam T Type of elements in the array (must be comparable) + * @param arr Sorted array to search in + * @param target Element to search for + * @return Index of the first element greater than target, or arr.size() if no such element exists + */ +template +requires std::totally_ordered +int upper_bound(const std::vector& arr, const T& target) { + int left = 0; + int right = arr.size(); // Note: right is arr.size(), not arr.size() - 1 + + while (left < right) { + int mid = left + (right - left) / 2; + + if (arr[mid] <= target) { + left = mid + 1; + } else { + right = mid; + } + } + + return left; // Returns arr.size() if all elements are less than or equal to target +} + +/** + * @brief Binary search on a predicate function + * + * @tparam T Type of elements in the search space + * @param left Left boundary of the search space (inclusive) + * @param right Right boundary of the search space (exclusive) + * @param predicate Function that returns true for elements that satisfy the condition + * @return The first element in the range [left, right) for which predicate returns true, or right if none exists + */ +template +requires std::integral +T binary_search_predicate(T left, T right, const std::function& predicate) { + while (left < right) { + T mid = left + (right - left) / 2; + + if (predicate(mid)) { + right = mid; + } else { + left = mid + 1; + } + } + + return left; // Returns right if no element satisfies the predicate +} + +} // namespace search +} // namespace clavis + +#endif // BINARY_SEARCH_HPP diff --git a/tests/graph/kruskal_test.cpp b/tests/graph/kruskal_test.cpp new file mode 100644 index 0000000..4b3e1e2 --- /dev/null +++ b/tests/graph/kruskal_test.cpp @@ -0,0 +1,144 @@ +#include "../src/graph/kruskal.hpp" + +#include + +/** + * @brief Test fixture for Kruskal's Algorithm + */ +class KruskalTest : public ::testing::Test { + protected: +}; + +/** + * @test Basic test for small graph + */ +TEST_F(KruskalTest, SmallGraph) { + // Create a small graph with 4 vertices + // 0 -- (1, weight=1) -- 1 + // | | + // (2, weight=2) (4, weight=4) + // | | + // 2 -- (3, weight=3) -- 3 + std::vector edges = { + {0, 1, 1}, // Edge 0-1 with weight 1 + {0, 2, 2}, // Edge 0-2 with weight 2 + {2, 3, 3}, // Edge 2-3 with weight 3 + {1, 3, 4}, // Edge 1-3 with weight 4 + }; + + auto [mst, totalWeight] = kruskal(4, edges); + + // The MST should include edges 0-1, 0-2, and 2-3 + // Total weight should be 1 + 2 + 3 = 6 + EXPECT_EQ(mst.size(), 3); + EXPECT_EQ(totalWeight, 6); + + // Check if the graph is connected + EXPECT_TRUE(isConnected(4, mst)); + + // Verify the edges in the MST + // The first edge should be 0-1 with weight 1 + EXPECT_EQ(mst[0].from, 0); + EXPECT_EQ(mst[0].to, 1); + EXPECT_EQ(mst[0].weight, 1); + + // The second edge should be 0-2 with weight 2 + EXPECT_EQ(mst[1].from, 0); + EXPECT_EQ(mst[1].to, 2); + EXPECT_EQ(mst[1].weight, 2); + + // The third edge should be 2-3 with weight 3 + EXPECT_EQ(mst[2].from, 2); + EXPECT_EQ(mst[2].to, 3); + EXPECT_EQ(mst[2].weight, 3); +} + +/** + * @test Test with a disconnected graph + */ +TEST_F(KruskalTest, DisconnectedGraph) { + // Create a disconnected graph with 5 vertices + // Component 1: 0 -- (1, weight=1) -- 1 + // Component 2: 2 -- (3, weight=3) -- 3 -- (4, weight=4) -- 4 + std::vector edges = { + {0, 1, 1}, // Edge 0-1 with weight 1 + {2, 3, 3}, // Edge 2-3 with weight 3 + {3, 4, 4}, // Edge 3-4 with weight 4 + }; + + auto [mst, totalWeight] = kruskal(5, edges); + + // The MST should include all 3 edges + // Total weight should be 1 + 3 + 4 = 8 + EXPECT_EQ(mst.size(), 3); + EXPECT_EQ(totalWeight, 8); + + // Check if the graph is disconnected + EXPECT_FALSE(isConnected(5, mst)); +} + +/** + * @test Test with a cycle in the graph + */ +TEST_F(KruskalTest, CycleGraph) { + // Create a graph with a cycle + // 0 -- (1, weight=1) -- 1 + // | | + // (4, weight=4) (2, weight=2) + // | | + // 3 -- (3, weight=3) -- 2 + std::vector edges = { + {0, 1, 1}, // Edge 0-1 with weight 1 + {1, 2, 2}, // Edge 1-2 with weight 2 + {2, 3, 3}, // Edge 2-3 with weight 3 + {0, 3, 4}, // Edge 0-3 with weight 4 + }; + + auto [mst, totalWeight] = kruskal(4, edges); + + // The MST should include edges 0-1, 1-2, and 2-3 + // Total weight should be 1 + 2 + 3 = 6 + EXPECT_EQ(mst.size(), 3); + EXPECT_EQ(totalWeight, 6); + + // Check if the graph is connected + EXPECT_TRUE(isConnected(4, mst)); + + // The edge 0-3 should not be in the MST because it creates a cycle + bool hasEdge03 = false; + for (const auto& edge : mst) { + if ((edge.from == 0 && edge.to == 3) || (edge.from == 3 && edge.to == 0)) { + hasEdge03 = true; + break; + } + } + EXPECT_FALSE(hasEdge03); +} + +/** + * @test Test with multiple possible MSTs + */ +TEST_F(KruskalTest, MultipleMSTs) { + // Create a graph where multiple MSTs are possible + // 0 -- (1, weight=1) -- 1 + // | | + // (1, weight=1) (1, weight=1) + // | | + // 2 -- (1, weight=1) -- 3 + std::vector edges = { + {0, 1, 1}, // Edge 0-1 with weight 1 + {0, 2, 1}, // Edge 0-2 with weight 1 + {1, 3, 1}, // Edge 1-3 with weight 1 + {2, 3, 1}, // Edge 2-3 with weight 1 + }; + + auto [mst, totalWeight] = kruskal(4, edges); + + // The MST should include 3 edges, all with weight 1 + // Total weight should be 3 + EXPECT_EQ(mst.size(), 3); + EXPECT_EQ(totalWeight, 3); + + // Check if the graph is connected + EXPECT_TRUE(isConnected(4, mst)); +} diff --git a/tests/search/binary_search_test.cpp b/tests/search/binary_search_test.cpp new file mode 100644 index 0000000..af08676 --- /dev/null +++ b/tests/search/binary_search_test.cpp @@ -0,0 +1,165 @@ +#include "../src/search/binary_search.hpp" + +#include +#include +#include + +/** + * @brief Test fixture for Binary Search algorithms + */ +class BinarySearchTest : public ::testing::Test { + protected: + // Test vectors + std::vector sortedInts; + std::vector sortedDoubles; + std::vector sortedStrings; + + void SetUp() override { + // Setup sorted integer array + sortedInts = {-10, -5, 0, 1, 2, 5, 10, 20, 50, 100}; + + // Setup sorted double array + sortedDoubles = {-10.5, -5.5, 0.0, 1.5, 2.5, 5.5, 10.5, 20.5, 50.5, 100.5}; + + // Setup sorted string array + sortedStrings = {"apple", "banana", "cherry", "date", "elderberry", "fig", "grape"}; + } +}; + +/** + * @test Test basic binary search with integers + */ +TEST_F(BinarySearchTest, BasicIntSearch) { + // Using a lambda to explicitly specify the template parameter + auto int_binary_search = [](const std::vector& arr, const int& target) { + return clavis::search::binary_search(arr, target); + }; + + // Test finding existing elements + EXPECT_EQ(int_binary_search(sortedInts, -10), 0); + EXPECT_EQ(int_binary_search(sortedInts, 0), 2); + EXPECT_EQ(int_binary_search(sortedInts, 10), 6); + EXPECT_EQ(int_binary_search(sortedInts, 100), 9); + + // Test finding non-existing elements + EXPECT_EQ(int_binary_search(sortedInts, -100), -1); + EXPECT_EQ(int_binary_search(sortedInts, 3), -1); + EXPECT_EQ(int_binary_search(sortedInts, 200), -1); +} + +/** + * @test Test binary search with doubles + */ +TEST_F(BinarySearchTest, DoubleSearch) { + // Using a lambda to explicitly specify the template parameter + auto double_binary_search = [](const std::vector& arr, const double& target) { + return clavis::search::binary_search(arr, target); + }; + + // Test finding existing elements + EXPECT_EQ(double_binary_search(sortedDoubles, -10.5), 0); + EXPECT_EQ(double_binary_search(sortedDoubles, 0.0), 2); + EXPECT_EQ(double_binary_search(sortedDoubles, 10.5), 6); + EXPECT_EQ(double_binary_search(sortedDoubles, 100.5), 9); + + // Test finding non-existing elements + EXPECT_EQ(double_binary_search(sortedDoubles, -100.5), -1); + EXPECT_EQ(double_binary_search(sortedDoubles, 3.5), -1); + EXPECT_EQ(double_binary_search(sortedDoubles, 200.5), -1); +} + +/** + * @test Test binary search with strings + */ +TEST_F(BinarySearchTest, StringSearch) { + // Using a lambda to explicitly specify the template parameter + auto string_binary_search = [](const std::vector& arr, const std::string& target) { + return clavis::search::binary_search(arr, target); + }; + + // Test finding existing elements + EXPECT_EQ(string_binary_search(sortedStrings, "apple"), 0); + EXPECT_EQ(string_binary_search(sortedStrings, "cherry"), 2); + EXPECT_EQ(string_binary_search(sortedStrings, "grape"), 6); + + // Test finding non-existing elements + EXPECT_EQ(string_binary_search(sortedStrings, "apricot"), -1); + EXPECT_EQ(string_binary_search(sortedStrings, "kiwi"), -1); + EXPECT_EQ(string_binary_search(sortedStrings, "zebra"), -1); +} + +/** + * @test Test lower_bound function + */ +TEST_F(BinarySearchTest, LowerBound) { + // Test with integers + EXPECT_EQ(clavis::search::lower_bound(sortedInts, -20), 0); // First element + EXPECT_EQ(clavis::search::lower_bound(sortedInts, -10), 0); // Existing element + EXPECT_EQ(clavis::search::lower_bound(sortedInts, -7), 1); // Between elements + EXPECT_EQ(clavis::search::lower_bound(sortedInts, 3), 5); // Between elements + EXPECT_EQ(clavis::search::lower_bound(sortedInts, 100), 9); // Last element + EXPECT_EQ(clavis::search::lower_bound(sortedInts, 200), 10); // Beyond last element + + // Test with empty vector + std::vector empty; + EXPECT_EQ(clavis::search::lower_bound(empty, 5), 0); +} + +/** + * @test Test upper_bound function + */ +TEST_F(BinarySearchTest, UpperBound) { + // Test with integers + EXPECT_EQ(clavis::search::upper_bound(sortedInts, -20), 0); // Before first element + EXPECT_EQ(clavis::search::upper_bound(sortedInts, -10), 1); // Existing element + EXPECT_EQ(clavis::search::upper_bound(sortedInts, -7), 1); // Between elements + EXPECT_EQ(clavis::search::upper_bound(sortedInts, 3), 5); // Between elements + EXPECT_EQ(clavis::search::upper_bound(sortedInts, 100), 10); // Last element + EXPECT_EQ(clavis::search::upper_bound(sortedInts, 200), 10); // Beyond last element + + // Test with empty vector + std::vector empty; + EXPECT_EQ(clavis::search::upper_bound(empty, 5), 0); +} + +/** + * @test Test binary_search_predicate function + */ +TEST_F(BinarySearchTest, PredicateSearch) { + // Find the first number >= 10 in range [0, 20) + auto result1 = clavis::search::binary_search_predicate(0, 20, [](int x) { return x >= 10; }); + EXPECT_EQ(result1, 10); + + // Find the first square number >= 100 in range [0, 20) + auto result2 = clavis::search::binary_search_predicate(0, 20, [](int x) { return x * x >= 100; }); + EXPECT_EQ(result2, 10); + + // Find the first number that doesn't satisfy any condition + auto result3 = clavis::search::binary_search_predicate(0, 10, [](int x) { return x > 15; }); + EXPECT_EQ(result3, 10); // Should return the right boundary +} + +/** + * @test Test edge cases + */ +TEST_F(BinarySearchTest, EdgeCases) { + // Using a lambda to explicitly specify the template parameter + auto int_binary_search = [](const std::vector& arr, const int& target) { + return clavis::search::binary_search(arr, target); + }; + + // Test with single element vector + std::vector singleElement = {42}; + EXPECT_EQ(int_binary_search(singleElement, 42), 0); + EXPECT_EQ(int_binary_search(singleElement, 41), -1); + EXPECT_EQ(clavis::search::lower_bound(singleElement, 41), 0); + EXPECT_EQ(clavis::search::lower_bound(singleElement, 42), 0); + EXPECT_EQ(clavis::search::lower_bound(singleElement, 43), 1); + EXPECT_EQ(clavis::search::upper_bound(singleElement, 41), 0); + EXPECT_EQ(clavis::search::upper_bound(singleElement, 42), 1); + EXPECT_EQ(clavis::search::upper_bound(singleElement, 43), 1); + + // Test with empty vector + std::vector empty; + EXPECT_EQ(int_binary_search(empty, 42), -1); +}