DFS
DFS核心:
构造递归,F(0)、F(1)是递归边界,递归式就是岔路口。枚举的思想+递归的表达方式:
提示:类似全排列模板,给定一个序列,枚举这个序列的所有子序列(可以不连续)
void DFS(int index, int sumw,int sumc)
{
if(index==n){
if(sumw<=v && sumc>maxvalue)
maxvalue=sumc;
}
DFS(index+1,sumw,sumc);
DFS(index+1,sumw+w[index],sumc+c[index]);
}
简单剪枝:
void DFS(int index, int sumw,int sumc)
{
if(index==n){
return;
}
DFS(index+1,sumw,sumc);
if(sumw+w[index]<=v){ // 当数量不超过时记录并递归下去
if(sumc+c[index]>ans)
ans=sumc+c[index];
DFS(index+1,sumw+w[index],sumc+c[index]);
}
}
图的DFS
相较于一般DFS多出了vis判断,依旧是枚举+递归;
伪代码:
DFS(u){
vis[u]=true;
for(从u出发能到达的所有顶点v)
if vis[v]==false
DFS(V);
}
DFSTrave(G)
{
for(G的所有顶点u)
if vis[u]==false
DFS(u);
}
1)邻接矩阵
const int Maxv=1000;//最大顶点
const int Inf=100000000;
int n,G[Maxv][Maxv];
bool vis[Maxv]={false};
void DFS(int u,int depth){
vis[u]=true;
//从u出发能到达的所有顶点v
for(int v=0;v<n;v++)
if(vis[v]==false && G[u][v]!=Inf){
DFS(v,depth+1);
}
}
void DFSTrave()
{
for(int u=0;u<n;u++){
//G的所有顶点u
if(vis[u]==false)
DFS(u,1);
}
}
2)邻接表
const int Maxv=1000;//最大顶点
const int Inf=100000000;
//区别一:用vector定义邻接表
vector<int> adj[Maxv];
int n;
bool vis[Maxv]={false};
void DFS(int u,int depth){
vis[u]=true;
//从u出发能到达的所有顶点v
for(int v=0;v<n;v++){
//区别二:提取出结点u->i
int v=adj[u][i];
if(vis[v]==false){
DFS(v,depth+1);
}
}
}
void DFSTrave()
{
for(int u=0;u<n;u++){
//G的所有顶点u
if(vis[u]==false)
DFS(u,1);
}
}
BFS:
场景:适合走迷宫
基本模板:
void BFS(int s)
{
queue<int> q;
q.push(s);
while(!q.empty()){
取出队首元素top;
访问队首元素top;
将队首元素出队;
将top的下层结点中未曾入队的结点全部入队,并设置为已入队;
}
}
提示:对于迷宫类,会用到增量数组
int x[]={1,-1,0,0};
int y[]={0,0,1,-1};
详细版本:
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int maxn=100;
struct node{
int x,y;
}nod;
int n,m;
int matrix[maxn][maxn];
//定义的是入队情况,BFS只允许入队一次,而不是是否遍历/visit,因为后者可能出现多次入队
//(比如,A、B都连接C,A访问时C入队,B访问时有可能再一次让C入队。)
bool inq[maxn][maxn]={false};
int Y[]={1,-1,0,0};
int X[]={0,0,1,-1};
//边界检查
bool judge(int x,int y)
{
if(x>=n ||x<0 || y>=m || y<0) return false;
if(matrix[x][y]==0 || inq[x][y]==true ) return false;
return true;
}
void BFS(int x,int y){
queue<node> q;
nod.x=x,nod.y=y;
q.push(nod);
inq[x][y]=true;
while(!q.empty())
{
node top=q.front();
q.pop();
//将top的下层结点中未曾入队的结点全部入队,并设置为已入队;
for(int i=0;i<4;i++)
{
int newx=top.x+X[i];
int newy=top.y+Y[i];
//剪枝
if(judge(newx,newy)){
nod.x=newx,nod.y=newy;
q.push(nod);
inq[newx][newy]=true;
}
}
}
}
细节强调:STL的queue时,元素入队的push操作只是制造了该元素的一个副本入队,因此在入队后对原元素的修改不会影响队列中的副本,而对队列中副本的修改也不会改变原元素。因而会引起bug(一般由结构体产生)。
即入队时q.push(i);比q.push(a[i]);好前者可以直接通过a[q.front()]对结构体或者数组进行修改。
强化详见PAT A1091 Acute Stroke (30 分)
https://pintia.cn/problemsets/994805342720868352/problems/994805375457411072
图的BFS
模板:
邻接矩阵版:
int n,G[maxv][maxv];
bool inq[maxv]={false};
void BFS(int u)
{
queue<int> q;
q.push(u);
inq[u]=true;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int v=0;v<n;v++)
if(inq[v]==false && G[u][v]!=inf)
{
q.push(v);
inq[v]=true;
}
}
}
BFStrave(G)
{
for(int u=0;u<n;u++){
if(inq[u]==false)
{
BFS(u);
}
}
}
邻接表:
int n;
//区别一:定义不一样
vector<int> adj[maxv];
bool inq[maxv]={false};
void BFS(int u)
{
queue<int> q;
q.push(u);
inq[u]=true;
while(!q.empty())
{
int u=q.front();
q.pop();
//区别二:取点方式有差异
for(int i=0;i<adj[u].size();i++){
int v=adj[u][i];
if(inq[v]==false && G[u][v]!=inf)
{
q.push(v);
inq[v]=true;
}
}
}
}
BFStrave(G)
{
for(int u=0;u<n;u++){
if(inq[u]==false)
{
BFS(u);
}
}
}
图算法存储方式提示
领接表:
vector adj[n];
如,添加一条从1号顶点到3号顶点的有向边:adj[1].push_back(3);
同时存放边的终点和边权:
struct node{
int v; //边的终点编号
int w; //边权
}
vector adj[n];
更快的是构造结构体node的构造函数:
struct node
{
int v,w;
Node(int _v,int _w):v(_v),w(_w) {}
//构造函数(就是new对象时,能够同时进行一些操作,比如赋值什么的.
//如果你对构造方法进行重载的话,就可以根据不同的参数来构造不同初始值的对象.)
}
好处是不用定义临时变量,adj[1].push_back(Node(3,4));