From b1028b7016fa944275a02c627e9860f824580e82 Mon Sep 17 00:00:00 2001 From: Wenguang Wang Date: Sat, 9 Oct 2021 17:33:11 -0700 Subject: [PATCH] A merge sort of list with O(1) memory overhead This change solves the sort_list problem without recursion, which reduces its memory usage from O(log N) to O(1). It merges the data in a bottom up fashion, by building sorted runs of length 1, 2, 4, 8, etc. The sorted runs are put onto two lists alternatively so that it makes the next iteration of sorting easier. It also runs about 40% faster than the previous solution based on the average run time printed by "make sort_list". --- epi_judge_cpp_solutions/sort_list.cc | 109 +++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 16 deletions(-) diff --git a/epi_judge_cpp_solutions/sort_list.cc b/epi_judge_cpp_solutions/sort_list.cc index 5279c6a4c..0d19e8334 100644 --- a/epi_judge_cpp_solutions/sort_list.cc +++ b/epi_judge_cpp_solutions/sort_list.cc @@ -2,27 +2,104 @@ #include "list_node.h" #include "test_framework/generic_test.h" -#define main _main -#include "sorted_lists_merge.cc" -#undef main - -shared_ptr> StableSortList(shared_ptr> L) { - // Base cases: L is empty or a single node, nothing to do. - if (L == nullptr || L->next == nullptr) { - return L; + +typedef shared_ptr> IntNode; + +static inline void Append(IntNode **tailpp, IntNode node) { + **tailpp = node; + *tailpp = &node->next; +} + +static inline void Merge(IntNode *src1p, IntNode *src2p, IntNode **destTailp, size_t len) +{ + size_t n1 = 0, n2 = 0; + IntNode src1 = *src1p, src2 = *src2p, *destTail = *destTailp; + + while (n1 < len || n2 < len) { + if (n1 == len || src1 == nullptr) { + if (n2 == len || src2 == nullptr) { + break; + } + while (n2++ < len && src2) { + Append(&destTail, src2); + src2 = src2->next; + } + } else { + if (n2 == len || src2 == nullptr) { + while (n1++ < len && src1) { + Append(&destTail, src1); + src1 = src1->next; + } + } else { + if (src1->data <= src2->data) { + ++n1; + Append(&destTail, src1); + src1 = src1->next; + } else { + ++n2; + Append(&destTail, src2); + src2 = src2->next; + } + } + } } - // Find the midpoint of L using a slow and a fast pointer. - shared_ptr> pre_slow = nullptr, slow = L, fast = L; - while (fast && fast->next) { - pre_slow = slow; - fast = fast->next->next, slow = slow->next; + *src1p = src1; + *src2p = src2; + *destTail = nullptr; // mark the dest list as terminated + *destTailp = destTail; +} + +IntNode StableSortList(IntNode L) { + size_t curSrcSize = 1; + size_t total = 0, n; + IntNode head = L; + IntNode src1 = nullptr, src2 = nullptr; + IntNode *srcTail1 = &src1, *srcTail2 = &src2; + + // split L into src1 and src2 + while (head) { + Append(&srcTail1, head); + head = head->next; + ++total; + if (head) { + Append(&srcTail2, head); + head = head->next; + ++total; + } } + *srcTail1 = nullptr; + *srcTail2 = nullptr; - if (pre_slow) { - pre_slow->next = nullptr; // Splits the list into two equal-sized lists. + while (curSrcSize < total) { + IntNode dest1 = nullptr, *destTail1 = &dest1; + IntNode dest2 = nullptr, *destTail2 = &dest2; + size_t iter = 0; + + // get sorted items of at most curSrcSize items from src1 & src2 + // and put them to dest1 and dest2 alternately + n = 0; + while (n < total) { + Merge(&src1, &src2, &destTail1, curSrcSize); + n += curSrcSize; + if (n < total) { + Merge(&src1, &src2, &destTail2, curSrcSize); + n += curSrcSize; + } + ++iter; + } + curSrcSize *= 2; + src1 = dest1; + src2 = dest2; + if (iter == 1) { + break; + } } - return MergeTwoSortedLists(StableSortList(L), StableSortList(slow)); + + // now src1 and src2 has one single sorted run, make the final merge + IntNode dest = nullptr, *destTail = &dest; + Merge(&src1, &src2, &destTail, total); + return dest; } int main(int argc, char* argv[]) {