python常用算法(7)——动态规划,回溯法

引言:从斐波那契数列看动态规划

  斐波那契数列:Fn = Fn-1 + Fn-2    ( n = 1,2     fib(1) = fib(2) = 1)

练习:使用递归和非递归的方法来求解斐波那契数列的第 n 项

  代码如下:


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

# _*_coding:utf-8_*_

def fibnacci(n):

    if n == 1 or n == 2:

        return 1

    else:

        return fibnacci(n - 1) + fibnacci(n - 2)

# 写这个是我们会发现计算f(5) 要算两边f(4)

# f(5) = f(4)+f(3)

# f(4) = f(3)+f(2)

# f(3) = f(2)+f(1)

# f(3) = f(2)+f(1)

# f(2) = 1

# 那么同理,算f(6),我们会计算两次f(5),三次f(4)....

# 当然不是说所有的递归都会重复计算,

# 时间随着数字越大,时间越长

print(fibnacci(10))  # 55

def fibnacci_n_recurision(n):

    f = [0, 1, 1]

    if n > 2:

        for in range(n - 2):

            num = f[-1] + f[-2]

            f.append(num)

    return f[n]

print(fibnacci_n_recurision(10))

  为了让我们的说服更有理一些,这里写了一个装饰器,我们通过运行时间看。同样对于上面两个函数,一个递归,一个非递归,我们输入 n=15


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

# cal_time.py 函数代码如下:

import time

def cal_time(func):

    def wrapper(*args, **kwargs):

        t1 = time.time()

        result = func(*arg, **kwargs)

        t2 = time.time()

        print("%s running time: %s secs." % (func.__name__, t2 - t1))

        return result

    return wrapper

    

运行结果:

fibnacci running time: 0.01000070571899414 secs.

fibnacci_n_recurision running time: 0.0 secs.

  总结来说,就是递归非常非常的慢,那非递归相对来说就比较快了。那为什么呢?就是为什么递归的效率低。我们上面代码也说过了,就是对子问题进行重复计算了。那第二个函数为什么快呢,我们将每次的计算结果存在了函数里,直接调用,避免了重复计算(当然不是说所有的递归都会重复计算子问题),第二个函数我们其实可以看做是动态规划的思想,从上面的代码来看:

  动态规划的思想==递推式+重复子问题

  怎么理解呢,下面继续学习。

1,什么是动态规划

  动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。

1.1,使用动态规划特征

  • 1. 求一个问题的最优解
  • 2. 大问题可以分解为子问题,子问题还有重叠的更小的子问题
  • 3. 整体问题最优解取决于子问题的最优解(状态转移方程)
  • 4. 从上往下分析问题,从下往上解决问题
  • 5. 讨论底层的边界问题

1.2,动态规划的基本思想

  若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有效。

  动态规划最重要的有三个概念:1、最优子结构 2、边界 3、状态转移方程

  所以我们在学习动态规划要明白三件事情:

1,目标问题

2,状态的定义:opt[n]

3,状态转移方差:opt[n] = best_of(opt[n-1], opt[n-2])

2,钢条切割问题

  某公司出售钢条,出售价格与钢条长度直接的关系如下表:

  问题:现在有一条长度为 n 的钢条和上面的价格表,求切割钢条方案,使得总收益最大。

  分析:长度为4的钢条的所有切割方案如下:(C方案最优)

  思考:长度为 n 的钢条的不同切割方案有几种?

  下面是当长度为n的时候,最优价格的表格( i 表示长度为 n ,r[i] 表示最优价格)

2.1,递推式解决钢条切割问题

  设长度为 n 的钢条切割后最优收益值为 Rn,可以得到递推式:

  第一个参数Pn 表示不切割

  其他 n-1个参数分别表示另外 n-1种不同切割方案,对方案 i=1,2,...n-1 将钢条切割为长度为 i 和 n-i 两段

  方案 i  的收益为切割两段的最优收益之和,考察所有的 i,选择其中收益最大的方案

