邻接表(转载)
(1.无向图)
邻接矩阵看上去是个不错的选择,首先是容易理解,第二是索引和编排都很舒服~
但是我们也发现,对于边数相对顶点较少的图,这种结构无疑是存在对存储空间的极大浪费。
因此我们可以考虑另外一种存储结构方式,例如把数组与链表结合一起来存储,这种方式在图结构也适用,我们称为邻接表(AdjacencyList)。
邻接表的处理方法是这样:
图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易地读取顶点信息,更加方便。
图中每个顶点Vi的所有邻接点构成一个线性表,由于邻接点的个数不确定,所以我们选择用单链表来存储。
(2.有向图)
若是有向图,邻接表结构也是类似的,我们先来看下把顶点当弧尾建立的邻接表,这样很容易就可以得到每个顶点的出度:
但也有时为了便于确定顶点的入度或以顶点为弧头的弧,我们可以建立一个有向图的逆邻接表:
此时我们很容易就可以算出某个顶点的入度或出度是多少,判断两顶点是否存在弧也很容易实现。
(3.网)
对于带权值的网图,可以在边表结点定义中再增加一个数据域来存储权值即可:
强连通分量&缩点(转载)
强连通分量&强连通图:在一个图的子图中,任意两个点相互可达,也就是存在互通的路径,那么这个子图就是强连通分量。(如果一个有向图的任意两个点相互可达,那么这个图就称为强连通图)。
Tarjan算法描述:
(1)我们先对每一个顶点判断,如果已经被运行过了,则不动,否则进行拓展
(2)对于每一个点,我们设2个值来表示(dfn和low){low[i]表示结点i的根结点的dfn值},dfn则是结点i的时间戳
(3)在第一次顺序遍历时,dfn[u]=low[u]=++cnt,初始化序列
(4)然后将遍历到的点压入栈,并且置为已做过
(5)在整个图中枚举一条以u为右端点的边(from,to),然后判断左端点是否已经入栈,如果已经入栈,则修改low[u]:low[u]=max(low[u],dfn(to)),如果没入栈,则对其进行递归处理,返回时low[u]=min(low[u],low[to])
(6)当出现dfn[u]==low[u]时,则表明已经出现一个强联通分量,依次弹出并消除标记即可,此时cnt++,则该作用为统计联通块数量
tarjan缩点:
首先跑一遍tarjan
然后把一个强连通分量的点染上色
当发现这个强连通分量只有一个点就不染,等tarjan之后从1-n把没染色的点染上色
因为颜色是从1开始取得
所以tarjan里面染的颜色,即强连通分量>1的一个点颜色必定小于等于cl(在tarjan里每次inc(cl) color[i]:=cl);
在外面,即强连通分量只有一个的点(重新建完图)的颜色必定大于cl
重新建图!
每次枚举第一次建图的链式前向星的sumedge,判断下它连接的两个点的颜色是否相同,不相同则加一条连这两个点的边。
注意在tarjan时候强连通分量不止一个的时候要用一个类似链前的结构来储存这个点里面原来的点是什么
在外面的时候可以用一个fanxiang数组来存这个颜色对应原来哪个点。(因为一个颜色只有一个点可以一一对应)。
代码段:
if dfn[u]=low[u] then
begin
if zhan[pp]<>u then begin
inc(cl);
repeat
v:=zhan[pp];
nt[v]:=hd[cl];
hd[cl]:=v;
ss[zhan[pp]]:=false;
color[v]:=cl;
dec(pp);
until u=v;
end
else begin v:=zhan[pp]; ss[zhan[pp]]:=false; dec(pp);end;
end;
外面的点染色:
for i:= 1 to n do
if color[i]=0 then begin inc(cl);color[i]:=cl; fanxiang[cl]:=i; end;
//今日代码
fzu7月23日专题训练
B - A strange lift
(从电梯层数 i 出发能向上 x 层或向下 y 层,问从 a 层到 b 层至少按下电梯按钮的次数。 DP)
#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
int dp[250];
int f[250][2];//i层能向上、下到达的层数
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
int n,a,b;
while(cin >> n && n!=0)
{
cin >> a >> b;
memset(dp,0x3f,sizeof(dp));
memset(f,-1,sizeof(f));
for(int i = 1; i <= n; i++)
{
int t;
cin >> t;
if(i+t <= n) f[i][0] = i+t;
if(i-t >= 1) f[i][1] = i-t;
}
//--work
dp[a]=0;
while(true)
{
int num=0;//更新次数
for(int i=1;i<=n;i++) if(dp[i]<INF)
for(int j=0;j<2;j++) if(f[i][j]!=-1)
{
if(dp[f[i][j]]>dp[i]+1)
{
dp[f[i][j]]=dp[i]+1;
num++;
}
}
if(num==0)//无法继续更新
break;
}
if(dp[b]==INF)
dp[b]=-1;
printf("%d\n",dp[b]);
}
return 0;
}
C - 一个人的旅行
(从给定的结点中的任意一个出发到任意给定终点结点的最小值。Dijkstra)
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define LL long long
#define INF 0xFFFFFFF
using namespace std;
#define MAX 1010
int T,S,D;
LL value[MAX][MAX];
int sCity[MAX];
int eCity[MAX];
LL dis[MAX];
bool vis[MAX];
void init()
{
for(int i = 1 ; i < MAX; i++)
{
for(int j = 1 ; j < MAX; j++)
value[i][j] = INF;
}
}
void solve(int s)
{
int pos;
memset(vis , 0 , sizeof(vis));
for(int i = 1 ; i < MAX; i++)
dis[i] = INF;
dis[s] = 0;
for(int i = 1 ; i < MAX ; i++)
{
pos = -1;
for(int j = 1 ; j < MAX; j++)
if(!vis[j] && (pos == -1 || dis[j] < dis[pos]))
pos = j;
if(pos == -1)
break;
vis[pos] = 1;
for(int j = 1 ; j < MAX ; j++)
if(!vis[j] && dis[j] > dis[pos] + value[pos][j])
dis[j] = dis[pos] + value[pos][j];
}
}
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
int a , b , v , ans;
while(scanf("%d%d%d",&T,&S,&D) != EOF)
{
init();
for(int i = 0 ; i < T; i++)
{
scanf("%d%d%d",&a,&b,&v);
if(value[a][b] > v)
value[a][b] = value[b][a] = v;
}
for(int i = 0 ; i < S; i++)
scanf("%d" , &sCity[i]);
for(int i = 0 ; i < D; i++)
scanf("%d" , &eCity[i]);
ans = INF;
for(int i = 0 ; i < S; i++){
solve(sCity[i]);
for(int j = 0 ; j < D; j++)
ans = ans < dis[eCity[j]] ? ans : dis[eCity[j]];
}
printf("%d\n" , ans);
}
return 0;
}
E - 畅通工程
(给出部分边权求能连通的最小花费。边数小于结点数-1则输出?做法是把各边按distance从小到大排列,然后并查集跑最小生成树。)
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define LL long long
#define INF 0x1f1f1f1f
using namespace std;
int fa[2000];
struct road
{
int x;
int y;
int dis;
}r[2000];
bool compare(const road &r1,const road &r2)
{
return r1.dis<r2.dis;
}
int find(int x)
{
if(fa[x] == x) return x;
else return fa[x] = find(fa[x]);
}
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
int n,m;
while(cin >> n >> m && n!=0)
{
for(int i = 1; i <= m; i++)
fa[i] = i;
for(int i = 1; i <= n; i++)
cin >> r[i].x >> r[i].y >> r[i].dis;
int ans = 0;
int cnt = 0;
sort(r,r+n+1,compare);
for(int i = 1; i <= n; i++)
{
int xx = find(r[i].x);
int yy = find(r[i].y);
if(xx != yy)
{
ans+=r[i].dis;
fa[xx] = yy;
cnt++;
}
}
if(cnt == m-1) cout << ans << endl;
else cout <<"?" << endl;
}
return 0;
}
F - Agri-Net
(给定n个结点各个之间的权数,问走遍所有结点需要的最小花费。和E题一样的并查集最小生成树。)
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#define LL long long
#define INF 0x1f1f1f1f
using namespace std;
struct road
{
int x,y,d;
}r[110*110];
int fa[110];
bool cmp(const road & r1, const road & r2)
{
return r1.d < r2.d;
}
int find(int x) //查找x的父节点
{
if(fa[x] == x) return x;
else return fa[x] = find(fa[x]);
}
int main()
{
freopen("xx.in","r",stdin);
freopen("xx.out","w",stdout);
int n;
while(cin >> n)
{
int cnt = 0;
for(int i = 1; i <=n; i++)
for(int j = 1; j <= n ; j++)
{
int temp;
cin >> temp;
if(i >= j) continue;
r[cnt].x = i;
r[cnt].y = j;
r[cnt++].d = temp;
}
//初始化i的父节点为i
for(int i = 1; i <= n; i++)
fa[i] = i;
sort(r,r+cnt,cmp);
LL ans = 0;
for(int i = 0; i < cnt; i++)
{
int xx = find(r[i].x);
int yy = find(r[i].y);
if(xx != yy) //如果不在一个集合中,合并
{
fa[xx] = yy;
ans += r[i].d;
}
}
cout << ans << endl;
}
return 0;
}