图论基础
图的存储
在进行图的搜索之前,我们需要先了解图是通过怎样的方式存储下来的.
方式一:
邻接矩阵
开一个大小为O(|V|*|V|)的bool或int数组,用来存两个点之间是否有边以及边的其他信息(如边的数量以及边的权值等)
int G[MAXN+5][MAXN+5];
......
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(满足某个条件)
G[i][j]=G[j][i]=1;(有向图:G[i][j]=1)
优势:
这样可以用O(1)的之间来访问两点之间是否有边以及边上的信息,建图也十分方便,但是要注意的是,某些情况下需要进行特殊的初始化(如边权为0但是又有边时需要初始化为INF).有时也可采用多个邻接矩阵来存储各种信息.
局限:
内存占用巨大,遍历边的耗时过多,对于稀疏图的存储很亏.
方式二:
邻接表:
邻接表分两种存储方式.
vector邻接表:
vector<int> G[MAXN+5];
......
G[i].push_back(j);
//需要存边的信息时:
struct edge
{
int v,val;
//也可写一个重构函数
};
vector<edge> G[MAXN+5];
......
G[i].push_back(edge(v,val));
手写邻接表:
struct node
{
int v,/*边的其他信息*/;
node *next,/**back*/;
}
node *Adj[MAXN+5],*ncnt=&edge[0];
//如果有多组数据,切记要重新置为&edge[0]
......
void AddEdge(int u,int v)
{
node *p=++ncnt;
p->v=v;
p->next=Adj[u];
Adj[u]=p;
/*反向边
node *q=++ncnt;
q->v=u;
q->next=Adj[v];
Adj[v]=q;
p->back=q,q->back=p;
*/
}
......
//遍历:
for(node *p=Adj[u];p!=NULL;p=p->next)
{
.....(对边的处理)
}
也可以用在结构体里存vector,顺便存下各节点的信息的方法来构图;
优势:
节省内存(O(|V|+|E|)).
局限:
查找一条边是否存在是需要遍历,耗时巨大(但可以将邻接矩阵配合使用以节省时间).
图的遍历
以下均用手写邻接表的方式呈现
DFS
bool vis[MAXN+5];
void DFS(int u)
{
vis[u]=true;
for(node *p=Adj[u];p!=NULL;p=p->next)
{
int v=p->v;
if(vis[v]==true)
continue;
DFS(v);
}
//vis[u]=false;如果需要的话,这叫"恢复现场"
}
例题:二染色问题,哈密顿路,计算连通块的数量
BFS
queue<int> q;
while(q.empty()==false)
{
int u=q.front();
q.pop();
if(满足某个条件)
break;
for(node *p=Adj[u];p!=NULL;p=p->next)
{
int v=p->v;
if(vis[v]==false)
{
vis[v]=true;
q.push(v);
}
}
}
例题:
走迷宫(需要在)过程中用pre[]数组记录路径.
图的遍历问题最注重的应该是效率,因此每次在做决策之前应先计算一下遍历所需的时间以及空间大小(非常容易炸掉),如果不行,再想办法优化或者更换方法.
对于图的拓扑排序以及欧拉遍历等问题,将在后继推出.