Python算法—计数排序

计数排序

  • 1.算法介绍
    • 2.算法思想
  • 3.算法过程
  • 4.python代码实现
    • 代码1
    • 优化后的代码3

1.算法介绍

  • 计数排序是一种非基于比较的排序算法,其空间复杂度和时间复杂度均为O(n+k),其中k是整数的范围。基于比较的排序算法时间复杂度最小是O(nlogn)的。
  • 计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
  • 计数排序对于数据范围很大的数组,需要大量时间和内存。

2.算法思想

假设要排序的数组为 A = {1,0,3,1,0,1,1}这里最大值为3,最小值为0,
那么我们创建一个数组C,长度为3+1-0=4。然后一趟扫描数组A,得到A中各个元素的总数,并保持到数组C的对应单元中。比如0 的出现次数为2次,则 C[0] = 2;1 的出现次数为4次,则C[1] = 4。C=[2,4,0,1]由于C 是以A的元素为下标的,所以这样一做,A中的元素在C中自然就成为有序的了,然后我们分别统计比0,1,2,3小的元素个数,如比1小(包括1)的元素有6个。更新C,C=[2,6,6,7],更新C是为了保证排序稳定。然后把这个在C中的记录按每个元素的计数展开到输出数组B中,排序就完成了。 也就是B[0] 到 B[1] 为0, B[2] 到 B[5] 为1 这样依此类推。

3.算法过程

  • 首先找出数组中的最大值,那么数组中的所有值肯定在0到最大值之间,比如如果数组的范围是从0-10,那么数组中的值肯定都在0-10这11个数之间
  • 建立一个元素全是0的数组,数组下标为0到最大值,假设最大值=10,则如下图所示:
元素 0 0 0 0 0 0 0 0 0 0 0
元素下标 0 1 2 3 4 5 6 7 8 9 10

举例讲解

  • 先假设20个随机整数的值是:2,4,2,3,7,1,1,0,0,5,6,9,8,5,7,4,0,10,9,10
    先遍历这个无序的随机数组,每个元素按照其值对号入座,再将数组下标的元素进行+1操作,比如,第一个元素是2,则下标为2的元素加1:
元素 0 0 1 0 0 0 0 0 0 0 0
元素下标 0 1 2 3 4 5 6 7 8 9 10

第二个元素是4,则下标为4的元素加1:

元素 0 0 1 0 1 0 0 0 0 0 0
元素下标 0 1 2 3 4 5 6 7 8 9 10

第三个元素是2,则下标为2的元素加1:

元素 0 0 2 0 1 0 0 0 0 0 0
元素下标 0 1 2 3 4 5 6 7 8 9 10

如此遍历原数组,最终结果:

元素 3 2 2 1 2 2 1 2 1 2 2
元素下标 0 1 2 3 4 5 6 7 8 9 10

这样就统计出了数组中每个值出现的次数,有了这个统计结果就可以直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次

4.python代码实现

代码1

def count_sort1(arr):
    maxi = max(arr)  # 找出最大值
    count_arr = [0 for _ in range(maxi + 1)]  # 创建一个全0列表
    # 统计序列中每个元素出现的次数
    for value in arr:
        count_arr[value] += 1
    arr.clear()  # 将原列表清空
    for index, element in enumerate(count_arr):  # index:元素下标
        for i in range(element):
            arr.append(index)  


def count_sort2(arr):
    maxi = max(arr)   # 找出数组中的最大值
    count_arr = [0 for _ in range(maxi + 1)]  # 创建一个全0列表
    # 统计序列中每个元素出现的次数
    for value in arr:
        count_arr[value] += 1

    new_arr = []  # 创建一个新列表
    for i in range(len(count_arr)):
        while count_arr[i] != 0:
            new_arr.append(i)
            count_arr[i] -= 1
    return new_arr       


import random
numbers = [random.randint(0, 10)   for _ in range(20)]
print(numbers)
count_sort1(numbers)
print(numbers)
numbers2=[3,4,5,6,9,5,2,1,2,3,6,8,9,7]
print(count_sort2(numbers2))

