文章目录

  • 一. 基础概念:
    • * 图的三种表示方法:
      • 1. 邻接矩阵:
      • 2. 关联矩阵:
      • 3. 邻接表:
  • 二. 广度优先搜索BFS:
    • 1. 实现栈和队列:
    • 2. 实现邻接表描述的图:
    • 4. 实现广度优先搜索:
    • 5. 广度优先搜索的简单应用:
  • 三. 深度优先搜索DFS:
  • 四. 所有代码:

一. 基础概念:

图的数学表述为 G=(V,E)G=(V,E)G=(V,E),即图是由一组顶点和一组边构成,例如:


相关概念如下:

  • 相邻节点;
  • 度:相邻节点的个数;
  • 路径:从某个节点到另一个节点的连续序列;
  • 简单路径:无重复节点的路径;
  • 无环的
  • 连通的:每两个节点之间都存在路径;
  • 无向的
  • 有向的;
  • 加权的;
  • 未加权的
  • 强连通的:对于有向图,每两个顶点之间都存在双向的路径;

例如上面的例子中展现的就是一张无环的、无向的、未加权的、连通的图,这样的图里任意两个节点间都存在简单路径。


* 图的三种表示方法:

1. 邻接矩阵:

array[i][j]={1,i与j为相邻顶点;0,i与j不相邻;array[i][j] = \begin{cases} 1, & i与j为相邻顶点; \\ 0, & i与j不相邻; \end{cases}array[i][j]={1,0,​i与j为相邻顶点;i与j不相邻;​

容易得到对于邻接矩阵MMM,MT=MM^T = MMT=M。

2. 关联矩阵:

array[v][e]={1,v是e的入射点;0,v不是e的入射点;array[v][e] = \begin{cases} 1, & v是e的入射点; \\ 0, & v不是e的入射点; \end{cases}array[v][e]={1,0,​v是e的入射点;v不是e的入射点;​

对于有向图可稍作修改,例如

3. 邻接表:

邻接表最简单的描述方式是使用字典,以某顶点为键,以该顶点的相邻顶点为值即可。例如最开始的例子中的图可以表示为一个Python字典:

{A:[B,C,D],B:[A,E,F],C:[A,G],D:[A,H,I,J],    E:[B,K,L],F:[B],G:[C],H:[D,M],I:[D],J:[D],K:[E],L:[E],  M:[H]
}

后面我们将采用这种方式实现图,进而使用BFS与DFS算法遍历图中的节点。


二. 广度优先搜索BFS:

广度优先搜索是一种对图进行遍历的算法,其遍历思想是“先宽后深”,优先访问同一层的节点;而深度优先搜索的遍历思想则是“先深后宽”,从指定顶点开始,沿着某条路径直到这条路径的最后一个节点,再原路退回,探索下一条路径。

对于这两种算法,我们其实只需要将队列应用到BFS中、将栈应用到DFS中,即可非常相似的实现两种算法。这一点我们后续可以更清楚的看到。那么首先让我们实现简单的栈和队列:

1. 实现栈和队列:

栈和队列的构建都很简单,我们使用Python提供的列表存储数据,然后遵守相应的“先进后出”、“先进先出”原则定义入栈/出栈、入队/出队的方法即可。最后,我们需要一个方法动态获取栈/队列的长度,将size定义为方法而不是属性可以简化代码、避免手动更新size属性。

代码如下:

class MyQueue(object):'''构建队列'''def __init__(self):self.myQueue = []def push(self,item):self.myQueue.append(item)return self.myQueuedef pop(self):return self.myQueue.pop(0)def size(self):'''将队列的大小动态定义为方法,其它方法中无需对其进行显示更新'''return len(self.myQueue)class MyStack(object):'''构建栈'''def __init__(self):self.myStack = []def push(self,item):self.myStack.append(item)return self.myStackdef pop(self):return self.myStack.pop(len(self.myStack) - 1)def size(self):return len(self.myStack)

2. 实现邻接表描述的图:

我们在构建邻接表描述的图时,主要任务就是构建一个之前提到过的字典,形如{节点:相邻节点},其中相邻节点使用列表存储。另外为了后续的方便,我们另外构造一个列表存储图中所有的节点,技巧是每次更新图时使用列表转集合再转列表进行降重。最后,我们添加了一个方法,返回描述我们表示的图的字符串,便于检查。

代码如下:

class MyGraph(object):'''构建图'''def __init__(self):self.vertexName = []self.myGraph = {}def push(self,v_first,v_next):self.vertexName.append(v_first)self.vertexName = list(set(self.vertexName))            #节点名构成的列表是无序的if self.myGraph.get(v_first) == None:self.myGraph[v_first] = []self.myGraph[v_first].append(v_next)return self.myGraphdef get(self,v):return self.myGraph[v] def view(self):                                '''以便于检查的形式输出整个图的结构'''view = ''for v in self.vertexName:view_now = '{} ->'.format(v)for v_next in self.myGraph[v]:view_now += str(v_next) + ' 'view += view_now + '\n'return view

另外,为了避免重复构造同一结构的图,我们使用json库保存构造好的图,具体来说是保存构造好的字典与节点列表,所以我们定义两个常用的函数,一个用于保存数据、一个用于加载数据:

def load(path):'''加载数据'''with open(path,'r') as f_obj:return json.loads(f_obj.read())def save(data,path):'''保存数据'''with open(path,'w') as f_obj:f_obj.write(json.dumps(data))

有了这些工具,我们就可以实现用邻接表表述的图了。为了方便输入,我们用一个新函数简化构造过程:

def get_myGraph(path='myGraph.json'):'''构建邻接表表示的实际的图'''myGraph = MyGraph()while True:data = '[' + input("plz input data as data:list:") + ']' #将输入格式化为JSON字符串if data == '[over]':break                                                    #输入字符床over即可结束输入工作print(data)input()data = json.loads(data)for i in range(len(data[1])):myGraph.push(data[0],data[1][i])print(myGraph.view())save([myGraph.vertexName,myGraph.myGraph],path)return myGraph.vertexName,myGraph.json                             #返回字典结构的图和列表结构的所有节点

有了这个函数,我们只需要按照规定的形式输入我们想要抽象表示的图,即可将其生成的字典与节点列表存储到JSON文件中,并且在我们想使用的时候也可以很方便的将其加载。

下面,我们用邻接表表示最开始给出的示例,得到的字典、节点列表以及图的字符串描述分别如下:

{"A": ["B", "C", "D"],
"B": ["A", "E", "F"],
"C": ["A", "G"],
"D": ["A", "H", "I", "J"],
"E": ["B", "K", "L"],
"F": ["B"],
"G": ["C"],
"H": ["D", "M"],
"I": ["D"],
"J": ["D"],
"K": ["E"],
"L": ["E"],
"M": ["H"]}
["E", "B", "G", "C", "J", "F", "K", "M", "D", "H", "I", "L", "A"]
E ->B K L
B ->A E F
G ->C
C ->A G
J ->D
F ->B
K ->E
M ->H
D ->A H I J
H ->D M
I ->D
L ->E
A ->B C D

经检查无误。

4. 实现广度优先搜索:

我们使用三种“颜色”表示节点的不同状态:

  • 所有节点的原始状态都是white;
  • 当某个节点被首次发现时,其状态转换为gray;
  • 当某个节点的所有相邻节点都被发现后,其状态转换为black;

我们主要关注的是前两个状态,而black状态可以用于检查,也可以应用在拓扑排序中。

广度优先搜索的基本过程为选定一个初始节点,获取其相邻节点并将初始节点的状态变为gray,然后将初始节点的相邻节点添加到队列中,这样初始节点的状态又被转换为black。

此后,我们只需不断弹出队列中的当前节点,获取当前节点的相邻节点,将其中状态为white的节点添加到队列中并将其状态改变为gray,最后改变当前节点状态为black即可。通过white-gray两个状态的转换与检查,我们可以避免多次访问同一个节点,而通过队列的添加与弹出,我们实现了广度优先的搜索思路。此后实现的深度优先搜索只需将队列换成栈即可,其它思路一点都没有变化。

可以看到,为了避免某个节点多次被访问(多次入队),我们需要标记其状态,所以我们先利用一个函数初始化所有节点的状态:

def get_colorDict(vertexName):'''构造后续使用的节点-状态字典'''colorDict = {}for vertex in vertexName:colorDict[vertex] = 'white'           #将所有节点的状态初始化未white,意味未被发现return colorDict

有了上述工具后我们就可以进行广度优先搜索:

def bfs_demo(v_first,path='myGraph.json'):'''按照广度优先方法访问图中节点'''[vertex,graph] = load(path)colorDict = get_colorDict(vertex)queue = MyQueue()visitSequence = []#处理根节点:colorDict[v_first] = 'gray'visitSequence.append(v_first)for vertex in graph[v_first]:queue.push(vertex)colorDict[vertex] = 'gray'colorDict[v_first] = 'black'#开始广度优先搜索while queue.size() != 0:vertex_now = queue.pop()visitSequence.append(vertex_now)for vertex in graph[vertex_now]:if colorDict[vertex] == 'white':queue.push(vertex)colorDict[vertex] = 'gray'colorDict[vertex_now] = 'black'       return visitSequence

我们将需要的图、节点列表、状态字典、队列一一初始化之后,还需要初始化一个访问顺序列表用来检查我们的访问时候满足广度优先的条件。

当我们将某个节点的状态转换为gray的时候,我们访问了该节点,所以将其添加到访问顺序列表中。在单独处理完根节点后,我们在队列上进行循环,检查尚未访问的节点并将其入队,直到队列为空。最后我们返回访问顺序列表:

当将A作为根节点时:

['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M']

当将L作为根节点时:

['L', 'E', 'B', 'K', 'A', 'F', 'C', 'D','G', 'H', 'I', 'J', 'M']

可以看到,访问时是一层一层访问的。


5. 广度优先搜索的简单应用:

我们可以通过广度优先搜索实现许多应用。此处给出一个简单的例子:应用广度优先搜索获得图中所有节点到某个指定节点的最短路径:

def get_recallDict(vertexName):recallDict = {}for vertex in vertexName:recallDict[vertex] = Nonereturn recallDictdef bfs(v_first,path='myGraph.json'):'''使用广度优先方法遍历指定节点到其它所有节点的最短路径'''[vertexName,graph] = load(path)colorDict = get_colorDict(vertexName)queue = MyQueue()recallDict = get_recallDict(vertexName)#处理根节点colorDict[v_first] = 'gray'recallDict[v_first] = v_first          for vertex in graph[v_first]:queue.push(vertex)colorDict[vertex] = 'gray'recallDict[vertex] = v_firstcolorDict[v_first] = 'black'#广度优先搜索并记录发现的节点的前节点while queue.size() != 0:vertex_now = queue.pop()for vertex in graph[vertex_now]:if colorDict[vertex] == 'white':colorDict[vertex] = 'gray'recallDict[vertex] = vertex_nowqueue.push(vertex)colorDict[vertex_now] = 'black'pathDict = {}for vertex in vertexName:path = '{} -> '.format(vertex)v_next = recallDict[vertex]while v_next != v_first:path += '{} -> '.format(v_next)v_next = recallDict[v_next]path += '{}'.format(v_first)pathDict[vertex] = pathprint(path)return pathDict

为此,我们首先通过函数初始化了一个回溯字典,该字典记录我们的访问过程中某个节点的前一个节点。有了这样的回溯字典,我们可以得到整个路径。

之后,我们只需要在初始化的时候多初始化一个回溯字典,即可开始我们的搜索。过程相似,只是在将某个节点状态转换为gray的时候,我们在回溯字典中记录该节点的前节点。

访问完成后,我们即可进行回溯。首先定义一个路径字典,用于存储某个节点到指定节点的路径,最后我们也会返回路径字典,便于针对性的查找。但是为了便于检查,我们一次性将所有的结果视图化,将视图化的结果(字符串)打印出来。

结果如下:
以A为指定节点:

E -> B -> A
B -> A
G -> C -> A
C -> A
J -> D -> A
F -> B -> A
K -> E -> B -> A
M -> H -> D -> A
D -> A
H -> D -> A
I -> D -> A
L -> E -> B -> A
A -> A

以L为指定节点:

E -> L
B -> E -> L
G -> C -> A -> B -> E -> L
C -> A -> B -> E -> L
J -> D -> A -> B -> E -> L
F -> B -> E -> L
K -> E -> L
M -> H -> D -> A -> B -> E -> L
D -> A -> B -> E -> L
H -> D -> A -> B -> E -> L
I -> D -> A -> B -> E -> L
L -> L
A -> B -> E -> L

三. 深度优先搜索DFS:

对于深度优先搜索,我们只需要将队列改为栈即可,所以此处不进行代码展示,想了解可以直接看文章的第四部分。

下面是一些结果:

以A为根节点的访问顺序:

['A', 'D', 'J', 'I', 'H', 'M', 'C', 'G','B', 'F', 'E', 'L', 'K']

以L为根节点的访问顺序:

['L', 'E', 'K', 'B', 'F', 'A', 'D', 'J',
'I', 'H', 'M', 'C', 'G']

同样的,我们分别以A和L作为指定节点,可以得到路径组:

E -> B -> A
B -> A
G -> C -> A
C -> A
J -> D -> A
F -> B -> A
K -> E -> B -> A
M -> H -> D -> A
D -> A
H -> D -> A
I -> D -> A
L -> E -> B -> A
A -> A
E -> L
B -> E -> L
G -> C -> A -> B -> E -> L
C -> A -> B -> E -> L
J -> D -> A -> B -> E -> L
F -> B -> E -> L
K -> E -> L
M -> H -> D -> A -> B -> E -> L
D -> A -> B -> E -> L
H -> D -> A -> B -> E -> L
I -> D -> A -> B -> E -> L
L -> L
A -> B -> E -> L

我们选取的图比较简答,但是方便理解BFS与DFS的访问顺序,可以选取更复杂的图进行测试,看看二者得到的路径有什么不同。需要注意的是,我们的图可以是有环的,但这样的代码需要保证图是连通的(强连通的),且目前只适用于无权图,后续我们将讨论更加实用的有权图。对于连通性的要求,我们只需针对特定的图稍加修改即可。


四. 所有代码:

import jsonclass MyQueue(object):'''构建队列'''def __init__(self):self.myQueue = []def push(self,item):self.myQueue.append(item)return self.myQueuedef pop(self):return self.myQueue.pop(0)def size(self):'''将队列的大小动态定义为方法,其它方法中无需对其进行显示更新'''return len(self.myQueue)class MyStack(object):'''构建栈'''def __init__(self):self.myStack = []def push(self,item):self.myStack.append(item)return self.myStackdef pop(self):return self.myStack.pop(len(self.myStack) - 1)def size(self):return len(self.myStack)class MyGraph(object):'''构建图'''def __init__(self):self.vertexName = []self.myGraph = {}def push(self,v_first,v_next):self.vertexName.append(v_first)self.vertexName = list(set(self.vertexName))          #节点名构成的列表是无序的if self.myGraph.get(v_first) == None:self.myGraph[v_first] = []self.myGraph[v_first].append(v_next)return self.myGraphdef get(self,v):return self.myGraph[v] def view(self):                                '''以便于检查的形式输出整个图的结构'''view = ''for v in self.vertexName:view_now = '{} ->'.format(v)for v_next in self.myGraph[v]:view_now += str(v_next) + ' 'view += view_now + '\n'return viewdef load(path):'''加载数据'''with open(path,'r') as f_obj:return json.loads(f_obj.read())def save(data,path):'''保存数据'''with open(path,'w') as f_obj:f_obj.write(json.dumps(data))def get_myGraph(path='myGraph.json'):'''构建邻接表表示的实际的图'''myGraph = MyGraph()while True:data = '[' + input("plz input data as data:list:") + ']' #将输入格式化为JSON字符串if data == '[over]':break                                                    #输入字符床over即可结束输入工作print(data)input()data = json.loads(data)for i in range(len(data[1])):myGraph.push(data[0],data[1][i])print(myGraph.view())save([myGraph.vertexName,myGraph.myGraph],path)return myGraph.vertexName,myGraph.json                             #返回字典结构的图和列表结构的所有节点def get_colorDict(vertexName):'''构造后续使用的节点-状态字典'''colorDict = {}for vertex in vertexName:colorDict[vertex] = 'white'           #将所有节点的状态初始化未white,意味未被发现return colorDictdef bfs_demo(v_first,path='myGraph.json'):'''按照广度优先方法访问图中节点'''[vertex,graph] = load(path)colorDict = get_colorDict(vertex)queue = MyQueue()visitSequence = []#处理根节点:colorDict[v_first] = 'gray'visitSequence.append(v_first)for vertex in graph[v_first]:queue.push(vertex)colorDict[vertex] = 'gray'colorDict[v_first] = 'black'#开始广度优先搜索while queue.size() != 0:vertex_now = queue.pop()visitSequence.append(vertex_now)for vertex in graph[vertex_now]:if colorDict[vertex] == 'white':queue.push(vertex)colorDict[vertex] = 'gray'colorDict[vertex_now] = 'black'      return visitSequencedef get_recallDict(vertexName):recallDict = {}for vertex in vertexName:recallDict[vertex] = Nonereturn recallDictdef bfs(v_first,path='myGraph.json'):'''使用广度优先方法遍历指定节点到其它所有节点的最短路径'''[vertexName,graph] = load(path)colorDict = get_colorDict(vertexName)queue = MyQueue()recallDict = get_recallDict(vertexName)#处理根节点colorDict[v_first] = 'gray'recallDict[v_first] = v_first         for vertex in graph[v_first]:queue.push(vertex)colorDict[vertex] = 'gray'recallDict[vertex] = v_firstcolorDict[v_first] = 'black'#广度优先搜索并记录发现的节点的前节点while queue.size() != 0:vertex_now = queue.pop()for vertex in graph[vertex_now]:if colorDict[vertex] == 'white':colorDict[vertex] = 'gray'recallDict[vertex] = vertex_nowqueue.push(vertex)colorDict[vertex_now] = 'black'pathDict = {}for vertex in vertexName:path = '{} -> '.format(vertex)v_next = recallDict[vertex]while v_next != v_first:path += '{} -> '.format(v_next)v_next = recallDict[v_next]path += '{}'.format(v_first)pathDict[vertex] = pathprint(path)return pathDictdef dfs_demo(v_first,path='myGraph.json'):'''按照深度优先方法访问图中节点'''[vertex,graph] = load(path)colorDict = get_colorDict(vertex)stack = MyStack()visitSequence = []#处理根节点:colorDict[v_first] = 'gray'visitSequence.append(v_first)for vertex in graph[v_first]:stack.push(vertex)colorDict[vertex] = 'gray'colorDict[v_first] = 'black'#开始深度优先搜索while stack.size() != 0:vertex_now = stack.pop()visitSequence.append(vertex_now)for vertex in graph[vertex_now]:if colorDict[vertex] == 'white':stack.push(vertex)colorDict[vertex] = 'gray'colorDict[vertex_now] = 'black'        return visitSequencedef dfs(v_first,path='myGraph.json'):'''按照深度优先方法遍历指定节点到其它所有节点的最短路径'''[vertexName,graph] = load(path)colorDict = get_colorDict(vertexName)stack = MyStack()recallDict = get_recallDict(vertexName)#处理根节点colorDict[v_first] = 'gray'recallDict[v_first] = v_first            for vertex in graph[v_first]:stack.push(vertex)colorDict[vertex] = 'gray'recallDict[vertex] = v_firstcolorDict[v_first] = 'black'#深度优先搜索并记录发现的节点的前节点while stack.size() != 0:vertex_now = stack.pop()for vertex in graph[vertex_now]:if colorDict[vertex] == 'white':colorDict[vertex] = 'gray'recallDict[vertex] = vertex_nowstack.push(vertex)colorDict[vertex_now] = 'black'pathDict = {}for vertex in vertexName:path = '{} -> '.format(vertex)v_next = recallDict[vertex]while v_next != v_first:path += '{} -> '.format(v_next)v_next = recallDict[v_next]path += '{}'.format(v_first)pathDict[vertex] = pathprint(path)return pathDictif __name__ == '__main__':pass

图(一)| BFS与DFS算法 - Python实现相关推荐

  1. Python BFS和DFS算法

    Python BFS和DFS算法 看了b站灯神的视频,整理如下.最后再加上几条实战题. 1.BFS bfs全称是广度优先搜索,任选一个点作为起始点,然后选择和其直接相连的(按顺序展开)走下去.主要用队 ...

  2. python dfs算法_LeetCode | 一文帮你搞定BFS、DFS算法(python版)

    模板方法 使用BFS,DFS的题目,在leetcode上一般标记为medium或者hard.但从思维逻辑上看,其难度定义偏高.可能从代码量来看,写一道BFS或者DFS的篇幅比其他类型的题目要多. BF ...

  3. BFS和DFS算法原理(通俗易懂版)

    DFS 算法 思想:一直往深处走,直到找到解或者走不下去为止 BFS算法 DFS:使用栈保存未被检测的结点,结点按照深度优先的次序被访问并依次被压入栈中,并以相反的次序出栈进行新的检测. BFS:使用 ...

  4. js数据结构与算法 图的BFS和DFS

    本文为技术学习的笔记-<Learning JavaScript Data Structures and Algorithms, Third Edition> 1.图的相关术语 图是网络结构 ...

  5. 利用邻接表完成图的BFS和DFS

    #include <iostream> using namespace std; #define Maxsize 100 typedef char VertexType; typedef ...

  6. (造轮子)C 创建队列和图实现广度优先算法(BFS)和深度优先算法(DFS)(数据结构)

    链表.队列和图实现BFS和DFS算法(C+造轮子+详细代码注释) 1.队列的链式存储结构   队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表.头指针指向队头节点,尾指针指向 ...

  7. BFS和DFS优先搜索算法

    4.教你通透彻底理解:BFS和DFS优先搜索算法 作者:July  二零一一年一月一日 --------------------------------- 本人参考:算法导论  本人声明:个人原创,转 ...

  8. 分享两个常见的搜索算法:BFS和DFS

    本文分享自华为云社区<BFS和DFS算法初探>,作者: ayin. 本次分享两个常见的搜索算法 1.BFS 即广度优先搜索 2.DFS 即深度优先搜索 岛屿数量 给定一个由 '1'(陆地) ...

  9. 拓扑排序(Topological Sorting):Kahn 算法和 DFS 算法

    拓扑排序(Topological Sorting):Kahn 算法和 DFS 算法 Kahn 算法 DFS 算法 拓扑排序(TopoSort)有相当广泛的应用,比如依赖关系分析.图是否有环的检测.编译 ...

  10. 【数据结构】图的遍历(BFS和DFS)

    图的遍历 图的遍历是指从图中的某一顶点出发,按照某种搜索方式沿着途中的边对图中所有顶点访问一次且仅访问一次.图的遍历主要有两种算法:广度优先搜索和深度优先搜索. 广度优先遍历BFS 广度优先遍历(BF ...

最新文章

  1. Core Text 学习笔记-基础
  2. OpenGL背景照明
  3. GroupID和ArtifactID
  4. PowerBI,自定义编辑同一页面中不同图表之间的交互,使页面交互更灵活
  5. oracle最快访问行,Oracle技术网—在Oracle快速进行数据行存在性检查
  6. 会计准则应用指南2020pdf_如何提高企业会计准则体系的学习效率和效果
  7. 一天一点数据结构+算法:复习堆的知识
  8. 第十二章 演员评论家(Actor-Critic)-强化学习理论学习与代码实现(强化学习导论第二版)
  9. 格雷码编码器 c语言,格雷码编码器功能实现
  10. java 图片转pdf_在Java语言中将图像转换为PDF?Spire.PDF for Java轻松搞定!
  11. 同表父子关系 的SQL查询语句的写法
  12. 图像语义分割-CVPR2020-CPNet:结合不同类别的上下文信息先验--场景语义分割--注意力机制--Context Prior for Scene Segmentation--关键创新点代码复现
  13. GPL和LGPL的区别!
  14. 微型计算机原理与接口技术(慕课版),微机原理与接口技术(温淑焕)
  15. web服务器/app应用服务器
  16. 民间借贷的法定利息又降低了
  17. zoj-Swordfish-2022-5-6
  18. 采访手艺人的花絮视频
  19. pagehelper原理 分页
  20. BRAS和SR设备的定义区别

热门文章

  1. ezcad旋转轴标刻参数_激光打标机软件ezcad中菜单下的旋转轴标刻功能介绍及其操作设置...
  2. html 中加载字体太慢,css字体文件包太大无法引入怎么处理?
  3. 解决笔记本没有COM端口导致无法用SecureCRT或者超级终端配置交换机
  4. [问题探讨]H5打包为原生Android和IOS的移动APP后请求无法触发问题
  5. mcuisp下载程序
  6. android百度地图调用,Android 调用百度地图API
  7. 普通路由器改4g路由器_4G工业路由器在安防领域中的应用
  8. matlab怎么构建函数模型,matlab数学模型建立(如何用matlab建立数学模型及求解。哪位高手给个模版。)...
  9. android:scaleType=centerCrop
  10. 哔哩哔哩用户需求分析报告