算法工程师必知必会的10大基础算法与python示例(二)

算法工程师必知必会的10大基础算法与python示例(二)

  • 算法六:深度优先搜索(DFS)
    • leetcode例子
  • 算法七:广度优先搜索(BFS)
    • leetcode例子
  • 算法八:狄克斯特拉算法(Dijkstra)
    • 算法例子
  • 算法九:动态规划算法(dynamic programming)
    • leetcode 例子
  • 算法十:朴素贝叶斯分类算法

写在一起太长了,分两部分,第二部分放这~~~

算法六:深度优先搜索(DFS)

深度优先搜索(depth first search),是图搜索算法的一种,它会沿着图的某一条路径穷尽遍历,然后再回溯,到之前的vertex去遍历其他的路径。这种算法在一个特殊的图-----二叉树中用的比较多。以二叉树距离,DFS即从根节点出发,穷尽某条路径,直到叶节点,再回溯其父节点,穷尽该父节点的其他子节点。重复这个过程,知道遍历整个树,或者找到目标节点。
DFS属于盲目搜索。DFS是图论中的经典算法,利用DFS产生的目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题。

DFS算法步骤如下:

  1. 访问节点v。
  2. 一次从v的未访问节点出发,对图进行深度优先遍历,直至途中和v有路径相通的定点都被访问。
  3. 若此时途中尚有未被访问的顶点,则从一个未被访问的定点出发,重新进行深度优先遍历,直至途中所有的顶点均被访问。

比较经典的深度有点遍历例子就是数的先序(中序、后续)遍历,采用递归策略,代码如下:

'''
class Node:def __init__(self, val):self.val = valself.left = Noneself.right = None
'''
class Solution:def DFS(self, root):if not root:returnprint(root.val) #先序遍历self.DFS(root.left)#print(root.val) #中序遍历self.DFS(root.right)#print(root.val) #后续遍历

一般作为辅助手段,我们这里举leecode中的一个有意思的题,作为例子

leetcode例子

这是个序列化反序列化二叉树的例子,深度优先遍历这种递归解法很是优雅,贴在这里仅供观赏。

'''
297. 二叉树的序列化与反序列化
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。示例: 你可以将以下二叉树:1/ \2   3/ \4   5序列化为 "[1,2,3,null,null,4,5]"
'''# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = Noneclass Codec:def serialize(self, root):"""Encodes a tree to a single string.:type root: TreeNode:rtype: str"""def recurse_first(root):if not root:return ',null'else:return ','+str(root.val) +recurse_first(root.left)+recurse_first(root.right)re = recurse_first(root)return re[1:]def deserialize(self, data):"""Decodes your encoded data to tree.:type data: str:rtype: TreeNode"""data = data.split(',')def recurse_first(ind):if data[ind] == 'null':return ind, Noneelse:root = TreeNode(data[ind])l, left = recurse_first(ind + 1)root.left = leftr, right = recurse_first(l + 1)root.right = rightreturn r, rootreturn recurse_first(0)[1]

算法七:广度优先搜索(BFS)

说完DFS不得不说一下BSF,BFS是广度优先搜索(breadth first search)。这也是一种图所搜算法,不过与DFS不同的是,这种算法不是沿着一条路径穷尽再回溯,而是直接将vertex的临近节点穷尽,再穷尽临近节点的临近节点,知道遍历完整个图或者找到目标图位置。BFS一般用于找寻无权图的最优路径,一般情况下用一个队列来辅助,队列是FIFO的。
DFS的算法步骤如下:

  1. 首先将原点放入队列。
  2. 循环,当队列非空时,从队列中pop出一个节点,将该节点标记为已访问,然后将这个节点的所有临近节点全部压入队列。
  3. 再循环过程中,如果遇到目标节点,退出循环。队列为空,退出循环,意味着与原点联通的节点全部遍历完毕。

leetcode例子