代码中的一些解释

  1. enumerate()函数的用法:
    enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中,其用法如下:
    语法格式:enumerate(sequence, [start=0])
    参数说明:
    1.sequence : 一个序列、迭代器或其他支持迭代对象
    2.start : 下标起始位置
  • 举例说明:存在一个sequence,对其使用enumerate将会得到如下结果:
1  start        sequence[0]
2  start+1    sequence[1]
3  start+2      sequence[2]
...

实例:

seq = ['one', 'two', 'three']
for i, element in enumerate(seq):
    print(i, element)


结果输出:
0    one
1    two
2    three

这个函数会输出下元素值和其对应的下标索引

  1. for _ in range(maxi + 1)的用法:
list1 = [0  for _ in range(10)]
print(list)
list2=[2**2  for _ in range(5)]
print(list2)

输出结果:

list1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
list2:[2, 2, 2, 2, 2]

这里的下划线(_)是个占位符,不用管它里面什么内容,它的作用就是让前面的内容循环这么多次, 即 for _ in range(n)的作用是仅仅是重复循环 n 次

  1. random.randint(0, 10) : 随机返回0-10之间的一个数,不包括10

优化后的代码3

上面的代码在一开始就求得了数列的最大值maxi,创建的数组count_arr,长度是maxi+1,以保证数组最后一个下标是maxi。
但是这段代码并不严谨,假如数组中的最小值是50,最大值是59,那么按照上面代码则需要创建长度为60的数组,为前面50个数(0-49)是没有的,那么就会浪费空间,所以数组长度应该是 : 最大值-最小值+1,而把数组最小值作为一个偏移量。
举例说明:假如数组为:55,54,59,50,51,52,51,53,56,55
则偏移量=最小值(50),那么对于第一个数55而言,对应的数组下标应该是55-50=5

为了使排序是稳定的,我们可以依次统计比**[50,51,52, … ,59]小的元素个数,例如比 51小的元素个数(包括51)有3个
原来的计数数组为:

元素次数 1 2 1 1 1 2 1 0 0 1
偏移后下标 0 1 2 3 4 5 6 7 8 9
元素下标 50 51 52 53 54 55 56 57 58 59

相加后的计数数组为

相加的原则使每个元素出现的次数=它前面所有元素出现次数+该元素出现的次数

相加次数 1 3 4 5 6 8 9 9 9 10
偏移后下标 0 1 2 3 4 5 6 7 8 9
元素下标 50 51 52 53 54 55 56 57 58 59

相加后计数数组元素的值就等于元素的所在位置,比如下标是6的元素值是9,代表原数列的值56最终的排序是在第9位
优化后的代码:

def count_sort2(arr):
    maxi = max(arr)
    mini = min(arr)
    count_arr_length = maxi - mini + 1  # 计算待排序列表的数值区域长度,如4-9共9+1-4=6个数
    count_arr = [0 for _ in range(count_arr_length)]   # 创建一个全为0的列表

    for value in arr:
        count_arr[value - mini] += 1   # 统计列表中每个值出现的次数

    # 使count_arr[i]存放<=i的元素个数,就是待排序列表中比某个值小的元素有多少个
    for i in range(1, count_arr_length):
        count_arr[i] = count_arr[i] + count_arr[i - 1]

    new_arr = [0 for _ in range(len(arr))]   # 存放排序结果
    for i in range(len(arr)-1, -1, -1):    # 从后往前循环是为了使排序稳定
        new_arr[count_arr[arr[i] - mini] - 1] = arr[i]   # -1是因为下标是从0开始的
        count_arr[arr[i] - mini] -= 1   # 每归位一个元素,就少一个元素
    return new_arr


if __name__ == '__main__':
    user_input = input('输入待排序的数,用\",\"分隔:\n').strip()
    #strip() 方法用于移除字符串头尾指定的字符(默认为空格)
    unsorted = [int(item) for item in user_input.split(',')]
    print(count_sort2(unsorted))
    

这里我说的比较可能不是太易懂,关于计数排序的更详细介绍过程可以点击这里查看

计数排序适用于最大值和最小值相差不大的列表,如果一个列表的最小值是1,最大值是1亿,那么计数排序就需要开一个长度为1亿+1的列表,会比较费时

你可能感兴趣的:(python算法)