数据结构与算法--排序算法:基数排序 Python实现基数排序详解 绝对让你看懂!

基数排序概念

  • 介绍
    排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。
    基数排序就是这样,先按低位排序,逐次按高位排序,低位排序后元素的顺序在高位也相同时是不会改变的。
    基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。
    由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数
    在这里插入图片描述
  • 思路过程

(1)将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。
然后,从最低位开始,依次进行一次排序。
这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
基数排序的方式可以采用 LSD(Least significant digital)或 MSD(Most significant digital),
LSD 的排序方式由键值的最右边开始,而 MSD 则相反,由键值的最左边开始。

(2)详解过程:以待排序序列:{53,3,542,748,14,214} 使用基数排序,升序为例
在这里插入图片描述

(3)动图演示:这里我们按照个位数排序,待排序列为:{5,2,3, 44, 4, 5, 15, 36, 26, 46, 47, 27,38,48,19},
我们采取LSD方式对待排序列排序,效果如下

在这里插入图片描述

基数排序的实现

分步实现

def redix_sort(array):
    # 第一轮 针对每个元素的个位进行排序处理
    # 定义一个二维数组,表示10个桶,每个桶就是一个一维数组
    # 说明:
    # 1.二维数组包含10个一维数组,为防止在放入数的时候,数据 溢出,则每个桶大小定为len(array)
    # 2.这就表明了,基数排序是使用空间换时间的经典算法
    bucket = [[0 for i in range(len(array))] for j in range(10)]
    # 为了记录每个桶中,实际存放了多个数据,于是定义一个一维数组来记录各个桶,每次放入的数据个数
    bucket_item_count = [0] * 10  # bucket_item_count[0],记录的就是bucket[0]这个桶放入的数据个数
    for j in range(len(array)):
        digit_item = array[j] % 10  # 取出每个元素的个位数
        bucket[digit_item][bucket_item_count[digit_item]] = array[j]
        bucket_item_count[digit_item] += 1
    # 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来的数组)
    index = 0
    for k in range(len(bucket_item_count)):
        if bucket_item_count[k] != 0:
            for l in range(bucket_item_count[k]):
                # 取出元素放入 array中
                array[index] = bucket[k][l]
                index += 1
        bucket_item_count[k] = 0  # ***处理完一轮后,要将计数清0
    print(array)
    # <=========第二轮:对十位进行处理=======>
    for j in range(len(array)):
        digit_item = array[j] // 10 % 10  # 取出每个元素的十位数
        bucket[digit_item][bucket_item_count[digit_item]] = array[j]
        bucket_item_count[digit_item] += 1
    # 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来的数组)
    index = 0
    for k in range(len(bucket_item_count)):
        if bucket_item_count[k] != 0:
            for l in range(bucket_item_count[k]):
                # 取出元素放入 array中
                array[index] = bucket[k][l]
                index += 1
        bucket_item_count[k] = 0  # ***如果进行第三轮,这里也要将计数清
    print(array)
    # <=========第三轮:对百位进行处理=======>
    for j in range(len(array)):
        digit_item = array[j] // 100  # 取出每个元素的百位数
        bucket[digit_item][bucket_item_count[digit_item]] = array[j]
        bucket_item_count[digit_item] += 1
    # 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来的数组)
    index = 0
    for k in range(len(bucket_item_count)):
        if bucket_item_count[k] != 0:
            for l in range(bucket_item_count[k]):
                # 取出元素放入 array中
                array[index] = bucket[k][l]
                index += 1
    print(array)


if __name__ == '__main__':
    li = [53, 3, 542, 748, 14, 214]
    print(li)
    redix_sort(li)
'''
[53, 3, 542, 748, 14, 214]
[542, 53, 3, 14, 214, 748]
[3, 14, 214, 542, 748, 53]
[3, 14, 53, 214, 542, 748]
'''

通用基数排序