2.2,最优子结构解决钢条切割问题

  可以将求解规模为 n 的原问题,划分为规模更小的子问题:完成一次切割后,可以将产生的两段钢条看成两个独立的钢条切割问题。

  组合两个子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大的,构成原问题的最优解。

  钢条切割满足最优子结构:问题的最优解由相关子问题的最优解组合而成,这些子问题可以独立求解。

  钢条切割问题还存在更简单的递归求解方法:

  • 从钢条的左边切割下长度为 i 的一段,只对右边剩下的一段继续进行切割,左边的不再切割
  • 递推式简化为:
  • 不做切割的方案就可以描述为:左边一段长度为 n,收益为 pn,剩下一段长度为0,收益为 r0=0.

2.3,钢条切割问题——自顶向下递归代码及其时间复杂度

  代码如下:


1

2

3

4

5

6

7

def _cut_rod(p, n):

    if n == 0:

        return 0

    q = 0

    for in range(1, n+1):

        q = max(q, p[i] + _cut_rod(p, n-i))

    return q

  如下图所示,当钢条的长度增加时候,切割的方案次数随着指数增加。当n=1的时候切割1次,n=2的时候切割2次,n=3的时候切割4次,n=4的时候切割8次。。。。

  所以:自顶向下递归实现的时间复杂度为 O(2n)

2.4,两种方法的代码实现

  代码如下:


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

# _*_coding:utf-8_*_

import time

# 给递归函数一个装饰器,它就递归的装饰!! 所以为了防止这样我们再套一层即可

def cal_time(func):

    def wrapper(*args, **kwargs):

        t1 = time.time()

        result = func(*args, **kwargs)

        t2 = time.time()

        print(‘%s running time : %s secs‘ % (func.__name__, t2 - t1))

        return result

    return wrapper

# p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 28, 30, 33, 36, 39, 40]

p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30]

def cut_rod_recurision_1(p, n):

    if n == 0:

        return 0

    else:

        res = p[n]

        for in range(1, n):

            res = max(res, cut_rod_recurision_1(p, i) + cut_rod_recurision_1(p, n - i))

        return res

# print(cut_rod_recurision_1(p, 9))

def cut_rod_recurision_2(p, n):

    if n == 0:

        return 0

    else:

        res = 0

        for in range(1, n + 1):

            res = max(res, p[i] + cut_rod_recurision_2(p, n - i))

        return res

# print(cut_rod_recurision_2(p, 9))

@cal_time

def c1(p, n):

    return cut_rod_recurision_1(p, n)

@cal_time

def c2(p, n):

    return cut_rod_recurision_2(p, n)

print(c1(p, 10))

print(c2(p, 10))

‘‘

c1 running time : 0.02000117301940918 secs

30

c2 running time : 0.0010001659393310547 secs

30

‘‘

  我们通过计算时间,发现第二个递归方法明显比第一个递归方法快很多。那么是否还有更简单的方法呢?肯定有,下面学习动态规划。

2.5,动态规划解决钢条切割问题

  递归算法由于重复求解相同子问题,效率极低。即使优化过后的递归也效率不高。那这里使用动态规划。

  动态规划的思想

  1. 每个子问题只求解一次,保存求解结果
  2. 之后需要此问题时,只需要查找保存的结果

  动态规划解法代码:


1

2

3

4

5

6

7

8

def cut_rod_dp(p, n):

    r = [0 for in range(n+1)]

    for in range(1, n+1):

        q = 0

        for in range(1, j+1):

            q = max(q, p[i]+r[j-i])

        r[j] = q

    return r[n]

  或者便于理解这样:


1

2

3

4

5

6

7

8

def cut_rod_dp(p, n):

    r = [0]

    for in range(1, n+1):

        res = 0

        for in range(1, i+1):

            res = max(res, p[j]+r[i-j])

        r.append(res)

    return r[n]

  时间复杂度: O(n2)

2.6,钢条切割问题——重构解

  如何修改动态规划算法,使其不仅输出最优解,还输出最优切割方案?

  对于每个子问题,保存切割一次时左边切下的长度

  下图为r[i] 表示最优切割的价格,s[i]表示切割左边的长度。

  代码如下:


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

def cut_rod_extend(p, n):

    r = [0]

    s = [0]

    # 这个循环的意思是从底向上计算

    for in range(1, n+1):

        res_r = 0  # 用来记录价格的最优值

        res_s = 0  # 用来记录切割左边的最优长度

        for in range(1, i+1):

            if p[j] + r[i-j] > res_r:

                res_r = p[j] + r[i-j]

                res_s = j

        r.append(res_r)

        s.append(res_s)

    return r[n], s

