Segment tree sorting
(If you aren't familiar with what a segment tree is then I would recommend reading https://cp-algorithms.com/data_structures/segment_tree.html )
So I came up with a cool way of sorting an array with a segment tree. I'm not sure if this has been thought of before, but if it hasn't, then it would be cool to share. I did some comparisons against other ways of sorting, and it outperformed the other ways, aside from std::sort. My implementation of the other sorting techniques could probably be improved though. So here is the general idea:
General idea
Each node of the segment tree stores two elements. It stores the minimum value of all nodes beneath it, as well as storing the index of this minimum node. Start by creating the segment tree with these elements in it, then we repeat N times : 1. Add node 1's minimum value to our sorted list. 2. Update the index of that minimum value to INT_MAX, as after adding it to the sorted array, you want to effectively remove it from the tree.
Time complexity: O(n log(n))
Implementation details
We start by constructing the array with this:
void construct(vector<int> *v) {
for(int i = 0; i < n; i++) {
seg[i + n] = {(*v)[i], i};
}
for(int i = n- 1; i> 0; i--) {
if(seg[i * 2 + 1].first < seg[i * 2].first) {
seg[i] = seg[i * 2 + 1];
}else {
seg[i] = seg[i * 2];
}
}
}
}
Then for i in range(n), we find the minimum value, seg[1].first, and then set seg[1].second to INT_MAX with:
void update(int index) {
seg[index + n].first = INT_MAX;
for(int i = (index + n)/2; i> 0; i/=2) {
pair<int, int> a = seg[i *2 + 1];
pair<int, int> b = seg[i * 2];
if(a.first < b.first) {
seg[i] = a;
}else {
seg[i] = b;
}
}
}
}
How did it compare to other sorting algorithms?
When I ran it on my computer for an array of size 10,000,000:
- Standard sort took 2.09876 seconds
- Segment tree sort took 3.10657 seconds
- Heap sort took 4.50604 seconds
- Merge sort took 4.61425 seconds
- Quick sort took 5.76152 seconds
Array size of 1,000,000:
- Standard sort took 0.177153 seconds
- Segment tree sort took 0.192566 seconds
- Heap sort took 0.372507 seconds
- Merge sort took 0.407037 seconds
- Quick sort took 0.529488 seconds
Array of size 100,000:
- Standard sort took 0.0153477 seconds
- Segment tree sort took 0.0158673 seconds
- Heap sort took 0.0318967 seconds
- Merge sort took 0.0390352 seconds
- Quick sort took 0.0490728 seconds
Array of size 10,000:
- Standard sort took 0.00122976 seconds
- Segment tree sort took 0.00126829 seconds
- Heap sort took 0.00270543 seconds
- Merge sort took 0.00367626 seconds
- Quick sort took 0.00454401 seconds
Even though my merge and quick sort could probably be optimized, the segment tree sort did very well, being comparable to std::sort.
Github link: https://github.com/quollcucumber/segtreesort









Auto comment: topic has been updated by quollcucumber1 (previous revision, new revision, compare).
How is your heap sort running faster than merge sort and merge sort running than quick sort?
My merge sort is probably not as efficient as it could be. The same is true for quick sort, but for an even larger extent.
instead of making two arrays $$$a$$$ and $$$b$$$ for merge sort, make only one:
i tested it on my computer and run it on custom test in codeforces, it will run faster on high $$$n$$$ (i tested on $$$n = 10^7, n = 7×10^6, n = 5×10^6$$$)
Cool! How long did the sort take compared so segment tree sort? Can you post the times? Thanks you.
only ran Segment tree sort and Merge sort here is the result ($$$n = 10^7$$$ with no O3):
Segment tree sort took 3.74028 seconds
Merge sort took 2.46159 seconds
I don't think any of the other implementations are necessarily optimized (e.g. heapsort needing O(n log n) time to construct the heap) but for a small demonstration it doesn't really matter. You could check out the similar Tournament Tree
Update: When I compiled this again, but with O3 optimization, the segment tree performed significantly worse compared to the other ways of sorting.
This was the results when compiled for 10,000,000 elements with O3:
Which is very different from the results we saw before. So it might not be the most useful method of sorting, but it is a cool idea in theory.
cool, but is this not just selection sort sped up to nlogn by using segtree rmq?
Yes, that is one way of looking at it.
damn you must really love segtrees
If you made a spotify playlist titled "segtrees are the best," you must like them also right.
Try using bitwise shift operations for multiplication/division with 2 instead of normal operators. (ie. >> 1 for /2, << 1 for *2). These do not affect performance if used only few time, but at complexity of O(n logn) it will definately make an impact for larger n. Reason is that * and / operators (specially division) uses more CPU instructions at runtime than that of bitwise shift operations. Give it a try.
Hi, Thanks for this recommendation, I implemented it, and these were my times (without pragma optimizations):
Standard sort took 2.06599 seconds
Segment tree sort took 3.08233 seconds
Heap sort took 4.79794 seconds
Merge sort took 4.5813 seconds
Quick sort took 5.77187 seconds
As you can see, it saves a small amount of time, but not a huge difference.
When compiled with optimizations, it barely changes because I believe the compiler already optimizes *2 into <<1.
Thanks for your recommendation though, and more are welcome anytime.
Oh okay, I thought it would make an impact. Glad you tried it.
Maybe it is because the updates are simple comparisons which the CPU can speculatively execute well. The memory access is also friendly as it is continuous. Recursion-based algorithms are worse here.
Yeah, recursive algorithms are usually quite a bit slower. I usually use recursive segment tree, but I did everything iteratively for speed for this case.