基本概念
顶点:表示某个事物或对象
边:表示事物与事物之间的关系
有向图:有向图中的边是有方向性的,无向图则没有方向
权重:即每条边都有与之对应的值
路径::一个顶点序列i1,i2........ik是图的一条路径,当且仅当边(i1,i2)(i2,i3).........(ik-1,ik)都在图中。如果除了第一个顶点和最后一个顶点之外,其余的顶点均不相同,那么这条路径称为简单路径
环:在路径的终点添加一条指向起点的边,就构成一条环路
连通图:设图G是无向图,当且仅当G的每一对顶点之间都有一条路径,则称G是连通图
强连通图:图G是一个有向图,当且仅当每一对不同的顶点u,v,从u到v和v到u都有一条有向路径
生成树:如果图H是图G的子图,且他们的顶点集合相同,并且H是没有环路的无向连通图(即一棵树),则称H是G的一棵生成树
二分图:图G的顶点被分为两个子集,而且每条边只能从一个子集到另一个子集
有向无环图
/*==================================================*\
| DAG的深度优先搜索标记
| INIT: edge[][]邻接矩阵; pre[], post[], tag全置0;
| CALL: dfstag(i, n); pre/post:开始/结束时间
\*==================================================*/
int edge[V][V], pre[V], post[V], tag;
void dfstag(int cur, int n)
{ // vertex: 0 ~ n-1
pre[cur] = ++tag;
for (int i=0; i<n; ++i) if (edge[cur][i]) {
if (0 == pre[i]) {
printf("Tree Edge!\n");
dfstag(i, n);
} else {
if (0 == post[i]) printf("Back Edge!\n");
else if (pre[i] > pre[cur]) printf("Down Edge!\n");
else printf("Cross Edge!\n");
}
}
post[cur] = ++tag;
}
拓扑排序
/*==================================================*\
| 拓扑排序
| INIT:edge[][]置为图的邻接矩阵;count[0…i…n-1]:顶点i的入度.
\*==================================================*/
void TopoOrder(int n){
int i, top = -1;
for( i=0; i < n; ++i ) {
if( count[i] == 0 ) { // 下标模拟堆栈
count[i] = top; top = i;
}
for( i=0; i < n; ++i ) {
if( top == -1 ) {
printf("存在回路\n");
return ;
} else {
int j = top; top = count[top];
printf("%d", j);
for( int k=0; k < n; ++k )
if( edge[j][k] && (--count[k]) == 0 ){
count[k] = top; top = k;
}
}
}
}
}
最短路径(Dijkstra)
/*==================================================*\
| Dijkstra数组实现 O(N^2)
| Dijkstra --- 数组实现(在此基础上可直接改为STL的Queue实现)
| lowcost[] --- beg到其他点的近距离
| path[] -- beg为根展开的树,记录父结点
\*==================================================*/
#define INF 0x03F3F3F3F
const int N;
int path[N], vis[N];
void Dijkstra(int cost[][N], int lowcost[N], int n, int beg) {
int i, j, min;
memset(vis, 0, sizeof(vis));
vis[beg] = 1;
for (i=0; i<n; i++) {
lowcost[i] = cost[beg][i];
path[i] = beg;
}
lowcost[beg] = 0;
path[beg] = -1; // 树根的标记
int pre = beg;
for (i=1; i<n; i++) {
min = INF;
for (j=0; j<n; j++)
// 下面的加法可能导致溢出,INF不能取太大
if (vis[j]==0 && lowcost[pre]+cost[pre][j]<lowcost[j]) {
lowcost[j] = lowcost[pre] + cost[pre][j];
path[j] = pre;
}
for (j=0; j<n; j++)
if (vis[j] == 0 && lowcost[j] < min){
min = lowcost[j];
pre = j;
}
vis[pre] = 1;
}
}
/*==================================================*\
| Dijkstra O(E * log E)
| INIT: 调用init(nv, ne)读入边并初始化;
| CALL: dijkstra(n, src); dist[i]为src到i的短距离
\*==================================================*/
#define typec int // type of cost const
typec inf = 0x3f3f3f3f; // max of cost
typec cost[E], dist[V];
int e, pnt[E], nxt[E], head[V], prev[V], vis[V];
struct qnode {
int v; typec c;
qnode (int vv = 0, typec cc = 0) : v(vv), c(cc) {}
bool operator < (const qnode& r) const { return c>r.c; }
};
void dijkstra(int n, const int src) {
qnode mv;
int i, j, k, pre;
priority_queue<qnode> que;
vis[src] = 1; dist[src] = 0;
que.push(qnode(src, 0));
for (pre = src, i=1; i<n; i++) {
for (j = head[pre]; j != -1; j = nxt[j]) {
k = pnt[j];
if (vis[k] == 0 && dist[pre] + cost[j] < dist[k]){
dist[k] = dist[pre] + cost[j];
que.push(qnode(pnt[j], dist[k]));
prev[k] = pre;
}
}
while (!que.empty() && vis[que.top().v] == 1) que.pop();
if (que.empty()) break;
mv = que.top(); que.pop();
vis[pre = mv.v] = 1;
}
}
inline void addedge(int u, int v, typec c) {
pnt[e] = v; cost[e] = c;
nxt[e] = head[u]; head[u] = e++;
}
void init(int nv, int ne) {
int i, u, v;
typec c;
e = 0;
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(prev, -1, sizeof(prev));
for (i = 0; i < nv; i++) dist[i] = inf;
for (i = 0; i < ne; ++i) {
scanf("%d%d%d", &u, &v, &c); // %d: type of cost
addedge(u, v, c); // vertex: 0 ~ n-1, 单向边
}
}
/*==================================================*\
| 第K短路(Dijkstra)
| dij变形,可以证明每个点经过的次数为小于等于K,所有把dij的数组dist
| 由一维变成2维,记录经过该点1次,2次。。。k次的小值。
| 输出dist[n-1][k]即可
\*==================================================*/
//WHU1603 int g[1010][1010];
int n,m,x;
const int INF=1000000000;
int v[1010]; int dist[1010][20];
int main() {
while (scanf("%d%d%d",&n,&m,&x)!=EOF) {
for (int i=1; i<=n; i++)
for (int j=1;j<=n;j++)
g[i][j]=INF;
for (int i=0; i<m; i++) {
int p,q,r;
scanf("%d%d%d",&p,&q,&r);
if (r<g[p][q]) g[p][q]=r;
}
for (int i=1;i<=n;i++) {
v[i]=0;
for (int j=0;j<=x;j++)
dist[i][j]=INF;
}
dist[1][0]=0;
dist[0][0]=INF;
while (1) {
int k=0;
for (int i=1;i<=n;i++)
if (v[i]<x && dist[i][v[i]]<dist[k][0])
k=i;
if (k==0) break;
if (k==n && v[n]==x-1) break;
for (int i=1;i<=n;i++) {
if (v[i]<x && dist[k][v[k]]+g[k][i]<dist[i][x]) {
dist[i][x]=dist[k][v[k]]+g[k][i];
for (int j=x;j>0;j--)
if (dist[i][j]<dist[i][j-1])
swap(dist[i][j],dist[i][j-1]);
}
}
v[k]++;
}
if (dist[n][x-1]<INF) printf("%d\n",dist[n][x-1]);
else printf("-1\n");
}
return 0;
}
Bellman-Ford
/*==================================================*\
| BellmanFord单源最短路O(VE)
| 能在一般情况下,包括存在负权边的情况下,解决单源短路径问题
| INIT: edge[E][3]为边表
| CALL: bellman(src);有负环返回0;dist[i]为src到i的短距
| 可以解决差分约束系统: 需要首先构造约束图,构造不等式时>=表示求 小值,
| 作为长路,<=表示求最大值, 作为最短路 (v-u <= c:a[u][v] = c)
\*==================================================*/
#define typec int // type of cost
const typec inf=0x3f3f3f3f; // max of cost
int n, m, pre[V], edge[E][3];
typec dist[V];
int relax (int u, int v, typec c) {
if (dist[v] > dist[u] + c) {
dist[v] = dist[u] + c;
pre[v] = u;
return 1;
}
return 0;
}
int bellman (int src) {
int i, j;
for (i=0; i<n; ++i) {
dist[i] = inf; pre[i] = -1;
}
dist[src] = 0; bool flag;
for (i=1; i<n; ++i) {
flag = false; // 优化
for (j=0; j<m; ++j) {
if( 1 == relax(edge[j][0], edge[j][1], edge[j][2]) )
flag = true;
}
if( !flag ) break;
}
for (j=0; j<m; ++j) {
if (1 == relax(edge[j][0], edge[j][1], edge[j][2]))
return 0; // 有负圈
}
return 1;
}
检测负环
bool find_negative_loop() { //返回true表示存在负圈
memset(d,0,sizeof(d));
for(int i=0;i<V;i++) {
for(int j=0;j<E;j++) {
edge e=es[j];
if(d[e.to]>d[e.from]+e.cost)
d[e.to]=d[e.from]+e.cost;
if(i==V-1) //如果第n次仍然更新了,则存在负圈
return truel
}
}
return false;
}
Floyd
void floyd() {
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (g[i][k] + g[k][j] < g[i][j]) {
g[i][j] = g[i][k] + g[k][j];
}
}
}
}
}
Prmie
/*==================================================*\
| Prim求MST
| INIT: cost[][]耗费矩阵(inf为无穷大);
| CALL: prim(cost, n); 返回-1代表原图不连通;
\*==================================================*/
#define typec int // type of cost
const typec inf = 0x3f3f3f3f; // max of cost
int vis[V]; typec lowc[V];
typec prim(typec cost[][V], int n) // vertex: 0 ~ n-1
{
int i, j, p;
typec minc, res = 0;
memset(vis, 0, sizeof(vis));
vis[0] = 1;
for (i=1; i<n; i++) lowc[i] = cost[0][i];
for (i=1; i<n; i++) {
minc = inf; p = -1;
for (j=0; j<n; j++)
if (0 == vis[j] && minc > lowc[j]) {
minc = lowc[j]; p = j;
}
if (inf == minc) return -1; // 原图不连通
res += minc; vis[p] = 1;
for (j=0; j<n; j++)
if (0 == vis[j] && lowc[j] > cost[p][j])
lowc[j] = cost[p][j];
}
return res;
}
Kruskal
struct edge {
int u, v, w;
}e[MAX_M];
int fa[MAX_N], n, m; // fa 数组记录了并查集中结点的父亲
bool cmp(edge a,edge b) {
return a.w < b.w;
}
// 并查集相关代码
int ancestor(int x) { // 在并查集森林中找到 x 的祖先,也是所在连通块的标识
if(fa[x] == x) return fa[x];
else return fa[x] = ancestor(fa[x]);
}
int same(int x, int y) { // 判断两个点是否在一个连通块(集合)内
return ancestor(x) == ancestor(y);
}
void merge(int x, int y) { // 合并两个连通块(集合)
int fax = ancestor(x), fay = ancestor(y);
fa[fax] = fay;
}
int Kruskal() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
int rst = n, ans = 0; // rst 表示还剩多少个集合,ans 保存最小生成树上的总边权
for (int i = 1; i <= m && rst > 1; i++) {
int x = e[i].u, y = e[i].v;
if (same(x, y)) {
continue; // same 函数是查询两个点是否在同一集合中
} else {
merge(x, y); // merge 函数用来将两个点合并到同一集合中
rst--; // 每次将两个不同集合中的点合并,都将使 rst 值减 1
ans += e[i].w; // 这条边是最小生成树中的边,将答案加上边权
}
}
return ans;
}
有向图最小树
/*==================================================*\
| 有向图最小树形图
| INIT: eg置为边表; res置为0; cp[i]置为i;
| CALL: dirtree(root, nv, ne); res是结果;
\*==================================================*/
#define typec int // type of res
const typec inf = 0x3f3f3f3f; // max of res
typec res, dis[V];
int to[V], cp[V], tag[V];
struct Edge { int u, v; typec c; } eg[E];
int iroot(int i){
if (cp[i] == i) return i;
return cp[i] = iroot(cp[i]);
}
int dirtree(int root, int nv, int ne) // root: 树根
{ // vertex: 0 ~ n-1
int i, j, k, circle = 0;
memset(tag, -1, sizeof(tag));
memset(to, -1, sizeof(to));
for (i = 0; i < nv; ++i) dis[i] = inf;
for (j = 0; j < ne; ++j) {
i = iroot(eg[j].u);
k = iroot(eg[j].v);
if (k != i && dis[k] > eg[j].c) {
dis[k] = eg[j].c;
to[k] = i;
}
}
to[root] = -1; dis[root] = 0; tag[root] = root;
for (i = 0; i < nv; ++i) if (cp[i] == i && -1 == tag[i]) {
j = i;
for ( ; j != -1 && tag[j] == -1; j = to[j])
tag[j] = i;
if (j == -1) return 0;
if (tag[j] == i) {
circle = 1; tag[j] = -2;
for (k = to[j]; k != j; k = to[k])
tag[k] = -2;
}
}
if (circle) {
for (j = 0; j < ne; ++j) {
i = iroot(eg[j].u);
k = iroot(eg[j].v);
if (k != i && tag[k] == -2)
eg[j].c -= dis[k];
}
for (i = 0; i < nv; ++i) if (tag[i] == -2) {
res += dis[i]; tag[i] = 0;
for (j = to[i]; j != i; j = to[j]) {
res += dis[j]; cp[j] = i; tag[j] = 0;
}
}
if (0 == dirtree(root, nv, ne)) return 0;
} else {
for (i = 0; i < nv; ++i)
if (cp[i] == i) res += dis[i];
}
return 1; // 若返回0代表原图不连通
}
二分图判断
bool check() {
memset(used,-1,sizeof(used));
queue<int>Q;
Q.push(1);
used[1]=0;
while(!Q.empty()) {
int now=Q.front();
for(int i=1;i<=n;i++) { //遍历所有点
if(map[now][i]==0) //邻接矩阵存图
continue;
int v=i;
if(used[v]==-1) {
used[v]=(used[now]+1)%2;
Q.push(v);
} else {
if(used[v]==used[now])
return false;
}
}
Q.pop();
}
return true;
}
二分图匹配(匈牙利算法)
const int MAX_N = 100; // X 集合中的顶点数上限
const int MAX_M = 10000; // 总的边数上限
struct edge {
int v, next;
} e[MAX_M];
int p[MAX_N], eid;
void init() {
memset(p, -1, sizeof(p));
eid = 0;
}
// 从 X 集合顶点 u 到 Y 集合顶点 v 连一条边,注意 u 和 v 的编号无关
void insert(int u, int v) {
e[eid].v = v;
e[eid].next = p[u];
p[u] = eid++;
}
bool vst[MAX_N]; // 标记一次 dfs 过程中,Y 集合中的顶点是否已访问
int ans[MAX_N]; // 标记 Y 集合中的顶点匹配的 X 集合中的顶点编号
int n, m; // n 表示 X 集合中的顶点数,假设顶点编号为 0..n-1
bool dfs(int u) {
for (int i = p[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if (!vst[v]) { // 如果 Y 集合中的 v 还没有被访问
vst[v] = true;
// 如果 v 没有匹配点,或 v 的匹配点能找到一条
// 到一个未匹配点的增广路,则将 v 的匹配点设为 u
if (ans[v] == -1 || dfs(ans[v])) {
ans[v] = u;
return true;
}
}
}
return false; // 没找到增广路
}
int maxmatch() {
int cnt = 0;
memset(ans, -1, sizeof(ans));
for (int i = 0; i < n; ++i) {
memset(vst, 0, sizeof(vst)); // 进行 dfs 前,将 vst 清空
cnt += dfs(i); // 如果找到增广路,则将 cnt 累加 1
}
return cnt; // cnt 是找到增广路的次数,也是总的最大匹配数
}
带权二分图匹配(KM算法)
int w[MAX][MAX];
int link[MAX];//代表当前与Y集合中配对的X集合中的点
int visx[MAX], visy[MAX];
int lx[MAX], ly[MAX];
int n, m;//代表X和Y中元素的个数
int can(int t) {
visx[t] = 1;
for(int i = 1; i <= m; i++){
// 这里“lx[t]+ly[i]==w[t][i]”决定了这是在
// 相等子图中找增广路的前提,非常重要
if(!visy[i] && lx[t] + ly[i] == w[t][i]){
visy[i] = 1;
if(link[i] == -1 || can(link[i])){
link[i] = t;
return 1;
}
}
}
return 0;
}
int km() {
int sum = 0;
memset(ly, 0, sizeof(ly));
//把各个lx的值都设为当前w[i][j]的最大值
for(int i = 1; i <= n; i++){
lx[i] = -inf;
for(int j = 1; j <= n; j++){
if(lx[i] < w[i][j])
lx[i] = w[i][j];
}
}
memset(link, -1, sizeof(link));
for(int i = 1; i <= n; i++){
while(1){
memset(visx, 0, sizeof(visx));
memset(visy, 0, sizeof(visy));
if(can(i))//如果它能够形成一条增广路径,那么就break
break;
int d = inf;//否则,后面应该加入新的边,这里应该先计算d值
// 对于搜索过的路径上的XY点,设该路径上的X顶点集为S,
// Y顶点集为T,对所有在S中的点xi及不在T中的点yj
for(int j = 1; j <= n; j++)
if(visx[j])
for(int k = 1; k <= m; k++)
if(!visy[k])
d = min(d, lx[j] + ly[k] - w[j][k]);
if(d == inf)
return -1;//找不到可以加入的边,返回失败(即找不到完美匹配)
for (int j = 1; j <= n; j++)
if (visx[j])
lx[j] -= d;
for(int j = 1; j <= m; j++)
if(visy[j])
ly[j] += d;
}
}
for(int i = 1; i <= m; i++)
if(link[i] > -1)
sum += w[link[i]][i];
return sum;
}
最大团
/*==================================================*\
| 最大团问题 DP + DFS
| INIT: g[][]邻接矩阵;
| CALL: res = clique(n);
\*==================================================*/
int g[V][V], dp[V], stk[V][V], mx;
int dfs(int n, int ns, int dep){
if (0 == ns) {
if (dep > mx) mx = dep;
return 1;
}
int i, j, k, p, cnt;
for (i = 0; i < ns; i++) {
k = stk[dep][i]; cnt = 0;
if (dep + n - k <= mx) return 0;
if (dep + dp[k] <= mx) return 0;
for (j = i + 1; j < ns; j++) {
p = stk[dep][j];
if (g[k][p]) stk[dep + 1][cnt++] = p;
}
dfs(n, cnt, dep + 1);
}
return 1;
}
int clique(int n){
int i, j, ns;
for (mx = 0, i = n - 1; i >= 0; i--) {
// vertex: 0 ~ n-1
for (ns = 0, j = i + 1; j < n; j++)
if (g[i][j]) stk[1][ ns++ ] = j;
dfs(n, ns, 1); dp[i] = mx;
}
return mx;
}
Tarjan(连通图)
/*==================================================*\
| Tarjan强连通分量
| INIT: vec[]为邻接表; stop, cnt, scnt置0; pre[]置-1;
| CALL: for(i=0; i<n; ++i) if(-1==pre[i]) tarjan(i, n);
\*==================================================*/
vector<int> vec[V];
int id[V], pre[V], low[V], s[V], stop, cnt, scnt;
void tarjan(int v, int n) // vertex: 0 ~ n-1
{
int t, minc = low[v] = pre[v] = cnt++;
vector<int>::iterator pv;
s[stop++] = v;
for (pv = vec[v].begin(); pv != vec[v].end(); ++pv) {
if(-1 == pre[*pv]) tarjan(*pv, n);
if(low[*pv] < minc) minc=low[*pv];
}
if(minc < low[v]) {
low[v] = minc; return;
}
do {
id[t = s[--stop]] = scnt;
low[t] = n;
} while(t != v);
++scnt; // 强连通分量的个数
}
欧拉路径
/*==================================================*\
| 欧拉路径O(E)
| INIT: adj[][]置为图的邻接表; cnt[a]为a点的邻接点个数;
| CALL: elpath(0); 注意:不要有自向边
\*==================================================*/
int adj[V][V], idx[V][V], cnt[V], stk[V], top;
int path(int v) {
for (int w ; cnt[v] > 0; v = w) {
stk[ top++ ] = v;
w = adj[v][ --cnt[v] ];
adj[w][ idx[w][v] ] = adj[w][ --cnt[w] ];
// 处理的是无向图—-边是双向的,删除v->w后,还要处理删除w->v
}
return v;
}
void elpath (int b, int n){ // begin from b
int i, j;
for (i = 0; i < n; ++i) // vertex: 0 ~ n-1
for (j = 0; j < cnt[i]; ++j)
idx[i][ adj[i][j] ] = j;
printf("%d", b);
for (top = 0; path(b) == b && top != 0; ) {
b = stk[ --top ];
printf("-%d", b);
}
printf("\n");
}
割
/*==================================================*\
| 无向图连通度(割)
| INIT: edge[][]邻接矩阵;vis[],pre[],anc[],deg[]置为0;
| CALL: dfs(0, -1, 1, n);
| k=deg[0], deg[i]+1(i=1…n-1)为删除该节点后得到的连通图个数
| 注意:0作为根比较特殊!
\*==================================================*/
int edge[V][V], anc[V], pre[V], vis[V], deg[V];
void dfs(int cur, int father, int dep, int n)
{// vertex: 0 ~ n-1
int cnt = 0;
vis[cur] = 1; pre[cur] = anc[cur] = dep;
for (int i=0; i<n; ++i) if (edge[cur][i]) {
if (i != father && 1 == vis[i]) {
if (pre[i] < anc[cur])
anc[cur] = pre[i]; //back edge
}
if (0 == vis[i]) { //tree edge
dfs(i, cur, dep+1, n);
++cnt; // 分支个数
if (anc[i] < anc[cur]) anc[cur] = anc[i];
if ((cur==0 && cnt>1) || (cnt!=0 && anc[i]>=pre[cur]))
++deg[cur]; // link degree of a vertex
}
}
vis[cur] = 2;
}
桥
/*==================================================*\
| 无向图找桥
| INIT: edge[][]邻接矩阵;vis[],pre[],anc[],bridge 置0;
| CALL: dfs(0, -1, 1, n);
\*==================================================*/
int bridge, edge[V][V], anc[V], pre[V], vis[V];
void dfs(int cur, int father, int dep, int n)
{ // vertex: 0 ~ n-1
if (bridge) return;
vis[cur] = 1; pre[cur] = anc[cur] = dep;
for (int i=0; i<n; ++i) if (edge[cur][i]) {
if (i != father && 1 == vis[i]) {
if (pre[i] < anc[cur]) anc[cur] = pre[i];//back edge
}
if (0 == vis[i]) { //tree edge
dfs(i, cur, dep+1, n);
if (bridge) return;
if (anc[i] < anc[cur]) anc[cur] = anc[i];
if (anc[i] > pre[cur]) { bridge = 1; return; }
}
}
vis[cur] = 2;
}
最小点基
/*==================================================*\
| 有向图最小点基(邻接阵)O(n^2)
| 点基B满足:对于任意一个顶点Vj,一定存在B中的一个Vi,使得Vi是Vj
| 的前代。
\*==================================================*/
//返回点基大小和点基
//传入图的大小n和邻接阵mat,不相邻点边权0
//需要调用强连通分支
#define MAXN 100
int base_vertex(int n,int mat[][MAXN],int* sets) {
int ret=0,id[MAXN],v[MAXN],i,j;
j=find_components(n,mat,id);
for (i=0;i<j;v[i++]=1);
for (i=0;i<n;i++)
for (j=0;j<n;j++)
if (id[i]!=id[j]&&mat[i][j])
v[id[j]-1]=0;
for (i=0;i<n;i++)
if (v[id[i]-1])
v[id[sets[ret++]=i]-1]=0;
return ret;
}
代码多源于网络,更多模板