**本博客内容根据 Mooc 浙江大学 数据结构 1.3课 什么是算法一课总结得到
如果对于博客中的算法有任何疑问请您斧正**
问题: 子序列问题
有一个序列N, 其中包含了 [A1,.... AN]个整数, 求解得到其中的最大连续子序列和。
例子:
N: [1, 2, -1]
几个连续子序列:
[1]
[2]
[-1]
[1, 2]
[2, -1]
[1, 2, -1]
其中最大的连续子序列就是3
面对这个问题, 最暴力的方式就是,穷举得到所有的子序列,然后得到其中最大的子序列和。
C1 循环法:
def get_max_sequence_sum(nums: list) -> int:
length = len(nums)
this_sum = max_sum = 0
# 遍历子序列的起始index
for first_index in range(length):
# 遍历子序列的终止index
for end_index in range(first_index, length):
# 计算得到当前的子序列和
for num in nums[first_index: end_index+1]:
this_sum += num
max_sum = this_sum if this_sum > max_sum else max_sum
this_sum = 0
return max_sum
对于这个算法, 在计算的过程中进行了3次循环,T(n) = n ^ 3
明显可以发现,在计算子序列和的时候,可以通过上一个子序列和加上下一个整数得到新子序列和
C2 循环法2.0
def get_max_sequence_sum(nums: list) -> int:
length = len(nums)
this_sum = max_sum = 0
# 遍历子序列的起始index
for first_index in range(length):
# 遍历子序列的终止index
for end_index in range(first_index, length):
# 当前子序列和 为 上一个子序列和加上当前整数
this_sum += nums[end_index]
max_sum = this_sum if this_sum > max_sum else max_sum
this_sum = 0
return max_sum
通过对于子序列和的优化后, 可以减少每次计算子序列和的循环, T(n) = n ^ 2
在进行减少循环的优化后,我们还可以进一步的使用,分治法的思想来优化这个算法。
对于最大子序列和问题,我们可以将问题分解成
将序列按照中间分成左右两个序列
然后计算 左子序列的最大子序列和 右子序列的最大子序列和 以及从中间往两边扫描得到的最大子序列和
第一步 得到左右两边序列 [4, -3, 5, 2], [-1, 2, 6, 2]
第二步 对于左右序列计算最大子序列和
左序列继续分解成 [4, -1], [5, 2]
左左继续分解成 [4],[-1]; [5], [2]
得到左左左 序列和最大为 4
左左右 序列最大和为5
然后左序列[4, -3,|5, 2]从分割线往两边得到最大子序列和为6
因此左序列的最大子序列和为6
以此类推 右序列的最大子序列和为 8
第三步: 最初序列从分割线往左右两边扫描的最大序列和为11
第四步: 比较三个部分的和 得到最大值为11
(写起来, 比较复杂 QAQ ,如果看不懂大家可以看下参考课程老师的视频)
分治法的思路 就是不断分解问题 然后再将子问题的结果合起来得到最终结果
从而, 我们可以得到第三种算法
C3 分治法
def get_max_sequence_sum(nums: list) -> int:
length = len(nums)
middle_index = length // 2
middle_to_left_this_sequence_sum = middle_to_left_max_sequence_sum = 0
middle_to_right_this_sequence_sum = middle_to_right_max_sequence_sum = 0
# 递归终止条件
if length == 0:
return 0
if length == 1:
return nums[0] if nums[0] > 0 else 0
# 通过递归的方式得到 左边子序列 和 右边子序列的最大子序列和
left_max_sequence_sum = get_max_sequence_sum(nums[:middle_index])
right_max_sequence_sum = get_max_sequence_sum(nums[middle_index:])
# 计算从中间往两边扫描得到的最大子序列和
# 从中间往左边扫描得到最大的
for num in nums[:middle_index]:
middle_to_left_this_sequence_sum += num
middle_to_left_max_sequence_sum = middle_to_left_this_sequence_sum if \
middle_to_left_this_sequence_sum > middle_to_left_max_sequence_sum else \
middle_to_left_max_sequence_sum
# 从中间往右边扫描得到最大的
for num in nums[middle_index:]:
middle_to_right_this_sequence_sum += num
middle_to_right_max_sequence_sum = middle_to_right_this_sequence_sum if \
middle_to_right_this_sequence_sum > middle_to_right_max_sequence_sum else \
middle_to_right_max_sequence_sum
middle_max_sequence_sum = middle_to_left_max_sequence_sum + middle_to_right_max_sequence_sum
return max([left_max_sequence_sum, right_max_sequence_sum, middle_max_sequence_sum])
利用这种分治法, 每次都会将问题分解一半, 然后扫描对应整个序列, 因此最后 T(n) = nlogn。
一般情况下利用分治法, 我们已经可以得到比较优化的解法, 但是对于某些问题 我们还可以通过更为精妙的数学思路来优化问题。
例如对于子序列和的计算问题, 因为是连续子序列, 如果得到的某个子序列和 < 0,那我们就完全可以抛弃掉这个子序列,而直接从下一个数继续往下得到新的子序列和。这种处理方法也叫做"在线处理问题"。
C4 在线处理法
def get_max_sequence_sum_4(nums: list) -> int:
this_sum = max_sum = 0
for num in nums:
this_sum += num
max_sum = this_sum if this_sum > max_sum else max_sum
# 如果当前子序列和 < 0 直接抛弃这个子序列
if this_sum < 0:
this_sum = 0
return max_sum
我们可以很明显的看到,这种算法的, 平均时间复杂度为n。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。