@LinKArftc
2015-08-26T13:59:31.000000Z
字数 4933
阅读 1972
图论
只有一个顶点的图,即阶n = 1 的图称为平凡图。相反,阶n>1 的图称为**非平凡图(nontrivial graph)**。
**边**的集合E(G)为空的图,称为零图。
指的是边与点之间的关系
图G 所有顶点的最小的度数,记为δ(G)。
图G 所有顶点的最大的度数,记为Δ(G)。
若把图G 所有顶点的度数排成一个序列s,则称s 为图G 的度序列
一个非负整数组成的有限序列如果是某个无向图的度序列,则称该序列是可图的。
判定一个序列是否是可图的,有以下**Havel-Hakimi 定理**。
path,又叫**通路**
路径上各顶点互相不重复
若路径上第一个顶点vi 与最后一个顶点vj 重合,则称这样的路径为回路
除第一个和最后一个顶点外,没有顶点重复的回路称为简单回路。简单回路也称为圈(cycle)
由非负整数组成的非增序列s:d1, d2, …, dn(n ≥ 2, d1 ≥ 1)是可图的,当且仅当序列
s1: d2 – 1, d3 – 1, …, dd1+1 – 1, dd1+2, …, dn
是可图的。
序列s1 中有n-1 个非负整数,s 序列中d1 后的前d1 个度数(即d2~dd1+1)减1 后构成s1 中的前d1 个数。
例如,判断序列s: 7, 7, 4, 3, 3, 3, 2, 1 是否是可图的。删除序列s 的首项7,对其后的7 项每项减1,得到:6, 3, 2, 2, 2, 1, 0。继续删除序列的首项6,对其后的6 项每项减1,得到:2, 1, 1, 1, 0, -1,到这一步出现了负数。由于图中不可能存在负度数的顶点,因此该序列不是可图的。
再举一个例子,判断序列s: 5, 4, 3, 3, 2, 2, 2, 1, 1, 1 是否是可图的。删除序列s 的首项5,对其后的5 项每项减1,得到:3, 2, 2, 1, 1, 2, 1, 1, 1,重新排序后为:3, 2, 2, 2, 1, 1, 1, 1, 1。继续删除序列的首项3,对其后的3 项每项减1,得到:1, 1, 1, 1, 1, 1, 1, 1。如此再陆续得到序列:1, 1, 1, 1, 1,1, 0;1, 1, 1, 1, 0, 0;1, 1, 0, 0, 0;0, 0, 0, 0。由此可判定该序列是可图的。
Havel-Hakimi 定理实际上给出了根据一个序列s 构造图(或判定s 不是可图的)的方法:把序列s 按照非增顺序排序以后,其顺序为d1, d2, …, dn;度数最大的顶点(设为v1),将它与度数次大的前d1 个顶点之间连边,然后这个顶点就可以不管了,即在序列中删除首项d1,并把后面的d1个度数减1;再把剩下的序列重新按非增顺序排序,按照上述过程连边;…;直到建出完整的图,或出现负度数等明显不合理的情况为止。
一般不合理的情形有两种:
(1) 某次对剩下序列排序后,最大的度数(设为d1)超过了剩下的顶点数;
(2) 对最大度数后面的d1 个度数各减1 后,出现了负数。
练习题:POJ 1659
prim过程中,在将某个顶点的时候,如果某个顶点的扩展方式不唯一,那么最小生成树不唯一。
利用这种思路判断最小生成树不唯一的方法为:
首先当前已经找到min = lowcost[v],因此,待扩展的顶点是v,待扩展的边是(nearvex[v], v)。
接下来要判断在集合T 内是否还有其他顶点到v 的距离也等于lowcost[v],根据以下四个条件来判定:
- nearvex[j] == -1:只考虑集合T 内的顶点;
- graph[v][j] < MAX:要排除min刚好等于∞的情况,这个条件实际上可以去掉;
- graph[v][j] == min:顶点j 到v 的距离刚好也等于min;
- nearvex[v] != j:而且j 不等于nearvex[v]。
如果以上四个条件同时成立,即可判定扩展顶点v 的方式不唯一,从而判定最小生成树不唯一。
说明
以上思路在判断出当前顶点扩展方式不唯一时能判定MST不唯一,但即使每个顶点的扩展方式均唯一也不能判定MST 唯一。以为prim在选第一个点的时候是随机的
判定最小生成树是否唯一的一个正确思路为:
- 对图中每条边,扫描其他边,如果存在相同权值的边,则对该边作标记;
- 然后用Kruskal 算法(或Prim 算法)求MST;
- 求得MST 后,如果该MST中未包含作了标记的边,即可判定MST唯一;如果包含作了标记的边,则依次去掉这些边再求MST,如果求得的MST 权值和原MST 的权值相同,即可判定MST 不唯一。
(补充:求次小生成树应该也是可行的,但是没有写过)
例题:POJ 1679
无向图:
1) 设G 是连通无向图,则称经过G 的每条边一次并且仅一次的路径为欧拉通路;
2) 如果欧拉通路是回路(起点和终点是同一个顶点),则称此回路为欧拉回路(Euler circuit);
3) 具有欧拉回路的无向图G 称为欧拉图(Euler graph)。
有向图:
1) 设D 是有向图,D 的基图连通,则称经过D 的每条边一次并且仅一次的有向路径为有向欧拉通路;
2) 如果有向欧拉通路是有向回路,则称此有向回路为有向欧拉回路(directed Euler circuit);
3) 具有有向欧拉回路的有向图D 称为有向欧拉图(directed Euler graph)。
无向图G 存在欧拉通路的充要条件是:
G 为连通图,并且G 仅有两个奇度结点(度数为奇数的顶点)或者无奇度结点。
1) 当G 是仅有两个奇度结点的连通图时,G 的欧拉通路必以此两个结点为端点。
2) 当G 是无奇度结点的连通图时,G 必有欧拉回路。
3) G 为欧拉图(存在欧拉回路)的充分必要条件是G 为无奇度结点的连通图。
有向图D 存在欧拉通路的充要条件是:
D 为有向图,D 的基图连通,并且所有顶点的出度与入度都相等;或者除两个顶点外,其余顶点的出度入度都相等,而这两个顶点中一个顶点的出度与入度之差为1,另一个顶点的出度与入度之差为-1。
1) 当 D 除出、入度之差为1,-1的两个顶点之外,其余顶点的出度与入度都相等时,D的有向欧拉通路必以出、入度之差为1 的顶点作为始点,以出、入度之差为-1 的顶点作为终点。
2) 当D 的所有顶点的出、入度都相等时,D 中存在有向欧拉回路。
3) 有向图D 为有向欧拉图的充分必要条件是D 的基图为连通图,并且所有顶点的出、入度都相等。
/*
ID: LinKArftc
PROG: Demo.cpp
LANG: C++
*/
#include <map>
#include <set>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <cstdio>
#include <string>
#include <utility>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define eps 1e-8
#define randin srand((unsigned int)time(NULL))
#define input freopen("input.txt","r",stdin)
#define debug(s) cout << "s = " << s << endl;
#define outstars cout << "*************" << endl;
const double PI = acos(-1.0);
const int inf = 0x3f3f3f3f;
const int INF = 0x7fffffff;
typedef long long ll;
const int maxn = 100;
const int maxm = 10000;
struct Arc {
int c, f; //容量,流量
};
Arc edge[maxn][maxn];
int n, m;
int flag[maxn]; //顶点状态:-1-未标号,0-已标号未检查,1-已标号已检查
int prev[maxn]; //标号的第一个分量:指明标号从哪个顶点得到,以便找出可改进量
int alpha[maxn]; //标号的第二个分量:可改进量α
void ford() {
while (1) { //标号直至不存在可改进路
//标号前对顶点状态数组初始化
memset(flag, -1, sizeof(flag)); //将3 个数组各元素初始化为-1
memset(prev, -1, sizeof(prev));
memset(alpha, -1, sizeof(alpha));
flag[0] = 0;
prev[0] = 0;
alpha[0] = inf; //源点为已标号未检查顶点
queue <int> q;
q.push(0);
while (!q.empty() && flag[n-1]==-1) {
int u = q.front();
q.pop();
for (int v = 0; v < n; v ++) { //检查顶点v 的正向和反向"邻接"顶点
if (flag[v] == -1) { //顶点i 未标号
if (edge[u][v].c < inf && edge[u][v].f < edge[u][v].c) { //"正向"且未"满"
flag[v] = 0;
prev[v] = u; //给顶点i 标号(已标号未检查)
alpha[v] = min(alpha[u], edge[u][v].c - edge[u][v].f);
q.push(v);
} else if (edge[v][u].c < inf && edge[v][u].f > 0) { //"反向"且有流量
flag[v] = 0;
prev[v] = -u; //给顶点i 标号(已标号未检查)
alpha[v] = min(alpha[u], edge[v][u].f);
q.push(v);
}
}
}
flag[u] = 1; //顶点v 已标号已检查
}
//当汇点没有获得标号,或者汇点的调整量为0,应该退出while 循环
if (flag[n-1] == -1 || alpha[n-1] == 0) break;
//当汇点有标号时,应该进行调整了
int k1 = n - 1, k2 = abs(prev[k1]);
int a = alpha[n-1]; //可改进量
while (1) {
if (edge[k2][k1].f < inf) //正向
edge[k2][k1].f = edge[k2][k1].f + a;
else edge[k1][k2].f = edge[k1][k2].f - a; //反向
if (k2 == 0) break; //调整一直到源点v0
k1 = k2;
k2 = abs(prev[k2]);
}
}
//输出各条弧及其流量,以及求得的最大流量
int maxFlow = 0;
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
if (i == 0 && edge[i][j].f < inf)//求源点流出量,即最大流
maxFlow += edge[i][j].f;
if (edge[i][j].f < inf) printf("%d->%d : %d\n", i, j, edge[i][j].f);
}
}
printf("maxFlow : %d\n", maxFlow);
}
int main() {
input;
int u, v, c, f;
scanf("%d%d", &n, &m);
memset(edge, 0x3f, sizeof(edge));
for (int i = 0; i < m; i ++) {
scanf("%d %d %d %d", &u, &v, &c, &f);
edge[u][v].c = c;
edge[u][v].f = f;
}
ford(); //标号法求网络最大流
return 0;
}
/*
Sample Input
6 10
0 1 8 2
0 2 4 3
1 3 2 2
1 4 2 2
2 1 4 2
2 3 1 1
2 4 4 0
3 4 6 0
3 5 9 3
4 5 7 2
Sample Output
0->1 : 4
0->2 : 4
1->3 : 2
1->4 : 2
2->1 : 0
2->3 : 1
2->4 : 3
3->4 : 0
3->5 : 3
4->5 : 5
maxFlow : 8
*/