分治算法:
排序
冒泡排序
不断交换相邻元素的位置来将元素按照从小到大(或从大到小)的顺序排列。
import random
# 生成随机数列表
num_list = [random.randint(1, 100) for _ in range(10)]
print("原始列表:", num_list)
# 冒泡排序
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]
bubble_sort(num_list)
print("排序后:", num_list)
时间复杂度:O(n方),空间复杂度O(1)
选择排序
不断选择未排序序列中最小(或最大)的元素来将元素按照从小到大(或从大到小)的顺序排列。
import random
# 生成随机数列表
num_list = [random.randint(1, 100) for _ in range(10)]
print("原始列表:", num_list)
# 选择排序
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]
selection_sort(num_list)
时间复杂度为O(n^2),空间复杂度为O(1)
插入排序
它通过不断将未排序元素插入已排序序列中来将元素按照从小到大(或从大到小)的顺序排列。
插入排序的主要思想是将数组分为已排序和未排序两部分。初始时,将第一个元素视为已排序,然后从未排序部分逐个选择元素,插入到已排序部分的适当位置,以保持已排序部分始终有序。
时间复杂度为O(n^2),空间复杂度为O(1)
import random
# 生成随机数列表
num_list = [random.randint(1, 100) for _ in range(10)]
print("原始列表:", num_list)
# 插入排序
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
key = arr[i]
j = i - 1
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
insertion_sort(num_list)
print("排序后:", num_list)
快速排序
通过选取一个基准元素将数组分为两个子数组,然后递归对两个子数组进行排序来将元素按照从小到大(或从大到小)的顺序排列。
快速排序是一种高效的分治法排序算法,其主要思想是选择一个基准元素,将数组分成小于基准的左子数组和大于基准的右子数组,然后递归地对左右子数组进行排序,最终将它们合并起来。
import random
# 生成随机数列表
num_list = [random.randint(1, 100) for _ in range(10)]
print("原始列表:", num_list)
# 快速排序
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)
sorted_list = quick_sort(num_list)
print("排序后:", sorted_list)
时间复杂度为O(n log n)(平均情况,在最坏情况下可能达到O(n^2)),空间复杂度为O(log n)
归并排序
归并排序是一种稳定的分治法排序算法,它通过将数组分为两个子数组,递归对两个子数组进行排序,然后将两个有序子数组归并为一个有序数组来将元素按照从小到大(或从大到小)的顺序排列。
时间复杂度为O(n log n),空间复杂度为O(n)。
归并排序的优点之一是它不受输入数据分布的影响,始终保持O(n log n)的时间复杂度,但其空间复杂度较高,需要额外的存储空间来保存临时数组。在处理大型数据集或要求稳定排序的情况下,归并排序是一个很好的选择。
import random
# 生成随机数列表
num_list = [random.randint(1, 100) for _ in range(10)]
print("原始列表:", num_list)
# 归并排序
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
left_half = merge_sort(left_half)
right_half = merge_sort(right_half)
return merge(left_half, right_half)
def merge(left, right):
result = []
left_index, right_index = 0, 0
while left_index < len(left) and right_index < len(right):
if left[left_index] < right[right_index]:
result.append(left[left_index])
left_index += 1
else:
result.append(right[right_index])
right_index += 1
result.extend(left[left_index:])
result.extend(right[right_index:])
return result
sorted_list = merge_sort(num_list)
print("排序后:", sorted_list)
查找
线性查找
若数据是无序的, 则只能采取顺序查找。
二分查找
元素必须是有序的,如果是无序的则要先进行排序操作。
使用两次二分查找,也就是 O(logn) 的时间复杂度
基本思想:也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。
一种是在[0, n - 1]这个左闭右闭区间内找,一种是在[0, n)这个左闭右开区间内找
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] > target:
right = mid - 1
else:
left = mid + 1
return left
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] > target:
right = mid
else:
left = mid + 1
return left
第一步,我们先使用一次二分查找来找到对应的 target 值所在的一维数组里面,一旦锁定一维数组,就可以使用我们平时最熟悉的一维数组的二分查找了。但是需要注意的是,在第一次二分的过程中,需要注意边界的处理。
class Solution:
def searchMatrix(self, matrix, target):
# 初始化行搜索的左右边界
top = 0
bottom = len(matrix) - 1
# 在行中进行二分查找,寻找目标值所在的行
while top < bottom:
mid_row = (top + bottom) // 2
# 如果中间行的第一个元素小于目标值,且下一行的第一个元素大于等于目标值
if matrix[mid_row][0] < target and matrix[mid_row + 1][0] <= target:
top = mid_row + 1
else:
bottom = mid_row
# 初始化列搜索的左右边界
left = 0
right = len(matrix[top]) - 1
# 在确定的行中进行二分查找,寻找目标值
while left < right:
mid_col = (left + right) // 2
# 如果中间列的元素大于等于目标值,则缩小搜索范围到左侧
if matrix[top][mid_col] >= target:
right = mid_col
else:
# 否则缩小搜索范围到右侧
left = mid_col + 1
# 检查最终确定的元素是否是目标值
return matrix[top][left] == target
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# 取起始下标
l, r = 0, len(nums) - 1
while l < r:
mid = (l + r) // 2
if nums[mid] >= target:
r = mid
else:
l = mid + 1
# 没找到
if not nums or nums[l] != target:
return [-1,-1]
# 取结束下标
a, b = l, len(nums) - 1
while a < b:
mid = (a + b + 1) // 2
if nums[mid] <= target:
a = mid
else:
b = mid - 1
return [l,a]
在旋转点左侧的元素是升序的,在旋转点右侧的元素也是升序的。我们可以利用这一性质来修改二分查找算法,使其适用于旋转数组。
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 判断哪部分是有序的
if nums[mid] >= nums[left]: # 左侧有序
# 判断目标值是否在左侧有序部分
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else: # 右侧有序
# 判断目标值是否在右侧有序部分
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1 # 如果找不到目标值,返回 -1
# 示例
nums = [4, 5, 6, 7, 0, 1, 2]
target = 0
print(search(nums, target)) # 输出应为 4
153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)
最小元素是唯一一个比其右侧元素小的元素.
def find_min(nums):
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] > nums[right]:
left = mid + 1
else:
right = mid
return nums[left]
# 示例
nums = [4, 5, 6, 7, 0, 1, 2]
print(find_min(nums)) # 输出应为 0
4. 寻找两个正序数组的中位数 - 力扣(LeetCode)
def findMedianSortedArrays(nums1, nums2):
# 确保 nums1 是较短的数组
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
imin, imax, half_len = 0, m, (m + n + 1) // 2
while imin <= imax:
i = (imin + imax) // 2
j = half_len - i
if i < m and nums1[i] < nums2[j - 1]:
# i 太小,必须增加
imin = i + 1
elif i > 0 and nums1[i - 1] > nums2[j]:
# i 太大,必须减小
imax = i - 1
else:
# i 正确
if i == 0: max_of_left = nums2[j - 1]
elif j == 0: max_of_left = nums1[i - 1]
else: max_of_left = max(nums1[i - 1], nums2[j - 1])
if (m + n) % 2 == 1:
return max_of_left
if i == m: min_of_right = nums2[j]
elif j == n: min_of_right = nums1[i]
else: min_of_right = min(nums1[i], nums2[j])
return (max_of_left + min_of_right) / 2.0
raise ValueError("Input arrays are not sorted or of zero length.")