Just do a merge-sort that knows how to deal with duplicates.
Why does this work? Well, at the second pass, at least half of the most common value will be absorbed into a duplicate. At every pass remaining, half the remainder will work. This means that the average copy of the most common value only lasts for 3 passes.
The second most common value starts the cascade at 3, and the average disappears at 4.
And so on.
If you calculate this out, this means that the average element can expect to be needed for 3 passes, half needs another, a quarter needs another, and so on. This turns into 4 passes on average. Which is O(n)
.
Note that we are not particularly using the power of 2 setup. We're just absorbing duplicates. Which means that we'll also perform well in a wide range of situations where relatively few values tend to appear a relatively large number of times. A common situation where that will apply is whenever Zipf's law describes the frequency of the most common elements.
The biggest downside is that handling duplicates requires a lot of extra tracking. But here is a simple version in Python that should do reasonably well.
from collections import deque
def mergesort (things):
values = deque(things)
counts = deque((1 for _ in things))
group_sizes = deque((1 for _ in things))
while 1 < len(group_sizes):
i = 0
group_i = [values.popleft() for _ in range(group_sizes.popleft())]
counts_i = [counts.popleft() for _ in group_i]
j = 0
group_j = [values.popleft() for _ in range(group_sizes.popleft())]
counts_j = [counts.popleft() for _ in group_j]
size = 0
while i < len(group_i) and j < len(group_j):
if group_i[i] < group_j[j]:
values.append(group_i[i])
counts.append(counts_i[i])
i += 1
elif group_j[j] < group_i[i]:
values.append(group_j[j])
counts.append(counts_j[j])
j += 1
else:
values.append(group_i[i])
counts.append(counts_i[i] + counts_j[j])
i += 1
j += 1
size += 1
while i < len(group_i):
values.append(group_i[i])
counts.append(counts_i[i])
i += 1
size += 1
while j < len(group_j):
values.append(group_j[j])
counts.append(counts_j[j])
j += 1
size += 1
group_sizes.append(size)
answer = []
for i in range(len(values)):
for j in range(counts[i]):
answer.append(values[i])
return answer