Hello大家好!感谢大家的支持!不知道上一篇文章有没有对各位小伙伴们起到一点帮助(应该是有的吧,我还是比较有自信的哈哈哈哈bushi)
好了,咱们话不多说,进入这篇今天文章的主题——排序算法
我们主要讲三种重要且基础、日后我们会经常使用或在题目中看到的排序算法:冒泡、插入、选择
最后会提一下检验算法正确性的方法——对数器
1、冒泡排序
简单来说就是通过重复遍历待排序的列表,依次比较相邻的元素,并根据大小交换它们的位置,直到整个列表有序。
在每次遍历过程中,较大的元素逐渐“冒泡”到数组的末尾,而较小的元素则慢慢“沉淀”到前面。因此,冒泡排序的特点是每次遍历后,数组的后部分变得有序,而前部分仍可能是无序的。
具体步骤如下:
1.从列表的第一个元素开始,比较当前元素和下一个元素。
2.果当前元素比下一个元素大,则交换它们的位置。
3.移动到下一个元素,重复步骤2,直到到达列表的末尾。
4.重复上述过程,忽略每次遍历后最后一个已经排序好的元素。
5.当某次遍历中没有发生任何交换时,排序完成。
代码实现如下:
def bubble_sort(arr):
n = len(arr)
# 遍历数组所有元素
for i in range(n):
# 从头到尾遍历数组,减去i是因为最后i个元素已经排序好
# 减去1是因为防止底下的arr[j+1]跳出数组边界
for j in range(0, n-i-1):
# 如果当前元素比下一个元素大,则交换它们
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
#从小到大排好
# 时间复杂度: O(n^2),因为有两次循环遍历
# 空间复杂度: O(1),冒泡排序在原地对数组进行排序,因此不需要额外的存储空间来存放输入数据。
这里再给一个实例,大家可以对照着看:
以数组 [5, 2, 9, 1, 5, 6]
为例:
第一次外层循环 i=0
- 内层循环范围:
range(0, n-1)
即range(0, 5)
- 比较和交换:
- 比较
arr[0]
和arr[1]
:5 和 2,交换[2, 5, 9, 1, 5, 6]
- 比较
arr[1]
和arr[2]
:5 和 9,不交换 - 比较
arr[2]
和arr[3]
:9 和 1,交换[2, 5, 1, 9, 5, 6]
- 比较
arr[3]
和arr[4]
:9 和 5,交换[2, 5, 1, 5, 9, 6]
- 比较
arr[4]
和arr[5]
:9 和 6,交换[2, 5, 1, 5, 6, 9]
- 比较
第二次外层循环 i=1
- 内层循环范围:
range(0, n-2)
即range(0, 4)
- 比较和交换:
- 比较
arr[0]
和arr[1]
:2 和 5,不交换 - 比较
arr[1]
和arr[2]
:5 和 1,交换[2, 1, 5, 5, 6, 9]
- 比较
arr[2]
和arr[3]
:5 和 5,不交换 - 比较
arr[3]
和arr[4]
:5 和 6,不交换
- 比较
第三次外层循环 i=2
- 内层循环范围:
range(0, n-3)
即range(0, 3)
- 比较和交换:
- 比较
arr[0]
和arr[1]
:2 和 1,交换[1, 2, 5, 5, 6, 9]
- 比较
arr[1]
和arr[2]
:2 和 5,不交换 - 比较
arr[2]
和arr[3]
:5 和 5,不交换
- 比较
#如果还是不能理解的同学也没有关系,第一次接触肯定会有点懵,就不要去理解把他记下来,等你多练多记多想,后面慢慢就懂了。
2、插入排序
有人说插入排序像打牌,手里拿着一叠牌,新来的一张要放在哪里呢?这个时候就是从左到右视线划过去,寻找对应位置,将牌插入,也就是我们的插入排序。
也可以把它想成倒着的冒泡,冒泡排序是从数组尾部开始有序,而插入排序是从数组前头开始有序。
具体步骤如下:
1.从第一个元素开始,该元素认为已排序。
2.取出下一个元素,在已排序序列中从后向前扫描。
3.如果已排序元素大于新元素,将已排序元素向右移动一位。
4.重复步骤 3,直到找到已排序元素小于或等于新元素的位置。
5.将新元素插入到该位置中。
6.重复步骤 2~5,直到所有元素均插入到有序序列中。
下面是代码展示:
def insertion_sort(arr):
# 从第二个元素开始,因为第一个元素默认是已排序的
for i in range(1, len(arr)):
key = arr[i] # 取出当前要排序的元素
j = i - 1 # 已排序部分的最后一个元素的索引
# 给j加范围保证其不越界
# 从后向前扫描已排序部分
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j] # 将已排序元素向右移动一位
j -= 1
arr[j + 1] = key # 将当前元素插入到正确位置
# 一层for循环一层while循环,O(n^2)
# 插入排序是一个原地排序算法,其空间复杂度为O(1),即只需要常数级别的额外空间。
#以下举一个实例帮助同学们理解:
以数组 [12, 11, 13, 5, 6]
为例:
- 初始状态:[12, 11, 13, 5, 6]
- 第一步:取出第二个元素 11,比较后插入到 12 前面:[11, 12, 13, 5, 6]
- 第二步:取出第三个元素 13,保持不动:[11, 12, 13, 5, 6]
- 第三步:取出第四个元素 5,插入到正确位置:[5, 11, 12, 13, 6]
- 第四步:取出第五个元素 6,插入到正确位置:[5, 6, 11, 12, 13]
#在部分的数组情况下,插入排序的时间可以是O(n),会比其他两个算法快的多,有兴趣的同学可以去了解一下,这里就不展开说啦。
3、选择排序
它的基本思想是每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的末尾,直到全部待排序的数据元素排完。
这里以数组 [64, 25, 12, 22, 11]
为例:
第一步:找到最小元素
- 找到最小元素
11
,与第一个元素64
交换。 - 数组变为
[11, 25, 12, 22, 64]
。
第二步:找到剩余部分的最小元素
- 在剩余未排序部分
[25, 12, 22, 64]
中找到最小元素12
,与第二个元素25
交换。 - 数组变为
[11, 12, 25, 22, 64]
。
第三步:继续寻找最小元素
- 在剩余未排序部分
[25, 22, 64]
中找到最小元素22
,与第三个元素25
交换。 - 数组变为
[11, 12, 22, 25, 64]
。
第四步:继续寻找最小元素
- 在剩余未排序部分
[25, 64]
中找到最小元素25
,不需要交换。 - 数组保持
[11, 12, 22, 25, 64]
。
最后一步:只剩一个元素
- 最后一个元素
64
已经在正确位置。 - 最终数组为
[11, 12, 22, 25, 64]
。
代码展示如下:
def selection_sort(arr):
n = len(arr)
# 遍历数组所有元素
for i in range(n):
min_idx = i
# 找到未排序部分中最小元素的索引
# i+1代表着不计算前面排序好的数
for j in range(i+1, n):
if arr[j] < arr[min_idx]:
min_idx = j
# 将最小元素交换到已排序部分的末尾
arr[i], arr[min_idx] = arr[min_idx], arr[i]
# 从小到大排序
# 时间复杂度: O(n^2)
# 空间复杂度: O(1)
#注意三种排序在第2层循环时的范围,这里很容易搞混或者搞错,要记住数组的范围取前不取后
4、对数器
对数器(也称为测试工具、验证器)是一种用于验证算法正确性和性能的工具。它通过生成大量随机数据,使用不同的算法对这些数据进行排序,并比较结果是否一致,从而验证算法的正确性。
他的工作原理简单来说就是随机生成一组数据,去测试你的算法和已知正确的算法(如Python的内置函数),计算出来的结果是否一致,来检查你的算法是否正确。
下面我们来一起看看他的代码吧
import random
def generate_random_array(max_size, max_value):
"""
生成一个随机数组
max_size: 数组的最大长度
max_value: 数组元素的最大值
return: 生成的随机数组
"""
size = random.randint(0, max_size) # 随机确定数组长度
return [random.randint(0, max_value) for _ in range(size)] # 生成随机数组
def is_equal(arr1, arr2):
"""
比较两个数组是否相等
arr1: 第一个数组
arr2: 第二个数组
return: 如果两个数组相等返回True,否则返回False
"""
if len(arr1) != len(arr2):
return False # 长度不同直接返回False
for i in range(len(arr1)):
if arr1[i] != arr2[i]:
return False # 有元素不同返回False
return True # 所有元素都相同返回True
def copy_array(arr):
"""
复制一个数组
arr: 需要复制的数组
return: 复制后的新数组
"""
return arr[:] # 使用切片复制数组
def comparator(arr):
"""
参考排序算法,使用内置排序函数
arr: 需要排序的数组
"""
arr.sort() # Python内置的排序算法
def bubble_sort(arr):
"""
冒泡排序算法
arr: 需要排序的数组
"""
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j] # 交换相邻元素
def test_sorting_algorithm(sorting_algorithm, test_times=5000, max_size=100, max_value=100):
"""
测试排序算法的对数器
sorting_algorithm: 需要测试的排序算法函数
test_times: 测试的次数
max_size: 随机数组的最大长度
max_value: 随机数组元素的最大值
"""
succeed = True # 记录算法是否通过所有测试
for _ in range(test_times):
arr1 = generate_random_array(max_size, max_value) # 生成随机数组
arr2 = copy_array(arr1) # 复制数组用于待测排序算法
arr3 = copy_array(arr1) # 复制数组用于参考排序算法
sorting_algorithm(arr2) # 使用待测排序算法排序
comparator(arr3) # 使用参考排序算法排序
if not is_equal(arr2, arr3): # 比较排序结果是否相同
succeed = False
print(f"Test failed! Original: {arr1}")
print(f"Sorted by custom algorithm: {arr2}")
print(f"Sorted by reference algorithm: {arr3}")
break # 测试失败,退出循环
if succeed:
print("Sorting algorithm correct!") # 所有测试通过
else:
print("Sorting algorithm incorrect!") # 有测试失败
# 测试 bubble_sort 函数
test_sorting_algorithm(bubble_sort)
#random
是 Python 标准库中的一个模块,提供了生成随机数的函数和工具。这个模块可以生成各种形式的随机数,包括整数、浮点数、随机选择元素、随机排列列表等。
#random.randint(a, b)
是 Python 的 random
模块中的一个函数,用于生成一个位于 a
和 b
(包含 a
和 b
)之间的随机整数。这个随机整数被赋值给变量 size
,表示将要生成的数组的长度。
#range(size)
生成一个从 0
到 size-1
的整数序列。这个序列的长度是 size
。
#for _ in range(size)
是一个循环,用于遍历 range(size)
生成的序列。由于我们只需要循环的次数,不需要序列中的具体值,所以用 _
作为循环变量。
#random.randint(0, max_value)
在每次循环中都会生成一个从 0
到 max_value
之间的随机整数。
#如果直接写 return [random.randint(0, max_value)]
只能生成一个长度为 1 的数组,因为它只会生成一个随机整数并把它放入一个单元素列表中。
#arr[:]
表示从列表 arr
中选取从头到尾的所有元素,生成一个新的列表。
#def表示你定义了一个函数模块,后面括号内的变量是该函数所需要的参数,你可以直接定义(test_times=5000, max_size=100,),也可以在使用函数的时候导入(test_sorting_algorithm(bubble_sort))
5、一些小tip
这篇笔记主要记录了一写常见的算法与方法,希望对大家有所帮助,我在代码上面的注释还是比较详细的,主要是面向刚刚接触这一块内容的朋友,希望大家能够避开我当时学习的时候跳的坑,或者少掉几个坑哈哈哈。当然有经验的朋友也可以看看,说不定有会有新的发现
如果觉得这篇文章对你有帮助的话可以点赞支持一下哟,有任何问题也可以私信交流,我看到会立马回复哒!
下一篇文章内容可能是关于机器学习方面的基础介绍以及相应的算法,像线性回归啥的,感兴趣的朋友可以关注一下呀
好啦,这篇笔记就到这里啦,祝大家万事顺利!