(一)图
基本方法
DFS
运用递归,深度优先遍历。
代码框架为:
void dfs(int u) {
//对当前节点进行操作
for (int v: edges[u]) {//遍历当前节点的邻居
//(如果未遍历过)递归DFS
dfs(v);
}
//对当前节点后续操作
}
时间复杂度: O ( n + m ) O(n+m) O(n+m)
空间复杂度: O ( n + m ) O(n+m) O(n+m),其中 O ( m ) O(m) O(m)为存储所有边需要的空间, O ( n ) O(n) O(n)为深度优先搜索中使用的栈空间。
BFS
利用队列迭代操作,广度优先遍历。
代码框架为:
queue<int>q;
q.push(i);//初始化队列
while(!q.empty()){
int u=q.front();
q.pop();
//取出队首元素,操作
for(auto v:edges[u]){//遍历队首元素的每一条边
//相应操作
//(if...)子节点入队
q.push(v);
}
}
时间复杂度: O ( n + m ) O(n+m) O(n+m)
空间复杂度: O ( n + m ) O(n+m) O(n+m)。为了对图进行广度优先搜索,我们需要存储成邻接表的形式,空间复杂度为 O ( n + m ) O(n+m) O(n+m)。在广度优先搜索的过程中,我们需要最多 O ( n ) O(n) O(n) 的队列空间(迭代)进行广度优先搜索。因此总空间复杂度为 O ( n + m ) O(n+m) O(n+m)。
并查集
主要操作:(加权)并+(压缩路径)查
代码框架为:
class UnionFind{
public:
vector<int>parent;
vector<int>size;
int setcount;
public:
UnionFind(int n){
setcount=n;
parent.resize(n);
for(int i=0;i<n;i++)
{
parent[i]=i;
}
size.resize(n,1);
}
int findset(int x){
return parent[x]==x?x:findset(parent[x]);
}
bool unite(int x,int y){
int rx=findset(x);
int ry=findset(y);
if(rx==ry)
return false;
if(size[rx]<size[ry])
swap(rx,ry);
parent[ry]=rx;
size[rx]+=size[ry];
setcount--;
return true;
}
bool connected(int x,int y){
return findset(x)==findset(y);
}
};
时间复杂度: O ( m ⋅ α ( n ) ) O(m \cdot \alpha(n)) O(m⋅α(n)),其中 α \alpha α 是阿克曼函数的反函数。
空间复杂度: O ( n ) O(n) O(n),即为并查集需要使用的空间。
应用
岛屿类问题
判断base case+访问相邻节点
避免重复遍历→状态标记数组
连通性
天然并查集(DFS也可以)
拓扑排序
DFS/BFS
-
DFS
对于每个节点有未搜索,搜索中,已搜索三个状态,u是v的先修课程也就是v→u。初始化未搜索,选择一个节点进行DFS变成搜索中,当一个节点所有子节点都完成搜索后它变成已搜索,入栈。如果它的子节点有搜索中,则说明有环,无法拓扑排序。拓扑排序结果就是从栈底到栈顶的序列。
-
BFS
u是v的先修课程也就是u→v,将每个入度为0的点入队,每次把队首节点加入答案时,它决定的其他节点入度减一,更新队列。迭代操作直到队伍为空,如果答案中节点数目等于课程数目则说明能拓扑排序。