无权图的话,是可以用bfs找最短路径的。leetcode上面有一个有意思的题,直接上吧

'''
给你一个 m x n 的网格图 grid 。 grid 中每个格子都有一个数字,对应着从该格子出发下一步走的方向。 grid[i][j] 中的数字可能为以下几种情况:1 ,下一步往右走,也就是你会从 grid[i][j] 走到 grid[i][j + 1]
2 ,下一步往左走,也就是你会从 grid[i][j] 走到 grid[i][j - 1]
3 ,下一步往下走,也就是你会从 grid[i][j] 走到 grid[i + 1][j]
4 ,下一步往上走,也就是你会从 grid[i][j] 走到 grid[i - 1][j]
注意网格图中可能会有 无效数字 ,因为它们可能指向 grid 以外的区域。一开始,你会从最左上角的格子 (0,0) 出发。我们定义一条 有效路径 为从格子 (0,0) 出发,每一步都顺着数字对应方向走,最终在最右下角的格子 (m - 1, n - 1) 结束的路径。有效路径 不需要是最短路径 。你可以花费 cost = 1 的代价修改一个格子中的数字,但每个格子中的数字 只能修改一次 。请你返回让网格图至少有一条有效路径的最小代价。示例 1:输入:grid = [[1,1,1,1],[2,2,2,2],[1,1,1,1],[2,2,2,2]]
输出:3
解释:你将从点 (0, 0) 出发。
到达 (3, 3) 的路径为: (0, 0) --> (0, 1) --> (0, 2) --> (0, 3) 花费代价 cost = 1 使方向向下 --> (1, 3) --> (1, 2) --> (1, 1) --> (1, 0) 花费代价 cost = 1 使方向向下 --> (2, 0) --> (2, 1) --> (2, 2) --> (2, 3) 花费代价 cost = 1 使方向向下 --> (3, 3)
总花费为 cost = 3.来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-cost-to-make-at-least-one-valid-path-in-a-grid
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
'''class Solution:def minCost(self, grid: List[List[int]]) -> int:dx = [0, 0, 1, -1]dy = [1, -1, 0, 0]import queuestore = queue.Queue()dis = [[100000000 for i in range(len(grid[0]))] for j in range(len(grid))]store.put([0, 0])dis[0][0] = 0while not store.empty():[x, y] = store.get()for i in range(4):nx = x + dx[i]ny = y + dy[i]if nx<0 or nx>=len(grid) or ny<0 or ny>=len(grid[0]):continuenew_dis = dis[x][y] + 0 if i+1 == grid[x][y] else dis[x][y] + 1if new_dis

算法八:狄克斯特拉算法(Dijkstra)

bfs可以解决无权图最短路径问题。可是,在实际工程过程中,边带权重的图才更加广泛。嗯,带权图的最短路径算法狄克斯特拉算法应运而生。该算法由荷兰计算机科学家狄克斯特拉提出。

Dijkstra算法使用广度优先搜索思路来解决非负有权有向的单源最短路径问题,算法最终得到一个最短路径树。该算法的输入包含了一个有权重的有向图 G G G以及 G G G中的一个来源顶点 S S S,我们用 V V V标识 G G G中的所有顶点的集合。

图都是加权有向的,所以表示起来稍微麻烦些。用有序数对 ( u , v ) (u, v) (u,v)表示从顶点 U U U到顶点 V V V有路径连接,我们用 E E E表示 G G G中的所有边的集合,而边的权重则有权重函数 w : E → [ 0 , ∞ ) w:E\to[0, \infty) w:E[0,)定义,即用数组 w ( u , v ) w(u, v) w(u,v)表示有向路径 ( u , v ) (u, v) (u,v)的非负权重。

狄克斯特拉算法用于在单源有向图中寻找源点到其他顶点的最短路径。对于不含负权的有向图,狄克斯特拉算法是目前已知的最快的单源最短路径算法。

狄克斯特拉算法如下:

1.初始时,令 S = v 0 , T = o t h e r v e r t e x S={v_0}, T={other vertex} S=v0,T=othervertex T T T中顶点对应的距离值,若存在 ( v 0 , v i ) (v_0, v_i) (v0,vi),则令 d ( v 0 , v i ) = w ( v 0 , v i ) d(v_0, v_i)=w(v_0, v_i) d(v0,vi)=w(v0,vi);若不存在,令 d ( v 0 , v i ) = ∞ d(v_0, v_i)=\infty d(v0,vi)=

2.在 T T T中选取一个其距离值为最小的顶点 W W W且不在 S S S中,加入 S S S

3.在 W W W有连接的顶点,更新其距离值。

4不断重复2, 3两步,直至 S S S中包含所有顶点为止。

算法例子

我们这里举一个特别简单的例子,图如代码示例中所示。
假设 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3是四个公交站,他们都是单程的,每条边上边 ⟨ i , j ⟩ \langle i, j\rangle i,j标出了从站 i i i到站 j j j所需时间。从图中我们可以得到如下数据

w = ( 0 6 2 ∞ ∞ 0 ∞ 1 ∞ 3 0 5 ∞ ∞ ∞ 0 ) v = [ 0 , 1 , 2 , 3 ] d i r = { 0 : [ 1 , 2 ] , 2 : [ 1 , 2 ] , 1 : [ 3 ] } w = \begin{pmatrix}0&6&2&\infty\\ \infty&0&\infty&1\\ \infty&3&0&5\\ \infty&\infty&\infty&0\end{pmatrix}\\ v = [0, 1, 2, 3]\\ dir = \{0:[1, 2], 2:[1, 2], 1:[3]\} w=060320150v=[0,1,2,3]dir={0:[1,2],2:[1,2],1:[3]}
式中 v v v表示顶点, d i r dir dir是邻接表, w w w是路径的权重。


'''#####->  # 1 #6 /     #####   \ 1/        ^       \
#####         |         ->###### 0 #        | 3         # 3 #
#####         |         ->#####\  2      |       / 5\     #####  /->   # 2 ######'''
import sys
class Dijkstra:def __init__(self, v, w, dire):self.w = w #距离矩阵self.v = v #vertex集合self.dir = dire #邻接表self.visit = set([0]) #用于记录是否被访问过self.path = {} #用于记录每个被访问到的点的路径self.dis = [sys.maxsize]*len(v) #用于记录每个点到源点的距离def dijkstra(self):for i, v in enumerate(self.v):self.dis[i] = self.w[0][i]self.path[i] = [0, i]while len(self.visit) self.dis[tmp_ind]+self.w[tmp_ind][v]:self.dis[v] = self.dis[tmp_ind]+self.w[tmp_ind][v]self.path[v] = self.path[tmp_ind] + [v] #跟新路径class Solution:def dijkstra(self, v, w, dire):self.dij = Dijkstra(v, w, dire)self.dij.dijkstra()if __name__ == "__main__":v = [0,1,2,3]dire = {0:[1,2],2:[1,2],1:[3]}w = [[0, 6, 2, sys.maxsize], [sys.maxsize, 0, sys.maxsize, 1], [sys.maxsize, 3, 0, 5], [sys.maxsize, sys.maxsize, sys.maxsize, 0]]solution = Solution()solution.dijkstra(v, w, dire)print(solution.dij.dis[3], solution.dij.path[3])

算法九:动态规划算法(dynamic programming)

