四种求最大子序列和的算法与分析(python描述)

转载出处:https://blog.****.net/qq_37977106/article/details/82531461

算法1——穷举法

def method_of_exhaustion(lst):
    length = len(lst)
    this_sum = max_sum = 0
    for i in range(length):
        for j in range(i, length):
            this_sum = 0
            for k in range(i, length):
                this_sum += lst[k]
            if this_sum > max_sum:
                max_sum = this_sum
    return max_sum
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第一种算法是最容易也是最容易懂的一种算法,算法的核心思想很简单,就是穷举所有的可能解,然后通过比较返回最优解

算法分析

第一层循环的索引i表示子序列左端的位置,第二层循环表示子列右端位置,第三层循环计算该子列的大小
四种求最大子序列和的算法与分析(python描述)
从示意图可以看到,i从0遍历到length(数组的长度),j从遍历到length,在j的每一趟中,最内层循环都要从i到j计算一次子序列的大小,其实这是完全没必要的,假设数组a在i的某趟中,j + 1时的子序列的值为a[j] + a[j + 1],即j的前一个子序列的值加上当前索引j在数组中的值。由此衍生出第二个算法。

时间复杂度分析

1、直观的看可以看到函数中有三个循环,所以时间复杂度为O(N3N3)。

2、精确的分析来看,该函数是由三重嵌套for循环组成的,最外层的循环次数为N=lengthN=length

我们必须假设最坏的情况,而这可能会使最终的界有些大。

第3个循环的大小为ji+1j−i+1,因为它们只是两层循环内部的简单表达式。

3、更精确的分析来看,考虑到这些循环的实际大小,更精确的分析指出答案是Θ(N3)Θ(N3)

算法2——优化版穷举法

def optimized_method_of_exhaustion(lst):
    length = len(lst)
    this_sum = max_sum = 0
    for i in range(length):
        this_sum = 0
        for j in range(i, length):
            this_sum += lst[j]
            if this_sum > max_sum:
                max_sum = this_sum
    return max_sum
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

第二种算法是在第一个算法的基础上去掉了重复计算子序列大小的循环

时间复杂度分析

和算法1类似地可以知道算法2的时间复杂度为O(N2)O(N2)

算法3——分治法

def divide_and_conquer(lst, left, right):
    if left == right:
        if lst[left] > 0:
            return lst[left]
        else:
            return 0

    center = (left + right) // 2
    # 左边界最大子序列和右边界最大子序列
    max_left_sum = divide_and_conquer(lst, left, center)
    max_right_sum = divide_and_conquer(lst, center + 1, right)

    max_left_border_sum = left_border_sum = 0
    for i in range(center, left -1, -1):
        left_border_sum += lst[i]
        if left_border_sum > max_left_border_sum:
            max_left_border_sum = left_border_sum

    max_right_border_sum = right_border_sum = 0
    for i in range(center + 1, right + 1):
        right_border_sum += lst[i]
        if right_border_sum > max_right_border_sum:
            max_right_border_sum = right_border_sum

    # 左、右与跨越边界的子序列
    return max(max_left_sum, max_right_sum, max_left_border_sum + max_right_border_sum)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

算法分析

分治法是一种使用广泛的算法,其基本思想是:“如果整个问题比较复杂,可以将问题分化,各个击破”。分治包含“分”和“治”两个过程,先将问题分成两个大致相等的子问题,然后递归地对它们求解。

在此例中,先将序列等分成左右两份,最大子序列只可能出现在三个地方:
1. 整个子序列出现在左半部分;
2. 整个子序列出现在右半部分;
3. 跨越左右边界出现在中间。

四种求最大子序列和的算法与分析(python描述)

前两种情况可以通过递归地从中间向两边累加得到,第三种情况可以将左部分最大和和右部分最大和及他们中间的元素相加得到。在代码中表现在第10行、第11行和第26行中的max的第三个参数,第26行返回的是三者中最大的子序列。

疑问1:代码中的max_left_sum(max_right_sum)和max_left_border_sum(max_right_border_sum)有什么区别?
:max_left_sum(max_right_sum)是递归求出来的边界左边的最大子序列,但是它不一定是挨着边界的,而max_left_border_sum是从边界延伸向左边的最大子序列。
四种求最大子序列和的算法与分析(python描述)
四种求最大子序列和的算法与分析(python描述)
紧挨左边界的最大子序列与紧挨右边界的最大子序列相加就能得到跨越边界的最大子序列。

时间复杂度分析

T(N)T(N)

递归使N / 2 并代入T(N2)T(N2)

=2[2T(N22)+cN2]+cN=2[2T(N22)+cN2]+cN

递归除以2直到,当N除以k次2时,N2k=1N2k=1,即

=2kO(1)+ckN=2kO(1)+ckN

=O(NlogN)=O(NlogN)

算法4——在线算法

def online_algorithm(lst):
    length = len(lst)
    this_sum = max_sum = 0
    for i in range(length):
        this_sum += lst[i]
        if this_sum > max_sum:
            max_sum = this_sum
        elif this_sum < 0:
            this_sum = 0
    return max_sum
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

算法分析

在理解这个算法之前,首先要知道最大子序列的两端的元素(或者连续子序列,整个子序列可以看作是一个元素)是不可能为负的,因为如果为负,总是会有通过将这个负的元素(或连续子序列)去掉而使子序列和更大。

在线算法从0开始累加,当累加到i时,若i0lst[i]<0∑0ilst[i]<0,则可以将[0, i]这一段看作是最左端的小于0的连续子序列,此时[0, i]这一段便可以舍弃,并从i开始重新累加,当累加到某个值j时又小于0,则舍弃[i, j]这一段继续从j开始重新累加。

