算法网课:程序设计与算法(二)算法基础(python实现)

本文精选了多种算法案例,包括枚举、动态规划等经典算法的实际应用,如称硬币问题、数字三角形最大和问题、最长公共子序列问题等,通过实例详细讲解了算法的设计思路与实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

网课地址及引用材料:程序设计与算法(二)算法基础

1. 枚举

1.3 称硬币(POJ1013)

有12枚硬币。其中有11枚真币和1枚假币。假币和真 币重量不同,但不知道假币比真币轻还是重。现在, 用一架天平称了这些币三次,告诉你称的结果,请你 找出假币并且确定假币是轻是重(数据保证一定能找 出来)。
输入
每组数据有三行,每行表示一次称量的结果。银币标号 为A-L。每次称量的结果用三个以空格隔开的字符串表示: 天平左边放置的硬币 天平右边放置的硬币 平衡状态。其中平衡状态用up'',down’’, 或 ``even’'表示, 分 别为右端高、右端低和平衡。天平左右的硬币数总是相等的。

输出
输出哪一个标号的银币是假币,并说明它比真币轻还是重。
输入样例
ABCD EFGH even
ABCI EFJK up
ABIJ EFGH even
输出样例
K is the counterfeit coin and it is light.

解题思路:
对于每一枚硬币先假设它是轻的,看这样是否符合 称量结果。如果符合,问题即解决。如果不符合,就 假设它是重的,看是否符合称量结果。把所有硬币都 试一遍,一定能找到特殊硬币。
假设是在CoinisFake函数里,用第二个输入bool变量,作为判断的标准。值得学习这种思想。

代码

# 创建矩阵的方法
left = [[0] for i in range(3)] 
right = [[0] for i in range(3)]
result = [[0] for i in range(3)]

def CoinisFake(curr, light, left, right,result):
    for i in range(3):
        if light:
            pleft = left[i]
            pright = right[i]
        else:
            pleft = right[i]
            pright = left[i]
        
        while result[i][0]:
            if result[i][0]=='u':
                if curr not in pright:
                    return False
                break
            if result[i][0]=='d':
                if curr not in pleft:
                    return False
                break
            if result[i][0]=='e':
                if curr in pleft or curr in pright:
                    return False
                break
            
    return True # 注意indent!需要在i循环之外。

left[0] = ['A','B','C','D']
left[1] = ['A','B','C','I']
left[2] = ['A','B','I','J']

right[0] = ['E','F','G','H']
right[1] = ['E','F','J','K']
right[2] = ['E','F','G','H']

result[0] = 'even'
result[1] = 'up'
result[2] = 'even'

list_AtoL = list(map(chr, range(ord('A'), ord('L') + 1) ) )
# 注意:使用ord作为编码,得到A到L的list

for curr in list_AtoL:
    if CoinisFake(curr, True, left, right,result):
        print(curr, " is the counterfeit coin and it is light.")
    elif CoinisFake(curr, False, left, right,result):
        print(curr, " is the counterfeit coin and it is heavy.")

1.4 熄灯问题

解题思路:
用二进制位来记录灯的开关(01)变动。
也可以用位运算来记录全部的状态。

6. 动态规划(一)

6.1 数字三角形

输入格式:
5 //三角形行数。下面是三角形
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
要求输出最大和

递归思路

解题思路:
动态规划。需要反转思路,类似于递归,但并不是由前推往后,而是由后推往前的过程。

用二维数组存放数字三角形。
D ( r , j ) D( r, j) D(r,j) : 第 r r r行第 j j j 个数字( r , j r,j r,j从1开始算)
M a x S u m ( r , j ) MaxSum(r, j) MaxSum(r,j) : 从 D ( r , j ) D(r,j) D(r,j)到底边的各条路径中,最佳路径的数字之和。
问题:求 M a x S u m ( 1 , 1 ) MaxSum(1,1) MaxSum(1,1), 是典型的递归问题。
D ( r , j ) D(r, j) D(r,j)出发,下一步只能走 D ( r + 1 , j ) D(r+1,j) D(r+1,j)或者 D ( r + 1 , j + 1 ) D(r+1, j+1) D(r+1,j+1)。故对于 N N N行的三角形:

if ( r == N) //只能是本身
	MaxSum(r,j) = D(r,j) 
