C++ TimSort: An Efficient and Stable Modern Sorting Library

In C++ programming, sorting is a fundamental and critical operation. Although the standard library provides <span>std::sort</span> (which is typically an efficient Introsort), it is not stable and does not optimally handle partially ordered data.<span>cpp-TimSort</span> library fills this gap by bringing the well-tested TimSort algorithm from languages like Python and Java to C++, providing developers with an efficient, stable, and adaptive sorting solution.

1. Core Principle: An Intelligent Hybrid Sorting Algorithm

TimSort is a complex hybrid sorting algorithm designed by Tim Peters in 2002 for Python. It is not a brand new sorting method but cleverly combines the advantages of insertion sort and merge sort, and is deeply optimized for real-world partially ordered datasets.

The workflow mainly consists of three steps:

  1. Data Chunking and Run Creation The algorithm traverses the array to be sorted, looking for naturally occurring increasing or strictly decreasing contiguous subsequences (called “runs”). If a run is decreasing, it is immediately reversed to an increasing run to ensure stability. If the length of a run is too small (less than a preset value<span>minrun</span>, typically 32 or 64), it is extended to <span>minrun</span> length using insertion sort. This step ensures that each run is ordered and of a certain size.

  2. Run Merging After all runs are identified and extended, they are pushed onto a stack. The core wisdom of TimSort lies in its merging strategy. The stack always maintains an invariant: the size of each run from the top to the bottom of the stack is non-decreasing (or follows a Fibonacci relationship). When pushing a new run would violate this invariant, the algorithm triggers a merge operation, merging the smaller runs at the top of the stack into a larger run until the invariant is restored.

  3. Intelligent Merging and Galloping Mode When merging two ordered runs, TimSort employs the “Galloping Mode.” Regular merging compares elements one by one. However, when the algorithm finds that multiple consecutive elements in one run are smaller than the current element of another run, it enters “galloping mode,” performing exponential searches (e.g., skipping 1, 3, 7, 15… elements) to quickly locate the position where a large segment of elements should be inserted. This greatly reduces the number of comparisons for highly ordered data.

2. Main Advantages

  • Stability: TimSort is a stable sort, meaning the relative order of equal elements remains unchanged after sorting. This is crucial when handling multi-key sorting (e.g., sorting by name first, then by score while maintaining the order of names for those with the same score).
  • Efficiency: Its time complexity is **O(n log n)** in the worst and average cases. For arrays that are already partially ordered or contain sorted subsequences, its performance approaches **O(n)**, far exceeding traditional sorting algorithms.
  • Adaptability: The algorithm can sense the structural characteristics of the input data and adjust its strategy accordingly (such as utilizing existing ordered runs and galloping mode), achieving excellent performance in practical applications.

3. C++ Code Example

<span>cpp-TimSort</span> library provides a set of STL-like interfaces that are very easy to use.

1. Installation and Integration Typically, you only need to include the header file <span>gfx::timsort.hpp</span> in your project. You can also install it via package managers (like vcpkg).

# For example, using vcpkg
vcpkg install cpp-timsort

2. Basic Usage: Sorting a Vector

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;gfx/timsort.hpp&gt; // Include header file

int main() {
    std::vector&lt;int&gt; vec = {5, 7, 2, 1, 8, 3, 9, 4, 6, 0};

    // Use gfx::timsort to sort
    // Usage is exactly the same as std::sort
    gfx::timsort(vec.begin(), vec.end());

    std::cout &lt;&lt; "Sorted: ";
    for (int num : vec) {
        std::cout &lt;&lt; num &lt;&lt; " ";
    }
    std::cout &lt;&lt; std::endl;

    return 0;
}

3. Advanced Usage: Sorting Custom Objects and Demonstrating Stability This is where its value is most evident.

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;string&gt;
#include &lt;gfx/timsort.hpp&gt;

// Define a custom Employee class
struct Employee {
    std::string department;
    int salary;
    
    // Overload &lt;&lt; operator for output
    friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, const Employee&amp; emp) {
        os &lt;&lt; "(" &lt;&lt; emp.department &lt;&lt; ", " &lt;&lt; emp.salary &lt;&lt; ")";
        return os;
    }
};

// Comparison function: sort by department first, then by salary
bool compareByDepartmentAndSalary(const Employee&amp; a, const Employee&amp; b) {
    if (a.department == b.department) {
        return a.salary &lt; b.salary;
    }
    return a.department &lt; b.department;
}

int main() {
    // Create an initial list, departments are roughly ordered
    std::vector&lt;Employee&gt; employees = {
        {"Engineering", 80000},
        {"Engineering", 75000}, // Same department, different salary
        {"HR", 60000},
        {"HR", 65000},
        {"Engineering", 90000}, // This element should stably be at the end of the same department
        {"Marketing", 70000},
        {"HR", 55000} // This element should stably be at the beginning of the same department
    };

    std::cout &lt;&lt; "Before sorting:" &lt;&lt; std::endl;
    for (const auto&amp; emp : employees) {
        std::cout &lt;&lt; emp &lt;&lt; std::endl;
    }

    // Use stable timsort to sort
    gfx::timsort(employees.begin(), employees.end(), compareByDepartmentAndSalary);

    std::cout &lt;&lt; "\nAfter sorting (stable TimSort):" &lt;&lt; std::endl;
    for (const auto&amp; emp : employees) {
        std::cout &lt;&lt; emp &lt;&lt; std::endl;
    }

    // Key observation: note that ("Engineering", 90000) is still at the end of the Engineering group,
    // ("HR", 55000) is still at the beginning of the HR group, their original relative order is preserved.
    // If using unstable std::sort, the salary order within equal departments may be disrupted.

    return 0;
}

4. Sorting Arrays

#include &lt;iostream&gt;
#include &lt;gfx/timsort.hpp&gt;

int main() {
    int arr[] = {10, 2, 8, 1, 5, 9, 3, 7, 4, 6};
    int size = sizeof(arr) / sizeof(arr[0]);

    // Sort C-style array
    gfx::timsort(arr, arr + size);

    std::cout &lt;&lt; "Sorted array: ";
    for (int i = 0; i &lt; size; ++i) {
        std::cout &lt;&lt; arr[i] &lt;&lt; " ";
    }
    std::cout &lt;&lt; std::endl;

    return 0;
}

4. When to Choose TimSort?

  • When stable sorting is required: This is the primary reason for choosing it.
  • When handling partially ordered data: Such as log files, lists after inserting by a certain key, its performance advantage is very evident.
  • When performance is critically required and data characteristics are unknown: TimSort performs very robustly on real-world datasets, rarely encountering the worst-case O(n log n) scenario of Introsort.

Conclusion

<span>cpp-TimSort</span> library is a high-quality C++ third-party library that perfectly encapsulates the industrial-grade TimSort algorithm in an STL style. By providing stable, adaptive, and efficient sorting capabilities, it is a powerful complement to the standard library <span>std::sort</span> in many scenarios. For developers needing to handle complex data types or known partially ordered data, incorporating it into their toolbox is undoubtedly a wise choice.

Leave a Comment