The function:

double sum1(std::vector& v)
    if (v.empty()) {
        return 0.0;
    for(size_t i = 0; i < v.size() - 1; ++i) {
        std::sort(v.begin()+i, v.end());
        v[i+1] += v[i];
    return v.back();

There are n-1 sorts each one shorter by 1, so time taken by the function for n elements will be:

T(n) = C*n + n*log(n) + (n-1)*log(n-1) + ... + 2*log(2) + 1*log(1)

How to solve this polynomial? Or I missed something?

  • $\begingroup$ I'm voting to close this question as off-topic because the answer is dependent on the exact behaviour of C++'s std::sort routine. $\endgroup$ – David Richerby Aug 15 '16 at 22:44
  • $\begingroup$ Actually, it would be more helpful of me to have pointed to our reference question on solving recurrences. $\endgroup$ – David Richerby Aug 15 '16 at 22:46
  • $\begingroup$ I think it can't be represented as recurrence, because sum1() is not recursive. $\endgroup$ – midenok Aug 16 '16 at 5:16
  • $\begingroup$ $T(0)=0$, $T(n)=n\log n + T(n-1)$. $\endgroup$ – David Richerby Aug 16 '16 at 7:36

How to solve the polynomial: Clearly it's less than n * n * log (n) = O (n^2 log n). And clearly it is more than (n/2) * (n/2) * log (n/2) = O (n^2 log n). So it's n^2 log n.

However, your assumption that the sort is O (n log n) is not justified. Each time except the first you are sorting an array that is already sorted with the exception of the first array element. Many sort implementations will sort that kind of array in O (n), so the total time may be just O (n^2).

| cite | improve this answer | |
  • $\begingroup$ "And clearly it is more than (n/2) * (n/2) * log (n/2) = O (n^2 log n)" -- so, it's Theta then? $\endgroup$ – midenok Aug 16 '16 at 2:44
  • 1
    $\begingroup$ And why it's "clearly"? I can't see that n*log(n) + (n-1)*log(n-1) + ... + 2*log(2) + 1*log(1) > (n/2) * (n/2) * log(n/2) $\endgroup$ – midenok Aug 16 '16 at 5:26
  • 1
    $\begingroup$ @midenok Take the first n/2 of the terms in the sum: they are all bigger than n/2 * log (n/2), so the sum is at least n/2 * (n/2*log(n/2)) $\endgroup$ – adrianN Aug 16 '16 at 10:45

Standard c++ std::sort implements quicksort algorithm, switching to mergesort if recursion is too deep.

Quicksort algorithm takes $\Omega(n*log(n))$ time in the best case, so you could assume repeating it $O(n)$ times will give a $O(n^2*log(n)$ time.

The thing which just might break our lower bound is the structure of your algorithm. If quicksort takes the last/first element (new element added to sorting on each iteration) it will break into sorting the sorted array on 1st recursive subcall, invoking its worst case performance of $O(n^2)$. This will bring your algorithms complexity up to $O(n^3)$.

Note that this fault is valid only if quicksort does always use first/last pivot. If randomization is doing the work of selecting a pivot, the runtime in the average and best case is $O(n^2*log(n))$, and $O(n^3)$ in worst case (rare).

Let us assume that your std::sort uses insertion sort. Insertion sort performs best on already sorted algorithms, and just needs to insert the last element added into its place in $O(n)$. This would make the whole algorithm run in $O(n^2)$.

This example is a perfect explanation of why it is imporant to learn underlaying aspects of sorting algorithms before relying on particular boxed function.

| cite | improve this answer | |
  • $\begingroup$ One variant of a sorting algorithm which anticipates the array already being mostly sorting finds an initial ordered sequence (increasing or decreasing) at the start and a final order sequence at the end of the array, and use these sequences if they are long enough (longer than n / log n). $\endgroup$ – gnasher729 Aug 15 '16 at 21:50
  • $\begingroup$ @gnasher729 Do you mind explaining it in more detail or posting some links? $\endgroup$ – Petar Mihalj Aug 15 '16 at 22:55
  • $\begingroup$ Somewhere on Apple's WWDC sessions. There are about 100 of them every year and it was some time in the last three years.... But the principle is simple: You have n array elements. You determine that the first m are in ascending order, or in descending order. You determine the last k are in ascending order, or in descending order. If m and/or k are both large (much bigger than n / log n), then you use Quicksort to sort the elements in the middle, from m to n-k; obviously a lot faster because it is fewer elements. Then you have one range ascending or descending, one range ascending, and a... $\endgroup$ – gnasher729 Aug 16 '16 at 20:21
  • $\begingroup$ ...third range ascending or descending, and you merge the ranges. Obviously if m or k or both are small you ignore them. This works really well if you sort a sorted array or an array sorted in reverse order (which happens a lot in practice). Or if you concatenate two sorted arrays and sort them. Or change a single array element and then sort. It helps if you concatenate a sorted and an unsorted array and sort the result. Or if you modify few random elements and then sort. $\endgroup$ – gnasher729 Aug 16 '16 at 20:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.