BFS的模板:
-
- 如果不需要确定当前遍历到了哪一层,BFS 模板如下。
while queue 不空:
cur = queue.pop()
for 节点 in cur的所有相邻节点:
if 该节点有效且未访问过:
queue.push(该节点)
-
- 如果要确定当前遍历到了哪一层,BFS 模板如下。 这里增加了 level 表示当前遍历到二叉树中的哪一层了,也可以理解为在一个图中,现在已经走了多少步了。size 表示在当前遍历层有多少个元素,也就是队列中的元素数,我们把这些元素一次性遍历完,即把当前层的所有元素都向外走了一步。
level = 0
while queue 不空:
size = queue.size()
while (size --) {
cur = queue.pop()
for 节点 in cur的所有相邻节点:
if 该节点有效且未被访问过:
queue.push(该节点)
}
level ++;
DFS模板(回溯)
- 1、最本质的法宝是“画图”,千万不能偷懒,拿纸和笔“画图”能帮助我们更好地分析递归结构,这个“递归结构”一般是“树形结构”,而符合题意的解正是在这个“树形结构”上进行一次“深度优先遍历”,这个过程有一个形象的名字,叫“搜索”;我们写代码也几乎是“看图写代码”,所以“画树形图”很重要。
- 2、然后使用一个状态变量,一般我习惯命名为 path、pre ,在这个“树形结构”上使用“深度优先遍历”,根据题目需要在适当的时候把符合条件的“状态”的值加入结果集;这个“状态”可能在叶子结点,也可能在中间的结点,也可能是到某一个结点所走过的路径。
- 3、在某一个结点有多个路径可以走的时候,使用循环结构。当程序递归到底返回到原来执行的结点时,“状态”以及与“状态”相关的变量需要“重置”成第 1 次走到这个结点的状态,这个操作有个形象的名字,叫“回溯”,“回溯”有“恢复现场”的意思:意即“回到当时的场景,已经走过了一条路,尝试走下一条路”。第 2 点中提到的状态通常是一个列表结构,因为一层一层递归下去,需要在列表的末尾追加,而返回到上一层递归结构,需要“状态重置”,因此要把列表的末尾的元素移除,符合这个性质的列表结构就是“栈”(只在一头操作)。
- 4、当我们明确知道一条路走不通的时候,例如通过一些逻辑计算可以推测某一个分支不能搜索到符合题意的结果,可以在循环中 continue 掉,这一步操作叫“剪枝”。“剪枝”的意义在于让程序尽量不要执行到更深的递归结构中,而又不遗漏符合题意的解。因为搜索的时间复杂度很高,“剪枝”操作得好的话,能大大提高程序的执行效率。“剪枝”通常需要对待搜索的对象做一些预处理,例如第 47 题、第 39 题、第 40 题、第 90 题需要对数组排序。“剪枝”操作也是这一类问题很难的地方,有一定技巧性。总结一下:“回溯” = “深度优先遍历” + “状态重置” + “剪枝”,写好“回溯”的前提是“画图”。因此,非要写一个模板,我想它可能长这个样子:
def backtrack(待搜索的集合, 递归到第几层, 状态变量 1, 状态变量 2, 结果集):
# 写递归函数都是这个套路:先写递归终止条件
if 可能是层数够深了:
# 打印或者把当前状态添加到结果集中
return
for 可以执行的分支路径 do //分支路径
# 剪枝
if 递归到第几层, 状态变量 1, 状态变量 2, 符合一定的剪枝条件:
continue
对状态变量状态变量 1, 状态变量 2 的操作(#)
# 递归执行下一层的逻辑
backtrack(待搜索的集合, 递归到第几层, 状态变量 1, 状态变量 2, 结果集)
对状态变量状态变量 1, 状态变量 2 的操作(与标注了 # 的那一行对称,称为状态重置)
end for
Dijkstra最小路径模板
- 算法的思路
Dijkstra算法采用的是一种贪心的策略,声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,初始时,原点 s 的路径权重被赋为 0 (dist[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s。
然后,从dist数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,OK,此时完成一个顶点,
然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,那么就替换这些顶点在dist中的值。
然后,又从dist中找出最小值,重复上述动作,直到T中包含了图的所有顶点。 - 题目
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。 - 输入格式
— 第一行包含整数 n 和 m。接下来 m 行每行包含三个整数x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。 - 输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。如果路径不存在,则输出 −1。 - 输入:
3 3
1 2 2
2 3 1
1 3 4
- 输出:
3
- 模板
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int n,m;
int g[N][N]; //使用邻接矩阵来储存
int dist[N]; //储存所有点到1号点的距离
int book[N];//用来判断点是否已经被确定
int dijktra()
{
memset(dist,0x3f,sizeof dist); // memset(首地址,初始化值,大小),0x3f 可以表示无穷大。
dist[1]=0;
for(int i=0;i<n;i++)//循环n个点
{
int t=-1;
for(int j=1;j<=n;j++)//每次寻找距离1号点最近且还没有被确定的点
{
if(!book[j]&&(t==-1||dist[t]>dist[j]))
t=j; //最近的点。
}
book[t]=1; //标记已经放入 s
for(int j=1;j<=n;j++)//更新每个点的最短值,更新剩下的点的距离。
{
dist[j]=min(dist[j],g[t][j]+dist[t]); //直接距离与 1-t的距离+t-j的距离。
}
}
if(dist[n]==0x3f3f3f3f)return -1;//如果1号点到不了n号点,就return -1
return dist[n];
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof g);//先把边的权重设为无穷大
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);//因为存在重边,所以我们需要的就是权值最短的边
}
cout<<dijktra()<<endl;
}
拓扑排序
- leecode : offer ii 114 外星文字典
现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不同。
给定一个字符串列表 words ,作为这门语言的词典,words 中的字符串已经 按这门新语言的字母顺序进行了排序 。
请你根据该词典还原出此语言中已知的字母顺序,并 按字母递增顺序 排列。若不存在合法字母顺序,返回 “” 。
若存在多种可能的合法字母顺序,返回其中 任意一种 顺序即可。
字符串 s 字典顺序小于 字符串 t 有两种情况:
在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。
如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。
深度优先+ 拓扑排序
- 第一步:建图
# 根据点的先后顺序建立图,形式为列表的字典,列表中存放相邻后继点。
g = {} # 字典,key 值为每个单词中出现的每个字母。
for c in words[0]:
g[c] = []
for s,t in pairwise(words):
# 相邻的两个单词两两组成一对。
for c in t :
g.setdefault(c, []) # 默认设置为空列表
for u,v in zip(s,t):
if u != v:
g[u].append(v)
break
else:
# 如果能够顺利完成循环
# 则需要比较s,t 的长度。
if len(s) > len(t):
return ""
# 不合法
# 建图完毕
- 第二步:进行深度优先的拓扑。
后继点有多种状态
已经访问:也就是它的所有后继点都放入到结果序列中了,才可以形成的状态。
正在访问:那么就说明从当前点回到了之前正在访问的点,那么就形成了环路,返回false.
没有访问过:那么对其进行访问,其状态之后可能出现以上两种情况。
VISITING, VISITED = 1, 2
states = {}
order = []
def dfs(u:str) -> bool:
# 访问节点u :
# 目的是将其入栈。
states[u] = VISITING
for v in g[u]:
# 如果 v 的下一个链接的字母没有被访问过,则访问它
if v not in states:
# 访问,并且下一个字母可以顺利的入栈。
if not dfs(v):
return False # 下一个字母不能顺利的入栈。
elif states[v] == VISITING:
# 如果下一个字母也是访问中,那么说明由当前点到下一个点可以形成环路,当前点可以到下一个点,下一个点在之前也访问中,可以到当前点。
return False
order.append(u) # 将当前点放入序列中。
states[u] = VISITED
return True
# 改变状态。
for u in g :
if u not in states:
if not dfs(u):
return "" # 有非法的形式。
return "".join(reversed(order)) '''
广度优先搜索+拓扑排序:
依然分为两步,按照先后顺序建图,但是关键还需要为每个节点统计入度,开始的时候是以入度0的结点开始进行广度遍历。
- 第一步:建图
不仅要建立边的相关关系g,还要建立每个节点的入度数 inDeg.
广度遍历两者缺一不可,前者用来确定每次遍历对象,后者则用来判断进入序列的条件。
g = defaultdict(list)
inDeg = {c:0 for c in words[0]}
for s,t in pairwise(words):
for c in t:
inDeg.setdefault(c, 0) # 默认的入度为0
for u,v in zip(s,t):
if u != v:
# 添加边,广度优先搜索必要的
g[u].append(v)
# 添加度
inDeg[v] += 1
break
else:
# 如果是循环结束,则需要比较 s,t 的长度。
if len(s) > len(t):
return ""
- 第二步:广度遍历,从入度为0 开始遍历。
首先将所有入度为0的点入序列,然后进行遍历,减少与之相邻的节点的入度,如果有节点的入度为0,则将其放入队列。
q = [u for u,d in inDeg.items() if d == 0] # 将所有入度为0的值加入到初始列表中。
# bfs
for u in q:
for v in g[u]:
inDeg[v] -= 1
if inDeg[v] == 0:
q.append(v) # 如果入度减少到0,则需要添到列表中。
# 判断字母是否全部在q中,没有全部进去则表示有环。
if len(q) == len(inDeg):
return "".join(q)
else:
return ""