DFS和BFS
节点间通路
节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。
提示:
节点数量n在[0, 1e5]范围内。
节点编号大于等于 0 小于 n。
图中可能存在自环和平行边。
思路: 判断两点之间是否连通,用DFS或者BFS,用栈实现DFS,用队列实现BFS。我不喜欢递归,递归要迭代,我脑子里建不起迭代的过程图。
用栈实现DFS伪码:
-
栈初始化,并让起始顶点入栈,起始顶点改为已访问
-
栈不为空:
2.1 取栈顶元素
2.2 栈顶元素的邻接点 n n n,并且 n n n没有被访问,则:将邻接点 n n n标记为已访问,并且让点 n n n进栈
2.3 如果2.2没有邻接点进展,需要将当前结点退栈。
class Solution {
public:
vector<vector<int>> construct_map(int n, vector<vector<int>>& graph){
vector<vector<int>> map(n);
int row = graph.size();
for(int i = 0;i<row;i++){
int node_1 = graph[i][0];
int node_2 = graph[i][1];
map[node_1].push_back(node_2);
}
return map;
}
// DFS with stack
bool findWhetherExistsPath(int n, vector<vector<int>>& graph, int start, int target) {
if(start == target) return true;
vector<vector<int>> map = construct_map(n, graph);
//
vector<int> visit(n, 0);
stack<int> S;
S.push(start);
visit[start] = 1;
while(!S.empty()){
int tmp = S.top();
int len = map[tmp].size();
bool flag = false;
for(int i=0;i<len;i++){
int node = map[tmp][i];
if(!visit[node]){
S.push(node);
visit[node] = 1;
flag = true;
if(node == target){
return true;
}
break;
}
}
if(!flag){
S.pop();
}
}
return false;
}
// BFS with queue
bool findWhetherExistsPath(int n, vector<vector<int>>& graph, int start, int target) {
if(start == target) return true;
vector<vector<int>> map = construct_map(n, graph);
//
vector<int> visit(n, 0);
queue<int> Q;
Q.push(start);
visit[start] = 1;
while(!Q.empty()){
int tmp = Q.front();
int len = map[tmp].size();
bool flag = false;
for(int i=0;i<len;i++){
int node = map[tmp][i];
if(!visit[node]){
visit[node] = 1;
Q.push(node);
flag = true;
if(node == target){
return true;
}
}
}
if(!flag){
Q.pop();
}
}
return false;
}
};
判断二分图
给定一个无向图graph,当这个图为二分图时返回true。
如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。
graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。
思路: 二分图染色法判定二分图。对一个无向图,要给每个图上顶点进行染色,最多用两种染色,任意相邻的顶点染色不同,如果可以对图进行染色,那就说明是一个二分图。题目中没说给给定的图是连通图,所以还需要考虑到非连通的图。
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
int n = graph.size();
stack<int> S;
vector<int> vis(n, 0);
vector<int> color(n, -1);
// 考虑图非连通
for(int i=0;i<n;i++){
if(color[i]==-1){
S.push(i);
vis[i] = 1;
color[i] = 1;
while(!S.empty()){
int tmp = S.top();
int len = graph[tmp].size();
bool flag = false;
for(int i=0;i<len;i++){
int node = graph[tmp][i];
if(vis[node]){
if(color[node] == color[tmp]) return false;
}else{
S.push(node);
vis[node] = 1;
flag = true;
color[node] = color[tmp] ^ 1;
break;
}
}
if(!flag) S.pop();
}
}
}
return true;
}
};
课程表
现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?
思路: 判断是否是有向无环图DAG。DAG才有拓扑排序,非DAG没有拓扑排序。使用顶点入度+BFS,判断是否能够拓扑排序。
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
int row = prerequisites.size();
vector<int> degree(numCourses, 0);
for(int i=0;i<row;i++){
degree[prerequisites[i][1]]++;
}
queue<int> Q;
for(int i=0;i<numCourses;i++){
if(!degree[i]){
Q.push(i);
}
}
while(!Q.empty()){
int tmp = Q.front();
for(int i=0;i<row;i++){
if(tmp == prerequisites[i][0]){
int node = prerequisites[i][1];
degree[node]--;
if(degree[node]==0){
Q.push(node);
}
}
}
Q.pop();
numCourses--;
}
if(numCourses==0) return true;
else return false;
}
};
不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
思路: 假设网格的大小为 n ∗ m n*m n∗m,规定只能向下或者向右,从网格的左上角到右下角需要走 n + m − 2 n+m-2 n+m−2步,其中向下走了 n − 1 n-1 n−1,向右走了 m − 1 m-1 m−1,所以总的路径数就是 C m + n − 2 m i n ( n , m ) − 1 C_{m+n-2}^{min(n,m)-1} Cm+n−2min(n,m)−1
求组合数用到了 C n m = C n − 1 m − 1 + C n − 1 m C_{n}^{m} = C_{n-1}^{m-1} + C_{n-1}^{m} Cnm=Cn−1m−1+Cn−1m
class Solution {
public:
int uniquePaths(int m, int n) {
int total = m + n - 2;
int select = min(m, n) - 1;
return combination(total, select);
}
int combination(int total, int select){
if(total==select||select==0){
return 1;
}else if(select == 1){
return total;
}
return combination(total-1, select-1) + combination(total-1, select);
}
};