Day2-part1
Graphs and Breadth-First Search 图和广度优先搜索
问题描述
在这里介绍了图,以及实现了广度优先搜索,具体详解决的问题如图所示。
图的表示方法
图的表示方法通过字典来实现
字典的key值对应图中的每个点。value值对应连接的其余节点。
广度优先搜索
广度优先搜索的目的是什么? 寻找最短路径
广度搜索的实现方法是什么?逐层完成搜索实现。
一层一层进行寻找,直到找到目标点。
如何实现对图的广度搜索呢? S:存储已经出现过的节点的集合 T:一颗N叉树 Q:一个便于进进出出的队列
下面我们来看一下广度搜索的伪代码吧。
上面的伪代码核心在于哪里呢?是不是在于队列方面的应用?
如果我们的队列不为空?
我们就不断从队列的左端pop一个节点出来,判断他的值是否是target,如果是target那就说明已经找到了节点,随后返回我们的最短路径就好了。
但是如果不是target,那么针对所有的子节点,全部放入队列里,重复上述操作。
看一下流程图吧!
G = { 'A' : [ 'B', 'J', 'Me' ],
'B' : [ 'A' ],
'C' : [ 'D', 'P' ],
'D' : [ 'C', 'O' ],
'E' : [ 'F', 'J', 'K' ],
'F' : [ 'E', 'M' ],
'G' : [ 'K', 'S', 'Ed' ],
'H' : [ 'O', 'U' ],
'Me': [ 'A', 'I', 'N', 'V' ],
'I' : [ 'Me', 'Q' ],
'J' : [ 'A', 'E', 'L', 'Q', 'W' ],
'K' : [ 'E', 'G' ],
'L' : [ 'J', 'W' ],
'M' : [ 'E', 'S', 'T' ],
'N' : [ 'Me', 'V' ],
'O' : [ 'H', 'D' ],
'P' : [ 'C', 'U' ],
'Q' : [ 'I', 'J', 'X', 'Y' ],
'R' : [ 'Ed' ],
'S' : [ 'G', 'M', 'Y', 'Z' ],
'T' : [ 'M' ],
'U' : [ 'H', 'P' ],
'V' : [ 'Me', 'N' ],
'W' : [ 'J', 'L' ],
'X' : [ 'Q', 'Y' ],
'Y' : [ 'Q', 'S', 'X', 'Z' ],
'Ed': [ 'G', 'R', 'Z' ],
'Z' : [ 'S', 'Y', 'Ed' ] }
class tree:
class treenode:
def __init__(self,val=0,children=[]):
self.val=val
self.children=[]
self.next=None
def __init__(self):
self.root=None
def add(self,cur,v):
cur.children.append(v)
def BFS(G,root,target):
s=[]
T=tree()
s.append(root)
node=tree.treenode(val=root)
T.root=node
from collections import deque
que=deque([T.root])
while que:
a=que.popleft()
if a.val==target:
result=[]
while(a!=None):
result.append(a.val)
a=a.next
return list(reversed(result))
for v in G[a.val]:
if v not in s:
s.append(v)
node=tree.treenode(val=v)
node.next=a
T.add(node,a)
que.append(node)
print(DFS(G,'Me','Ed'))
有点困了,就先放在这里吧!
看一下这段代码,第一遍看的时候包含 result 和 next 的内容先不用看。
先看一下其它部分的代码,其实也就是对伪代码的实现。定义了一颗N叉树,定义了一个存放 已经出现过的节点的集合s。
将这个N叉树的根节点设置为 value=‘Me’的树节点,然后利用队列的性质完成遍历。针对每一层,逐步把他们都放入Tree的children中,同时也把他们都放在que队列的最末端。
这样实现遍历,最后得到搜索结果。
result和next是做什么用的呢?是为了展示搜索的最短路径!
这里我想的是通过链表来解决问题,他不是一个正向的链表,而是一个反向的链表,让下一个节点指向上一个节点。
这样就能够确保找到target目标点之后,可以通过不断的进行next,返回到root根节点。宝贝自己看一下能不能想的明白呢?
图论这里就这样喽。
Day 2-part 2
Intro to Dynamic Programming 初识动态规划
问题描述
斐波那契数列问题,棒料切割问题和有向图问题,在这里对你来说应该是小菜一碟了!
斐波那契数列问题
这里我直接放代码了
def f(n):
if n<2:
return n
else:
return f(n-1)+f(n-2)
def fib(n):
dp=[0]*(n+1)
if n==0:
return dp[0]
dp[1]=1
for i in range(2,n+1):
dp[i]=dp[i-1]+dp[i-2]
return dp[n]
def fib(n):
return int((pow((1+5**0.5)/2,n)-pow((1-5**0.5)/2,n))/5**0.5)
分别对应了三种方法,自己回忆一下吧。
棒料切割问题
你有一根长度为10的棒料,你应该以什么方法划分,才能够得到棒料划分的最大价值!
这里因为是引入动态规划的一节,所以ppt里面的代码写的沾点让人看不懂了哦。
我们用动态规划来做。
[1 ] 确定dp数组(dp table)以及下标的含义
[2 ] 确定递推公式
[3 ] dp数组如何初始化
[4 ] 确定遍历顺序
[5 ] 举例推导dp数组
1. 确定dp数组
dp数组很显然应该是一维数组,长度对应的是这根棒子的总长度。dp数组存储的内容是什么呢?dp数组存储的是当前棍棒长度,通过划分可以存放的最大价值。
对初始化进行考虑,根据dp数组的定义,在最开始,还没有考虑划分问题。那么dp[ i ]肯定全部是0
2.确定递推公式
dp[ j ]=max(dp[ j ],dp[ j -method[ i ][ 0 ]]+method[ i ][ 1 ])
自己画一下图想一下这个公式是什么意思吧!
因为可以用一种划分方法重复划分,所以这就是一个完全背包问题!
method=[
[1,1],
[2,5],
[3,8],
[4,9]
]
def cut(n):
dp=[0]*(n+1)
for i in range(len(method)):
for j in range(method[i][0],n+1):
dp[j]=max(dp[j],dp[j-method[i][0]]+method[i][1])
return dp[n]
print(cut(10))
有向图问题
以这张图为例,有向图是什么呢?第一,只能沿着箭头通行;第二,走每条路都需要花费一定的代价。我们想解决的问题是,形成一个矩阵,便于我们查阅从点A到点B的最小代价。
针对这个图,我们用二维邻接矩阵来表示。
A[ i , j ]表示从点 i 走到点 j 的代价。极大值代表两者之间无通路,走不通。
max_value=float('inf')
A[0]=[
[0,max_value,max_value,1],
[2,0,4,5],
[max_value,max_value,0,3],
[max_value,7,1,0]
]
针对A[ i ][ j ]为从i到j的最短路径应该是:
A[ i ][ j ]=min(A[ i ][ u ]+w[ u ][ j ]),就是说从i点到j点的最短路径,一定是从i点到u点的最短路径加上从u点到j点的路径,最开始看这个公式其实很难理解,我们借助图来看一下这些公式。
A[ k ][ s ][ t ]=min(A[ k-1 ][ s ][ t ], A[ k-1 ][ s ][ k ]+A[ k-1 ][ k ][ t ])
借助代码实现一下:
max_value=float('inf')
A=[[]]
A[0]=[
[0,max_value,max_value,1],
[2,0,4,5],
[max_value,max_value,0,3],
[max_value,7,1,0]
]
def find(A):
n=len(A[0][0])
for i in range(1,n+1):
A.append(A[0])
for j in range(n):
for k in range(n):
A[i][j][k]=min(A[i-1][j][k],A[i-1][j][i-1]+A[i-1][i-1][k])
return A
find(A)
print(A[len(A[0][0])])
这一部分的内容又算是学完了,包含很多图的问题,我应该讲的时候会和你argue很久。但是你能学会就好了,针对每个算法,自己理解下,动手画画,让他们成为你自己的东西!我相信你一定可以的。