假设我的输入为(a
,b
和c
以区分相等的键)
1 6a 8 3 6b 0 6c 4
我的计算排序将保存为(丢弃a
,b
和c
信息!!)
0(1) 1(1) 3(1) 4(1) 6(3) 8(1)
会给我结果
0 1 3 4 6 6 6 8
那么,这种稳定的排序如何? 我不确定它是如何“用相同的键保持记录的相对顺序。”
请解释。
答案 0 :(得分:37)
要理解为什么计数排序是稳定的,您需要了解计数排序不仅可以用于排序整数列表,还可以用于排序键为整数的元素列表,以及这些元素将按键分类,同时提供与每个键相关的其他信息。
使用附加信息对元素进行排序的计数排序示例将帮助您理解这一点。例如,我们想按价格对三种股票进行排序:
[(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”一次完成,整个计数数组将在此之后成为一个全零数组。
但是,我们希望在排序输出中也有股票名称。我们怎样才能获得这些额外的信息(似乎count数组已经丢弃了这条信息)?那么,相关信息存储在原始未排序数组中。在未排序的数组[(GOOG 3),(CSCO 1),(MSFT 1)]中,我们有股票名称及其价格。如果我们知道最终排序数组中应该位于哪个位置(GOOG 3),我们可以将此元素复制到排序数组中的排序位置。
要获取已排序数组中每个元素的最终位置,与排序整数数组不同,不要直接使用counts数组来输出已排序的元素。相反,计数排序还有一个额外的步骤,它计算来自计数数组的累积和数组:
counts array: [0, 2, 2, 3] (i from 0 to 3: counts[i] = counts[i] + counts[i - 1])
此累积和数组告诉我们当前最终排序数组中每个值的位置。例如,counts[1]==2
表示当前具有值1
的项应放在已排序数组的2nd
位中。直观地说,因为counts[i]
是左起的累积和,它显示了ith
值之前有多少个较小的项,它告诉您ith
值的位置应该在哪里。
如果第一次出现1美元的价格股票,它应该输出到排序数组的第二个位置,如果第一次出现3美元的价格股票,它应该输出到排序数组的第三个位置。如果出现1美元的股票并且其元素被复制到已排序的数组,我们将减少其在count数组中的计数。
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)
正如您所看到的,在数组排序后,counts数组(即[0,0,2,2])不会像整数数组那样成为全零数组。计数数组不用于表示整数出现在未排序数组中的次数,而是用于指示元素在最终排序数组中的位置。而且由于我们每次输出一个元素时都会减少计数,因此我们基本上会使具有相同键的下一个外观最终位置的元素变小。这就是为什么我们需要从后向迭代未排序的数组以确保其稳定性。
<强>结论:强>
由于每个元素不仅包含一个整数作为键,而且还包含一些附加信息,即使它们的键相同,您也可以通过使用附加信息来判断每个元素是否不同,这样您就可以判断它是否相同是一种稳定的排序算法(是的,如果适当实施,它是一种稳定的排序算法)。
<强>参考文献:强>
一些很好的材料解释计数排序及其稳定性:
答案 1 :(得分:13)
简单,真实:它不是每个“桶”的简单计数器,而是链表。
即代替
0(1) 1(1) 3(1) 4(1) 6(3) 8(1)
你得到了
0(.) 1(.) 3(.) 4(.) 6(a,b,c) 8(.)
(这里我使用.
表示存储桶中的某个项目。)
然后将它们转储回一个排序列表:
0 1 3 4 6a 6b 6c 8
也就是说,当您找到一个包含密钥x
的项目时,知道它可能有其他信息可以将其与具有相同密钥的其他项目区分开来,您不只是增加一个桶{{1 (这将丢弃所有这些额外信息)。
相反,您为每个存储桶都有一个链接列表(或具有恒定时间摊销附加值的类似排序数据结构),并在扫描输入左侧时将该项目附加到存储桶x
的列表末尾向右。
因此,x
计数器不是O(k)
空间,而是k
最初为空的列表,在“计数”结束时,其长度总和为O(k)
算法的一部分。计数排序的这种变体仍然像以前一样n
。
答案 2 :(得分:7)
您的解决方案不是完整的计数排序,并会丢弃相关的值。
这是完整的计数排序算法。
计算直方图后:
0(1) 1(1) 3(1) 4(1) 6(3) 8(1)
你必须计算累计总和 - 每个单元格将包含多少元素小于或等于该值:
0(1) 1(2) 3(3) 4(4) 6(7) 8(8)
现在您从原始列表的 end 开始,然后向后。
最后一个元素是4
。有4个元素小于或等于4.所以4
将位于第4位。您递减4
的计数器。
0(1) 1(2) 3(3) 4(3) 6(7) 8(8)
下一个元素是6c
。有7个元素小于或等于6.所以6c
将进入第7个位置。再次,您递减6
的计数器。
0(1) 1(2) 3(3) 4(3) 6(6) 8(8)
^ next 6 will go now to 6th position
如您所见,此算法是一种稳定的排序。将保留具有相同密钥的元素的顺序。
答案 3 :(得分:4)
如果您的三个“6”值是可区分的,那么您的计数排序是错误的(它会丢弃有关值的信息,这是真正的排序不能执行的操作,因为真正的排序只会对值重新排序)。
如果您的三个“6”值无法区分,那么排序是稳定的,因为输入中有三个无法区分的“6”,输出中有三个。谈论他们是否已经“重新订购”是毫无意义的:他们是完全相同的。
非稳定性的概念仅适用于值具有不参与订单的某些相关信息的情况。例如,如果你将指针排序到那些整数,那么你可以通过查看它们的不同地址来“区分”三个6。那么询问任何特定种类是否稳定是有意义的。基于整数值的计数排序则不会对指针进行排序。基于指针值的计数排序不会按整数值排序,而是按地址排序。