def cut_rod_solution(p, n):

    r, s = cut_rod_extend(p, n)

    ans = []

    while n>0:

        ans.append(s[n])

        n-= s[n]

    return ans

print(cut_rod_extend(p, 10))

# (30, [0, 1, 2, 3, 2, 2, 6, 1, 2, 3, 10])

print(cut_rod_solution(p, 9))

# [3, 6]

2.7,什么问题可以使用动态规划方法?

  1,最优子结构

  • 原问题的最优解中涉及多少个子问题
  • 在确定最优解使用那些子问题时,需要考虑多少种选择

  2,重叠子问题

3,最长公共子序列

  一个序列的子序列是在该序列中删去若干元素后得到的序列。例如:ABCD 和 BDF 都是 ABCDEFG 的子序列。

  在一个序列中,子串是连续的,子序列可以不连续

  最常公共子序列(LCS)问题:给定两个序列 X 和 Y,求 X 和 Y 长度最大的公共子序列。例如 X = ABBCBDE,  Y = DBBCDB ,  LCS(X, Y) = BBCD 。

  应用场景:字符串相似度比对。

3.1,最长公共子序列的思路——暴力穷举法

  当X的长度为m,Y的长度为n,则时间复杂度为: 2^(m+n) 

  虽然我们最先想到的时暴力穷举法,但是很显然,由其时间复杂度可知,这是不可取的。

3.2,最长公共子序列的思路——LCS是否具有最优子结构性质

  例如:要求 a = ABCBDAB  与 b = BDCABA 的LCS:

  由于最后一位 B!= A

  因此LCS(a, b)应该来源于  LCS(a[: -1], b)与 LCS(a, b[: -1]) 中更大的哪一个。

  最优解的递推式如下:

  c[i,j] 表示 Xi 和 Yj 的LCS 长度。

3.3,最长公共子序列的代码

  代码如下:


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

def lcs_length(x, y):

    m = len(x)

    n = len(y)

    c = [[0 for in range(n + 1)] for in range(m + 1)]

    for in range(1, m + 1):

        for in range(1, n + 1):

            if x[i - 1] == y[j - 1]:  # i,j位置上的字符匹配的时候,来自于左上方

                c[i][j] = c[i - 1][j - 1] + 1

            else:

                c[i][j] = max(c[i - 1][j], c[i][j - 1])

    # 逐行打印

    for in c:

        print(_)

    return c[m][n]

# 最优值出来了,但是过程没有出来,也就是只有最长,不知道公共子序列

# print(lcs_length("ABCBDAB", "BDCABA"))

‘‘

[0, 0, 0, 0, 0, 0, 0]

[0, 0, 0, 0, 1, 1, 1]

[0, 1, 1, 1, 1, 2, 2]

[0, 1, 1, 2, 2, 2, 2]

[0, 1, 1, 2, 2, 3, 3]

[0, 1, 2, 2, 2, 3, 3]

[0, 1, 2, 2, 3, 3, 4]

[0, 1, 2, 2, 3, 4, 4]

4

‘‘

def lcs(x, y):

    m = len(x)

    n = len(y)

    c = [[0 for in range(n + 1)] for in range(m + 1)]

    # 1左上方 2上方 3 左方

    b = [[0 for in range(n + 1)] for in range(m + 1)]

    for in range(1, m + 1):

        for in range(1, n + 1):

            if x[i - 1] == y[j - 1]:  # i,j位置上的字符匹配的时候,来自于左上方

                c[i][j] = c[i - 1][j - 1] + 1

                b[i][j] = 1

            elif c[i - 1][j] > c[i][j - 1]:  # 来自于上方

                c[i][j] = c[i - 1][j]

                b[i][j] = 2

            else:

                c[i][j] = c[i][j - 1]

                b[i][j] = 3

    return c[m][n], b

# c, b = lcs("ABCBDAB", "BDCABA")

# for _ in b:

#     print(_)

‘‘

[0, 0, 0, 0, 0, 0, 0]

[0, 3, 3, 3, 1, 3, 1]

[0, 1, 3, 3, 3, 1, 3]

[0, 2, 3, 1, 3, 3, 3]

[0, 1, 3, 2, 3, 1, 3]

[0, 2, 1, 3, 3, 2, 3]

