Recently, I was solving this problem : 2219C - Coloring a Red Black Tree While writing the solution, I encountered a runtime error with my submission 377542151.
After trying to find out the bug for 30 mins I finally got the error. I have written a decent amount of C++ but this is the first time I have encountered a runtime error caused by std::sort
The exact problem lies in this part of the code :
sort (tree[u].adj.begin(), tree[u].adj.end(), [&tree](int a, int b) {
return ((tree[a].exp0 - tree[a].exp1) <= (tree[b].exp0 - tree[b].exp1));
});
while it looks pretty normal the bug is caused by the use of <= in std::sort while investigating I found out that there are some properties that must be satisfied while using std::sort
- The comparator must obey strict weak ordering i.e
compare(A, A)must return false - If
compare(A, B)returns true,compare(B, A)must return false. - If
compare(A, B)returns true,compare(B, C)returns true, thencompare(A, C)must also be true.
While the last point seems obvious, the other 2 are important findings.
How does it cause the segfault?
Quicksort partitions the array using two pointers moving toward each other. The pointers stop moving when they hit an element equal to the pivot, allowing the algorithm to swap them. This happens because a valid < comparator returns false when comparing two equal elements, acting as a brake.
By using <=, my comparator returned true when it hit elements equal to the pivot. The pointer never hit the brake.
Boom—Segmentation Fault.
Don't downvote bcz it's a common thing to know as I DID NOT KNOW ABOUT IT ! ( T_T )








https://codeforces.me/blog/entry/70237
As I said, I was not aware of it. But thanks for the link
In other words, the binary operation (comparator) must be irreflexive, asymmetric, and transitive. (Technically it also needs to have transitivity of incomparability/equivalency.)
I'd recommend std::ranges::sort(), available in C++20 and later.
ranges::sort()lets you pass a vector (actually a random-access range) instead of a pair of iterators. Also it supports projection; projection applies a transformation on each value and the sorter uses the transformed values instead of the raw values.With these, your code would become:
(The second argument is the comparator, and the default comparator is good in this case.)
I don't recommend it. It's harder to read, requires extra skill to learn and doesn't fix anything that's broken in competitions except typing out small chunks of code faster once you're really used to typing out those particular chunks. A pair of random access iterators already describes a random access range and that range be nicely customised. Stacking multiple operations into one line makes it less obvious what's happening and harder to debug. Sorting pairs
(compared value, id)is often useful. Just because there's a newer way to do something doesn't mean it's always a better way and has to be applied across all code from now on.I mean, if the latter 2 properties are not true for your sort comparator you should not use it.
I mean the 2nd property is a generalization of the 1st one so ya. And I agree, 3rd point is pretty obvious
Nvm, i misread the comment.
There's actually one more condition:
Define
equiv(A, B)as!compare(A, B) && !compare(B, A). Then, ifequiv(A, B) && equiv(B, C)is true,equiv(A, C)should also be true.Thanks for pointing out
I don't know much about C++ but I've had the same problem long ago. Surely, the main lesson you should take out of this situation is that you sould always make sure that your comparator satisfies the conditions you mentioned. However, I also noticed the following interesting property: in situations like this when you get runtime error because of std::sort, replacing it with std::stable_sort gets rid of RE. I don't know if it always works but it worked every time I encountered this problem. This is definitely not a good idea to do this! Please don't! And I don't know why it works. Please see it as a fun fact, not a piece of advice. Here is your code with std::sort replaced with std::stable_sort getting AC: 377751788
I researched a bit into it.while std::sort is mainly built on quicksort, std::stable_sort primarily is built on mergesort. The bug that causes runtime error is very specific to quicksort in a specific step. Moreover, stable_sort ensures that the order of similar elements remain same as the order in the original array whereas std::sort does not. Also, as stable_sort uses mergesort, it requires O(n) memory as well. That's more or less the difference between both