【Python 算法零基础 4.排序 ③ 插入排序】
目录
一、引言
二、算法思想
三、算法分析
1.时间复杂度
2.空间复杂度
3.算法的优点和缺点
① 算法的优点
② 算法的缺点
四、实战练习
1491. 去掉最低工资和最高工资后的工资平均值
思路与算法
① 插入排序算法 (insertSort 方法)
Ⅰ、初始化
Ⅱ、遍历未排序元素
Ⅲ、元素后移
② 计算平均工资 (average 方法)
Ⅰ、排序
Ⅱ、切片求和:
编辑
1619. 删除某些元素后的数组均值
思路与算法
① 插入排序算法 (insertSort 方法)
Ⅰ、初始化
Ⅱ、遍历未排序元素
Ⅲ、元素后移
② 计算截断均值 (trimMean 方法)
Ⅰ、排序数组
Ⅱ、计算截断比例
Ⅲ、切片求和
编辑
1984. 学生分数的最小差值
思路与算法
① 插入排序算法 (insertSort 方法)
Ⅰ、初始化
Ⅱ、遍历未排序元素
Ⅲ、元素后移
② 计算最小分数 (minimumDifference 方法)
Ⅰ、排序数组
Ⅱ、初始化结果
Ⅲ、遍历所有可能的子数组
Ⅳ、返回结果
接受平庸不是逃避,别让欲望击垮勇敢的你
—— 25.5.20
选择排序回顾
① 初始化:从未排序序列开始,初始时整个数组都是未排序的。
② 寻找最小值:遍历未排序部分的所有元素,找到其中的最小值。使用变量min
记录最小值的索引,初始时假设当前未排序部分的第一个元素是最小的。
③ 交换元素:将找到的最小值与未排序部分的第一个元素交换位置。此时,未排序部分的第一个元素成为已排序序列的一部分。
④ 重复步骤 2-3:缩小未排序部分的范围(从下一个元素开始),重复寻找最小值并交换的过程,直到整个数组排序完成。
def selection_sort(arr: List):n = len(arr)for i in range(n):min = ifor j in range(i+1, n):if arr[min] > arr[j]:arr[min], arr[j] = arr[j], arr[min]return arr
冒泡排序回顾
① 初始化:获取数组长度 n
。
② 外层循环(控制轮数):遍历 i
从 0
到 n-1
,共进行 n
轮。
作用:每轮确定一个最大元素的位置(第 i
轮确定倒数第 i+1
大的元素)。
③ 内层循环(相邻元素比较):对于每轮 i
,遍历 j
从 0
到 n-i-1
:比较 arr[j]
和 arr[j+1]
。若 arr[j] > arr[j+1]
,则交换两者位置。
作用:将当前未排序部分的最大元素逐步交换到右侧。
④ 终止条件:当 i
达到 n-1
时,所有元素已排序完成。
def bubble_sort(arr):n = len(arr)# 遍历所有数组元素for i in range(n):# 最后i个元素已经就位,无需再比较for j in range(0, n - i - 1):# 遍历数组从0到n-i-1# 交换元素如果当前元素大于下一个元素if arr[j] > arr[j+1]:arr[j], arr[j+1] = arr[j+1], arr[j]return arr
一、引言
插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入,直到整个数组有序。
二、算法思想
具体的算法步骤如下:
① 从第一个元素开始,将其视为已排序部分。
② 取出下一个元素,与已排序部分的元素进行比较。
③ 如果该元素小于已排序部分的最后一个元素,则将其插入到已排序部分的适当位置。
④ 重复步骤 ② 和 ③,直到整个数组都被排序
首先,需要将第二个元素和第一个元素进行比较,如果前者 ≤ 后者,则将后者进行向后移动,前者则执行插入
进行第二轮比较时,即第三个元素和第二、第一个元素比较,直到前三个元素都保持有序
最后,经过一定轮次的比较和移动之后,一定可以保证所有元素都是按照升序排列的
三、算法分析
1.时间复杂度
我们假设 「比较」和「移动」 的时间复杂度为 O(1)。
「插入排序」 中有两个嵌套循环:
外循环正好运行 n一1 次迭代。但内部循环运行变得越来越短:
当i = 1,内层循环1次「比较」操作。
当i = 2,内层循环 2次「比较」操作。
当i = 3,内层循环3次「比较」操作。
……
当i = n - 2,内层循环n-2次「比较」操作,
当i = n-1,内层循环n-1次「比较」操作。
因此,总「比较」次数如下:1+2+...+(n-1) = n(n-1)/2。总的时间复杂度为:O(n^2)。
2.空间复杂度
由于算法在执行过程中,只有「移动」变量时候,需要事先将变量存入临时变量x,而其它没有采用任何的额外空间,所以空间复杂度为 O(1)。
3.算法的优点和缺点
① 算法的优点
1.简单易懂,易于实现。
2.适用于小型数组或基本有序的数组。
3.稳定性好,不会改变相等元素的相对顺序。
② 算法的缺点
1.对于大型无序数组,效率较低。
2.不适合大规模数据排序。
四、实战练习
1491. 去掉最低工资和最高工资后的工资平均值
给你一个整数数组
salary
,数组里每个数都是 唯一 的,其中salary[i]
是第i
个员工的工资。请你返回去掉最低工资和最高工资以后,剩下员工工资的平均值。
示例 1:
输入:salary = [4000,3000,1000,2000] 输出:2500.00000 解释:最低工资和最高工资分别是 1000 和 4000 。 去掉最低工资和最高工资以后的平均工资是 (2000+3000)/2= 2500示例 2:
输入:salary = [1000,2000,3000] 输出:2000.00000 解释:最低工资和最高工资分别是 1000 和 3000 。 去掉最低工资和最高工资以后的平均工资是 (2000)/1= 2000示例 3:
输入:salary = [6000,5000,4000,3000,2000,1000] 输出:3500.00000示例 4:
输入:salary = [8000,9000,2000,3000,6000,1000] 输出:4750.00000提示:
3 <= salary.length <= 100
10^3 <= salary[i] <= 10^6
salary[i]
是唯一的。- 与真实值误差在
10^-5
以内的结果都将视为正确答案。
思路与算法
① 插入排序算法 (insertSort
方法)
Ⅰ、初始化
获取数组长度 n
。从索引 1
开始遍历(因为索引 0
单独作为已排序部分)。
Ⅱ、遍历未排序元素
对于每个索引 i
(从 1
到 n-1
):保存当前元素 current = arr[i]
。
设置指针 j = i-1
,指向已排序部分的最后一个元素。
Ⅲ、元素后移
循环条件:当 j >= 0
且 arr[j] > current
时:将 arr[j]
后移一位至 arr[j+1]
。j
减 1,继续向前检查。
插入当前元素:当循环结束时,j+1
即为 current
的正确位置,将其插入。
② 计算平均工资 (average
方法)
Ⅰ、排序
调用 insertSort
对工资数组 salary
进行升序排序。
Ⅱ、切片求和:
截取数组的中间部分(排除第一个和最后一个元素):salary[1:-1]
。
计算切片的总和,并除以元素个数 len(salary) - 2
class Solution:def insertSort(self, arr: List):n = len(arr)# 从第二个元素开始遍历,因为第一个元素已经是有序的for i in range(1, n):# 当前要插入的元素current = arr[i]# 已排序部分的最后一个元素的索引j = i - 1# 从后向前扫描已排序部分,将比current大的元素向后移动while j >= 0 and arr[j] > current:arr[j + 1] = arr[j] # 元素后移j -= 1# 插入当前元素到正确位置arr[j + 1] = currentreturn arrdef average(self, salary: List[int]) -> float:self.insertSort(salary)return sum(salary[1: -1]) / (len(salary) - 2)
1619. 删除某些元素后的数组均值
给你一个整数数组
arr
,请你删除最小5%
的数字和最大5%
的数字后,剩余数字的平均值。与 标准答案 误差在
10-5
的结果都被视为正确结果。示例 1:
输入:arr = [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3] 输出:2.00000 解释:删除数组中最大和最小的元素后,所有元素都等于 2,所以平均值为 2 。示例 2:
输入:arr = [6,2,7,5,1,2,0,3,10,2,5,0,5,5,0,8,7,6,8,0] 输出:4.00000示例 3:
输入:arr = [6,0,7,0,7,5,7,8,3,4,0,7,8,1,6,8,1,1,2,4,8,1,9,5,4,3,8,5,10,8,6,6,1,0,6,10,8,2,3,4] 输出:4.77778示例 4:
输入:arr = [9,7,8,7,7,8,4,4,6,8,8,7,6,8,8,9,2,6,0,0,1,10,8,6,3,3,5,1,10,9,0,7,10,0,10,4,1,10,6,9,3,6,0,0,2,7,0,6,7,2,9,7,7,3,0,1,6,1,10,3] 输出:5.27778示例 5:
输入:arr = [4,8,4,10,0,7,1,3,7,8,8,3,4,1,6,2,1,1,8,0,9,8,0,3,9,10,3,10,1,10,7,3,2,1,4,9,10,7,6,4,0,8,5,1,2,1,6,2,5,0,7,10,9,10,3,7,10,5,8,5,7,6,7,6,10,9,5,10,5,5,7,2,10,7,7,8,2,0,1,1] 输出:5.29167提示:
20 <= arr.length <= 1000
arr.length
是20
的 倍数0 <= arr[i] <= 105
思路与算法
① 插入排序算法 (insertSort
方法)
Ⅰ、初始化
获取数组长度 n
。从索引 1
开始遍历(因为索引 0
单独作为已排序部分)。
Ⅱ、遍历未排序元素
对于每个索引 i
(从 1
到 n-1
):保存当前元素 current = arr[i]
。
设置指针 j = i-1
,指向已排序部分的最后一个元素。
Ⅲ、元素后移
循环条件:当 j >= 0
且 arr[j] > current
时:将 arr[j]
后移一位至 arr[j+1]
。j
减 1,继续向前检查。
插入当前元素:当循环结束时,j+1
即为 current
的正确位置,将其插入。
② 计算截断均值 (trimMean
方法)
Ⅰ、排序数组
调用 insertSort
对数组进行升序排序。
Ⅱ、计算截断比例
数组长度为 n
,则需去除的元素数量为 s = n // 20
(即 5%)。
Ⅲ、切片求和
截取数组中间的部分:arr[s:-s]
,即去除前 s
个和后 s
个元素。
计算切片的总和,并除以剩余元素的数量(即 0.9 * n
,因为去除了 10% 的元素)。
class Solution:def insertSort(self, arr: List):n = len(arr)# 从第二个元素开始遍历,因为第一个元素已经是有序的for i in range(1, n):# 当前要插入的元素current = arr[i]# 已排序部分的最后一个元素的索引j = i - 1# 从后向前扫描已排序部分,将比current大的元素向后移动while j >= 0 and arr[j] > current:arr[j + 1] = arr[j] # 元素后移j -= 1# 插入当前元素到正确位置arr[j + 1] = currentreturn arrdef trimMean(self, arr: List[int]) -> float:arr = self.insertSort(arr)n = len(arr)s = n // 20return sum(arr[s:-s]) / (0.9 * n)
1984. 学生分数的最小差值
给你一个 下标从 0 开始 的整数数组
nums
,其中nums[i]
表示第i
名学生的分数。另给你一个整数k
。从数组中选出任意
k
名学生的分数,使这k
个分数间 最高分 和 最低分 的 差值 达到 最小化 。返回可能的 最小差值 。
示例 1:
输入:nums = [90], k = 1 输出:0 解释:选出 1 名学生的分数,仅有 1 种方法: - [90] 最高分和最低分之间的差值是 90 - 90 = 0 可能的最小差值是 0示例 2:
输入:nums = [9,4,1,7], k = 2 输出:2 解释:选出 2 名学生的分数,有 6 种方法: - [9,4,1,7] 最高分和最低分之间的差值是 9 - 4 = 5 - [9,4,1,7] 最高分和最低分之间的差值是 9 - 1 = 8 - [9,4,1,7] 最高分和最低分之间的差值是 9 - 7 = 2 - [9,4,1,7] 最高分和最低分之间的差值是 4 - 1 = 3 - [9,4,1,7] 最高分和最低分之间的差值是 7 - 4 = 3 - [9,4,1,7] 最高分和最低分之间的差值是 7 - 1 = 6 可能的最小差值是 2提示:
1 <= k <= nums.length <= 1000
0 <= nums[i] <= 105
思路与算法
① 插入排序算法 (insertSort
方法)
Ⅰ、初始化
获取数组长度 n
。从索引 1
开始遍历(因为索引 0
单独作为已排序部分)。
Ⅱ、遍历未排序元素
对于每个索引 i
(从 1
到 n-1
):保存当前元素 current = arr[i]
。
设置指针 j = i-1
,指向已排序部分的最后一个元素。
Ⅲ、元素后移
循环条件:当 j >= 0
且 arr[j] > current
时:将 arr[j]
后移一位至 arr[j+1]
。j
减 1,继续向前检查。
插入当前元素:当循环结束时,j+1
即为 current
的正确位置,将其插入。
② 计算最小分数 (minimumDifference
方法)
Ⅰ、排序数组
调用 insertSort
对数组进行升序排序。
Ⅱ、初始化结果
设置初始结果 res
为 100000
(根据题目约束,这是可能的最大值)。
Ⅲ、遍历所有可能的子数组
对于每个起始索引 i
(范围 0
到 n - k
):计算对应的结束索引 r = i + k - 1
。计算子数组 nums[l:r+1]
的分数(即 nums[r] - nums[l]
)。更新 res
为当前分数与历史最小分数的较小值。
Ⅳ、返回结果
遍历结束后,res
即为最小分数。
class Solution:def insertSort(self, arr: List):n = len(arr)# 从第二个元素开始遍历,因为第一个元素已经是有序的for i in range(1, n):# 当前要插入的元素current = arr[i]# 已排序部分的最后一个元素的索引j = i - 1# 从后向前扫描已排序部分,将比current大的元素向后移动while j >= 0 and arr[j] > current:arr[j + 1] = arr[j] # 元素后移j -= 1# 插入当前元素到正确位置arr[j + 1] = currentreturn arrdef minimumDifference(self, nums: List[int], k: int) -> int:self.insertSort(nums)# 题目中给的最大值是10 ^ 5n = len(nums)res = 100000for i in range(n + 1 - k):l = ir = i + k -1res = min(res, nums[r] - nums[l])return res