算法学习笔记(一):桶式排序及其稳定性

来源:互联网 发布:数据帧 格式 编辑:程序博客网 时间:2024/06/05 06:57
相比选择排序、交换排序、插入排序,桶式排序是一种令人拍案称奇的巧妙排序。然而巧妙的方法总是有一定的局限性,所以桶式排序的局限是:

1、待排序数列的所有值在一个可枚举的范围之内;

2、待排序数列的可枚举范围不应太大。(如果值的分布还紧凑,便是极好的了。)

举个简单的例子,有如下待排序数列:

5    1   2     4    2

这个数列在1、2、3、4、5这个可枚举范围之内,且范围不大。所以很适合来个桶式排序。

有没有发现1、2、3、4、5这样的枚举多么像数组的索引那么井然有序啊?是的,这里需要引进数组了。


第一步:造桶。对这个可枚举范围建立一个对应的数组:

int[] buckets = new int[max - min + 1]; //max 为待排序数列的最大值,min 为待排序数列的最小值

这样各桶的初始状态为:

buckets[0] = 0

buckets[1] = 0

buckets[2] = 0

buckets[3] = 0

buckets[4] = 0

这个“桶”的编号(即数组的索引)对应了待排序数列的值,而每个桶要盛的东西将是对应值出现的次数。------巧妙之一


第二步:装桶。统计每个值出现的次数,并装到相应的桶中。

//dataSet为待排序数组//以上面的例子:int[] dataSet = {5, 1, 2, 4, 2};for(int i = 0; i < dataSet.length; i++) {     buckets[dataSet[i]-min]++; //数组的索引从0开始,而枚举范围从1开始,通过表达式 dataSet[i]-min  就可以得到统一。记住,buckets数组是用索引来记录待排序数列的各值。}

这样各桶的状态发生了变化:

buckets[0] = 1   表示“1”出现1次

buckets[1] = 2   表示“2”出现2次

buckets[2] = 0   表示“3”出现0次

buckets[3] = 1   表示“4”出现1次

buckets[4] = 1   表示“5”出现1次


第三步:重装。这一步很重要。如何重装呢?将前一个桶所盛的数目(非桶的编号)加到后一个桶所盛的数目上。------巧妙之二

先贴代码,再解释。

for(int i = 1; i < buckets.length; i++) {     buckets[i] += buckets[i-1]; // i对应了值“i+min”,表示值“i+min” 前面有了buckets[i-1]个数,这些数是小于自己本身的。也就定位了值”i+min“自身的位置}
同样各桶的状态发生了变化:
buckets[0] = 1    表示“1”在有序数列中是第1个数,毫无疑问。
buckets[1] = 3    表示“2”在有序数列中是第3个数。那么有序数列中的第2个数呢,还是值“2” 
buckets[2] = 3    表示“3”没出现
buckets[3] = 4    表示“4”在有序数列中是第4个数,因为前面已经有了三个小于“4”的数
buckets[4] = 5    表示“5”在有序数列中是第5个数,因为前面已经有了四个小于“5”的数
至此待排序数列的各值已有了顺序。


第四步:安排。既然知道了各值对应的在有序数组中的位置,那么我们需要做的就是建一个新数组,存放有序数列。这里有三个版本:
版本一:

int[] newDataSet = new int[dataSet.length];for(int i = 0; i < dataSet.length; i++) {     newDataSet[buckets[(dataSet[i]-min)] - 1] = dataSet[i]; //注意与下面两个版本的区别}

版本二:
int[] newDataSet = new int[dataSet.length];for(int i = 0; i < dataSet.length; i++) {                   //注意与第三个版本的区别     newDataSet[--buckets[(dataSet[i]-min)]] = dataSet[i];}

版本三:
int[] newDataSet = new int[dataSet.length];for(int i = dataSet.length - 1; i >= 0; i--) {     newDataSet[--buckets[(dataSet[i]-min)]] = dataSet[i];}

讨论一,准确性:
很显然第一个版本是错误的。当待排序数列中出现重复值时,就会出现错误。如果重复的是“0”,就看不出来。以上例为例,最后的结果是:

第二、三版本是正确的,结果均如下。

然而这两个版本仍有差异。这个差异就是稳定性。


讨论二,稳定性:

为了显示差异,我们用*来区别待排序数列中的重复值。dataSet  = {5, 1, 2, 4, 2*},结果分别如下:
第二个版本结果:

第三个版本结果:


所以第三个版本是稳定的,才称得上一个好算法!这也就是巧妙之三了。

好了,算是记完了流水账。至于稳定性的巧妙之处需要亲自写代码,才能更好体会!


桶式排序的图解:


那么元素 0 在有序数列的第1位,元素 5 在有序数列的第5位,元素 3 在有序数列的第3位,元素 4 在有序数列的第4位,元素 2 在有序数列的第2位。


末了,桶式排序代码如下:

public int[] bucketsort() {int min = this.min();           //min(),返回出待排序数列的最小值int max = this.max();           //max(),返回待排序数列的最大值int len = max - min + 1;        //计算可枚举范围int[] buckets = new int[len];for(int i = 0; i < dataSet.length; i++) {buckets[dataSet[i]-min]++;}for(int i = 1; i < len; i++) {buckets[i] += buckets[i-1];}int[] newDataSet = new int[dataSet.length];  //有序数列for(int i = dataSet.length - 1; i >= 0; i--) {newDataSet[--buckets[dataSet[i]-min]] = dataSet[i];}return newDataSet;}