DP问题是一个超大课题,可以写很多专题来讲解,这里放在必知必会的第九个,也算合理吧。我感觉leetcode的系列文章不错,可以参考下
dp的问题跨度太大,不过总体有一个共同特点,如果状态设计合理的话,可以四两拨千斤,用最快的速度,最小的内存来解决最难的问题。
dp类似于数学归纳法,从小规模的问题推到出大规模的解法,并且存储这些中间过程。最直观的dp是菲波那切数列的求解。菲波那切数列定义中所周知
f 0 = 1 f 1 = 1 f n = f n − 1 + f n − 2 f_0 = 1\\ f_1 = 1\\ f_n = f_{n-1} + f_{n-2} f0=1f1=1fn=fn1+fn2
如果递归解的话,指数发散会。这个用dp接, d p [ n ] = f n dp[n]=f_n dp[n]=fn,这样转移方程即是 d p [ n ] = d p [ n − 1 ] + d p [ n − 2 ] dp[n] = dp[n-1]+dp[n-2] dp[n]=dp[n1]+dp[n2]

嗯,总结下动态规划,其实就两部分,一是DP的状态,二是状态的转移方程。DP的状态学问比较大,好的状态事半功倍,坏的状态~~~~,嗯坏的状态解题解题解到撞墙也应该解不出来。状态的确定有两大原则,即最优子结构和无后效性。

1.最优子结构:
动态规划就是将大问题化为一个一个的小问题,然后求解,这个子问题就是子结构,对于每个子问题,其最优值均由更小规模的子问题的最优值推到而出,即为最优子结构。

2.无后效性:
对于无后效性,就是我们只关心子问题的最优解,不关心子问题的最优解是怎么得到的。即子问题的最优解求解过程不会对后续有影响。这一点很重要,坏的状态一般都是有后效性的,这个用dp求解,好像就是暴力求解了。

我们将最优子结构的最优值都存储下来,状态转移方程给出了我们如何从最优子结构的最优值得到更大的结构的最优值,即我们从存储的这些值里面,既能推到出当下结构的最优值。

其余关于dp的一些总结与问题,可以参看其他资料了(以后可能也会总结到这里),我们这里只说最基本的。

leetcode 例子

dp算法leetcode题的经典解法,例题太多,我们这里找个简单明了的说一下。其实dp最经典的是背包问题,大神们都说看懂了背包问题,dp问题就不是问题了。有点想绕口令哈。直接上题吧。

'''
硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)示例1:输入: n = 5输出:2解释: 有两种方式可以凑成总金额:
5=5
5=1+1+1+1+1
示例2:输入: n = 10输出:4解释: 有四种方式可以凑成总金额:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1
'''
class Solution:def waysToChange(self, n: int) -> int:dp = [1] + [0]*ncoins = [1, 5, 10, 25]for coin in coins:for i in range(coin, n+1):dp[i] += dp[i-coin]return dp[n]%1000000007if __name__=='__main__':solution = Solution()n=10print(solution.waysToChange(n))

算法十:朴素贝叶斯分类算法

朴素贝叶斯分类算法十一中基于贝叶斯定理的简单概率分类算法,贝叶斯分类的基础是概率推理。就是在各种条件的存在不确定,仅知其出现概率的情况下,如何完成推理和决策任务。
朴素贝叶斯分类器是基于独立假设的,即假设样本每个特征与其他特征都不相关,概率上是相互独立的。
这个leetcode上面貌似没有例题了,这属于machine learning中的基础算法了。我们用《machine learning in action》中的例子来说明这个问题吧,这本书中分类章节专门讲的朴素贝叶斯分类,讲的清晰明了,核心就两个问题,一是特征概率独立,二是贝叶斯推断方程,即
p ( x 1 , x 2 , . . . , x n ) = p ( x 1 ) p ( x 2 ) . . . p ( x n ) p ( y / x ) = p ( x / y ) p ( y ) p ( x ) p(x_1, x_2, ..., x_n) = p(x_1)p(x_2)...p(x_n)\\ p(y/x)=\frac{p(x/y)p(y)}{p(x)} p(x1,x2,...,xn)=p(x1)p(x2)...p(xn)p(y/x)=p(x)p(x/y)p(y)
书中给的例子是分类垃圾邮件,代码较长,我就不贴这里了,有兴趣的同学可以自行查看。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部