20191209-八大排序之基数排序

wuxiaosi0 2019-12-09

1. 基数排序

算法核心思想

基数排序(radix sort)是一种只适用于数字或字母类型的排序方法,它检查数字或字母的每一位,将之分类,按照位数的特定顺序,来将元素排列。以数字为例,将所有元素按照个位数字分类,分类好后,将个位数字大小排列组合起来,再按照十位数字分类,再按照数字大小排列组合起来,一直到最大数位为止。

基数排序分类

基数排序的方式分为2类:

  1. LSD(Least significant digital):LSD的排序方式由键值的最右边开始,先比较最低位,也就是个位,进行分桶,分桶过程中分到一个桶中的数据直接追加到桶中即可,无需排序。然后将所有同种的元素按桶的顺序拿出,重新组成序列,然后比较十位,进行分桶…直到比较到最高位,重新组成序列即可完成排序。
  2. MSD(Most significant digital):由键值的最左边开始,先比较最高位,最高位分到一个桶中的,再按照第二位进行分桶…,知道分到最后一位,然后再从最小的桶中逐层向上,把元素都拿出来,即完成排序。
  3. 首先确定待排序数组中值的最大位数,用于确定程序中要作几轮排序;
  4. 定义一个集合数组,数组大小十,对应角标范围0-9,用来保存每轮排序过程中对应的位数上的值;
  5. 每轮排序依次从原始数组中取值,并判断对应位数上的值,不足位数时在前面补上相应的0,然后放到对应角标的桶内;
  6. 每轮排序后,按角标大小依次取出不为空的桶内的值,合并成为一个部分排序后的新数组;
  7. 将上述部分排序后的新数组作为下一轮高一位位数上的值排序的原始数组,重复上述排序的步骤,直到生成最终排好序的数组。

LSD具体逻辑如下:

MSD具体逻辑如下:

从最高位数上的值开始排序,一轮排序结束后,并没有急着合并整理,而是判断每个桶内的元素,如果桶内的元素大于1个则继续对当前桶进行排序,直到排到最低位数上的值,最终做一次性合并整理,形成排序后的数组。

代码实现-数字LSD算法

def BucketSort(arr):
    max_num = max(arr)
    i = 0
    #(max_num//10**i)%10>0表示当数组中最大的数的最高位没有进行排序前,都一直循环
    while (max_num//10**i)%10>0:
        buckets = [[] for i in range(10)]
        for num in arr:
            # (num//10**i)%10获取当前排序的基准数(对应的桶号),如取320的第二个数,则需要放置在2号桶中
            bucketIndex = (num//10**i)%10
            buckets[bucketIndex].append(num)
        arr =[]
        #将桶中的数组从新组合为一个新的数组
        for bucket in buckets:
            arr+=bucket
        i +=1
    return arr

代码实现-数字MSD算法

def BucketSortMsd(arr,max_length):
    if max_length<=0:
        return arr
    print("对于当前数组%s按照右起第%s位数排序"%(arr,max_length))
    buckets = [[] for i in range(10)]
    for num in arr:
        #num // 10 ** (max_length - 1) % 10 ** (max_length) 获取当前数字的桶index
        bucket_idx = num//10**(max_length-1)%10**(max_length)
        buckets[bucket_idx].append(num)
    arr = []
    for bucket in buckets:
        if len(bucket)>2:
            arr+=BucketSortMsd(bucket,max_length-1)
        else:
            arr+=bucket
    return arr

代码实现-字母LSD算法

def BucketSortLetterLsd(arr):
    sort_arround = len(max(arr))
    while sort_arround>=0:
        buckets = [[] for i in range(26)]
        for letter in arr:
            if sort_arround>=len(letter):
                bucket_index = 0
            else:
                bucket_index = ord(letter[sort_arround])-97
            buckets[bucket_index].append(letter)
        arr = []
        for bucket in buckets:
            arr+=bucket
        sort_arround-=1
    return arr
arr = ["banana","apple","orange","ape","he"]
print(BucketSortLetterLsd(arr))

根据计算出的桶的索引放置单词,并且放置后按照桶的位置从0-26逐一取出桶中的单词放回序列,然后进行下一轮排序,一直到排序完最后一轮。

1. 不管待排列表是多少个元素,以及元素的长度为多少,都需要创建27个桶,分别对应其他字符以及a-z的26个英文字母

2. 获取待排元素的的最长的单词作为排序的轮数

3. 从低位到高位依次排序(注意:一定是要从地位到高位才能完成我们的字典序)

具体代码分为2个函数,一个函数是获取字母应该放置到哪个桶中,一个函数根据获取的桶的索引将元素放置入桶中

代码实现-字母MSD算法

def LetterBucketSortMsd(arr,sort_idx):
    """sort_idx表示按照给定arr中数组中字母的第几个索引进行排序"""
    # 如果排序的字母位索引超出最大长度,直接返回数组,如对字母abc按照下标3第4个字母进行排序,这种情况无需排序,直接返回
    if sort_idx>len(max(arr)):
        return arr
    #建立26个桶
    buckets = [[] for i in range(26)]
    for letter in arr:
        if len(letter)>sort_idx:
            #获取桶的索引
            bucket_index = ord(letter[sort_idx])-97
        else:
            bucket_index = 0
        buckets[bucket_index].append(letter)
    arr = []
    for bucket in buckets:
        #如果桶中的元素超过2个,则继续排序,最后合并桶
        if len(bucket)>=2:
            arr+=LetterBucketSortMsd(bucket,sort_idx-1)
        else:
            arr+=bucket
    return arr

执行解析

总结

LSD从低位开始排到高位,每排一位都是把各个桶合并,再按下一位排序;
MSD从高位开始排到低位,排完一位后不合并桶,相同的高位划分子桶继续分配,最后再合并。

堆排序本身没有普适性,仅能用于特定的数组排序,如字母和数字的排序。

相关推荐