使用 O(m) 空间在 O(n) 时间内对向量<int>(n) 进行排序?
Posted
技术标签:
【中文标题】使用 O(m) 空间在 O(n) 时间内对向量<int>(n) 进行排序?【英文标题】:Sort vector<int>(n) in O(n) time using O(m) space? 【发布时间】:2013-10-30 03:04:40 【问题描述】:我有一个大小为n
的vector<unsigned int> vec
。 vec
中的每个元素都在[0, m]
范围内,没有重复,我要对vec
进行排序。如果允许您使用 O(m) 空间,是否有可能比 O(n log n) 时间做得更好?在平均情况下m
比n
大得多,在最坏情况下m == n
。
理想情况下,我想要 O(n) 的东西。
我觉得有一种桶排序方式可以做到这一点:
unsigned int aux[m];
aux[vec[i]] = i;
以某种方式提取排列并置换vec
。
我不知道怎么做 3。
在我的应用程序中,m
大约为 16k。然而,这种类型在内部循环中,占我运行时的很大一部分。
【问题讨论】:
这个问题可能会更好:programmers.stackexchange.com m比n大多少?m
比n
大多少,比n log n
大多少?因为如果是这样的话,O(n log n)
仍然会比 linear O(m)
快
@DavidRodríguez-dribeas O(m) 空间,而不是时间。 O(m) 时间是一个简单的桶排序。
@Adam:如果你真的是指O(n)
时间,那就做不到了。要查找O(m)
空间中存在哪些元素,您需要的不仅仅是O(n)
【参考方案1】:
基数排序,也不需要值小于 m 的限制。
下面的示例实现是在 int 类型上模板化的。如果您的 m 始终小于 2^15,则应尽可能使用 int16_t 向量(如果值始终为正,则应使用更好的 uint16_t 以避免处理有符号整数的偏移量)。对于 32 位整数,这将只需要两次排序,而不是 4 次。如果你不能改变你的输入类型,你可以特殊情况下代码只执行两次并避免签名偏移。
这个实现是 O(n) 并且使用了 O(n) 额外空间(排序不到位)。
template<typename I>
void radix_sort(I first, I last)
using namespace std;
typedef remove_reference_t<decltype(*first)> int_t;
typedef make_unsigned<int_t>::type uint_t;
const uint_t signedOffset = is_signed<int_t>::value ? uint_t(numeric_limits<int_t>::max()) + uint_t(1) : 0;
auto getDigit = [=](uint_t n, int power) -> size_t return ((n + signedOffset) >> (power * 8)) & 0xff; ;
array<size_t, 256> counts;
vector<int_t> sorted(distance(first, last));
for (int power = 0; power < sizeof(int_t); ++power)
counts.fill(0);
for_each(first, last, [&](int_t i) ++counts[getDigit(i, power)]; );
partial_sum(begin(counts), end(counts), begin(counts));
for_each(reverse_iterator<I>(last), reverse_iterator<I>(first), [&](int_t i)
sorted[--counts[getDigit(i, power)]] = i;
);
copy(begin(sorted), end(sorted), first);
【讨论】:
标准库中有基数排序吗? 不,但是实现起来相对简单,而且您最初的要求实际上是 m 是基数的特殊情况。 最大值为m的基数排序的运行时间为O(n log m)。如果 m 真的比 n 大得多,这实际上并不比 n log n 快,而且可能更慢。 对于 32 位整数和基数 256,log m 为 4。 确实如此,尽管这似乎很奇怪,因为它根本不依赖 m。【参考方案2】:如果您知道 m = O(n2) 的事实,则可以执行 base-n 基数排序来对数组进行排序。这类似于普通的基数排序,但不是有 2 个桶或 10 个桶,而是有 n 个桶,每个可能的基数为 n 位的数字一个。
由于基数b中基数排序的运行时间是O(n logb U),其中U是最大值,在这种情况下我们知道运行时间是O(n logn n2) = O(n)。这比 O(n log n) 渐近地快。它也只需要 O(n) 内存,低于 O(m) 的限制。
希望这会有所帮助!
【讨论】:
n 是可变的。它通常很小,但它是可变的,在最坏的情况下可能高达 m。然而,在几乎所有情况下,它都小了 2-3 个数量级。所以基本上你是说平均有 2-3 次基数排序迭代?【参考方案3】:不,你需要这样做:
unsigned int aux[m + 1];
bzero(aux, sizeof(aux));
foreach x in (vec) aux[x]++;
for(x = 0; x <= m; x++)
for(qty = 0; qty < x; qty++)
output(x);
【讨论】:
不过,这是 O(m) 时间。 我将第一行写为unsigned int aux[m + 1] = 0
并放弃bzero
调用。以上是关于使用 O(m) 空间在 O(n) 时间内对向量<int>(n) 进行排序?的主要内容,如果未能解决你的问题,请参考以下文章
在 O(1) 空间和 O(n) 时间中查找 2 个字符串是不是是字谜