[0, 2, 2, 3, 1, 3, 1]

[0, 1, 2, 3, 2, 1, 3]

‘‘

def lcs_trackback(x, y):

    c, b = lcs(x, y)

    i = len(x)

    j = len(y)

    res = []

    while i > 0 and j > 0:

        if b[i][j] == 1:  # 来自左上方 =》匹配

            res.append(x[i - 1])

            i -= 1

            j -= 1

        elif b[i][j] == 2:  # 来自上方=》 不匹配

            i -= 1

        else:  # ==3  来自左方 =》不匹配

            j -= 1

    # 因为是回溯法,所以倒着写的,我们最后需要reverse回来

    return "".join(reversed(res))

print(lcs_trackback("ABCBDAB""BDCABA"))

# BDAB

  

4,最大子序和

  给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大值。

  示例:输入:[-2, 1, -3, 4, -1, 2, 1, -5, 4]   输出:输出:6

  思路:我们首先分析题目,为什么最大和的连续子数组不包括其他的元素而是这几个呢?如果我们想在现有的基础上去扩展当前连续子数组,相邻的元素是一定要被加入的,而相邻元素可能会减损当前的和。

4.1,遍历法

  算法过程:遍历数组,用 sum 去维护当前元素加起来的和,当 sum 出现小于0的情况时,我们把它设为0,然后每次都更新全局最大值。


1

2

3

4

5

6

7

8

9

def maxSubArray(self, nums: List[int]) -> int:

       sum = 0

       MaxSum = nums[0]

       for in range(len(nums)):

           sum += nums[i]

           MaxSum = max(sum, MaxSum)

           if sum <0:

               sum = 0

       return MaxSum

  那看起来这么简单,如何理解呢?一开始思考数组是个空的,我们每次选一个 nums[i] 加入当前数组中新增了一个元素,也就是用动态的眼光去考虑。代码简单为什么就能达到效果呢?

  我们进行的加和是按照顺序来的,当我们i 选出来后,加入当前 sum,这时候有两种情况:

1,假设我们当前 sum 一致都大于零,那每一次计算的 sum 都是包括开头元素的一端子序列。

2,出现小于0的时候,说明我们当前子序列第一次小于零,所以我们现在形成的连续数组不能包括之前的连续子序了,于是抛弃他们,重新开始新的子序。

4.2,动态规划

  设sum[i]为以第i个元素结尾的最大的连续子数组的和。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i]= max(sum[i-1] + a[i], a[i])。可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。

  代码如下:


1

2

3

4

5

6

7

8

def maxSubArray4(self, nums: List[int]) -> int:

    length = len(nums)

    for in range(1, length):

        # 当前值的大小与前面的值之和比较,若当前值更大,则取当前值,舍弃前面的值之和

        subMaxSum = max(nums[i]+nums[i-1], nums[i])

        # 将当前和最大的赋给 nums[i], 新的nums 存储的为何值

        nums[i] = subMaxSum

    return max(nums)

  只要遍历一遍。nums[i]表示的是以当前这第i号元素结尾(看清了一定要包含当前的这个i)的话,最大的值无非就是看以i-1结尾的最大和的子序能不能加上我这个nums[i],如果nums[i]>0的话,则加上。注意我代码中没有显式地去这样判断,不过我的Max表达的就是这个意思,然后我们把nums[i]确定下来。

  动态规划需要和回溯法搭配着使用,动态规划只负责求最优解,而回溯法则可以找到最优值的路径。

5,回溯法

  回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。回溯法有“通用的解题法”之称,也叫试探法,它是一种系统的搜索问题的解的方法。简单来说,回溯法采用试错的方法解决问题,一旦发现当前步骤失败,回溯方法就返回上一个步骤,选择另一种方案继续试错。

5.1  回溯法的基本思想

  从一条路往前走,能进则进,不能进则退回来,换一条路再试。

5.2  回溯法的一般步骤

1,定义一个解空间(子集树,排序树二选一)

2,利用适用于搜索的方法组织解空间

3,利用深度优先法搜索解空间

4,利用剪枝函数避免移动到不可能产生解的子空间

