排序算法(三)

 

如果我们不采用比较的方式来实现排序,可以采用其他方式实现排序么?是的,可以。

1.桶排序

例如,我们有一个数组,里面的元素如下:

索引:0  1     2     3     4     5     6     7     8     9  10     11    12    13    14

元素:0  3     3     0     1     1     0     3     0     2     0   1     1     2     0

 

根据上面给出的数据,我们可以统计数组中每个不同的数出现的次数

数字:0  1     2     3

次数:6  4     2     3

 

输出时,我们根据上面的数组,每个数字出现几次,我们就输出几次,得到:

0     0     0     0     0     0     1     1     1     1     2     2     3     3     3

现在有一个问题,上面的方法叫做排序么?根本就不是。可能大家有点疑惑,上面的数字明显就是按照顺序输出的啊,怎么不是排序?问题在于,我们排序时,大部分情况下都是对一个记录进行排序,每条记录有个关键字,我们只是通过关键字来对记录进行排序,也就是说,排序的东西仅仅是我们信息的一部分。上面的做法,我们只是把关键字输出了,但是每个关键字对应的那条记录的信息我们怎么对应起来?

Ok,我们来做一下改进,依然是对数据进行统计,不过我们记录统计信息时是把具有相同关键的字的记录用链表串起来。就像把数据放到一个个的桶里,每个桶上贴一个标签,标签就相当于数组中每个不同的数。如下面所示,记录的括号里表示关键字的值。

0à记录10à记录20à记录30à记录40à记录50à记录60

1à记录71à记录81à记录91à记录101

2à记录112à记录122

3à记录133à记录133à记录133

因此输出时,我们就可以按序输出,同时可以知道每条记录的信息了。

注意:大家可能看到我上面的叙述和哈希表里的单独链的实现方式好像类似,其实不是一回事,哈希表的第一步是求哈希码的值,第二步是处理冲突问题,单独链是解决冲突的方式之一。我后面有空,会写关于哈希表的东西的。而我们这里仅仅是根据记录里的关键字的值来进行统计。也就是,根本就不需要什么哈希函数进行哈希码值进行计算。

现在来看看时间复杂度,可以看到,统计一次,可以在线性时间内完成,放到桶里也可以在常数操作内实现,总是时间复杂度O(n),是线性的。

2.索引计数排序

例如,我们有一个数组arr,里面的元素如下:

索引:0  1     2     3     4     5     6     7     8     9  10     11    12    13    14

元素:0  3     3     0     1     1     0     3     0     2     0   1     1     2     0

 

根据上面给出的数据,

第一步,我们可以统计数组中每个不同的数出现的次数,放到数组count里,操作count[arr[i]+1]++,注意我们放的时候,是放到对应的后一个数字的位置里,例如数字2对应的次数是4,其实是说有41

数字:0  1     2     3     4

次数:0  6     4     2     3

 

第二步,我们计算累积值,同样是针对count数组,执行count[i+1]+=count[i]。例如,数字2对应的累计是10,表示小于2的数字个数是10。因此,现在累积值得含义是,遇到对应的索引值(也就是说,原始数组里的元素的值),应该开始放的位置。

数字:0  1     2     3     4

累计:0  6     10    12    15   

 

第三步,我们从原始数组里,根据累计,知道应该把那个数字放到哪个对应的位置,即根据元素知道知道元素对应的累积值,知道了这个元素开始放的位置。当然了,具体操作是先放到一个辅助的数组里aux里去。具体操作是aux[count[arr[i]]++]=arr[i];

索引:0  1     2     3     4     5     6     7     8     9  10     11    12    13    14

元素:0  3     3     0     1     1     0     3     0     2     0   1     1     2     0

我们具体来看,原始数组第一个位置的值是0,那么根据累积数组count[0]=0,即我们应该把它放到辅助数组的第一个位置,aux[0]=0,同时现在count[0]应该增1.第二个元素是1,我们看累积数组count[1]=6,那么我们应该1放到aux的索引6的位置,即aux[6]=1,最后count[1]应该增17,第二次遇到元素是6时,就放到aux索引7的位置。

 

第四步,我们把辅助数组里的内容copy到原始数组。

arr[i]=aux[i];

 

可以看到,计数排序也是线性时间内完成的。O(n)

3.基址排序

我这里主要说一下思路。基址排序一般会用计数排序的内容。例如,我们有下面的一些字符串记录记录,为了清楚,我用空格隔开了,例如dab,我写成d      a      b

d     a      b

a      d     d

c     a      b

f      a      d

f      e      e

b     a      d

d     a      d

b     e      e

f      e      d

b     e      d

e      b     b

a      c     e

基址排序的基本思想就是按位比较,具体到字符串,我们可以说是按字符比较,比如,对于dabadd字符串,根据他们的字符串首字母da,我们知道a<d的,那么add应该排到dab的前面,比较就可以停止了,根本没有必要再比较后面的内容。如果相同的话,继续取它们对应的下一个字符进行比较。

例如,上面的字符串数据,我们使用计数排序对它们的首字母进行排序后的结果如下:

a      d     d

a      c     e

b     a      d

b     e      e

b     e      d

c     a      b

d     a      b

d     a      d

e      b     b

f      a      d

f      e      e

f      e      d

现在,根据第一位字母相同的进行分组,继续对剩下的字符使用相同的计数排序,最后就可以得到完全排好序的字符串记录。

小结:我们采用桶排序,计数排序和基址排序,可以在线性时间内完成排序。其实基址排序还有很多深入的内容可以进一步说明的,我以后有空再写。现在为止,我已经对排序的10个基本的算法进行了说明。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值