diff --git a/greedy_algorithms/gale_shapley.cpp b/greedy_algorithms/gale_shapley.cpp new file mode 100644 index 00000000000..ddc6298fa44 --- /dev/null +++ b/greedy_algorithms/gale_shapley.cpp @@ -0,0 +1,129 @@ +/** + * @file + * @brief [Gale Shapley Algorithm](https://en.wikipedia.org/wiki/Gale%E2%80%93Shapley_algorithm) + * @details + * This implementation utilizes the Gale-Shapley algorithm to find stable matches. + * + * **Gale Shapley Algorithm** aims to find a stable matching between two equally sized + * sets of elements given an ordinal preference for each element. The algorithm was + * introduced by David Gale and Lloyd Shapley in 1962. + * + * Reference: + * [Wikipedia](https://en.wikipedia.org/wiki/Gale%E2%80%93Shapley_algorithm) + * [Wikipedia](https://en.wikipedia.org/wiki/Stable_matching_problem) + * + * @author [B Karthik](https://github.com/BKarthik7) + */ + +#include /// for std::u32int_t +#include /// for std::vector +#include /// for std::find +#include /// for assert + +/** + * @namespace + * @brief Greedy Algorithms + */ +namespace greedy_algorithms { +/** + * @namespace + * @brief Functions for the Gale-Shapley Algorithm + */ +namespace stable_matching { +/** + * @brief The main function that finds the stable matching between two sets of elements + * using the Gale-Shapley Algorithm. + * @note This doesn't work on negative preferences. the preferences should be continuous integers starting from + * 0 to number of preferences - 1. + * @param primary_preferences the preferences of the primary set should be a 2D vector + * @param secondary_preferences the preferences of the secondary set should be a 2D vector + * @returns matches the stable matching between the two sets + */ +std::vector gale_shapley(const std::vector>& secondary_preferences, const std::vector>& primary_preferences) { + std::uint32_t num_elements = secondary_preferences.size(); + std::vector matches(num_elements, -1); + std::vector is_free_primary(num_elements, true); + std::vector proposal_index(num_elements, 0); // Tracks the next secondary to propose for each primary + + while (true) { + int free_primary_index = -1; + + // Find the next free primary + for (std::uint32_t i = 0; i < num_elements; i++) { + if (is_free_primary[i]) { + free_primary_index = i; + break; + } + } + + // If no free primary is found, break the loop + if (free_primary_index == -1) break; + + // Get the next secondary to propose + std::uint32_t secondary_to_propose = primary_preferences[free_primary_index][proposal_index[free_primary_index]]; + proposal_index[free_primary_index]++; + + // Get the current match of the secondary + std::uint32_t current_match = matches[secondary_to_propose]; + + // If the secondary is free, match them + if (current_match == -1) { + matches[secondary_to_propose] = free_primary_index; + is_free_primary[free_primary_index] = false; + } else { + // Determine if the current match should be replaced + auto new_proposer_rank = std::find(secondary_preferences[secondary_to_propose].begin(), + secondary_preferences[secondary_to_propose].end(), + free_primary_index); + auto current_match_rank = std::find(secondary_preferences[secondary_to_propose].begin(), + secondary_preferences[secondary_to_propose].end(), + current_match); + + // If the new proposer is preferred over the current match + if (new_proposer_rank < current_match_rank) { + matches[secondary_to_propose] = free_primary_index; + is_free_primary[free_primary_index] = false; + is_free_primary[current_match] = true; // Current match is now free + } + } + } + + return matches; +} +} // namespace stable_matching +} // namespace greedy_algorithms + +/** + * @brief Self-test implementations + * @returns void + */ +static void tests() { + // Test Case 1 + std::vector> primary_preferences = {{0, 1, 2, 3}, {2, 1, 3, 0}, {1, 2, 0, 3}, {3, 0, 1, 2}}; + std::vector> secondary_preferences = {{1, 0, 2, 3}, {3, 0, 1, 2}, {0, 2, 1, 3}, {1, 2, 0, 3}}; + assert(greedy_algorithms::stable_matching::gale_shapley(secondary_preferences, primary_preferences) == std::vector({0, 2, 1, 3})); + + // Test Case 2 + primary_preferences = {{0, 2, 1, 3}, {2, 3, 0, 1}, {3, 1, 2, 0}, {2, 1, 0, 3}}; + secondary_preferences = {{1, 0, 2, 3}, {3, 0, 1, 2}, {0, 2, 1, 3}, {1, 2, 0, 3}}; + assert(greedy_algorithms::stable_matching::gale_shapley(secondary_preferences, primary_preferences) == std::vector({0, 3, 1, 2})); + + // Test Case 3 + primary_preferences = {{0, 1, 2}, {2, 1, 0}, {1, 2, 0}}; + secondary_preferences = {{1, 0, 2}, {2, 0, 1}, {0, 2, 1}}; + assert(greedy_algorithms::stable_matching::gale_shapley(secondary_preferences, primary_preferences) == std::vector({0, 2, 1})); + + // Test Case 4 + primary_preferences = {}; + secondary_preferences = {}; + assert(greedy_algorithms::stable_matching::gale_shapley(secondary_preferences, primary_preferences) == std::vector({})); +} + +/** + * @brief Main function + * @returns 0 on exit + */ +int main() { + tests(); // Run self-test implementations + return 0; +}