else // 由下面两者决定
	MaxSum( r, j) = Max{ MaxSum(r+ 1,j), MaxSum(r+1,j+1) }+ D(r,j)

改进:时间复杂度问题
递归牺牲时间,所以改成递推,需要对结果进行存储。

如果每算出一个 M a x S u m ( r , j ) MaxSum(r,j) MaxSum(r,j)就保存起来,下次用到其值的时候直接取用,则可免去重复计算。那么可以用 O ( n 2 ) O(n^2) O(n2)时间完成计算。因为三角形的数字总数是 n ( n + 1 ) / 2 n(n+1)/2 n(n+1)/2

代码

##import sys
##n = int(input())
##triangle = [[0] for i in range(n)]
##for i in range(n):
##    triangle[i] = list(map(int, input().strip().split()))

triangle1 = [[7], [3,8],[8,1,0], [2,7,4,4],[4,5,2,6,5]]
n = len(triangle1)

# 递归程序(会超时)
def MaxSum(i,j,n,triangle):
    if i == n-1:
        return triangle[i][j]
    return max(MaxSum(i+1,j,n,triangle), MaxSum(i+1,j+1,n,triangle)) + triangle[i][j]

import time
time_start=time.time()
print(MaxSum(0,0,n,triangle1))
time_end=time.time()
time1 = time_end-time_start
print('totally cost of first method',time1)

# 改进:存储之前的结果

def MaxSum2(i,j,n,triangle):
    # 只要该结果已经存储,直接return
    if mat_maxsum[i][j] != -1:
        return mat_maxsum[i][j]
    if i == n-1:
        mat_maxsum[i][j] = triangle[i][j]
    else:
        mat_maxsum[i][j] = max(MaxSum2(i+1, j, n, triangle), MaxSum2(i+1, j+1, n, triangle))\
                           + triangle[i][j]
    return mat_maxsum[i][j]

time_start=time.time()
mat_maxsum = [[-1] * n for i in range(n)]
print(MaxSum2(0,0,n,triangle1))
time_end=time.time()
time2 = time_end-time_start
print('totally cost of first method', time2)
print('时间倍数: ', round(time1/time2,2))
30
totally cost of first method 0.013000726699829102
30
totally cost of first method 0.008000373840332031
时间倍数:  1.63

改进2:节约空间

triangle1 = [[7], [3,8],[8,1,0], [2,7,4,4],[4,5,2,6,5]]
n = len(triangle1)
import time
time_start=time.time()

mat_maxsum = [[-1] * n for i in range(n)]

for j in range(n):
    mat_maxsum[n-1][j] = triangle1[n-1][j]
for i in range(n-1-1, 0-1, -1):
    for j in range(i+1):
        mat_maxsum[i][j] = max(mat_maxsum[i+1][j], mat_maxsum[i+1][j+1])\
                           + triangle1[i][j]
print(mat_maxsum[0][0])
time_end=time.time()
print('totally cost of first method', time_end-time_start)

改进3:直接把mat_maxsum放到原矩阵当中。

没必要用二维maxSum数组存储每一个MaxSum(r,j),只要从底层一行行向上
递推,那么只要一维数组maxSum[100]即可,即只要存储一行的MaxSum值就
可以。进一步考虑,连maxSum数组都可以不要,直接用D的第n行替代maxSum即可。
在这里插入图片描述

节省空间,时间复杂度不变

代码:

triangle1 = [[7], [3,8],[8,1,0], [2,7,4,4],[4,5,2,6,5]]
n = len(triangle1)

# 节约空间1
import time
time_start=time.time()
mat_maxsum = [[-1] * n for i in range(n)]

for j in range(n):
    mat_maxsum[n-1][j] = triangle1[n-1][j]
for i in range(n-1-1, 0-1, -1):
    for j in range(i+1):
        mat_maxsum[i][j] = max(mat_maxsum[i+1][j], mat_maxsum[i+1][j+1])\
                           + triangle1[i][j]
print(mat_maxsum[0][0])
time_end=time.time()
print('totally cost of first method', time_end-time_start)

triangle2 = triangle1.copy()

# 节约空间2
time_start=time.time()
mat_maxsum2 = triangle2[n-1]
for i in range(n-1-1, 0-1, -1):
    for j in range(i+1):
        mat_maxsum2[j] = max(mat_maxsum2[j], mat_maxsum2[j+1])+triangle2[i][j]

