基本概念
图G是一个有序二元组(V,E),其中V称为顶点集(Vertices Set), E称为边集(Edges set),它们亦可写成V(G)和E(G)。E的元素都是二元组,用(x,y)表示,其中x,y∈V。
图的存储方式(仅记录本人常用的链式前向星)
struct EDGE
{
int v;
int w;
int next;
}edge[MAX_E];
int head[MAX_V],cnt;
以上为存储一个图需要用到的数据结构
其中,一个EDGE结构体用于存储一条边的信息,v表示该边终点,w表示该边长度,next表示与该边拥有同一起点的下一条边的编号
head[u]表示以u为起点的第一条边,初值为0,表示该点不连接任何边,cnt记录存入边的编号,从1开始计数,便于与代表无边的0区别开
录入信息:
void Insert_E(int u,v,w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt++;
}
遍历:
for(int i=head[u];i;i=edge[i].next)
{
//略
}
若要存储无向图,则仅需在调用Insert_E(u,v,w)后再调用依次Insert_E(v,u,w)即可
Dijkstra 算法求最短路
Dijkstra的基本思路是维护一个堆,堆中元素是节点编号以及目前计算出的到达该点的最短路
初始时堆中仅有一个元素(s,0),s代表起点,0表示起点距离起点距离为0
另设一个数组dist[v],记录已计算得到的到达v点的最短距离,初始全部为INF,dist[0]=0
每次取出堆中到达路径最短的元素(此处可考虑使用结构体并对结构体进行运算符重载),若该距离大于dist中存储的最短距离,则说明该数据已无效,忽略并进行下一个元素的处理
否则,更新dist[v]为dist[u]+w,并将v以及dist[v]信息存入堆中
反复以上操作,直至堆空
代码:
typedef struct node
{
int id;
int dis;
bool operator<(const node b)const
{
return dis>b.dis;
}
}N;
priority_queue <node> que;
void dijkstra(int s)
{
N start;
start.id=s;
start.dis=0;
que.push(start);
while(!que.empty())
{
int u=que.top().id;
int D=que.top().dis;
que.pop();
if(D>d[u])continue;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v;
int w=e[i].w;
if(d[v]>d[u]+w)
{
d[v]=d[u]+w;
N point;
point.id=v;
point.dis=d[v];
que.push(point);
}
}
}
}
Dijkstra的缺点再于无法处理含负环的图
SPFA求最短路
SPFA既有dfs形式也有bfs形式,为了防止爆栈,一般采用bfs形式的spfa
bfs形式的SPFA的思路在于维护一个队列,队列中存放点,这些点是一开始的起点,和刚刚被进行松弛操作的边的终点
每次取出队首的点,搜索其每一条边,判断dist[u]+w是否小于dist[v],若是,则修改dist[v] (松弛),并将v存入队列
重复上述操作直至队空
此外,若一个点重复入队次数超过n次(边总数),则可认为图中含有负环
代码略
并查集
并查集可用于判断图中点与点之间是否连通
设置标记数组pa[MAX_V],初始时pa[i]=i,每个点都以自己为标记,表示暂时没有点连通
当加入连通信息时,搜索两个节点的标记,并将其统一,表示他们已被纳入同一个集合
需要注意的时,搜索标记的代码如下:
int find(x)
{
if(x==pa[x])return x;
return pa[x]=find(pa[x]);
}
合并标记的代码:
void union(int x, int y)
{
int px = Find(x);
int py = Find(y);
pa[px]=py;
}
并查集有按秩合并的改进策略,具体内容是增加rank数组记录其标记等级,总是将更小的树连接到更大的树上,当两个需要合并的标记等级相同时,它们合并后对合并成的标记的秩+1
if(rank[px]<=rank[py]){
pa[px]=py
if(rank[px]==rank[py])
rank[py]++;
}
else
pa[py]=px;
这段改进策略本人还未弄清需在什么情况下用到,暂先记下,之后的题目中可能会遇到
(Floyd与最小生成树笔记略)
例题
POJ – 1182 (并查集)
#include<cstdio>
#include<iostream>
using namespace std;
int pa[50010];
int re[50010]={0};
void clean(int n)
{
for(int i=1;i<=n;i++)pa[i]=i;
}
int find(int x)
{
if(x==pa[x])return x;
int tmp=pa[x];
pa[x]=find(pa[x]);
re[x]=(re[x]+re[tmp])%3;
return pa[x];
}
int main()
{
int n,k,ans=0;
scanf("%d%d",&n,&k);
clean(n);
while(k--)
{
int d,x,y;
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n){ans++;continue;}
int px=find(x);
int py=find(y);
if(d==1)
{
if(px==py&&re[x]==re[y]){continue;}
if(px==py&&re[x]!=re[y]){ans++;continue;}
pa[py]=px;
re[py]=(re[x]-re[y]+3)%3;
}
if(d==2)
{
if(px==py&&(re[x]+1)%3==re[y]){continue;}
if(px==py&&(re[x]+1)%3!=re[y]){ans++;continue;}
pa[py]=px;
re[py]=(re[x]-re[y]+4)%3;
}
}
printf("%d\n",ans);
return 0;
}