该算法附带的一个优点是,它只对数据进行一次扫描,一旦lst[i]被读入并被处理,它就不再需要被记忆。因此,如果数组在在磁盘上或通过互联网传送,那么它就可以被按顺序读入,在主存中不必存储任何部分。不仅如此,在任意时刻,算法都能对它已经读入的数据给出子序列问题的正确答案。具有这种特性的算法叫做在线算法(on-line algorithm),或联机算法。仅需要常量空间并以线性时间运行的在线算法几乎是完美的算法。

时间复杂度分析

显而易见,该算法以线性时间运行,时间复杂度为O(N)O(N)

四种算法在实际运行中的表现

图中第一个算法当数组个数仅为1024个(210210时所花费的时间仍不超过3s。
四种求最大子序列和的算法与分析(python描述)
将折线图前面的部分放大可以看到,其实分治法(橙色)运行的时间是要比前两种算法运行的时间花费的更多的,这可能是因为在python中调用函数花费的时间比执行少数的语句所花费的时间要长。
四种求最大子序列和的算法与分析(python描述)

完整的python代码(运行时间一分钟左右)

import timeit
from random import randint
import matplotlib.pyplot as plt
import progressbar

method_of_exhaustion_time_list = []
optimized_method_of_exhaustion_time_list = []
divide_and_conquer_time_list = []
online_algorithm_time_list = []
x_values = []

# 生成指定长度的范围为[-1000, 1000]的整数列表
def generate_integer_list(length = 10):
    lst = []
    for i in range(length):
        lst.append(randint(-1000, 1000))
    return lst

# 穷举法
def method_of_exhaustion(lst):
    length = len(lst)
    this_sum = max_sum = 0
    for i in range(length):
        for j in range(i, length):
            this_sum = 0
            for k in range(i, length):
                this_sum += lst[k]
                if this_sum > max_sum:
                    max_sum = this_sum
    return max_sum

# 穷举法优化
def optimized_method_of_exhaustion(lst):
    length = len(lst)
    this_sum = max_sum = 0
    for i in range(length):
        this_sum = 0
        for j in range(i, length):
            this_sum += lst[j]
            if this_sum > max_sum:
                max_sum = this_sum
    return max_sum

# 分治法
def divide_and_conquer(lst, left, right):
    if left == right:
        if lst[left] > 0:
            return lst[left]
        else:
            return 0

    center = (left + right) // 2
    # 左边界最大子序列和右边界最大子序列
    max_left_sum = divide_and_conquer(lst, left, center)
    max_right_sum = divide_and_conquer(lst, center + 1, right)

    max_left_border_sum = left_border_sum = 0
    for i in range(center, left -1, -1):
        left_border_sum += lst[i]
        if left_border_sum > max_left_border_sum:
            max_left_border_sum = left_border_sum

    max_right_border_sum = right_border_sum = 0
    for i in range(center + 1, right + 1):
        right_border_sum += lst[i]
        if right_border_sum > max_right_border_sum:
            max_right_border_sum = right_border_sum

    # 左、右与跨越边界的子序列
    return max(max_left_sum, max_right_sum, max_left_border_sum + max_right_border_sum)

# 在线处理
def online_algorithm(lst):
    length = len(lst)
    this_sum = max_sum = 0
    for i in range(length):
        this_sum += lst[i]
        if this_sum > max_sum:
            max_sum = this_sum
        elif this_sum < 0:
            this_sum = 0
    return max_sum

# 四种算法时间测试
def test(lst):
    time_list = []

    if len(lst) < 2 ** 11:
        start = timeit.default_timer()
        method_of_exhaustion(lst)
        end = timeit.default_timer()
        method_of_exhaustion_time_list.append(end - start)
    else:
        method_of_exhaustion_time_list.append(None)

    if len(lst) < 2 ** 15:
        start = timeit.default_timer()
        optimized_method_of_exhaustion(lst)
        end = timeit.default_timer()
        optimized_method_of_exhaustion_time_list.append(end - start)
    else:
        optimized_method_of_exhaustion_time_list.append(None)

    if len(lst) < 2 ** 21:
        start = timeit.default_timer()
        divide_and_conquer(lst, 0, len(lst) - 1)
        end = timeit.default_timer()
        divide_and_conquer_time_list.append(end - start)
    else:
        divide_and_conquer_time_list.append(None)

    start = timeit.default_timer()
    online_algorithm(lst)
    end = timeit.default_timer()
    online_algorithm_time_list.append(end - start)

def draw():
    plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
    plt.rcParams['axes.unicode_minus']=False #用来正常显示负号

    plt.plot(x_values, method_of_exhaustion_time_list, c='red')
    plt.plot(x_values, optimized_method_of_exhaustion_time_list, c='blue')
    plt.plot(x_values, divide_and_conquer_time_list, c='orange')
    plt.plot(x_values, online_algorithm_time_list, c='green')

    plt.title('四种求最大子序列算法花费时间折线图')
    plt.xlabel('序列的长度(2的x次方)')
    plt.ylabel('花费的时间(s)')

    plt.show()

if __name__ == '__main__':
    bar = progressbar.ProgressBar()
    for i in bar(range(1, 24)):
        lst = generate_integer_list(2 ** i)
        test(lst)
        x_values.append(i)
    print('开始绘图...')
    draw()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139

转载出处:https://blog.****.net/qq_37977106/article/details/82531461