资料链接:https://pan.quark.cn/s/3957fd44d232
提取码:CCUs
一.冒泡排序
- 冒泡排序是一种简单的排序算法,它重复地遍历待排序的列表,比较相邻的元素并交换它们的顺序,让较大的元素“冒泡”到列表的末端。
示例代码
def bubble_sort(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]
return arr
# 示例
if __name__ == "__main__":
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = bubble_sort(arr)
print("排序后的列表:", sorted_arr)
代码说明:
- 外层循环:控制要进行的轮数,总共需要进行
n
轮比较。 - 内层循环:对未排序的部分进行相邻元素比较和交换,确保较大的元素最终“冒泡”到数组的右端。每次内层循环后,都会将当前最大的元素放到末端。
- 交换:如果当前元素比下一个元素大,就交换它们的位置。
- 返回排序后的数组。
冒泡排序的时间复杂度
- 最坏情况下:O(n²),即原数组是逆序的情况下。
- 最好情况下:O(n),即原数组已经是有序的情况下(可以通过设置一个标志变量来优化)。
- 平均情况下:O(n²)。
尽管冒泡排序很简单,但它的效率较低,特别是对于大规模数据,通常不推荐使用。
二.选择排序
- 选择排序是一种简单的排序算法,它的基本思想是不断地选择未排序部分中的最小(或最大)元素,然后将其放到已排序部分的末尾。
示例代码
def selection_sort(arr):
n = len(arr)
# 外层循环控制已排序的部分
for i in range(n):
# 假设未排序部分的第一个元素是最小值
min_index = i
# 内层循环找到未排序部分的最小值
for j in range(i + 1, n):
if arr[j] < arr[min_index]:
min_index = j
# 交换已排序部分的最后一个元素与未排序部分的最小值
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
# 示例
if __name__ == "__main__":
arr = [64, 25, 12, 22, 11]
sorted_arr = selection_sort(arr)
print("排序后的列表:", sorted_arr)
代码说明:
- 外层循环:控制已排序部分的元素位置。
- 内层循环:从未排序部分中寻找最小的元素。
- 最小值索引:在内层循环中,如果当前元素小于当前的最小值,则更新最小值的索引。
- 交换:在内层循环完成后,将找到的最小值与已排序部分的下一个位置的元素进行交换。
选择排序的时间复杂度
- 最坏情况下:O(n²),无论原数组是否有序。
- 最好情况下:O(n²)。
- 平均情况下:O(n²)。
因为选择排序的比较次数与输入数组的排列方式无关,所以它在复杂度上比较稳定,但效率较低,通常不适合大规模数据的排序。
下面是对三种常见排序算法——快速排序、归并排序和插入排序的详细说明,包括它们的工作原理、优缺点及时间复杂度。
三. 快速排序(Quick Sort)
工作原理:
- 分治法:快速排序采用分治法,通过一个基准元素(pivot)将待排序数组分为两部分。
- 步骤:
- 从数组中选择一个基准元素(常见选择为中间元素)。
- 将数组重新排列,使得所有小于基准元素的值位于其左侧,所有大于基准元素的值位于其右侧。此时,基准元素的位置就是经过排序之后的位置。
- 对左侧和右侧的两个子数组递归应用上述步骤,直到数组被完全排序。
示列代码:
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2] # 选择基准元素
left = [x for x in arr if x < pivot] # 小于基准的元素
middle = [x for x in arr if x == pivot] # 等于基准的元素
right = [x for x in arr if x > pivot] # 大于基准的元素
return quick_sort(left) + middle + quick_sort(right)
# 示例
if __name__ == "__main__":
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = quick_sort(arr)
print("排序后的列表:", sorted_arr)
优缺点:
-
优点:
- 平均情况下性能良好,尤其适合大数据集。
- 原地排序,空间复杂度较低(O(log n))。
-
缺点:
- 最坏情况下时间复杂度为O(n²),例如,选择的基准元素恰好是最大或最小值。
- 不稳定排序,不保持相等元素的相对位置。
时间复杂度:
- 最坏情况下:O(n²)
- 平均情况下:O(n log n)
- 最好情况下:O(n log n)
四. 归并排序(Merge Sort)
工作原理:
- 分治法:归并排序也采用分治法,将数组分成两半,递归进行排序,然后合并已排序的部分。
- 步骤:
- 将数组分成两个子数组,直到每个子数组只有一个元素(单个元素是有序的)。
- 合并两个子数组,将它们合并成已排序数组。
- 递归应用上述合并过程,直到所有元素归并成一个有序数组。
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid]) # 左半部分
right = merge_sort(arr[mid:]) # 右半部分
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
# 合并两个已排序的数组
while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result += left[i:] # 添加剩余元素
result += right[j:]
return result
# 示例
if __name__ == "__main__":
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = merge_sort(arr)
print("排序后的列表:", sorted_arr)
优缺点:
-
优点:
- 稳定排序,能保持相同元素的相对位置。
- 在最坏情况下也能达到O(n log n)的时间复杂度,不受数据初始顺序的影响。
-
缺点:
- 需要额外的空间来存放合并的子数组,空间复杂度为O(n)。
- 在小数据集下性能不如插入排序。
时间复杂度:
- 最坏情况下:O(n log n)
- 平均情况下:O(n log n)
- 最好情况下:O(n log n)
五. 插入排序(Insertion Sort)
工作原理:
- 逐步构建有序序列:插入排序通过维护一个已排序的部分,并将未排序的元素逐一插入到已有序列中。
- 步骤:
- 从第二个元素开始,将当前元素(key)与已排序部分进行比较。
- 如果当前元素小于已排序的元素,则将已排序的元素向右移动一位,直到找到合适的位置插入当前元素。
- 继续对未排序的元素重复这一过程,直到所有元素都有序。
示列代码:
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i] # 当前要插入的元素
j = i - 1
# 将已排序部分中比 key 大的元素向右移动
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key # 插入 key 元素
return arr
# 示例
if __name__ == "__main__":
arr = [64, 34, 25, 12, 22, 11, 90]
sorted_arr = insertion_sort(arr)
print("排序后的列表:", sorted_arr)
优缺点:
-
优点:
- 简单直观,容易实现。
- 对于小规模的数组或部分已排序数据时效率较高。
- 稳定排序,保持相同元素相对位置。
-
缺点:
- 时间复杂度较高,尤其对于大型数据集,表现不佳。
- 不适合大型规模的数据,因为时间复杂度为O(n²)。
时间复杂度:
- 最坏情况下:O(n²)
- 平均情况下:O(n²)
- 最好情况下:O(n)(当输入数据基本有序时)
顺序搜索和二分搜索是两种基本的搜索算法,各自适用于不同的场合。以下是它们的详细介绍:
六. 顺序搜索(Linear Search)
工作原理:
- 顺序搜索可以在未排序的列表中查找目标元素。
- 步骤:
- 从列表的第一个元素开始,逐个比对每个元素与目标值。
- 如果找到与目标值相等的元素,则返回该元素的位置。
- 如果遍历完整个列表仍未找到目标值,返回“未找到”的标志。
代码示列:
def linear_search(arr, target):
for index, element in enumerate(arr):
if element == target:
return index # 返回目标值的索引
return -1 # 返回未找到标志
# 示例用法
array = [5, 3, 8, 4, 2]
target = 4
result = linear_search(array, target)
if result != -1:
print(f"目标值 {target} 在索引 {result} 处找到。")
else:
print(f"目标值 {target} 未找到。")
优缺点:
-
优点:
- 实现简单,无需额外的数据结构。
- 不需要预先对数据进行排序,适用于任何类型的集合。
-
缺点:
- 时间复杂度是O(n),效率较低,特别是数据规模较大时。
时间复杂度:
- 最坏情况下:O(n)
- 平均情况下:O(n)
- 最好情况下:O(1)(当目标值为第一个元素时)
七. 二分搜索(Binary Search)
工作原理:
- 二分搜索是一种高效的搜索算法,仅适用于已排序的列表。
- 步骤:
- 确定当前搜索范围的中间元素。
- 如果中间元素等于目标值,则返回该位置。
- 如果目标值小于中间元素,则在左半部分继续搜索。
- 如果目标值大于中间元素,则在右半部分继续搜索。
- 重复上述步骤,直到找到目标值或搜索范围为空。
def binary_search(arr, target):
left = 0
right = len(arr) - 1
while left <= right:
mid = left + (right - left) // 2 # 计算中间索引
if arr[mid] == target:
return mid # 返回目标值的索引
elif arr[mid] < target:
left = mid + 1 # 在右半部分继续搜索
else:
right = mid - 1 # 在左半部分继续搜索
return -1 # 返回未找到标志
# 示例用法
sorted_array = [2, 3, 4, 5, 8]
target = 5
result = binary_search(sorted_array, target)
if result != -1:
print(f"目标值 {target} 在索引 {result} 处找到。")
else:
print(f"目标值 {target} 未找到。")
优缺点:
-
优点:
- 效率高,时间复杂度为O(log n),适合大量数据的搜索。
- 每次搜索都能显著缩小查找范围。
-
缺点:
- 需对数据进行排序,适用性有限。
- 实现较顺序搜索复杂。
时间复杂度:
- 最坏情况下:O(log n)
- 平均情况下:O(log n)
- 最好情况下:O(1)(当目标值为中间元素时)
总结
- 使用场景:
- 顺序搜索:适用于小规模或未排序数据。
- 二分搜索:适用于大规模的已排序数据,使用效率更高。
八.补充—常用的取整方式:
在Python中,常用的取整方式主要有以下几种:
-
向下取整(floor):
使用math.floor()
函数可以向下取整到最接近的整数。import math num = 3.7 result = math.floor(num) # result = 3
-
向上取整(ceil):
使用math.ceil()
函数可以向上取整到最接近的整数。import math num = 3.1 result = math.ceil(num) # result = 4
-
四舍五入(round):
使用内置的round()
函数进行四舍五入。num = 3.5 result = round(num) # result = 4 num = 3.4 result = round(num) # result = 3
-
转换为整数(int):
使用int()
进行取整(会向零取整)。num = 3.7 result = int(num) # result = 3 num = -3.7 result = int(num) # result = -3
-
取整除法(//):
使用//
进行取整除法。num1 = 7 num2 = 3 result = num1 // num2 # result = 2 num1 = -7 num2 = 3 result = num1 // num2 # result = -3