# 通用基数排序写法
def redix_sort(array):
    # 需要解决两个问题
    # 1. 得到数组中最大的数的位数
    max_num = array[0]  # 假设最大的数是第一个数
    for i in range(1, len(array)):
        if array[i] > max_num:
            max_num = array[i]
    # 直接用max函数也行,max_num=max(array)
    # 2. 得到最大数是几位数
    max_num_len = len(str(max_num))
    
    bucket = [[0 for i in range(len(array))] for j in range(10)]
    n = 1  # 用来记录整除前移几位
    for i in range(max_num_len):
        # 为了记录每个桶中,实际存放了多个数据,于是定义一个一维数组来记录各个桶,每次放入的数据个数
        bucket_item_count = [0] * 10  # bucket_item_count[0],记录的就是bucket[0]这个桶放入的数据个数
        for j in range(len(array)):
            digit_item = array[j] // n % 10  # 取出每个元素的个位数
            bucket[digit_item][bucket_item_count[digit_item]] = array[j]
            bucket_item_count[digit_item] += 1
        # 按照这个桶的顺序(一维数组的下标依次取出数据,放入原来的数组)
        index = 0
        for k in range(len(bucket_item_count)):
            if bucket_item_count[k] != 0:
                for l in range(bucket_item_count[k]):
                    # 取出元素放入 array中
                    array[index] = bucket[k][l]
                    index += 1
            bucket_item_count[k] = 0  # 将计数清零处理
        n *= 10


if __name__ == '__main__':
    li = [53, 3, 542, 748, 14, 214]
    print(li)
    redix_sort(li)
    print(li)

简化思路

'''
第一步
以LSD为例,假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39

第二步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93

第三步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。

时间效率 :设待排序列为n个记录,d个关键码,关键码的取值范围为radix,
则进行链式基数排序的时间复杂度为O(d(n+radix)),其中,一趟分配时间复杂度为O(n),
一趟收集时间复杂度为O(radix),共进行d趟分配和收集。
空间效率:需要2*radix个指向队列的辅助空间,以及用于静态链表的n个指针。
'''
def radix_sort(a):
    i = 0  # 初始为个位排序
    n = 1  # 最小的位数置为1(包含0)
    max_num = max(a)  # 得到带排序数组中最大数
    while max_num > 10 ** n:  # 得到最大数是几位数;应该是“>=”?
        n += 1
    while i < n:
        bucket = {}  # 用字典构建桶
        for x in range(10):
            bucket.setdefault(x, [])  # 将每个桶置空
        for x in a:  # 对每一位进行排序
            radix = int((x / (10 ** i)) % 10)  # 得到每位的基数
            bucket[radix].append(x)  # 将对应的数组元素加入到相应位基数的桶中
        j = 0
        for k in range(10):
            if len(bucket[k]) != 0:  # 若桶不为空
                for y in bucket[k]:  # 将该桶中每个元素
                    a[j] = y  # 放回到数组中
                    j += 1
        i += 1


if __name__ == '__main__':
    a = [12, 3, 45, 3543, 214, 1, 4553]
    print("Before sorting...")
    print("---------------------------------------------------------------")
    print(a)
    print("---------------------------------------------------------------")
    radix_sort(a)
    print("After sorting...")
    print("---------------------------------------------------------------")
    print(a)
    print("---------------------------------------------------------------")

'''
Before sorting...
---------------------------------------------------------------
[12, 3, 45, 3543, 214, 1, 4553]
---------------------------------------------------------------
After sorting...
---------------------------------------------------------------
[1, 3, 12, 45, 214, 3543, 4553]
---------------------------------------------------------------
'''
# 随机测试

from random import randint


def radix_sort(lists, d):  # d是lists中最大元素的最高位数,一共需要d趟排序
    for k in range(d):
        s = [[] for i in range(10)]  # s=[[],[],[],[],[],[],[],[],[],[]]
        for data in lists:
            s[data // (10 ** k) % 10].append(data)  
            # k=0,1,2,...,d-1;k=0时,将个位相同(假设个位为2)的元素放入同一个(序号为2)桶中;k=1时,以此类推。
        lists = [item for each_s in s for item in each_s]  # 回收
        # 在此程序中,若将此句改写为lists=[each_s for each_s in s]会报错
    '''
    注意:
    虽然 each_s for each_s in s 也可以打印出与 item for each_s in s for item in
    each_s 一样的效果,但是第一条语句的默认结果是lists中的元素以列表为单位存在
    而第二条语句的默认结果是以元素为单位存在
    对于本程序而言,如果采用each_s for each_s in s , 那么就会报错
    因为对于语句s[data//(10**k)%10].append(data)来说,将会导致data//(10**k)报出
    类型错误:TypeError: unsupported operand type(s) for //: 'list' and 'int'
    '''
    return lists


lists = [randint(0, 520) for i in range(8)]
sorted_list = radix_sort(lists, 3)
print(sorted_list)

性能分析

在这里插入图片描述

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值