八大经典排序-python实现
Table of Contents
本博客只用于自身学习,如有错误,虚心求教!!!
简介
关于时间复杂度:
- 平方阶 (O(n2)) 排序:各类简单排序:直接插入、直接选择和冒泡排序。
- 线性对数阶 (O(nlog2n)) 排序:快速排序、堆排序和归并排序;
- O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数: 希尔排序
- 线性阶 (O(n)) 排序: 基数排序,此外还有桶、箱排序。
关于稳定性:
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
名词解释:
n:数据规模
k:“桶”的个数
In-place:占用常数内存,不占用额外内存
Out-place:占用额外内存
稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同
一、冒泡排序
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。
1. 算法步骤
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
2. 动图演示
3.python代码实现
# 冒泡排序
def bubble_sort(alist):
# i 控制总共交换的轮数(每轮选出一个最大的移动最后)
# if len(alist) = 9 的话,只需要 8 轮交换即可
for i in range(len(alist)-1):
print('##############, i = %d' %i)
# j控制: 选出最大的, 将最大移到最后
for j in range(len(alist)-1-i):
if(alist[j] > alist[j+1]): # 前面 > 后面 交换
alist[j], alist[j+1] = alist[j+1], alist[j]
print(alist)
out:
原列表为:[54, 26, 93, 18, 17, 31, 44, 55, 20]
##############, i = 0 # 选出最大的 93 移到最后
[26, 54, 93, 18, 17, 31, 44, 55, 20]
[26, 54, 93, 18, 17, 31, 44, 55, 20]
[26, 54, 18, 93, 17, 31, 44, 55, 20]
[26, 54, 18, 17, 93, 31, 44, 55, 20]
[26, 54, 18, 17, 31, 93, 44, 55, 20]
[26, 54, 18, 17, 31, 44, 93, 55, 20]
[26, 54, 18, 17, 31, 44, 55, 93, 20]
[26, 54, 18, 17, 31, 44, 55, 20, 93]
##############, i = 1 # # 选出次大的 55
[26, 54, 18, 17, 31, 44, 55, 20, 93]
[26, 18, 54, 17, 31, 44, 55, 20, 93]
[26, 18, 17, 54, 31, 44, 55, 20, 93]
[26, 18, 17, 31, 54, 44, 55, 20, 93]
[26, 18, 17, 31, 44, 54, 55, 20, 93]
[26, 18, 17, 31, 44, 54, 55, 20, 93]
[26, 18, 17, 31, 44, 54, 20, 55, 93]
##############, i = 2
[18, 26, 17, 31, 44, 54, 20, 55, 93]
[18, 17, 26, 31, 44, 54, 20, 55, 93]
[18, 17, 26, 31, 44, 54, 20, 55, 93]
[18, 17, 26, 31, 44, 54, 20, 55, 93]
[18, 17, 26, 31, 44, 54, 20, 55, 93]
[18, 17, 26, 31, 44, 20, 54, 55, 93]
##############, i = 3
[17, 18, 26, 31, 44, 20, 54, 55, 93]
[17, 18, 26, 31, 44, 20, 54, 55, 93]
[17, 18, 26, 31, 44, 20, 54, 55, 93]
[17, 18, 26, 31, 44, 20, 54, 55, 93]
[17, 18, 26, 31, 20, 44, 54, 55, 93]
##############, i = 4
[17, 18, 26, 31, 20, 44, 54, 55, 93]
[17, 18, 26, 31, 20, 44, 54, 55, 93]
[17, 18, 26, 31, 20, 44, 54, 55, 93]
[17, 18, 26, 20, 31, 44, 54, 55, 93]
##############, i = 5
[17, 18, 26, 20, 31, 44, 54, 55, 93]
[17, 18, 26, 20, 31, 44, 54, 55, 93]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
##############, i = 6
[17, 18, 20, 26, 31, 44, 54, 55, 93]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
##############, i = 7
[17, 18, 20, 26, 31, 44, 54, 55, 93]
新列表为:[17, 18, 20, 26, 31, 44, 54, 55, 93]
二、选择排序
选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
1. 算法步骤
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
2. 动图演示
3. Python 代码实现
def select_sort(alist):
for i in range(len(alist)-1):
min = i
# 在未排序中 选出最小的
for j in range(i+1, len(alist)):
if(alist[j] < alist[min]):
min = j
alist[min], alist[i] = alist[i], alist[min]
out:
原列表为:[54, 26, 93, 18, 17, 31, 44, 55, 20]
[17, 26, 93, 18, 54, 31, 44, 55, 20]
[17, 18, 93, 26, 54, 31, 44, 55, 20]
[17, 18, 20, 26, 54, 31, 44, 55, 93]
[17, 18, 20, 26, 54, 31, 44, 55, 93]
[17, 18, 20, 26, 31, 54, 44, 55, 93]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
新列表为:[17, 18, 20, 26, 31, 44, 54, 55, 93]
三、插入排序
插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
1. 算法步骤
- 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
2. 动图演示
3. Python 代码实现
def insert_sort(alist):
# i控制的是 未排序数组(角标)
for i in range(1, len(alist)):
# j 控制将拿到的元素放到前面有序序列中正确位置的过程
for j in range(i, 0, -1):
# 如果比前面的元素小,则往前移动
if alist[j] < alist[j - 1]:
alist[j], alist[j - 1] = alist[j - 1], alist[j]
# 否则代表比前面的所有元素都小,不需要再移动
else:
break
out:
原列表为:[54, 26, 93, 18, 17, 31, 44, 55, 20]
[26, 54, 93, 18, 17, 31, 44, 55, 20]
[26, 54, 93, 18, 17, 31, 44, 55, 20]
[18, 26, 54, 93, 17, 31, 44, 55, 20]
[17, 18, 26, 54, 93, 31, 44, 55, 20]
[17, 18, 26, 31, 54, 93, 44, 55, 20]
[17, 18, 26, 31, 44, 54, 93, 55, 20]
[17, 18, 26, 31, 44, 54, 55, 93, 20]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
新列表为:[17, 18, 20, 26, 31, 44, 54, 55, 93]
四、希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
1. 算法步骤
- 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
- 按增量序列个数 k,对序列进行 k 趟排序;
- 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
2. 排序过程
希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。
例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样(竖着的元素是步长组成):
13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10
然后我们对每列进行排序:
10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45
将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:
10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45
排序之后变为:
10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94
最后以1步长进行排序(此时就是简单的插入排序了)
3. Python 代码实现
# 希尔排序
def shell_sort(alist):
'''
分组之后更容易理解,对每列进行插入排序
'''
# 初始步长
gap = len(alist) // 2
while gap > 0:
print('###############,gap为:%d' %gap)
for i in range(gap, len(alist)):
# 每个步长进行插入排序
temp = alist[i]
j = i
# 插入排序 (分组,每组发生交换的执行)
while j >= gap and alist[j - gap] > temp:
alist[j] = alist[j - gap]
j -= gap
alist[j] = temp
print(alist)
# 得到新的步长
gap = gap // 2
out:
原列表为:[54, 26, 93, 18, 17, 31, 44, 55, 20]
###############,gap为:4
[17, 26, 93, 18, 54, 31, 44, 55, 20] # 54 > 17 交换
[17, 26, 93, 18, 54, 31, 44, 55, 20] # 26 < 31 不交换
[17, 26, 44, 18, 54, 31, 93, 55, 20] # 93 > 44 交换
[17, 26, 44, 18, 54, 31, 93, 55, 20] # 18 < 55 不交换
[17, 26, 44, 18, 20, 31, 93, 55, 54] # 17 > 20 > 54 交换
###############,gap为:2
[17, 26, 44, 18, 20, 31, 93, 55, 54]
[17, 18, 44, 26, 20, 31, 93, 55, 54]
[17, 18, 20, 26, 44, 31, 93, 55, 54]
[17, 18, 20, 26, 44, 31, 93, 55, 54]
[17, 18, 20, 26, 44, 31, 93, 55, 54]
[17, 18, 20, 26, 44, 31, 93, 55, 54]
[17, 18, 20, 26, 44, 31, 54, 55, 93]
###############,gap为:1
[17, 18, 20, 26, 44, 31, 54, 55, 93]
[17, 18, 20, 26, 44, 31, 54, 55, 93]
[17, 18, 20, 26, 44, 31, 54, 55, 93]
[17, 18, 20, 26, 44, 31, 54, 55, 93]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
[17, 18, 20, 26, 31, 44, 54, 55, 93]
新列表为:[17, 18, 20, 26, 31, 44, 54, 55, 93]
[Finished in 0.0s]
五、归并排序
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。且各层分治递归可以同时进行。
采用 divide-and-conquer approach:
Divide: 把长度为 n 的 array 分成两半。
Conquer: 把左半边 array 及右半边 array 分别以 Merge Sort 进行 sorting。 (recursive)
Combine: merge 左半边及右半边 sorted array。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
- 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
- 自下而上的迭代;
1. 算法步骤
递归法(Top-down)
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针到达序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
迭代法(Bottom-up)
2. 动图演示
当最左边的分到最细之后无法再划分左右然后开始进行合并。
第一次组合完成[4, 7]的合并
第二次组合完成[4, 7, 8]的合并
第三次组合完成[3, 5]的合并
第四次组合完成[3, 5, 9]的合并
第五次组合完成[3, 4, 5, 7, 8, 9]的合并结束排序。
3. Python 代码实现
非递归实现:
def merge_sort(alist):
for i in range(1, len(alist)):
for low in range(0, len(alist)):
mid = low + i
height = min(low + 2 * i, len(alist))
if mid < height:
left = seq[low:mid]
right = seq[mid:height]
alist[low:height] = merge(left, right)
low += 2 * i
i *= 2
return alist
def merge(left, right):
result = []
while left and right:
if left[0] <= right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
if left:
result += left
if right:
result += right
return result
if __name__ == '__main__':
seq = [5, 4, 3, 0, 1, 2, 7, 5, 11,9]
print(merge_sort(seq))
递归实现:
# 合并操作——递归实现
def merge_sort(alist):
if len(alist) <= 1:
return alist
mid = len(alist) // 2
left = alist[:mid]
right = alist[mid:]
left = merge_sort(left)
right = merge_sort(right)
return merge(left, right)
def merge(left, right):
result = []
while left and right:
if left[0] <= right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
if left:
result += left
if right:
result += right
return result
六、快速排序
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好,可是这是为什么呢,我也不知道。好在我的强迫症又犯了,查了 N 多资料终于在《算法艺术与信息学竞赛》上找到了满意的答案:
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
1. 算法步骤
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
2. 动图演示
快速排序采用的是一种分而治之的思想
平均时间复杂度:O(nlgn)
最差时间复杂度:O(n^2)
3. Python代码实现
# 快排算法
def quick_sort(alist):
if len(alist) < 2: # 基线条件
return alist
else: # 递归条件
pivot = alist[0]
less = [i for i in alist[1:] if i <= alist[0]]
greater = [i for i in alist[1:] if i > alist[0]]
print(less + [pivot] + greater)
return quick_sort(less) + [pivot] + quick_sort(greater)
out:
alist = [11, 3, 14, 5, 21, 2, 2]
print('alist快速排序之后:', quick_sort(alist))
# out:
[3, 5, 2, 2, 11, 14, 21]
[2, 2, 3, 5]
[2, 2]
[14, 21]
alist快速排序之后: [2, 2, 3, 5, 11, 14, 21]
解法2:
#coding:utf-8
def quick_sort(lists, left, right):
'''快速排序'''
# 跳出递归判断
if left >= right:
return lists
# 选择参考点,该调整范围的第1个值
key = lists[left]
low = left
high = right
# 循环判断直到遍历全部
while left < right:
# 从右边开始查找大于(实际为小于)参考点的值
while left < right and lists[right] >= key:
right -= 1
lists[left] = lists[right] # 这个位置的值先挪到左边
# 从左边开始查找小于(实际为大于)参考点的值
while left < right and lists[left] < key:
left += 1
lists[right] = lists[left] # 这个位置的值挪到右边
# 写回改成的值
lists[left] = key
# 递归,并返回结果
quick_sort(lists, low, left - 1) # 递归左边部分
quick_sort(lists, left + 1, high) # 递归右边部分
return lists
解法2-另写
def partition(alist, left, right):
'''快速排序'''
# 选择参考点,该调整范围的第1个值
key = alist[left]
# 循环判断直到遍历全部
while left < right:
# 从右边开始查找大于(实际为小于)参考点的值
while left < right and alist[right] >= key:
right -= 1
alist[left] = alist[right] # 这个位置的值先挪到左边
# 从左边开始查找小于(实际为大于)参考点的值
while left < right and alist[left] < key:
left += 1
alist[right] = alist[left] # 这个位置的值挪到右边
# 写回改成的值
alist[left] = key
return left
def quick_sort(alist, left, right):
if left >= right:
return alist
j = partition(alist, left, right)
quick_sort(alist, left, j - 1) # 递归左边部分
quick_sort(alist, j + 1, right) # 递归右边部分
return alist
if __name__ == '__main__':
seq = [5, 4, 3, 0, 1, 2, 7, 5, 11,9]
print(quick_sort(seq, 0, len(seq)-1))
解法2-另写
def partition(alist, left, right):
'''快速排序'''
# 选择参考点,该调整范围的第1个值
key = left
# 循环判断直到遍历全部
while left < right:
# 从右边开始查找大于(实际为小于)参考点的值
while left < right and alist[right] >= key:
right -= 1
#alist[left] = alist[right] # 这个位置的值先挪到左边
# 从左边开始查找小于(实际为大于)参考点的值
while left < right and alist[left] < key:
left += 1
#alist[right] = alist[left] # 这个位置的值挪到右边
alist[left], alist[right] = alist[right], alist[left]
# 写回改成的值
#alist[left] = key
alist[left], alist[key] = alist[key], alist[left]
return left
def quick_sort(alist, left, right):
if left >= right:
return alist
j = partition(alist, left, right)
quick_sort(alist, left, j - 1) # 递归左边部分
quick_sort(alist, j + 1, right) # 递归右边部分
return alist
if __name__ == '__main__':
seq = [5, 4, 3, 0, 1, 2, 7, 5, 11,9]
print(quick_sort(seq, 0, len(seq)-1))
快排的平均情况和最糟情况
七、堆排序
参考:https://blog.****.net/minxihou/article/details/51850001
https://blog.****.net/dujiahaogod/article/details/79688331
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
- 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
- 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
堆排序的平均时间复杂度为 Ο(nlogn)。
其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
- 将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn)
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成
堆可以分为大根堆和小根堆,这里用最大堆的情况来定义操作:
(1)最大堆调整(MAX_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点。这是核心步骤,在建堆和堆排序都会用到。比较i的根节点和与其所对应i的孩子节点的值。当i根节点的值比左孩子节点的值要小的时候,就把i根节点和左孩子节点所对应的值交换,当i根节点的值比右孩子的节点所对应的值要小的时候,就把i根节点和右孩子节点所对应的值交换。然后再调用堆调整这个过程,可见这是一个递归的过程。
(2)建立最大堆(Build_Max_Heap):将堆所有数据重新排序。建堆的过程其实就是不断做最大堆调整的过程,从len/2出开始调整,一直比到第一个节点。
(3)堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算。堆排序是利用建堆和堆调整来进行的。首先先建堆,然后将堆的根节点选出与最后一个节点进行交换,然后将前面len-1个节点继续做堆调整的过程。直到将所有的节点取出,对于n个数我们只需要做n-1次操作。
堆排序,顾名思义,就是基于堆。因此先来介绍一下堆的概念。
堆分为最大堆和最小堆,其实就是完全二叉树。最大堆要求节点的元素都要大于其孩子,最小堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求,其实很好理解。有了上面的定义,我们可以得知,处于最大堆的根节点的元素一定是这个堆中的最大值。其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
节点 i 的左孩子索引: 2*i+1
节点 i 的右孩子索引: 2*i+2
结点 i 的父节点索引 :(i-1)/2
1. 算法步骤
- 创建一个堆 H[0……n-1];
- 把堆首(最大值)和堆尾互换;
- 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
- 重复步骤 2,直到堆的尺寸为 1。
堆排序就是把堆顶的最大数取出,
将剩余的堆继续调整为最大堆,以递归实现
剩余部分调整为最大堆后,再次将堆顶的最大数取出,再将剩余部分调整为最大堆,这个过程持续到剩余数只有一个时结束
在堆的数据结构中,堆中的最大值总是位于根节点。堆中定义以下几种操作:
- 最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
- 创建最大堆(Build Max Heap):将堆中的所有数据重新排序
- 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
2. 动图演示
另一个堆排序动画展示:https://bajdcc.github.io/html/heap.html
3. Python代码实现(画树状图容易理解)
版本1
def heap_sort(lst):
#生成最大堆
for start in range(len(lst)//2-1, -1, -1):
shift_heap(lst, start, len(lst)-1)
#最大堆调整
for end in range(len(lst)-1, 0, -1):
lst[0], lst[end] = lst[end], lst[0]
shift_heap(lst, 0, end - 1)
return lst
def shift_heap(lst, start, end):
root = start
while True:
child = root * 2 + 1
if child > end:
break
if child + 1 <= end and lst[child] < lst[child + 1]:
child = child + 1
if lst[root] < lst[child]:
lst[root], lst[child] = lst[child], lst[root]
root = child
else:
break
lst = [1,2,3,5,4,2,3,5,6]
# lst = [3,3,2,2,1,4,1,4]
print(heap_sort(lst))
版本2
# 堆排序
def heap_sort(alist):
def max_heapify(start, end):
"""最大堆调整"""
root = start
while True:
child = 2 * root + 1
# child角标 超出角标最大值
if child > end:
break
# 左节点 < 右节点 child改为右节点角标
if child + 1 <= end and alist[child] < alist[child + 1]:
child += 1
# 根节点值 < child节点值 交换
if alist[root] < alist[child]:
alist[root], alist[child] = alist[child], alist[root]
root = child
else:
break
# 创建最大堆(保证了 根节点 大于 子节点!!!!!!!!!!!)
# (len(alist)-2)//2 = len(alist)//2 - 1 为根节点,从最底层根节点开始
for start in range((len(alist) - 2) // 2, -1, -1):
max_heapify(start, len(alist) - 1)
print(alist)
# 堆排序
for end in range(len(alist) - 1, 0, -1):
alist[0], alist[end] = alist[end], alist[0]
max_heapify(0, end - 1)
print('########')
print(alist)
return alist
out:
原列表为:[54, 26, 93, 18, 17, 31, 44, 55, 20]
[54, 26, 93, 55, 17, 31, 44, 18, 20] #坐标 3<->7 换,18<->55
[54, 26, 93, 55, 17, 31, 44, 18, 20] #坐标 2<->5 换,
[54, 55, 93, 26, 17, 31, 44, 18, 20] #坐标 1<->3 换,26<->55
[93, 55, 54, 26, 17, 31, 44, 18, 20] #坐标 0<->2 换,54<->93
########
[55, 26, 54, 20, 17, 31, 44, 18, 93]
########
[54, 26, 44, 20, 17, 31, 18, 55, 93]
########
[44, 26, 31, 20, 17, 18, 54, 55, 93]
########
[31, 26, 18, 20, 17, 44, 54, 55, 93]
########
[26, 20, 18, 17, 31, 44, 54, 55, 93]
########
[20, 17, 18, 26, 31, 44, 54, 55, 93]
########
[18, 17, 20, 26, 31, 44, 54, 55, 93]
########
[17, 18, 20, 26, 31, 44, 54, 55, 93]
新列表为:[17, 18, 20, 26, 31, 44, 54, 55, 93]
八、计数排序
参考:https://blog.****.net/wardseptember/article/details/81434641
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
1. 算法步骤
2. 动图演示
3. Python代码实现
# 计数排序
def count_Sort(alist):
alist_len = len(alist)
alist_max = max(alist)#返回最大值
alist_min = min(alist)#返回最小值
#计算待排序列表的元素数值区域长度,如4-9共9+1-4=6个数
count_alist_length = alist_max + 1 - alist_min
count_alist = [0] * count_alist_length #构造一个全为0列表
for number in alist:
count_alist[number - alist_min] += 1 #统计列表中每个值出现的次数,
#使counting_arr[i]存放<=i的元素个数,就是待排序列表中比某个值小的元素有多少个
for i in range(1, count_alist_length):
count_alist[i] = count_alist[i] + count_alist[i-1]
order_list = [0] * alist_len #存放排序结果
#使每个元素被放在ordered中正确的位置,升序
for i in range(alist_len):
order_list[count_alist[alist[i] - alist_min]-1] = alist[i]#-1是因为下标从0开始的
count_alist[alist[i] - alist_min] -= 1 #每归位一个元素,就少一个元素
return order_list
参考:https://github.com/hustcc/JS-Sorting-Algorithm
https://zh.wikipedia.org/wiki/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95
https://blog.****.net/mxz19901102/article/details/80087596
https://blog.****.net/aiya_aiya_/article/details/79846380