5.3  回溯法针对问题的特点

  回溯算法针对的大多数问题有以下特点:问题的答案有多个元素(可向下成走迷宫是有多个决定),答案需要一些约束(比如数独),寻找答案的方式在每一个步骤相同。回溯算法逐步构建答案,并在确定候选元素不满足约束后立刻放弃候选元素(一旦碰墙就返回),直到找到答案的所有元素。  

5.4回溯法题目——查找单词

  问题描述:你玩过报纸上那种查找单词的游戏吗?就是那种在一堆字母中横向或竖向找出单词的游戏。小明在玩一个和那个很像的游戏,只不过现在不仅可以上下左右连接字母,还可以拐弯。如图所示,输入world,就会输出“找到了”。

5.5  回溯法题目——遍历所有的排列方式

  问题描述:小米最近有四本想读的书:《红色的北京》,《黄色的巴黎》,《蓝色的夏威夷》,《绿色的哈萨里》,如果小明每次只能从图书馆借一本书,他一共有多少种借书的顺序呢?

  回溯法是一种通过探索所有可能的候选解来找出所欲的解的算法。如果候选解被确认,不是一个解的话(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变换排期该解。即回溯并且再次尝试。

  这里有一个回溯函数,使用第一个整数的索引作为参数  backtrack(first)。

1,如果第一个整数有索引 n,意味着当前排列已完成。

2,遍历索引 first 到索引 n-1 的所有整数 ,则:

  • 在排列中放置第 i 个整数,即 swap(nums[first], nums[i])
  • 继续生成从第 i 个整数开始的所有排列:backtrack(first +1)
  • 现在回溯,通过 swap(nums[first], nums[i]) 还原。

  代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

class Solution:

    def permute(self, nums):

        """

        :type nums: List[int]

        :rtype: List[List[int]]

        """

        def backtrack(first = 0):

            # if all integers are used up

            if first == n: 

                output.append(nums[:])

            for in range(first, n):

                # place i-th integer first

                # in the current permutation

                nums[first], nums[i] = nums[i], nums[first]

                # use next integers to complete the permutations

                backtrack(first + 1)

                # backtrack

                nums[first], nums[i] = nums[i], nums[first]

        

        n = len(nums)

        output = []

        backtrack()

        return output

  便于理解的代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class solution:

    def solvepermutation(self, array):

        self.helper(array, [])

    def helper(self, array, solution):

        if len(array) == 0:

            print(solution)

            return

        for in range(len(array)):

            newarray = array[:i] + array[i + 1:]  # 删除书本

            newsolution = solution + [array[i]]  # 加入新书

            self.helper(newarray, newsolution)  # 寻找剩余对象的排列组合

solution().solvepermutation(["红""黄""蓝""绿"])

  方法二:走捷径(直接使用Python的库函数,迭代函数itertools)


1

2

3

4

5

li = [‘A‘‘B‘‘C‘‘D‘]

def solutoin(li):

    import itertools

    res = list(itertools.permutations(li))

    return len(res)

  

5.6  回溯法问题——经典问题的组合

  问题描述:小明想上两门选修课,他有四种选择:A微积分,B音乐,C烹饪,D设计,小明一共有多少种不同的选课组合?

  当然第一个方法就是走捷径!,直接使用python的库函数itertools进行迭代:


1

2

3

4

5

li = [‘A‘‘B‘‘C‘‘D‘]

def solutoin(li):

    import itertools

    res = list(itertools.permutations(li, 2))

    return len(res)

  下面开始回溯法的学习。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class solution():

    def solvecombination(self, array, n):

        self.helper(array, n, [])

    def helper(self, array, n, solution):

        if len(solution) == n:

            print(solution)

            return

        for in range(len(array)):

            newarray = array[i + 1:]  # 创建新的课程列表,更新列表,即选过的课程不能再选

            newsolution = solution + [array[i]]  # 将科目加入新的列表组合

            self.helper(newarray, n, newsolution)

solution().solvecombination(["A""B""C""D"], 2)

  

5.7  回溯法问题——八皇后问题

  问题描述:保安负责人小安面临一个难题,他需要在一个8x8公里的区域里修建8个保安站点,并确保每一行、每一列和每一条斜线上都只有一个保安站点。苦恼的小安试着尝试布置了很多遍,但每一次都不符合要求。小安求助程序员小明,没过多久小明就把好几个布置方案(实际上,这个问题有92种答案)发给了小安,其中包括下面执行结果截图,试问小明是怎么做到的。

6,算法综合作业

  这是所有的算法学完后的综合作业,当然这也是算法学习的一个总结。当然下面的问题我都有涉及,这里不做一一解答。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

1. 实现以下算法并且编写解题报告,解题报告中需要给出题目说明、自己对

题目的理解、解题思路、对算法的说明和理解、以及算法复杂度分析等内容

2. 实现冒泡排序、插入排序、快速排序和归并排序

3. 以尽可能多的方法解决2-sum问题并分析其时间复杂度:给定一个列表和

一个整数,从列表中找到两个数,使得两数之和等于给定的数,返回两个数

的下标。题目保证有且只有一组解

4. 封装一个双链表类,并实现双链表的创建、查找、插入和删除

5. 使用至少一种算法解决迷宫寻路问题

6. 使用动态规划算法实现最长公共子序列问题

  

原文地址:https://www.cnblogs.com/cider/p/11831174.html

时间: 2024-10-05 22:57:00

python常用算法(7)——动态规划,回溯法的相关文章

五大常用算法之四:回溯法

(转自:http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741376.html) 1.概念 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径. 回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标.但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”. 许

(转)五大常用算法之四:回溯法

http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741376.html 1.概念 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径. 回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标.但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”. 许多复杂的,

python常用算法了解

这里从个人角度,总结下python常用算法,不罗嗦,直接看代码(文字解释及推到过程网上有很多,大家可以通过度娘了解) 以下排名仅从写代码人自己习惯的顺序! NO.1 二分查找 import time # 时间装饰器 def cal_time(func): def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() # print("%s r

python常用算法(6)——贪心算法,欧几里得算法

1,贪心算法 贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以考虑,他所做出的的时在某种意义上的局部最优解. 贪心算法并不保证会得到最优解,但是在某些问题上贪心算法的解就是最优解.要会判断一个问题能否用贪心算法来计算.贪心算法和其他算法比较有明显的区别,动态规划每次都是综合所有问题的子问题的解得到当前的最优解(全局最优解),而不是贪心地选择:回溯法是尝试选择一条路,如果选择错了的话可以“反悔”,也就是回过头来重新选择其他的试试. 1.1

python常用算法学习(4)——数据结构

数据结构简介 1,数据结构 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成.简单来说,数据结构就是设计数据以何种方式组织并存贮在计算机中.比如:列表,集合与字典等都是一种数据结构.而之前已经学习过列表,字典,集合,元组等,这里就简单说一下不再赘述. N.Wirth:“程序=数据结构+算法” 数据:数据即信息的载体,是能够输入到计算机中并且能被计算机识别,存储和处理的符号总称. 数据元素:数据元素是数据的基本单位,又称之为记录(Record),一般,数据元

算法思想之回溯法

一.概念 回溯:当把问题分成若干步骤并递归求解时,如果当期步骤没有合法选择,则函数将返回上一级递归调用,这种现象称为回溯. 回溯算法应用范围:只要把待求解问题分成不太多的步骤,每个步骤又只有不太多的选择,即可以考虑用回溯法. 回溯算法实际上是一个递归枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径. 回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标.但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不

Python常用算法

本节内容 算法定义 时间复杂度 空间复杂度 常用算法实例 1.算法定义 算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制.也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出.如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题.不同的算法可能用不同的时间.空间或效率来完成同样的任务.一个算法的优劣可以用空间复杂度与时间复杂度来衡量. 一个算法应该具有以下七个重要的特征: ①有穷性(Fin

算法学习笔记——回溯法

一.基本概念 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就"回溯"返回,尝试别的路径. 回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标.但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为"回溯点". 许多复杂的,规模较大的问题都可以使用回溯法,有"通用解题方法"的美称. 二.基本思想 在包

经典算法学习之回溯法

回溯法的应用范围:只要能把待求解的问题分成不太多的步骤,每个步骤又只有不太多的选择就可以考虑使用回溯法. 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束. 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束. 回溯法将问题的候选解按照某一顺序逐一枚举和检验.当发现当前候选解不可能是解时,就选择下一个候选解,若当前候选解符合要求,且还未达到求解的规模,则继续扩展当前候 选解的规模,如果候选解已经满足了所有要求,并且也达到了问题的规模,那么该候选解就是