Mergesort --------- In :numref:`c192_sorting`, we saw a simple sorting algorithm that turns out not to be very efficient. In order to sort :math:`n` items, it has to traverse the vector :math:`n` times, and each traversal takes an amount of time that is proportional to :math:`n`. The total time, therefore, is proportional to :math:`n^2`. .. index:: single: mergesort In this section I will sketch a more efficient algorithm called **mergesort**. To sort :math:`n` items, mergesort takes time proportional to :math:`n \log n`. That may not seem impressive, but as :math:`n` gets big, the difference between :math:`n^2` and :math:`n \log n` can be enormous. Try out a few values of :math:`n` and see. The basic idea behind mergesort is this: if you have two subdecks, each of which has been sorted, it is easy (and fast) to merge them into a single, sorted deck. Try this out with a deck of cards: #. Form two subdecks with about 10 cards each and sort them so that when they are face up the lowest cards are on top. Place both decks face up in front of you. #. Compare the top card from each deck and choose the lower one. Flip it over and add it to the merged deck. #. Repeat step two until one of the decks is empty. Then take the remaining cards and add them to the merged deck. The result should be a single sorted deck. Here’s what this looks like In pseudocode: :: card_deck merge (const card_deck& d1, const card_deck& d2) { // create a new deck big enough for all the cards card_deck result (d1.cards.size() + d2.cards.size()); // use the index i to keep track of where we are in // the first deck, and the index j for the second deck std::size_t i = 0; std::size_t j = 0; // the index k traverses the result deck for (std::size_t k = 0; k suits (4); suits[0] = "Clubs"; suits[1] = "Diamonds"; suits[2] = "Hearts"; suits[3] = "Spades"; std::vector ranks (14); ranks[1] = "Ace"; ranks[2] = "2"; ranks[3] = "3"; ranks[4] = "4"; ranks[5] = "5"; ranks[6] = "6"; ranks[7] = "7"; ranks[8] = "8"; ranks[9] = "9"; ranks[10] = "10"; ranks[11] = "Jack"; ranks[12] = "Queen"; ranks[13] = "King"; cout << ranks[rank] << " of " << suits[suit] << std::endl; } bool playing_card::is_greater (const playing_card& c2) const { if (suit > c2.suit) return true; if (suit < c2.suit) return false; if (rank > c2.rank) return true; if (rank < c2.rank) return false; return false; } bool playing_card::equals (const playing_card& c2) const { return (rank == c2.rank && suit == c2.suit); } card_deck::card_deck () { std::vector temp (52); cards = temp; std::size_t i = 0; for (int suit = clubs; suit <= spades; ++suit) { for (int rank = ace; rank <= king; ++rank) { cards[i].suit = static_cast(suit); cards[i].rank = static_cast(rank); i++; } } } card_deck::card_deck (std::size_t size) { std::vector temp (size); cards = temp; } void card_deck::print () const { for (std::size_t i = 0; i < cards.size(); i++) { cards[i].print (); } } std::size_t random_int(std::size_t low, std::size_t high) { static std::mt19937 engine(std::random_device{}()); return std::uniform_int_distribution{low, high}(engine); } void card_deck::swap_cards (std::size_t index1, std::size_t index2) { playing_card temp = cards[index1]; cards[index1] = cards[index2]; cards[index2] = temp; } std::size_t card_deck::find_lowest_card (std::size_t index) { std::size_t min = index; for (std::size_t i = index; i < cards.size(); ++i) { if (cards[min].is_greater(cards[i])) { min = i; } } return min; } card_deck card_deck::subdeck (std::ptrdiff_t low, std::ptrdiff_t high) const { if (low < 0 || low > std::ssize(cards) || high < low - 1 || high >= std::ssize(cards)) { throw std::out_of_range("subdeck range"); } card_deck sub (static_cast(high - low + 1)); for (std::size_t i = 0; i #include #include #include #include #include #include using std::cout; enum card_suit { clubs, diamonds, hearts, spades }; enum card_rank { ace=1, two, three, four, five, six, seven, eight, nine, ten, jack, queen, king }; std::size_t random_int(std::size_t low, std::size_t high); struct playing_card { card_rank rank; card_suit suit; playing_card (); playing_card (card_suit s, card_rank r); void print () const; bool is_greater (const playing_card& c2) const; bool equals (const playing_card& c2) const; }; struct card_deck { std::vector cards; card_deck (); card_deck (std::size_t n); void print () const; void swap_cards (std::size_t index1, std::size_t index2); std::size_t find_lowest_card (std::size_t index); void shuffle_deck (); void sort_deck (); card_deck subdeck (std::ptrdiff_t low, std::ptrdiff_t high) const; }; std::ptrdiff_t find_bisect (card_deck subdeck, playing_card card); card_deck merge (const card_deck& d1, const card_deck& d2) { // ``merge`` should merge d1 with d2 and return // a merged deck. Follow the pseudocode above, // delete the existing code, and write your // implementation here. card_deck deck(0); return deck; } int main() { card_deck deck; // Shuffle a deck of cards and split it in half deck.shuffle_deck(); card_deck d1 = deck.subdeck(0, 25); card_deck d2 = deck.subdeck(26, 51); // Sort each half d1.sort_deck(); d2.sort_deck(); cout << "Sorted first half:" << std::endl; d1.print(); cout << std::endl; cout << "Sorted second half:" << std::endl; d2.print(); cout << std::endl; // Merge sorted decks together card_deck finished = merge(d1, d2); // We should see a sorted standard deck of 52 cards cout << "Merged sorted full deck:" << std::endl; finished.print(); } .. tb-reveal:: merge Help :name: c192_mergesort_reveal_1 .. tb-parsons:: :name: c192_mergesort_help_1 First, let's write the code for the merge function. merge should take two decks as parameters and return a deck with the deck merged. .. code-block:: c++ {{group}} card_deck merge (const card_deck& d1, const card_deck& d2) { {{endgroup}} {{distractor}} {{group}} void merge (const card_deck& d1, const card_deck& d2) { {{endgroup}} {{group}} card_deck result (d1.cards.size() + d2.cards.size()); {{endgroup}} {{group}} std::size_t i = 0; std::size_t j = 0; {{endgroup}} {{group}} for (std::size_t k = 0; k < result.cards.size(); ++k) { {{endgroup}} {{group}} if (d1.cards.empty()) { result.cards[k] = d2.cards[j]; ++j; } {{endgroup}} {{distractor}} {{group}} if (d1.cards.empty()) { result.cards[k] = d1.cards[i]; ++i; } {{endgroup}} {{group}} else if (d2.cards.empty()) { result.cards[k] = d1.cards[i]; ++i; } {{endgroup}} {{distractor}} {{group}} else if (d1.cards.empty()) { result.cards[k] = d2.cards[j]; ++j; } {{endgroup}} {{group}} else { {{endgroup}} {{group}} if (j >= d2.cards.size()) { result.cards[k] = d1.cards[i]; ++i; } {{endgroup}} {{group}} else if (i >= d1.cards.size() || d1.cards[i].is_greater(d2.cards[j])) { result.cards[k] = d2.cards[j]; ++j; } {{endgroup}} {{group}} else { result.cards[k] = d1.cards[i]; ++i; } } {{endgroup}} {{group}} } return result; } {{endgroup}} Now that we've written ``merge``, it's time to write the ``merge_sort`` function. Try writing the non-recursive version of ``merge_sort`` first before writing the recursive version. Follow the comments in ``main`` to test your functions. If done correctly, the program should output a sorted deck of cards. If you get stuck, you can reveal the extra problems at the end for help. .. tb-code:: cpp :name: c192_mergesort_3-support :hidden: :compileargs: ['-Wall', '-Wextra', '-std=c++20'] playing_card::playing_card () { suit = spades; rank = ace; } playing_card::playing_card (card_suit s, card_rank r) { suit = s; rank = r; } void playing_card::print () const { std::vector suits (4); suits[0] = "Clubs"; suits[1] = "Diamonds"; suits[2] = "Hearts"; suits[3] = "Spades"; std::vector ranks (14); ranks[1] = "Ace"; ranks[2] = "2"; ranks[3] = "3"; ranks[4] = "4"; ranks[5] = "5"; ranks[6] = "6"; ranks[7] = "7"; ranks[8] = "8"; ranks[9] = "9"; ranks[10] = "10"; ranks[11] = "Jack"; ranks[12] = "Queen"; ranks[13] = "King"; std::cout << ranks[rank] << " of " << suits[suit] << std::endl; } bool playing_card::is_greater (const playing_card& c2) const { if (suit > c2.suit) return true; if (suit < c2.suit) return false; if (rank > c2.rank) return true; if (rank < c2.rank) return false; return false; } bool playing_card::equals (const playing_card& c2) const { return (rank == c2.rank && suit == c2.suit); } card_deck::card_deck () { std::vector temp (52); cards = temp; std::size_t i = 0; for (int suit = clubs; suit <= spades; ++suit) { for (int rank = ace; rank <= king; ++rank) { cards[i].suit = static_cast(suit); cards[i].rank = static_cast(rank); i++; } } } card_deck::card_deck (std::size_t size) { std::vector temp (size); cards = temp; } void card_deck::print () const { for (std::size_t i = 0; i < cards.size(); i++) { cards[i].print (); } } std::size_t random_int(std::size_t low, std::size_t high) { static std::mt19937 engine(std::random_device{}()); return std::uniform_int_distribution{low, high}(engine); } void card_deck::swap_cards (std::size_t index1, std::size_t index2) { playing_card temp = cards[index1]; cards[index1] = cards[index2]; cards[index2] = temp; } std::size_t card_deck::find_lowest_card (std::size_t index) { std::size_t min = index; for (std::size_t i = index; i < cards.size(); ++i) { if (cards[min].is_greater(cards[i])) { min = i; } } return min; } card_deck card_deck::subdeck (std::ptrdiff_t low, std::ptrdiff_t high) const { if (low < 0 || low > std::ssize(cards) || high < low - 1 || high >= std::ssize(cards)) { throw std::out_of_range("subdeck range"); } card_deck sub (static_cast(high - low + 1)); for (std::size_t i = 0; i= d2.cards.size()) { result.cards[k] = d1.cards[i]; ++i; } else if (i >= d1.cards.size() || d1.cards[i].is_greater(d2.cards[j])) { result.cards[k] = d2.cards[j]; ++j; } else { result.cards[k] = d1.cards[i]; ++i; } } } return result; } .. tb-code:: cpp :name: c192_mergesort_3 :caption: Example c192_mergesort_3 :run-after: c192_mergesort_3-support :compileargs: ['-Wall', '-Wextra', '-std=c++20'] #include #include #include #include #include #include #include enum card_suit { clubs, diamonds, hearts, spades }; enum card_rank { ace=1, two, three, four, five, six, seven, eight, nine, ten, jack, queen, king }; std::size_t random_int(std::size_t low, std::size_t high); struct playing_card { card_rank rank; card_suit suit; playing_card (); playing_card (card_suit s, card_rank r); void print () const; bool is_greater (const playing_card& c2) const; bool equals (const playing_card& c2) const; }; struct card_deck { std::vector cards; card_deck (); card_deck (std::size_t n); void print () const; void swap_cards (std::size_t index1, std::size_t index2); std::size_t find_lowest_card (std::size_t index); void shuffle_deck (); void sort_deck (); card_deck subdeck (std::ptrdiff_t low, std::ptrdiff_t high) const; card_deck merge_sort () const; card_deck merge_sort (card_deck deck) const; }; std::ptrdiff_t find_bisect (card_deck subdeck, playing_card card); card_deck merge (const card_deck& d1, const card_deck& d2); card_deck card_deck::merge_sort () const { // This version of ``merge_sort`` is the non-recursive version. // Follow the pseudocode above delete the existing code, // and write your implementation here. card_deck deck(0); return deck; } card_deck card_deck::merge_sort (card_deck deck) const { // This version of ``merge_sort`` is the recursive version. // Follow the pseudocode above delete the existing code, // and write your implementation here. card_deck deck1(0); return deck; } int main() { card_deck deck1; deck1.shuffle_deck(); card_deck sorted1 = deck1.merge_sort(); sorted1.print(); // Once you get the above code to work, comment it // out and uncomment the code below to test the // recursive version of ``merge_sort``. /* card_deck deck2; deck2.shuffle_deck(); card_deck sorted2 = deck2.merge_sort(deck2); sorted2.print(); */ } .. tb-reveal:: merge_sort Help :name: c192_mergesort_reveal_2 .. tb-parsons:: :name: c192_mergesort_help_2 Let's write the code for the merge_sort function. merge_sort should be a card_deck member function that returns a sorted deck. .. code-block:: c++ {{group}} card_deck card_deck::merge_sort () const { {{endgroup}} {{distractor}} {{group}} card_deck merge_sort () { {{endgroup}} {{group}} std::ptrdiff_t mid = std::ssize(cards) / 2; {{endgroup}} {{group}} card_deck d1 = subdeck(0, mid - 1); card_deck d2 = subdeck(mid, std::ssize(cards) - 1); {{endgroup}} {{group}} d1.sort_deck(); d2.sort_deck(); {{endgroup}} {{group}} return merge(d1, d2); } {{endgroup}} .. tb-reveal:: merge_sort Recursion Help :name: c192_mergesort_reveal_3 .. tb-parsons:: :name: c192_mergesort_help_3 Let's take it one step further and rewrite ``merge_sort`` as a recursive function. .. code-block:: c++ {{group}} card_deck card_deck::merge_sort (card_deck deck) const { {{endgroup}} {{group}} if (deck.cards.size() == 0 || deck.cards.size() == 1) { return deck; } {{endgroup}} {{group}} std::ptrdiff_t mid = std::ssize(deck.cards) / 2; {{endgroup}} {{group}} card_deck d1 = subdeck(0, mid - 1); card_deck d2 = subdeck(mid, std::ssize(deck.cards) - 1); {{endgroup}} {{group}} card_deck merged1 = d1.merge_sort(d1); card_deck merged2 = d2.merge_sort(d2); {{endgroup}} {{group}} return merge(merged1, merged2); } {{endgroup}}