前言
排序算法
排序算法是程序员平时编程中最常见也是最基本的算法之一
常见的排序算法包括:冒泡排序、选择排序、插入排序、归并排序、快速排序、计数排序、堆排序、希尔排序、桶排序、基数排序等等。他们的用途、时间复杂度和空间复杂度都有差别,本文将和大家一起学习探讨上述这些排序算法的代码以及相关特征。
一、冒泡排序
1、概述:
冒泡排序是一种常见且常用的排序算法。从无序区通过一次一次交换,将最大的元素放到有序区域内,就像泡泡,最大的泡泡先上浮。
(无序区域->有序区域)
2、原理:
- 比较相邻的元素大小,如果说前一个比后一个数大,就交换,反之不交换
- 对数组做顺序遍历操作,每对相邻的元素都做相同的操作,以此类推一直操作到最后一个元素,此时最大的数就像水里的泡泡一样,来到数组的最后一位
- 重新回到第一位,再重复上述操作,到倒数第二位停下(因为最后一位已经是最大了),以此类推
- 持续做上述操作,直到数组排序完成
3、代码
#冒泡排序
def bubble_sort(arr): # arr是需要排序的数组
lenth = len(arr)
# 外循环:未排序的区间为[0, i]
for i in range(lenth):
# 内循环将未排序区域的[0, i]中的最大值交换至该区间的最右侧
for j in range(lenth - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
if __name__ == '__main__':
tmp = [1,3,5,64,2,54,89,2,56,10]
bubble_sort(tmp)
print(tmp)
输出结果:[1, 2, 2, 3, 5, 10, 54, 56, 64, 89]
通过程序我们可以发现,在排序过程中,如果已经排序完成,代码依旧会一直走下去,于是我们可以加一个标志位,当一次冒泡过程中没有出现任何的元素交换,则代表排序完成,可以减少程序运行速度。
# 优化版本
def bubble_sort_1(arr): # arr是需要排序的数组
lenth = len(arr)
# 外循环:未排序的区间为[0, i]
for i in range(lenth):
flag = False
# 内循环将未排序区域的[0, i]中的最大值交换至该区间的最右侧
for j in range(lenth - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
flag = True
if not flag: # 增加判断,如果本轮没有交换任何元素,说明排序完成,直接跳出
break
if __name__ == '__main__':
tmp = [1,3,5,64,2,54,89,2,56,10]
bubble_sort_1(tmp)
print(tmp)
输出结果:[1, 2, 2, 3, 5, 10, 54, 56, 64, 89]
4、特征
时间复杂度:O(n²)
空间复杂度:O(1)
稳定排序:在冒泡过程中遇到相等的元素不会交换
内部排序
二、选择排序
1、概述:
是一种比较直观的排序方式,不过由于算法时间复杂度较高,适合数据量较少的时候。在无序区域找一个最小值(最大值)放在有序区域的后面(前面),相当于在无序区域遍历寻找符合条件的数字来扩充有序区域。
2、原理:
- 先在数组中遍历找出找出最大或者最小的元素,将数字放在数组的起始位置(0还是lenth - 1取决于想要从到大还是从大到小)
- 再从剩下的数组中寻找最大或者最小的元素,排到还未排序的末端
- 重复上述步骤,一直到所有的元素排序完成
3、代码
#选择排序
def selection_sort(arr):
lenth = len(arr)
# 外循环是未排序的区间为[i, n - 1]
for i in range(lenth - 1):
tmp_min = i
# 内循环是未找到排序区间内的最小元素
for j in range(i+1, lenth):
if arr[j] < arr[tmp_min]:
tmp_min = j
# 将最小元素和未排序区间的首个元素交换
arr[i], arr[tmp_min] = arr[tmp_min], arr[i]
if __name__ == '__main__':
tmp = [1,3,5,64,2,54,89,2,56,10]
selection_sort(tmp)
print(tmp)
输出结果:[1, 2, 2, 3, 5, 10, 54, 56, 64, 89]
4、特征
时间复杂度:O(n²)
空间复杂度:O(1)
内部排序
三、插入排序
1、概述:
是一种直观的算法,将第一个元素看作有序的或者可以当作一个标杆,后续的元素都是无序的,将后续元素和第一个元素比较,小的话放到第一个元素左边(交换位置或者值),大的话放在第一个元素右边,这样就扩充了有序序列,再从头开始以此类推。
2、原理:
- 选择第一个元素为有序序列
- 向后取一个元素A,在有序序列中从后向前扫描,遇到小于该元素的元素B
- 把该元素A插在已经排序的元素B的后面,形成新的有序序列
- 重复上述动作,不断的将无序元素插入有序序列中,最终形成完整的排序
3、代码:
# 插入排序
def insertion_sort(arr):
lenth = len(arr)
# 外循环已经排序区间为[0, i-1]
for i in range(1, lenth):
tmp = arr[i]
j = i - 1
# 内循环将tmp值插入到已经排序的[0, i-1]区间的正确位置
while j >= 0 and arr[j] > tmp:
arr[j + 1] = arr[j]
j -= 1
# 将tmp值赋值到正确的位置
arr[j + 1] = tmp
if __name__ == '__main__':
tmp = [100, 1, 3, 5, 64, 2, 54, 89, 2, 56, 10]
insertion_sort(tmp)
print(tmp)
输出结果:[1, 2, 2, 3, 5, 10, 54, 56, 64, 89, 100]
4、特征
时间复杂度:O(n²)
空间复杂度:O(1)
内部排序
在数据量较小时,速度较快
四、归并排序
1、概述
采用分治法的一种排序方法。就是把已经排好序的子序列进行合并,得到一个完整的有序序列,相对于先排序一片区域等排序完区域再拼接。
2、原理
- 将长度为length的序列分成数目相等的两份子序列(lenth/2)
- 对这两个子序列分别进行归并排序
- 将两个自序列合并成为一个最终排好序的序列
3、代码
#归并排序
def merge_sort(arr): # arr是需要排序的数组
lenth = len(arr)
if (lenth <= 1):# 递归的边界条件
return arr
mid = lenth // 2# 求出数组个数的中位数,以此下标为界限分割数组
llist, rlist = Merge_Sort(arr[:mid]), Merge_Sort(arr[mid:])# 调用函数分别为左右数组排序
result = []
Lenth_Llist = len(llist)
Lenth_Rlist = len(rlist)
i, j = 0, 0
while i < Lenth_Llist and j < Lenth_Rlist:# while循环合并两个有序数组
if rlist[j] < llist[i]:
result.append(rlist[j])
j += 1
else:
result.append(llist[i])
i += 1
result += llist[i:] + rlist[j:]# 把数组末添加的部分加到结果数组末尾
return result
if __name__ == '__main__':
tmp = [100, 1, 3, 5, 64, 2, 54, 89, 2, 56, 10]
print(merge_sort(tmp))
结果:[1, 2, 2, 3, 5, 10, 54, 56, 64, 89, 100]
4、特征
时间复杂度:O(n logn)
空间复杂度:O(n)
外部排序
五、快速排序
1、概述
是处理大数据最快的一种排序方式,因为他的内部循环可以很有效率的实现,所以比一般的O(nlogn)算法更快
2、原理
- 从数列中选出一个元素,作为一个基准
- 重新排序,所有比基准小的放在数组左边,所有比基准大的数字放在数组右边,在这个分区结束后,这个基准数字就在数组的最中间,这个被称为分区操作
- 利用递归把小于基准的子序列和大于基准的子序列进行排序,最终得到一个完整的排序数组
3、代码
#快速排序
def quick_sort(arr, start, end):
if start > end:
return
Base_Value, left, right = arr[start], start, end # 设置基准数字为arr[start]
while left < right:
while arr[right] >= Base_Value and left < right:# 从右向左找到首个小于基准数字的元素
right -= 1
arr[left] = arr[right]# 交换元素
while arr[left] < Base_Value and left < right:# 从左向右找到首个小于基准数字的元素
left += 1
arr[right] = arr[left]# 交换元素
arr[left] = Base_Value # 更新基准数
# 递归左子数组、右子数组
Quick_Sort(arr, start, left - 1)
Quick_Sort(arr, left + 1, end)
if __name__ == '__main__':
tmp = [100, 1, 3, 5, 64, 2, 54, 89, 2, 56, 10]
quick_sort(tmp, 0, len(tmp) - 1)
print(tmp)
结果:[1, 2, 2, 3, 5, 10, 54, 56, 64, 89, 100]
4、特征
时间复杂度:O(n logn)
空间复杂度:O(logn)
内部排序
比一般的O(nlogn)要快
六、总结
排序算法 | 平均时间复杂度 | 空间复杂度 | 排序方式 | 是否稳定 |
---|---|---|---|---|
冒泡排序 | O(n²) | O(1) | 内部排序 | 稳定 |
选择排序 | O(n²) | O(1) | 内部排序 | 不稳定 |
插入排序 | O(n²) | O(1) | 内部排序 | 稳定 |
归并排序 | O(nlogn) | O(n) | 外部排序 | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | 内部排序 | 不稳定 |