基本概念
图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;
}