本博客题目来源b站左程云的视频。使用python手敲。并记录了一些思路,后续会持续更新
ch2 O(N logN) 排序算法
2.1 归并排序
题目描述:
使用选择排序
的方法对数组进行排序。
思路:
归并排序是一种递归思路,递归的让左边排好序,右边排好序,然后将左右归并在一起。
代码:
class Mergesort:
def merge_sort(self, nums: List[int]):
if len(nums) < 2:
return
left, right = 0, len(nums)-1
self.process(nums, left, right)
def process(self, nums: List[int], left, right):
if left == right:
return
mid = ((right - left) >> 1) + left
self.process(nums,left, mid) # 递归的左边有序
self.process(nums, mid+1, right) # 递归的右边有序
self.merge(nums, left, mid, right) # merge到一起
def merge(self, nums: List[int], left: int, mid: int, right: int):
#辅助数组
result = []
i, j = left, mid+1
while i <= mid and j <= right:
if nums[i] < nums[j]:
result.append(nums[i])
i += 1
else:
result.append(nums[j])
j += 1
# result.extend(nums[i:mid+1]) # 也可以这么写
# result.extend(nums[j:right+1])
while i <= mid:
result.append(nums[i])
i += 1
while j <= right:
result.append(nums[j])
j += 1
for i in range(len(result)):
# 一定要注意是left+i!!!因为要写回原数组!!!
nums[left + i] = result[i]
m = Mergesort()
arr = [1, 3, 6, 2, 4, 5]
m.merge_sort(arr)
print(arr)
2.2 小和问题
题目描述:
求数组中所有小和。对于nums:[1,3,4,2,5]
小和的定义为,该元素左边有多少比它小的数,则这些数都算小和,例如对于nums
,1没有小和,3的小和为1,4的小和为1+3,2的小和为1,5的小和为1+3+4+2。所以所有小和为16。
思路:
思路1:对于每个数,遍历其左边所有数。根据平方差公式,时间复杂度为O(N^2)
思路2:
- 首先小和可以换个思路来看,
nums[i]
左边有多少比它小的数产生小和,可以变为nums[i]
右边有多少比它大的数,则产生这么多个数 *nums[i]
个小和,例如,对于元素1,一共产生4*1
个小和,3产生2*3
,4产生1*4
,5没有。 - 所以可以参考归并排序的思路,因为左右两边都是有序的,如果左边的数比右边的数小,则直接可以计算出产生多少小和,这样在归并排序的同时,将产生的小和结果累加,时间复杂度为O(NlogN)
代码:
class SmallSum:
def get_small_sum(self, nums: List[int])-> int:
if len(nums) < 2:
return 0
left, right = 0, len(nums)-1
return self.process(nums, left, right)
def process(self, nums: List[int], left: int, right: int):
if left == right:
return 0
mid = left + ((right - left) // 2)
# 返回所有小和
return self.process(nums, left, mid) \
+ self.process(nums, mid+1, right) \
+ self.merge(nums, left, mid, right)
# 归并需要O(N)的额外空间
def merge(self, nums: List[int], left: int, mid: int, right: int):
help_arr = []
result = 0
i, j = left, mid+1
while i <= mid and j <= right:
if nums[i] < nums[j]:
help_arr.append(nums[i])
result += nums[i] * (right - j + 1)
i += 1
else:
help_arr.append(nums[j])
j += 1
while i <= mid:
help_arr.append(nums[i])
i += 1
while j <= right:
help_arr.append(nums[j])
j += 1
for i in range(len(help_arr)):
nums[left + i] = help_arr[i]
return result
2.3 逆序对
题目描述:
打印数组中所有逆序对。逆序对的定义为,左边的元素比右边元素大,则构成逆序对。对于nums:[3,2,4,5,0]
所有逆序对为:3: (3,2)、(3, 0); 2:(2,0); 4:(4,0); 5:(5,0)
思路:
思路:利用归并排序的原理,归并时,当nums[i]比右边的数 nums[j]大时,则i到mid+1的数都对nums[j]产生逆序对。
代码:
class ReversePair:
def print_reverse_pair(self, nums: List[int]):
if len(nums) < 2:
print(None)
left, right = 0, len(nums)-1
res = self.process(nums, left, right)
print(res)
def process(self, nums: List[int], left: int, right: int) -> List[int]:
res = []
if left == right:
return res
mid = left + (right - left) // 2
res.extend(self.process(nums, left, mid))
res.extend(self.process(nums,mid+1, right))
res.extend(self. merge(nums, left, mid, right))
return res
def merge(self, nums: List[int], left: int, mid: int, right: int):
help_arr = []
reversed_pair_arr = []
i, j = left, mid+1
while i <= mid and j <= right:
if nums[i] <= nums[j]:
help_arr.append(nums[i])
i += 1
else:
help_arr.append(nums[j])
# 当nums[i]比右边的数 nums[j]大时,则i到mid+1的数都对nums[j]产生逆序对。
for k in range(i, mid+1):
reversed_pair_arr.append((nums[k], nums[j]))
j += 1
while i <= mid:
help_arr.append(nums[i])
i += 1
while j <= right:
help_arr.append(nums[j])
j += 1
for i in range(len(help_arr)):
nums[left+i] = help_arr[i]
return reversed_pair_arr
2.4 荷兰国旗问题
题目描述:
思路:
问题1:划分出两部分,一部分是≤num的区域,另一部分是>num的区域
问题2:划分出三部分,一部分是<区,一部分是=区,一部分是>区,请注意,从>区换出来的数字,还需要再次判断!
代码:
class Flag1:
def flag_arr(self, nums:List[int], target: int):
if len(nums) < 2:
return
less_idx = -1
for i in range(len(nums)):
if nums[i] <= target:
less_idx += 1
nums[less_idx], nums[i] = nums[i], nums[less_idx]
class Flag2:
def flag_arr(self, nums: List[int], target: int):
if len(nums) < 2:
return
less_idx = -1
more_idx = len(nums)
i = 0
while i < more_idx:
if nums[i] < target:
less_idx += 1
nums[less_idx], nums[i] = nums[i], nums[less_idx]
i += 1
elif nums[i] > target:
more_idx -= 1
nums[more_idx], nums[i] = nums[i], nums[more_idx]
else:
i += 1
2.5 快排1.0
题目描述:
思路:
利用**两色荷兰国旗
**问题的思路,每次拿最后一个数作为划分值,将小于该值的数放到左边,大于的放右边,最后把该数和大于区域的第一个数交换,这样该数就排好了,接下来递归做左右两边。
代码:
class QuickSortV1:
def quick_sort_v1(self, nums: List[int]):
if len(nums) < 2:
return
self.quick_sort(nums, 0, len(nums) -1) # 快排需要传入左右
def quick_sort(self, nums: List[int], left: int, right: int):
# 一定要注意左右边界的问题!!!
if left >= right:
return
# 根据两色荷兰国旗的思想,找到小于等于target区的less,和大于区的more
# 对于[3, 5, 2, 6, 4]。左边界为1,右边界为3(形如32|4|56)
less, more = self.partition(nums, left, right)
self.quick_sort(nums, left, less)
self.quick_sort(nums, more, right)
def partition(self, nums: List[int], left: int, right: int) -> (int, int):
less = left - 1
target = nums[right] # target就用最后一个数!
for i in range(left, right):
if nums[i] <= target:
less += 1
nums[i], nums[less] = nums[less], nums[i]
nums[less+1], nums[right] = nums[right], nums[less+1] # 这里一定不能用target换。而要写成nums[right]!!!
more = less + 2 # 大于区一定是less区边界+2,因为target换过来了
return less, more
# q = QuickSortV1()
# a = [3, 2, 4, 6, 5, 5, 2, 1, 4, 6, 7]
# q.quick_sort_v1(a)
# print(a)
2.6 快排2.0
题目描述:
思路:
利用**三色荷兰国旗
**问题的思路,每次拿最后一个数作为划分值,将小于该值的数放到左边,大于的放右边,等于的数放在中间。这样等于该数的数据就排好了,接下来递归做左右两边。
代码:
class QuickSortV2:
def quick_sort_v2(self, nums: List[int]):
if len(nums) < 2:
return
self.quick_sort(nums, 0, len(nums)-1)
def quick_sort(self, nums: List[int], left: int, right: int):
# 一定要注意左右边界的问题!!!
if left >= right:
return
less, more = self.partition(nums, left, right)
self.quick_sort(nums, left, less)
self.quick_sort(nums, more, right)
def partition(self, nums: List[int], left: int, right: int) -> (int, int):
less = left - 1
more = right + 1
target = nums[right]
while left < more:
if nums[left] < target:
less += 1
nums[less], nums[left] = nums[left], nums[less]
left += 1
elif nums[left] == target:
left += 1
else:
more -= 1
nums[more], nums[left] = nums[left], nums[more]
# 三色荷兰国旗问题后,无需交换!!因为区域已经排好了!!底下这句一定不能加
# nums[more], nums[right] = nums[right], nums[more]
return less, more
# q = QuickSortV2()
# a = [3, 2, 4, 6, 5, 5, 2, 1, 4, 6, 7, 4]
# q.quick_sort_v2(a)
# print(a)
2.7 快排3.0
题目描述:
思路:
思路和快排2.0完全一样,利用**三色荷兰国旗
**问题的思路,唯一的区别就是随机选一个数作为划分值。这样复杂度期望会降到O(NlogN)
代码:
import random
class QuickSortV3:
def quick_sort_v3(self, nums: List[int]):
if len(nums) < 2:
return
self.quick_sort(nums, 0, len(nums)-1)
def quick_sort(self, nums: List[int], left: int, right: int):
# 一定要注意左右边界的问题!!!
if left >= right:
return
less, more = self.partition(nums, left, right)
self.quick_sort(nums, left, less)
self.quick_sort(nums, more, right)
def partition(self, nums: List[int], left: int, right: int) -> (int, int):
less = left - 1
more = right + 1
# 随机选择一个元素!
target = random.choice(nums[left:right])
while left < more:
if nums[left] < target:
less += 1
nums[less], nums[left] = nums[left], nums[less]
left += 1
elif nums[left] == target:
left += 1
else:
more -= 1
nums[more], nums[left] = nums[left], nums[more]
# 三色荷兰国旗问题后,无需交换!!因为区域已经排好了!!底下这句一定不能加
# nums[more], nums[right] = nums[right], nums[more]
return less, more
# q = QuickSortV3()
# a = [3, 2, 4, 6, 5, 5, 2, 1, 4, 6, 7, 4]
# q.quick_sort_v3(a)
# print(a)