用排序来直击信息的本质
前言:
桶排序在某种意义上来讲它并不是一个排序算法,它更像一个策略方案,事实上的排序还是由别的排序算法完成,它的步骤分为两部分(1)数据装“桶”,(2)每个"桶"使用其余的排序算法进行排序。 计数排序实际上是桶排序的特例情况,桶排序代表的是一般情况,数据结构字典的实现思想和桶排序的实现思想一致。
算法原理:
1. 计算数据的最大值max,最小值min。
2.创建数据"桶",数量为len。(为什么桶的数量为len,当桶的数量等同于元素数量时,性能最佳,下面性能分析给出了原因)
3.遍历每一个元素,进行数据装桶。(使用映射函数计算下表hashKey )
4.对每一个"桶"使用冒泡。(因为划分桶之后的数据数量一般是较小所以数据指数效应不高,偏重考虑考虑系数性能,在这里因为以上文章中说过冒泡排序所以选用冒泡排序,但是需要具体情况具体来处理,在这里不过多的讨论应该使用哪个排序算法)
5.链接每个已排序好的桶,就得到了排序好的数据。
设:
排序数据数组为arr、长度为len、bucketSort为排序入口函数、hashKey为映射函数。
伪码:
getMaxMin(arr,len)
{
max = arr[0];
min = arr[0];
for(i=1;i<len;i++)
{
if(arr[i]>max)
{
max = arr[i];
}
else if(arr[i]<min)
{
min= arr[i];
}
}
return max,min;
}
hashKey(key,min,total,division)
{
return floor((key-min)/total* division);
}
getBucketArr(arr,len)
{
max,min = getMaxMin(arr,len);
sum = max-min;
mul= len-1;
bucetArr = size(len);
for(i=0;i<len;i++)
{
bucetArr[hashKey(arr[i],min,sum,mul)].push(arr[i]);
}
return bucetArr;
}
bucketSort(arr,len)
{
bucetArr = getBucketArr(arr,len);
for(i=0;i<bucetArr.len;i++)
{
bubbleSort(bucetArr[i],bucetArr[i].len);
}
sortArr = size(len);
k=0;
for(i=0;i<bucetArr.len;i++)
{
for(j=0;j<bucetArr[i].len;j++)
{
sortArr[k++] = bucetArr[i][j];
}
}
}
算法动图:
(1)桶数量和数据数量一致:
1.获取最大最小值:
2.数据装桶:
3.完成每一个bucket的排序:(冒泡排序) (在上图中可以看到,恰好一个数据放入了一个bucket中,无需排序,直接连接即可得到最终数组,这里就不在给出动画)
4.连接每一个bucket排序后的结果,得到最终数组。
(2)桶数量和数据数量不一致:
1.获取最大最小值:
2.数据装桶:
3.完成每一个bucket的排序:(冒泡排序)
4.连接每一个bucket排序后的结果,得到最终数组。
算法性能:
1.计算数据的最大最小值,这里需要遍历整个数组消耗性能为len。
2.创建一个二维数组,这里的创建空间的性能消耗为len。
3.遍历每一个元素,这里需要遍历整个数组消耗的性能为len。
4.对每一个"桶"使用快速排序,这里的性能为"桶"中元素数量q,所以性能为q² ,其中q在概率上我们可以认为1,所以性能消耗为1。(在桶排序中,最关键的问题是如何定义桶的数量,在一般情况中,可以认为数据是完全随机的,那么元素属在每一个桶中的概率都是相等的,所以桶的数量等于元素的数量时性能最佳,因为恰好一个元素可以放一个桶中,既不多也不少,每一个桶都无需开展排序算法,这是非常理想的情况。在一般应用中,我们使用的数据并不是完全随机的,例如对全国成绩的排序,很明显有几百万数据之多,但是成绩的可能出现的数字却远远没有几百万这么多,所以创建几百万个桶是没有意义的,且不现实的)
5.算法性能为len+len+len+ q²= 3n+1 = O(n)。
结语:
1.桶排序实际上是由两部分构成(1)数据分类、分块(2)使用具体的排序算法排序。(实际上装桶的目的就是为了降低比较排序时n的数量)
2.在计数排序中所使用的映射函数,会让一样的数值存储在一起,而在桶排序中映射函数,会让同一个区间内的数据存储在一起,区间内的数值位置不确定,所以需要在用其他比较排序算法进行排序,来确定数值的位置。数排序从这种角度讲是桶排序的特例,桶排序是一种一般情况。在不一样的信息维度上,获取两种算法,实质是对已知信息的利用率的提升,在无限集合到有限集合之间处理的差别。 (事实上信息的定义就是消除不确定,产生确定的事物。在桶排序和计数排序的区别中,实际上就是消除数据种类的不确定,确定了它为整数。在桶排序中,划分桶是为了确定桶与桶之间的关系,每个桶的比较排序,是为了确定元素之间的关系。 在实际项目中有许多的途径,能了解许多的信息,其实都可作为算法消除不确定的条件)
3.从以上算法过程中,可以知道,实际上桶的数量和桶中元素的数量是影响桶排序速度的关键,事实上也是通过对信息的一种假定处理得到了一个线性时间的排序算法,假定了数据是完全随机分布的,实际上绝大部分问题中数据并不是随机分布的,在假定条件不成立的情况下,桶排序会退化,会退化成具体使用的算法的排序性能。(例如在所有数据都是同一个数字的情况下,所有的数据都会被装入一个桶中,那么具体的排序算法,需要排序的数据总量并没有减少)
4.在实际项目中桶排序中的桶的数量,并不是如算法中设定的那么简单,或者说不会有那么理想。怎么让桶排序更小的概率出现退化现象,或者说让信息的假定成功,其实可以从信息的假定上入手,现在假定的信息为随机数据,事实上可以统计数据出现的概率建立函数图像(使用拟合等数学手段)。假定信息为可以建立函数图像的数据, 那么就可以根据这个函数来进行桶的创建,当数据比较"密集"时,可以创建更多的桶来装数据,当数据比较"稀疏"时,则可以少创建桶。归根结底就是如何保证每个桶中的元素数量尽量相等。
5.桶排序因为用到了假定信息的类型,事实上是有可能是不成立,因为假定信息并不是限制信息,假定可能并不成立,计数排序实际上是限定了信息,所以并不会有算法退化的现象。
6.桶排序,可能会发生极端退化情况,例如(使用快速排序的桶排序)处理元素完全相同的数据时,桶排序 -> 快速排序 ->冒泡排序,性能从理想中的n -> log2 -> n^2 一路退化。遗憾的是实际这种问题在实际项目中是经常出现的,也无法完美解决,但其实出现这种情况的时候都是特定数据,有很多种解决方案,让出现的概率降低,甚至无法遇到,从而解决。总之就是具体的问题需具体分析,没有最好的算法,只有最适用的算法。