print(mat_maxsum[0][0])
time_end=time.time()
print('totally cost of second method', time_end-time_start)

动态规划思路

在这里插入图片描述

动态规划解题一般思路

该2级标题所属内容均为引用。

  1. 将原问题分解为子问题
    把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)。
    子问题的解一旦求出就会被保存,所以每个子问题只需求解一次。
  2. 确定状态
    在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状态”。一个“状态”对应于一个或多个子问题,所谓某个“状态”下的“值”,就是这个“状态”所对应的子问题的解。
    所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。在数字三角形的例子里,一共有 N × ( N + 1 ) / 2 N×(N+1)/2 N×(N+1)/2个数字,所以这个问题的状态空间里一共就有 N × ( N + 1 ) / 2 N×(N+1)/2 N×(N+1)/2个状态。
    整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。
    在数字三角形里每个“状态”只需要经过一次,且在每个状态上作计算所花的时间都是和N无关的常数。
  3. 确定一些初始状态(边界状态)的值
    以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值。
  4. 确定状态转移方程
    定义出什么是“状态”,以及在该 “状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的“状态”,求出另一个“状态”的“值” (“人人为我”递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。

【对建立“状态”非常重要!】动态规划的特点

  1. 问题具有最优子结构性质。 如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质。
  2. 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。

6.2 例题2:最长上升子序列

在这里插入图片描述
输入数据
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给
出序列中的N个整数,这些整数的取值范围都在0到10000。
输出要求
最长上升子序列的长度。
输入样例
7
1 7 3 5 9 4 8
输出样例
4

解析:

在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述

代码

#import sys
#n = int(input())
#line = list(map(int, input().strip().split()))

n = 7
line = [1, 7, 3, 5, 9, 4, 8]
maxLen = list([1]* n)

def main():
    for i in range(1,n):
        for j in range(i):
            if line[i] > line[j]:
                maxLen[i] = max(maxLen[i], maxLen[j]+1 )
    print(max(maxLen))

main()

6.3 最长公共子序列

给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。
输入
abcfbc abfcab
programming contest
abcd mnp
输出
4
2
0

分析
输入两个串 s 1 , s 2 s1,s2 s1,s2,设 M a x L e n ( i , j ) MaxLen(i,j) MaxLen(i,j)表示: s 1 s1 s1的左边 i i i个字符形成的子串,与 s 2 s2 s2左边的 j j j个字符形成的子串的最长公共子序列的长度( i , j i,j i,j从0开始算)
M a x L e n ( i , j ) MaxLen(i,j) MaxLen(i,j) 就是本题的“状态”

假定 l e n 1 = s t r l e n ( s 1 ) , l e n 2 = s t r l e n ( s 2 ) len1 = strlen(s1),len2 = strlen(s2) len1=strlen(s1),len2=strlen(s2), 那么题目就是要求 M a x L e n ( l e n 1 , l e n 2 ) MaxLen(len1,len2) MaxLen(len1,len2)

显然:
M a x L e n ( n , 0 ) = 0 ( n = 0 … l e n 1 ) MaxLen(n,0) = 0 ( n= 0…len1) MaxLen(n,0)=0(n=0len1
M a x L e n ( 0 , n ) = 0 ( n = 0 … l e n 2 ) MaxLen(0,n) = 0 ( n=0…len2) MaxLen(0,n)=0(n=0len2
递推公式:

if ( s1[i-1] == s2[j-1] ) //s1的最左边字符是s1[0]
MaxLen(i,j) = MaxLen(i-1,j-1) + 1;
else
MaxLen(i,j) = Max(MaxLen(i,j-1),MaxLen(i-1,j) );

时间复杂度 O ( m n ) O(mn) O(mn) m , n m,n m,n是两个字串长度。

代码

word1 = 'programming'
word2 = 'contest'
len1 = len(word1)
len2 = len(word2)
# ------------- 用二维数组的情况 --------- #
# maxLen矩阵比字符串多一位,用以储存第一位“0”个字母
maxLen = [[0]*(len2+1) for _ in range(len1+1)]

# 这两个循环:两者任一是0个字母。无交集,结果为0
for i in range(len1+1):
    maxLen[i][0] = 0
for j in range(len2+1):
    maxLen[0][j] = 0

# i,j代表了第i,j个字母(1~len1 or len2),换到word1&2里需要减1获得位置。
for i in range(1,len1+1):
    for j in range(1,len2+1):
        if word1[i-1] == word2[j-1]:
            maxLen[i][j] = maxLen[i-1][j-1] + 1
        else:
            maxLen[i][j] = max(maxLen[i][j-1], maxLen[i-1][j])

print(maxLen[len1][len2])

# ------------- 只用一维数组的情况 --------- #
word3 = 'abcfbc'
word4 = 'abfcab' # 答案是4
def maxLength(word1, word2):
    n1 = len(word1)
    n2 = len(word2)
    dp = list(0 for _ in range(n1+1))
    start = 1
    temp = 1
    for i in range(1,n1+1):
        for j in range(start,n2+1):
            if word1[i-1] == word2[j-1]:
                dp[i] = dp[i-1] + 1
                temp = j-1
                break
            else:
                dp[i] = max(dp[i],dp[i-1])
        start = temp +1 
    return dp

print(maxLength(word1,word2), maxLength(word1,word2)[len(word1)]) # 2
print(maxLength(word3,word4),maxLength(word3,word4)[len(word3)]) # 4

6.4 最佳加法表达式

有一个由1…9组成的数字串.问如果将m个加号插入到这个数字串中,在各种可能形成的表达式中,值最小的那个表达式的值是多少?

# 遇到大数,可能存在时间复杂问题
def Numof(start, end, number): # 按照数据结构来看,0位开始
    alist = number[start:end+1]
    result = 0
    for i in alist:
        result = result *10 + int(i)
    return result

def main(num, line):
    lenline = len(line)
    dp = list([-1] * (lenline+1) for _ in range(num+1))
    for i in range(1,lenline+1):
        dp[0][i] =  Numof(0,i-1,line)
    if lenline <= num:
        return None
    else:
        for m in range(1,num+1):
            for n in range(m+1,lenline+1):
                temp = 10e9
                for i in range(m, n):
                    # 最好是画表格辅助
                    temp = min(temp,dp[m-1][i] + Numof(i+1-1, n-1,line))
                dp[m][n] = temp
    return dp[num][lenline]

line = '123456'
print(main(2, line))
print(main(1, line))

num2 = 4
line2 = '12345'
print(main(num2,line2))

7. 动态规划(二)

7.1 Help Jimmy(POJ1661)

在这里插入图片描述
场景中包括多个长度和高度各不相同的平台。地面是最低的平台,高度为零,长度无限。Jimmy老鼠在时刻0从高于所有平台的某处开始下落,它的下落速度始终为1米/秒。当Jimmy落到某个平台上时,游戏者选择让它向左还是向右跑,它跑动的速度也是1米/秒。当Jimmy跑到平台的边缘时,开始继续下落。 Jimmy每次下落的高度不能超过MAX米,不然就会摔死,游戏也会结束。设计一个程序,计算Jimmy到地面时可能的最早时间。

输入数据
第一行是测试数据的组数 t t t 0 &lt; = t &lt; = 20 0 &lt;= t &lt;= 20 0<=t<=20)。每组测试数据的第一行是四个整数 N , X , Y , M A X N, X, Y, MAX NXYMAX,用空格分隔。 N是平台的数目(不包括地面),X和Y是Jimmy开始下落的位置的横竖坐标, M A X MAX MAX是一次下落的最大高度。

接下来的N行每行描述一个平台,包括三个整数, X 1 [ i ] , X 2 [ i ] X1[i], X2[i] X1[i]X2[i] H [ i ] H[i] H[i]
H [ i ] H[i] H[i]表示平台的高度, X 1 [ i ] X1[i] X1[i] X 2 [ i ] X2[i] X2[i]表示平台左右端点的横坐标。
1 &lt; = N &lt; = 1000 1 &lt;= N &lt;= 1000 1<=N<=1000
− 20000 &lt; = X -20000 &lt;= X 20000<=X
X 1 [ i ] , X 2 [ i ] &lt; = 20000 X1[i], X2[i] &lt;= 20000 X1[i],X2[i]<=20000
0 &lt; H [ i ] &lt; Y &lt; = 20000 ( i = 1.. N ) 0 &lt; H[i] &lt; Y &lt;= 20000( i = 1..N) 0<H[i]<Y<=20000i=1..N

所有坐标的单位都是米。Jimmy的大小和平台的厚度均忽略不计。如果Jimmy恰好落在某个平台的边缘,被视为落在平台上。所有的平台均不重叠或相连。测试数据保Jimmy一定能安全到达地面。

输出要求
对输入的每组测试数据,输出一个整数,Jimmy到地面时可能的最早时间。
输入样例
1
3 8 17 20
0 10 8
0 10 13
4 14 3
输出样例
23

Jimmy跳到一块板上后,可以有两种选择,向左走,或向右走。走到左端和走到右端所需的时间,是很容易算的。如果我们能知道,以左端为起点到达地面的最短时间,和以右端为起点到达地面的最短时间,那么向左走还是向右走,就很容选择了。
因此,整个问题就被分解成两个子问题, 即Jimmy所在位置下方第一块板左端为起点到地面的最短时间,和右端为起点到地面的最短时间。这两个子问题在形式上和原问题是完全一致的。将板子从上到下从1开始进行无重复的编号(越高的板子编号越小,高度相同的几块板子,哪块编号在前无所谓),那么,和上面两个子问题相关的变量就只有板子的编号。

不妨认为Jimmy开始的位置是一个编号为0,长度为0的板子,假设LeftMinTime(k)表示从k号板子左端到地面的最短时间,RightMinTime(k)表示从k号板子右端到地面的最短时间,那么,求板子k左端点到地面的最短时间的方法如下:
在这里插入图片描述

参考

  • 代码(待更新)

7.3 神奇的背包

有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n( 1≤n ≤ 20)个想要得到的物品,每个物品的体积分别是a1, a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋, John就可以得到这些物品。现在的问题是, John有多少种不同的选择物品的方式。

输入
输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的
数目。接下来的n行,每行有一个1到40之间的正整数,分别
给出a1, a2……an的值。
输出
输出不同的选择物品的方式的数目。
输入样例
3
20
20
20
输出样例
3

##n = int(input())
##alist = []
##for _ in range(n):
##    alist.append(int(input()))

n = 4
alist = [20,10,10,20] # 答案是3:20+10+10,20+20,10+10+20
def main(n, alist):
    ways = [[0]*(n+1) for i in range(41)]
    ways[0][0] = 1
    for i in range(1, n+1):
        ways[0][i] = 1
    # 从前k种物品中选择一些,凑成体积w的做法数目
    for w in range(1,41):
        for k in range(1, n+1):
            # 如果前k-1个凑足,就不选第k个
            ways[w][k] = ways[w][k-1]
            # 如果可以选第k个,说明前k-1个是凑不齐的组合
            if w - alist[k-1] >= 0:
                ways[w][k] += ways[w-alist[k-1]][k-1]
    return ways#[40][n]

print(main(n, alist))
大数据在不论在研究还是工程领域都是热点之一,算法是大数据管理计算的核心主题。本程试图简要介绍大数据计算中涉及到的基本算法设计方法。适用于大数据研究开发人员,也适用于数据科学爱好者。 大数据算法这门程旨在通过讲授一些大数据上基本算法设计思想,包括概率算法、I/O有效算法和并行算法,让听的同学们接触到和传统算法程不一样的算法设计分析思路,并且以最新的研究成果为导向,让参这门程学习的同学了解大数据算法的前沿知识。通过这门程的学习,同学可以掌握大数据算法设计的基本思想,掌握大数据算法设计分析的技术。 【程目录】 第1章 大数据算法概述 大数据的定义特点 大数据算法 大数据算法设计分析 第2章 亚线性算法概述 亚线性算法的定义 水库抽样—空间亚线性算法 平面图直径—时间亚线性计算算法 全0数组判定—时间亚线性判定算法 第3章 亚线性算法例析 数据流中频繁元素 最小生成树 序列有序的判定 第4章 外存算法概述 外存存储结构外存算法 外存算法示例:外存排序算法 外存数据结构示例:外存查找树 第5章 外存查找结构 B树 KD树 第6章 外存图数据算法 表排序及其应用 时间前向处理方法 缩图法 第7章 基于MapReduce的并行算法设计 MapReduce概述 字数统计 平均数计算 单词共现矩阵的计算 第8章 MapReduce算法例析 连接(Join)算法算法 第9章 非MapReduce的并行算法设计 基于迭代处理平台的并行算法 基于图处理平台的并行算法 第10章 众包算法 众包的定义 众包的实例 众包的要素 众包算法例析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值