为了理解计数排序为什么是稳定的,你需要明白计数排序不仅可以用于对整数列表进行排序,还可以用于对键为整数的元素列表进行排序,这些元素将按照它们的键进行排序,并与每个元素相关联的附加信息一起排序。
一个使用附加信息对元素进行排序的计数排序示例将帮助你理解。例如,我们想按价格对三只股票进行排序:
[(GOOG 3), (CSCO 1), (MSFT 1)]
这里的股票价格是整数键,而股票名称则是它们相关的信息。
排序的预期输出应为:
[(CSCO 1), (MSFT 1), (GOOG 3)]
(containing both stock price and its name,
and the CSCO stock should appear before MSFT so that it is a stable sort)
为了排序这个数组(假设股票价格只能是0到3),将计算一个计数数组:
counts array: [0, 2, 0, 1] (price "1" appear twice, and price "3" appear once)
如果你只是在排序整数数组,那么你可以遍历计数数组并输出两次“1”和一次“3”,然后就完成了,此后整个计数数组将变成全零数组。
但是这里我们也想在排序输出中包含股票名称。我们如何获取这个额外的信息(似乎计数数组已经丢弃了这个信息)?嗯,
相关信息存储在原始未排序的数组中。在未排序的数组[(GOOG 3),(CSCO 1),(MSFT 1)]中,我们有股票名称和其价格。如果我们知道(GOOG 3)在最终排序数组中应该处于哪个位置,我们就可以将这个元素复制到排序数组中的相应位置。
为了获取排序数组中每个元素的最终位置,与排序整数数组不同,你不能直接使用计数数组来输出排序后的元素。相反,计数排序还有一个额外的步骤,从计数数组中计算累加和数组:
counts array: [0, 2, 2, 3] (i from 0 to 3: counts[i] = counts[i] + counts[i - 1])
这个累积和数组告诉我们每个值在最终排序数组中的位置。例如,
counts [1] == 2
意味着当前值为
1
的项应该放置在排序数组的第
2
个插槽中。直观地说,因为
counts [i]
是从左边开始的累计总和,它显示了前面有多少比
ith
值小的项,这告诉您
ith
值的位置应该在哪里。
如果一个 $1 的股票首次出现,则应将其输出到排序数组的第二个位置;如果一个 $3 的股票首次出现,则应将其输出到排序数组的第三个位置。如果出现一个 $1 股票并且它的元素被复制到排序数组中,我们将减少它在计数数组中的数量。
counts array: [0, 1, 2, 3]
(so that the second appearance of $1 price stock's position will be 1)
因此,我们可以从后向前迭代未排序的数组(这很重要以确保稳定性),根据计数数组检查其在排序数组中的位置,并将其复制到排序数组中。
sorted array: [null, null, null]
counts array: [0, 2, 2, 3]
iterate stocks in unsorted stocks from backwards
1. the last stock (MSFT 1)
sorted array: [null, (MSFT 1), null] (copy to the second position because counts[1] == 2)
counts array: [0, 1, 2, 3] (decrease counts[1] by 1)
2. the middle stock (CSCO 1)
sorted array: [(CSCO 1), (MSFT 1), null] (copy to the first position because counts[1] == 1 now)
counts array: [0, 0, 2, 3] (decrease counts[1] by 1)
3. the first stock (GOOG 3)
sorted array: [(CSCO 1), (MSFT 1), (GOOG 3)] (copy to the third position because counts[3] == 3)
counts array: [0, 0, 2, 2] (decrease counts[3] by 1)
如您所见,在数组排序后,计数数组(即[0, 0, 2, 2])并没有像对整数数组进行排序那样成为全零数组。计数数组不用于告诉我们整数在未排序数组中出现的次数,而是用于告诉我们元素在最终排序数组中应该处于哪个位置。由于每次输出一个元素时我们都会减少计数,因此我们实际上是使具有相同键的元素下一次出现的最终位置更小。这就是为什么我们需要从后向前迭代未排序数组以确保其稳定性。
结论:
由于每个元素不仅包含一个整数作为键,还包含一些附加信息,即使它们的键相同,您也可以使用附加信息来判断每个元素是否不同,因此您将能够确定它是否是稳定的排序算法(如果适当实现,则是稳定的排序算法)。
参考资料:
一些解释计数排序及其稳定性的好材料: