冒泡排序:
冒泡排序算法的运作如下:
- 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数(相比于冒泡排序,我更愿意称之为沉底排序)。
- 针对所有的元素重复以上的步骤,除了上一步已经排好的元素。
- 每次持续对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
#相比于下面的写法更好理解
def bubble_sort(list_):
"""冒泡排序(稳定排序)"""
for i in range(len(list_) - 1,0,-1):
# i代表每次遍历的右端点
# 外层循环控制循环次数
for j in range(i):
# 内层循环控制每一次的循环,每一次循环把最大的数值放到最后
if list_[j] > list_[j + 1]:
list_[j], list_[j + 1] = list_[j + 1], list_[j]
return list_
def bubble_sort(list_):
"""冒泡排序(稳定排序)"""
for i in range(len(list_) - 1):
# 外层循环控制循环次数
for j in range(len(list_) - i - 1):
# 内层循环控制每一次的循环,每一次循环把最大的数值放到最后
if list_[j] > list_[j + 1]:
list_[j], list_[j + 1] = list_[j + 1], list_[j]
return list_
def bubble_sort_1(list_):
# 优化1(优化外层循环)
for i in range(len(list_) - 1):
# 每次遍历标志位都要先置为0,才能判断后面的元素是否发生了交换
flag = True
# 外层循环控制循环次数
for j in range(len(list_) - i - 1):
# 内层循环控制每一次的循环,每一次循环把最大的数值放到最后
if list_[j] > list_[j + 1]:
list_[j], list_[j + 1] = list_[j + 1], list_[j]
flag = False # 只要有发生了交换,flag就置为False
# 判断标志位是否为True,如果为True,说明一次遍历中没有进行交换,即元素已经有序,所以直接结束(正是这个优化,使得当元素有序时,时间复杂度为O(n),这就是我们说的冒泡排序的最好时间复杂度为O(n))
if flag:
return list_
return list_
def bubble_sort_2(list_):
# 优化2(优化内层循环)
k = len(list_) - 1
# pos变量用来标记循环里最后一次交换的位置
pos = 0
for i in range(len(list_) - 1):
# 每次遍历标志位都要先置为0,才能判断后面的元素是否发生了交换
flag = True
# 外层循环控制循环次数
for j in range(k):
# 内层循环控制每一次的循环,每一次循环把最大的数值放到最后
if list_[j] > list_[j + 1]:
list_[j], list_[j + 1] = list_[j + 1], list_[j]
flag = False # 只要有发生了交换,flag就置为False
pos = j # 循环里最后一次交换的位置j赋给pos,下次循环就不需要判断pos及其右边的元素了,因为已经有序了
k = pos
# 判断标志位是否为True,如果为True,说明一次遍历中没有进行交换,即元素已经有序,所以直接结束
if flag:
return list_
return list_
# a = [2, 1, 4, 3, 5, 7, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 3]
# # a = [1, 2, 3, 4, 5, 6] # 本来就有序的,通过优化1可以使最好时间复杂度达到O(n)
# # a = [2, 1, 3, 5, 4, 6, 8, 7, 9, 10, 11, 12] # 最后几个元素已经有序的,通过优化2可以减少内部循环比较次数
# # 传递a[:],不会改变a的值
# print(bubble_sort(a[:]))
# print(bubble_sort_1(a[:]))
# print(bubble_sort_2(a[:]))
# # print(a)
冒泡排序动图演示:
选择排序
n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
初始状态:无序区为R[1…n],有序区为空;第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
n-1趟结束,数组有序化了。
def selection_sort(list_):
"""选择排序(不稳定排序)"""
for i in range(len(list_) - 1):
for j in range(i + 1, len(list_)):
if list_[i] > list_[j]:
list_[i], list_[j] = list_[j], list_[i]
# print(list_)
return list_
def selection_sort(list_):
"""选择排序(不稳定排序)"""
for i in range(len(list_) - 1):
min_ = i
for j in range(i + 1, len(list_)):
#在未排序的元素中,找到最小元素的下标
#注意,不是找到最小元素,而是找到最小元素的下标,因为后面这个下标的元素还要和i下标的元素进行交换
if list_[j] < list_[min_]:
min_ = j
#最后进行一次交换,不需要每次都交换
list_[min_], list_[i] = list_[i], list_[min_]
return list_
# a = [1, 2, 4, 3, 5, 7, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 3]
# # 传递a[:],不会改变a的值
# print(selection_sort(a[:]))
# # print(a)
选择排序动图演示:
插入排序
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
- 1.从第一个元素开始,该元素可以认为已经被排序;
- 2.取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 3.如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 5.将新元素插入到该位置后;
- 6.重复步骤2~5。
# def insertion_sort(list_):
# """插入排序(稳定排序)"""
# for i in range(1, len(list_)):
# temp = list_[i]
# flag = -1
# for j in range(i - 1, -1, -1):
# # if list_[i] < list_[j]:
# if temp < list_[j]:
# # 这里一定要用temp进行比较,找了半天bug,唉!
# # 因为list[i]值有可能会改变,值有可能变成list[j+1]的值
# list_[j + 1] = list_[j]
# flag = j
# if flag != -1:
# list_[flag] = temp
# # print(i, flag, list_)
# return list_
def insertion_sort(list_):
"""插入排序(稳定排序)"""
# 从第二个位置,即下标为1的元素开始向前插入
for i in range(1, len(list_)):
# 从第i个元素开始向前比较,如果小于前一个元素,交换位置
for j in range(i, 0, -1):
if list_[j] < list_[j - 1]:
list_[j], list_[j - 1] = list_[j - 1], list_[j]
# 加上else,可以使最好时间复杂度为O(n)
else:
# 左半部分已经有序了,所以如果无序部分的第一个元素不比
# 有序部分的最后一个元素小的话,代表此元素已经有序了
# 可以退出了,不需要再比了
break
# print(list_)
return list_
# 我自己的实现
def insertion_sort(list_):
for i in range(1, len(list_)):
temp = list_[i]
flag = False
j = i - 1
for j in range(i - 1, -1, -1):
# 把前面有序的部分往后移动,空出的位置就是当前元素要插入的位置
if temp < list_[j]:
flag = True
list_[j + 1] = list_[j]
else:
break
# 只有当循环里面发生移动的时候,才插到当前位置,否则,代表当前元素位置是对的
if flag:
list_[j] = temp
return list_
a = [1, 2, 4, 3, 5, 7, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 3]
# a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 传递a[:],不会改变a的值
print(insertion_sort(a[:]))
# insertion_sort(a[:])
# print(a)
插入排序动图演示:
希尔排序
def shell_sort(list_):
"""希尔排序(不稳定排序)"""
n = len(list_)
gap = n // 2
while gap:
# 插入算法,与普通的插入算法的区别就是gap步长
for i in range(gap, n):
# for j in range(i, 0, -1):
for j in range(i, 0, -gap):
if list_[j] < list_[j - gap]:
list_[j], list_[j - gap] = list_[j - gap], list_[j]
else:
break
# 缩短gap步长
gap = gap // 2
return list_
a = [1, 2, 4, 3, 5, 7, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 3, 3, 2, 1, 4, 5, 3, 2, 5, 6, 7, 9]
# a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 传递a[:],不会改变a的值
print(shell_sort(a[:]))
# insertion_sort(a[:])
# print(a)
快速排序
方法1,填坑法
图示如下:
import numpy as np
def quick_sort(alist, start, end):
"""快速排序,开头作为枢轴,从右到左进行判断"""
# 这个判断必须加
if start >= end:
return
pivot = alist[start] ####
left = start
right = end
while left < right:
# 从右边开始找,直到找到一个小于基准的元素
while left < right and alist[right] >= pivot:
right -= 1
# 这个if判断不加也行
if left < right:
alist[left] = alist[right]
# 左指针右移(这个元素就是刚刚交换过来的小于基准的,所以不用再进行判断了,所以向右一步)
left += 1
# 从左边开始找,直到找到一个大于基准的元素
while left < right and alist[left] <= pivot:
left += 1
if left < right:
alist[right] = alist[left]
# 右指针左移(这个元素就是刚刚交换过来的大于基准的,所以不用再进行判断了,所以向左一步)
right -= 1
# 从循环退出时,left=right
alist[left] = pivot
quick_sort(alist, start, left - 1)
quick_sort(alist, left + 1, end)
def quick_sort(alist, start, end):
"""快速排序,结尾作为枢轴,从左到右进行判断"""
# 这个判断必须加
if start >= end:
return
pivot = alist[end] ####
left = start
right = end
while left < right:
while left < right and alist[left] <= pivot:
left += 1
if left < right:
alist[right] = alist[left]
right -= 1
while left < right and alist[right] >= pivot:
right -= 1
# 这个if判断不加也行
if left < right:
alist[left] = alist[right]
left += 1
# 从循环退出时,left=right
alist[left] = pivot
quick_sort(alist, start, left - 1)
quick_sort(alist, left + 1, end)
a = np.array([7, 7, 4, 5, 0, 6, 8, 10, 9, 12, 14, 13, 11, 1, 2, 7, 3])
# x的值改变不影响a的值
x = a.copy()
quick_sort(x, 0, len(a) - 1)
print(x)
print(a)
方法2,交换法
比如要排序 6 1 2 7 9 3 4 5 10 8:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于 6 的数,再从左往右找一个大于 6 的数,然后交换他们。这里可以用两个变量 i 和 j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵 i”和“哨兵 j”。刚开始的时候让哨兵 i 指向序列的最左边(即 i=1),指向数字 6。让哨兵 j 指向序列的最右边(即 j=10),指向数字 8。
首先哨兵 j 开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵 j 先出动,这一点非常重要(请自己想一想为什么)。哨兵 j 一步一步地向左挪动(即 j–),直到找到一个小于 6 的数停下来。接下来哨兵 i 再一步一步向右挪动(即 i++),直到找到一个数大于 6 的数停下来。最后哨兵 j 停在了数字 5 面前,哨兵 i 停在了数字 7 面前。
现在交换哨兵 i 和哨兵 j 所指向的元素的值。交换之后的序列如下。
6 1 2 5 9 3 4 7 10 8
到此,第一次交换结束。接下来开始哨兵 j 继续向左挪动(再友情提醒,每次必须是哨兵 j 先出发)。他发现了 4(比基准数 6 要小,满足要求)之后停了下来。哨兵 i 也继续向右挪动的,他发现了 9(比基准数 6 要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下。
6 1 2 5 4 3 9 7 10 8
第二次交换结束,“探测”继续。哨兵 j 继续向左挪动,他发现了 3(比基准数 6 要小,满足要求)之后又停了下来。哨兵 i 继续向右移动,糟啦!此时哨兵 i 和哨兵 j 相遇了,哨兵 i 和哨兵 j 都走到 3 面前。说明此时“探测”结束。我们将基准数 6 和 3 进行交换。交换之后的序列如下。
3 1 2 5 4 6 9 7 10 8
到此第一轮“探测”真正结束。此时以基准数 6 为分界点,6 左边的数都小于等于 6,6 右边的数都大于等于 6。回顾一下刚才的过程,其实哨兵 j 的使命就是要找小于基准数的数,而哨兵 i 的使命就是要找大于基准数的数,直到 i 和 j 碰头为止。
注:如果选取最左边的数arr[left]作为基准数,那么先从右边开始可保证i,j在相遇时,相遇数是小于(等于)基准数的,如果选取最右边的数arr[right]作为基准数,那么先从左边开始可保证i,j在相遇时,相遇数是大于(等于)基准数的。
def quick_sort(alist, start, end):
"""快速排序,开头作为枢轴,从右到左进行判断"""
# 这个判断必须加
if start >= end:
return
pivot = alist[start] ####
low = start
high = end
while low < high:
# 从右向左
while low < high and alist[high] >= pivot:
high -= 1
while low < high and alist[low] <= pivot:
low += 1
if low < high:
alist[low], alist[high] = alist[high], alist[low]
# print(alist[low] <= pivot)
# 最终将基准数归位,下面两句等价,因为退出循环时,low==high
alist[start], alist[low] = alist[low], pivot #######
# alist[start], alist[high] = alist[high], pivot
quick_sort(alist, start, low - 1)
quick_sort(alist, low + 1, end)
def quick_sort(alist, start, end):
"""快速排序,结尾作为枢轴,从左到右进行判断"""
# 这个判断必须加
if start >= end:
return
pivot = alist[end] ######
low = start
high = end
while low < high:
# 从左向右
while low < high and alist[low] <= pivot:
low += 1
while low < high and alist[high] >= pivot:
high -= 1
if low < high:
alist[low], alist[high] = alist[high], alist[low]
# print(alist[low] >= pivot)
# 最终将基准数归位,下面两句等价,因为退出循环时,low==high
alist[end], alist[low] = alist[low], pivot #######
# alist[end], alist[high] = alist[high], pivot
quick_sort(alist, start, low - 1)
quick_sort(alist, low + 1, end)
归并排序
def merge_sort(alist):
"""归并排序"""
n = len(alist)
if n <= 1:
return alist
mid = n // 2
# left_li 采用归并排序后形成的有序的新的列表
left_li = merge_sort(alist[:mid])
# right_li 采用归并排序后形成的有序的新的列表
right_li = merge_sort(alist[mid:])
# 将两个有序的子序列合并为一个新的整体
# merge(left_li,right_li)
left_pointer, right_pointer = 0, 0
result = []
while left_pointer < len(left_li) and right_pointer < len(right_li):
if left_li[left_pointer] <= right_li[right_pointer]:
result.append(left_li[left_pointer])
left_pointer += 1
else:
result.append(right_li[right_pointer])
right_pointer += 1
result += left_li[left_pointer:]
result += right_li[right_pointer:]
return result
a = [1, 2, 5, 4, 6, 7, 9, 3, 6, 0]
b = merge_sort(a)
print(a)
print(b)
归并排序动图演示:
堆排序:
堆排序详解
堆排序的Python实现(附详细过程图和讲解)
堆排序的理解分析
堆排序算法之初始堆建立总结
这里的排序是指排序成非递减数列(升序)(用大根堆)。
非递增数列(降序)(用小根堆)
堆排序的基本思路:
- 首先将待排序的数组构造出一个大根堆
- 取出这个大根堆的堆顶节点(最大值),与堆的最下最右的元素进行交换,然后把剩下的元素再构造出一个大根堆
- 重复第二步,直到这个大根堆的长度为1,此时完成排序。
我自己的思路:
1.先把待排序数组建成大根堆(从最后一个非叶节点开始,依次往上,调整每一个非叶节点(调整方式是把每一个非叶节点与其较大的子节点交换),调整好的数列第一个元素是最大的)
2.交换第一个和最后一个元素(现在最后一个元素是最大的,已经满足了),再把从第一个到最后一个未排序的元素组成的数组建成大根堆(建大根堆方式和第一步相同)。
3.重复2,直到所有元素都排序好。
图示:
1.根据我的方法调整得到的不是大根堆,充其量只保证根节点是所有元素中最大的:
2.交换元素后,再建大根堆,每一个叶子节点都需要调整,所以时间复杂度是O(n/2)
时间复杂度是O(n/2) + [O(n/2)+O((n-1)/2)+O((n-2)/2)+…O(1/2) = O(n/2)+O((n^2+n/4)/4) = O(n^2)(我这种思路是不对的,堆排序时间复杂度应该是O(nlgn),我的方法根本不是堆排序)
def heap_sort(alist):
length = len(alist) - 1
not_leaf_node = length // 2
# 根据alist建一个大根堆(调整每一个非叶节点)
# 从最后一个非叶节点开始往上调整
for i in range(not_leaf_node):
heap_adjust(alist, not_leaf_node - i, length)
print(alist[1:], end='\n\n')
for i in range(length, 0, -1):
# 把第一个结点(最大值)和最后一个结点交换
alist[1], alist[i] = alist[i], alist[1]
print(alist[1:])
# 把最后的已排序好的元素去掉,重新把前面未排序的alist再调整为大根堆
# 从最后一个非叶节点开始往上调整,和上面的思路一样
for j in range(not_leaf_node):
heap_adjust(alist, not_leaf_node - j, i - 1)
print(alist[1:], end='\n\n')
return alist[1:]
def heap_adjust(alist, i, j):
# 三个判断是为了让已经排序好的在最后的元素不再进行调整
if j < 2 * i:
pass
elif j == 2 * i:
if alist[j] > alist[i]:
alist[i], alist[j] = alist[j], alist[i]
else:
if alist[2 * i] > alist[i] or alist[2 * i + 1] > alist[i]:
if alist[2 * i] >= alist[2 * i + 1]:
alist[2 * i], alist[i] = alist[i], alist[2 * i]
else:
alist[2 * i + 1], alist[i] = alist[i], alist[2 * i + 1]
if __name__ == '__main__':
alist = [50, 16, 30, 10, 60, 90, 2, 80, 70]
alist.insert(0, 0)
print(heap_sort(alist))
堆排序的正确思路:
1.先把待排序数组建成大根堆(从最后一个非叶节点开始,依次往上,调整每一个非叶节点,但与我之前的想法不同的是,调整每个非叶节点之后,还要注意,此次的调整会不会影响当前非叶节点的非叶 子节点
,还要从当前非叶节点,一直往下,到最后一个非叶节点,都要保证每一个非叶节点的值都大于其子节点的值
,调整好的数列第一个元素是最大的,且每个元素的值大于其左右子节点的值,具体操作如下图1)
2.交换第一个和最后一个元素(现在最后一个元素是最大的,已经满足了),再把从第一个到最后一个未排序的元素组成的数组建成大根堆(这里建大根堆方式和第一步中的方式不太一样,而是从根节点开始,往下调整,直到调整到上一次调整后的已排序元素的父节点为止,注:由于之前已经是一个堆了,现在最多只是首元素需要调整,只需要沿着一条路径,O(lgn)复杂度就可以了)。
3.重复2,直到所有元素都排序好。
图示:
1.
2.
从根节点2开始往下交换,(与其较大
的子节点进行交换),只会沿一条线,所以时间复杂度为O(logn)
时间复杂度是O(n)[建堆] + O(n*lgn)[首尾交换后,调整堆] = O(nlgn)
参考:
from collections import deque
def swap_param(L, i, j):
L[i], L[j] = L[j], L[i]
return L
def heap_adjust(L, start, end):
temp = L[start]
i = start
j = 2 * i
while j <= end:
if (j < end) and (L[j] < L[j + 1]):
j += 1
if temp < L[j]:
L[i] = L[j]
i = j
j = 2 * i
else:
break
L[i] = temp
def heap_sort(L):
L_length = len(L) - 1
first_sort_count = L_length // 2
for i in range(first_sort_count):
heap_adjust(L, first_sort_count - i, L_length)
for i in range(L_length - 1):
L = swap_param(L, 1, L_length - i)
heap_adjust(L, 1, L_length - i - 1)
return [L[i] for i in range(1, len(L))]
def main():
L = deque([50, 16, 30, 10, 60, 90, 2, 80, 70])
L.appendleft(0)
print heap_sort(L)
if __name__ == '__main__':
main()
### 比较一下各个排序算法的大概时间:
import numpy as np
import timeit
"""比较一下每个算法的时间"""
x = np.arange(10000)
# 数字个数还不够多,所以只是简单的比较一下
x = np.random.permutation(x)
sort_1 = timeit.Timer("bubble_sort.bubble_sort_1(x.copy())", "from __main__ import x;import bubble_sort")
print("bubble_sort:", sort_1.timeit(number=1), "seconds")
sort_2 = timeit.Timer("selection_sort.selection_sort(x.copy())", "from __main__ import x;import selection_sort")
print("selection_sort:", sort_2.timeit(number=1), "seconds")
sort_3 = timeit.Timer("insertion_sort.insertion_sort(x.copy())", "from __main__ import x;import insertion_sort")
print("insertion_sort:", sort_3.timeit(number=1), "seconds")
sort_4 = timeit.Timer("shell_sort.shell_sort(x.copy())", "from __main__ import x;import shell_sort")
print("shell_sort:", sort_4.timeit(number=1), "seconds")
sort_5 = timeit.Timer("quick_sort.quick_sort(x.copy(),0,len(x)-1)", "from __main__ import x;import quick_sort")
print("quick_sort:", sort_5.timeit(number=1), "seconds")
sort_6 = timeit.Timer("merge_sort.merge_sort(list(x.copy()))", "from __main__ import x;import merge_sort")
print("merge_sort:", sort_6.timeit(number=1), "seconds")
sort_7 = timeit.Timer("heap_sort.heap_sort(list(x.copy()))", "from __main__ import x;import heap_sort")
print("heap_sort:", sort_7.timeit(number=1), "seconds")
执行两次,输出结果如下:
第一次结果:
bubble_sort: 41.37893530824798 seconds
selection_sort: 36.76040679346766 seconds
insertion_sort: 15.426445501773813 seconds
shell_sort: 7.004434187005941 seconds
quick_sort: 0.09137854267068235 seconds
merge_sort: 0.08412521788113736 seconds
heap_sort: 0.08508587114629051 seconds
第二次结果:
bubble_sort: 44.71397844733827 seconds
selection_sort: 39.21281035055468 seconds
insertion_sort: 15.52704762458194 seconds
shell_sort: 13.61129115725214 seconds
quick_sort: 0.0906817418554624 seconds
merge_sort: 0.08633759973321276 seconds
heap_sort: 0.07730641200717514 seconds
可以看出,快排,归并排序和堆排序确实都很快。
又分别试了100000,1000000,10000000个随机数的三者排序时间,结果如下:
100000个随机数排序:
quick_sort: 1.535183860396925 seconds
merge_sort: 1.329339947444152 seconds
heap_sort: 1.2203657445180598 seconds
1000000个随机数排序:
quick_sort: 13.63125887197319 seconds
merge_sort: 13.200295944027681 seconds
heap_sort: 14.405367931509346 seconds
10000000个随机数排序:
quick_sort: 179.77304082357566 seconds
merge_sort: 159.00747503413984 seconds
heap_sort: 202.5589797914676 seconds
计数排序、基数排序、桶排序则属于非比较排序,算法时间复杂度O(n),优于比较排序。但是也有弊端,会多占用一些空间,相当于是用空间换时间。
比较和非比较的区别
- 常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置。
- 在冒泡排序之类的排序中,问题规模为n,又因为需要比较n次,所以平均时间复杂度为O(n²)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为logN次,所以时间复杂度平均O(nlogn)。
比较排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。 - 计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr之前有多少个元素,则唯一确定了arr在排序后数组中的位置。
非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n)。 - 非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。
1.计数排序(适用于元素个数有限):
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为i的元素出现的次数,存入数组counting_list的第i项;
- 对所有的计数累加(从counting_list中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素i放在新数组的counting_list[i]下标处,每放一个元素就将counting_list(i)减去1(下一个相同的元素的位置)。
# 计数排序
def counting_sort(alist): # k = max(a)
n = len(alist) # 计算a序列的长度
out = [0 for _ in range(n)] # 设置输出序列并初始化为0
counting_list = [0 for _ in range(max(alist) + 1)] # 设置计数序列并初始化为0,
# 统计alist中每个元素出现的次数,例如counting_list[3]=4,代表在alist中3出现了4次
for elem in alist:
counting_list[elem] = counting_list[elem] + 1
# print(counting_list) #[0, 2, 8, 2, 2, 0, 1, 2, 2, 2]
# 对于每一个元素i,统计小于等于i的元素个数,例如,counting_list[2]=10,代表小于等于2的元素共有10个
for i in range(1, len(counting_list)):
counting_list[i] += counting_list[i - 1]
# print(counting_list) [0, 2, 10, 12, 14, 14, 15, 17, 19, 21]
for elem in alist:
# 注意elem对应的下标要减1,例如,counting_list[2]=10
# 那么元素2在新的数组out中占据的位置是[10-alist.count(2),9],即2-9
out[counting_list[elem] - 1] = elem
counting_list[elem] = counting_list[elem] - 1
return out
alist = [2, 2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2]
print(counting_sort(alist))
# [0, 2, 8, 2, 2, 0, 1, 2, 2, 2]
# [0, 2, 10, 12, 14, 14, 15, 17, 19, 21]
# [1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8, 9, 9]
2.桶排序:
3.基数排序:
# 基数排序
"""基数排序适用于:
(1)数据范围较小,建议在小于1000
(2)每个数值都要大于等于0"""
def radix_sort(alist, maxdigit):
"""
:param alist:
:param maxdigit: 最大数据位数
:return:
"""
for i in range(maxdigit): # maxdigit轮排序
s = [[] for _ in range(10)] # 因每一位数字都是0~9,建10个桶
for elem in alist:
# 从每个元素的最低位开始进行排序
s[elem // (10 ** i) % 10].append(elem)
alist = [a for b in s for a in b]
print(alist)
return alist
alist = [3, 447, 318, 15, 47, 125, 36, 264, 275, 32, 46, 4, 149, 50, 48]
print(radix_sort(alist, 3))
# [50, 32, 3, 264, 4, 15, 125, 275, 36, 46, 447, 47, 318, 48, 149] #个位排序
# [3, 4, 15, 318, 125, 32, 36, 46, 447, 47, 48, 149, 50, 264, 275] #十位排序
# [3, 4, 15, 32, 36, 46, 47, 48, 50, 125, 149, 264, 275, 318, 447] #百位排序
# [3, 4, 15, 32, 36, 46, 47, 48, 50, 125, 149, 264, 275, 318, 447]