图论算法
1. 最小生成树(Kruscal算法)
/**** **** **** **** **** ****
- Function Name : 最小生成树(Kruscal算法)
- Description : ZJU 1203
**** **** **** **** **** ****/
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
struct struct_edges
{
int bv,tv; //bv 起点 tv 终点
double w; //权值
};
struct_edges edges[10100]; //边集
struct struct_a
{
double x;
double y;
};
struct_a arr_xy[101];
int point[101],n,e; //n 顶点数, e 边数(注意是无向网络)
double sum;
int kruscal_f1(int point[], int v)
{
int i = v;
while(point[i] > 0) i = point[i];
return i;
}
bool UDlesser(struct_edges a, struct_edges b)
{return a.w < b.w;}
void kruscal() //只需要准备好n,e,递增的边集edges[]即可使用
{
int v1,v2,i,j;
for(i=0; i<n ;i++) point[i]=0;
i = j = 0;
while(j<n-1 && i<e) {
v1 = kruscal_f1(point, edges[i].bv);
v2 = kruscal_f1(point, edges[i].tv);
if(v1 != v2) {
sum += edges[i].w; //注意sum初始为0
point[v1]=v2;
j++;
}
i++;
}
}
int main()
{
int k,i,j;
cin>>n;
k=0;
while(n != 0) {
sum=0;
k++;
for(i=0; i<n ;i++)
cin>>arr_xy[i].x>>arr_xy[i].y;
e=0;
for(i=0; i<n ;i++) //从0开始计数
for(j=i+1; j<n ;j++) //注意是无向网络
{
if(i == j) continue;
edges[e].bv=i;
edges[e].tv=j;
edges[e].w=sqrt((arr_xy[i].x-arr_xy[j].x)*(arr_xy[i].x-arr_xy[j].x)+(arr_xy[i].y-arr_xy[j].y)*(arr_xy[i].y-arr_xy[j].y));
e++;
}
sort(edges,edges+e,UDlesser); //得到一个递增的边集,注意是从0开始计数
kruscal();
printf("Case #%d:\n",k); //cout<<"Case #"<<k<<":"<<endl;
printf("The minimal distance is: %.2f\n",sum); //输出sum
cin>>n;
if(n != 0) printf("\n");
}
}
2. 最小生成树(Prim算法)
/**** **** **** **** **** ****
- Function Name : 最小生成树(Prim算法)
- Description : ZJU 1203 Swordfish O(N^2)
**** **** **** **** **** ****/
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
double sum, arr_list[101][101], min;
int i, j, k=0, n;
struct struct_a
{
float x;
float y;
};
struct_a arr_xy[101];
struct struct_b
{
int point;
float lowcost;
};
struct_b closedge[101];
void prim(int n) //prim 需要准备:n顶点数 arr_list[][]顶点的邻接矩阵也是从0开始计数
{
int i,j,k;
k=0;
for(j=0; j<n ;j++) {
if(j != k) {
closedge[j].point = k;
closedge[j].lowcost = arr_list[k][j];
}
}
closedge[k].lowcost=0;
for(i=0; i<n ;i++) {
min=10000;
for(j=0; j<n ;j++) {
if (closedge[j].lowcost != 0 && closedge[j].lowcost < min) {
k = j;
min = closedge[j].lowcost;
}
}
sum += closedge[k].lowcost; //不要改成sum+=min; sum即为所求值
closedge[k].lowcost = 0;
for(j=0; j<n ;j++) {
if(arr_list[k][j] < closedge[j].lowcost) {
closedge[j].point = k;
closedge[j].lowcost = arr_list[k][j];
}
}
}
}
/*
arr_list[][]= Wij 如果Vi, Vj有边
0 如果i=j
无限大 如果没有边
*/
int main()
{
cin>>n;
while(n != 0) {
sum=0;
k++;
for(i=0; i<n ;i++)
cin>>arr_xy[i].x>>arr_xy[i].y;
for(i=0; i<n ;i++)
for(j=0; j<n ;j++) //得到邻接矩阵arr_list[][]
arr_list[i][j]=arr_list[j][i]=sqrt((arr_xy[i].x-arr_xy[j].x)*(arr_xy[i].x-arr_xy[j].x)+(arr_xy[i].y-arr_xy[j].y)*(arr_xy[i].y-arr_xy[j].y));
prim(n);
cout<<"Case #"<<k<<":"<<endl;
printf("The minimal distance is: %.2f\n",sum);
cin>>n;
if(n!=0) printf("\n");
}
}
3. 单源最短路径(Bellman-ford算法)
/**** **** **** **** **** ****
- Function Name : 单源最短路径(Bellman-ford算法)
- Description : 可允许有负权
**** **** **** **** **** ****/
#include <stdio.h>
#define MAX 100
#define MAXNUM 1000000
typedef struct graphnode
{
int vexnum; //顶点数
int arcnum; //边数
int gra[MAX][MAX]; //图
}Graph;
Graph *G;
//arc数组中存储的第一个顶点到其他顶点的最短路径
//结果存在dis数组中
int dis[MAX];
int arc[MAX][MAX];
void bellman(Graph *G)
{
int i,j;
bool sign;
for(i=0; i < G->vexnum ;i++) dis[i]=MAXNUM;
dis[1] = 0;
sign = true;
for(i=1; i < G->vexnum ;i++) {
sign = false;
for(j=0; j < G->arcnum ;j++) {
if(dis[ arc[j][0] ] < MAXNUM
&& dis[ arc[j][1] ] > dis[ arc[j][0] ] + G->gra[ arc[j][0] ][ arc[j][1] ])
{
dis[ arc[j][1] ]=dis[ arc[j][0] ] + G->gra[ arc[j][0] ][ arc[j][1] ];
sign = true;
}
}
}
return;
}
4. 单源最短路径(Dijkstra算法)
/**** **** **** **** **** ****
- Function Name : 单源最短路径 (Dijkstra算法)
- Description : 贪心, O(N^2), 不能有负权
**** **** **** **** **** ****/
int matrix[200][200],n; //matrix[][], 30000表示无限大,即无边.否则为有边,其值为边的权值
void Dijkstra(int x,int y) //起点Vx 终点Vy
{
int i,j,k,path[40000],mark[40000];
int min,dist[40000];
for(i=1;i<=n;i++) {
mark[i] = 0;
dist[i] = matrix[x][i];
path[i] = x;
}
mark[x] = 1;
do {
min=30000;
k=0;
for(i=1;i<=n;i++)
if(mark[i]==0 && dist[i]<min) {
min = dist[i];
k = i;
}
if(k) {
mark[k] = 1;
for(i=1;i<=n;i++)
if(matrix[k][i]<30000 && min+matrix[k][i]<dist[i]) {
dist[i] = min + matrix[k][i];
path[i] = k;
}
}
}while(k);
cout<<dist[y]<<endl; //dist[y] 的值就是从Vx 到 Vy 的最短路径值
//如果希望得到路径,加入如下代码:
do {
cout<<k<<"<--";
k = path[k];
}while(k!=x);
cout<<x<<endl;
}
5. 全源最短路径(Folyd算法)
/**** **** **** **** **** ****
- Function Name : 全源最短路径(Folyd算法)
- Description : DP, O(N^3)
**** **** **** **** **** ****/
//初始化
//min_graph[i][j]=graph[i][j];
//path[i][j]=j;
void Floyd()
{
int i,j,k;
for(k=0;k<vertex_number;k++) {
for(i=0;i<vertex_number;i++) {
for(j=0;j<vertex_number;j++) {
if((graph[i][k]==-1) || (graph[k][j]==-1)) continue;
if((min_graph[i][j]==-1) || (min_graph[i][j] > graph[i][k]+graph[k][j]))
{
min_graph[i][j] = graph[i][k]+graph[k][j]; /*最短路径值*/
path[i][j] = k; /*最短路径*/
}
}
}
}
}
6. 拓扑排序
/**** **** **** **** **** ****
- Function Name : 拓扑排序
**** **** **** **** **** ****/
//degree[] 每个结点的入度
//f[] 每个结点所在的层
void Toplogical_sort()
{
int i,j;
bool p=true;
top=0;
while(p) {
p=false;
top++;
for(i=1;i<=n;i++)
if(degree[i]==0) {
p=true;
f[i]=top;
}
for(i=1;i<=n;i++)
if(f[i]==top) {
for(j=1;j<=n;j++)
if(map[i][j]) degree[j]--;
degree[i]=-1;
}
}
top--;
}
7. 网络预流和最大流
int rel[1000][10000]; //全局变量
int pre[1000];
//计算网络流
//如果是二分图的匹配, 可以先对其进行网络预流以简化后续的查找
int pre_flow(int n,vector<int> * v)
{
int ret = 0;
int i,j,t,t1;
for(i = 0 ; i < v[0].size() ; i++){
t = v[0][i]; //t是与节点0相邻接的点
for(j = 0 ; j < v[t].size() ; j++){
t1 = v[t][j]; //与t相邻接的点
if(rel[t1][n - 1] > 0){
ret++;
rel[0][t]--, rel[t][0]++;
rel[t][t1]--, rel[t1][t]++;
rel[t1][n - 1]--, rel[n - 1][t1]++;
break;
}
}
}
return ret;
}
/*
网络中求最大流
参数含义: n代表网络中节点数,第0节点为源点, 第n-1节点为汇点
rel是个二维数组, rel[i][j]代表从节点i到节点j的流量
v[]是一个节点数组, v[i]包含与节点i相邻接的所有节点
返回值: 最大流量
*/
int max_flow(int n,vector<int> * v)
{
int ret = 0,i;
int t,t1,tm;
queue<int> q;
const int Infinite = 2000000000;
while(1){
for(t = 0 ; t < n ; t++) pre[t] = -1;
while(!q.empty()) q.pop();
q.push(0);
while(!q.empty()){ //find a augmenting path using breath-first search
t = q.front();
q.pop();
if(t == n - 1) break; //到达汇点
for(i = 0 ; i < v[t].size() ; i++){ //对于t相邻接的所有点查找可行路径
t1 = v[t][i];
if(rel[t][t1] > 0 && pre[t1] == -1){
pre[t1] = t;
q.push(t1);
}
}
}
if(q.empty() && t != n - 1) break;
tm = Infinite; //此处寻找路径最小值在二分图中可省略
while(t != 0){ //find the minimal num in the path
t1 = pre[t];
if(rel[t1][t] < tm) tm = rel[t1][t];
t = t1;
}
// tm = 1; //二分图中
t = n - 1;
while(t != 0){ //change the relation
t1 = pre[t];
rel[t1][t] -= tm;
rel[t][t1] += tm;
t = t1;
}
ret += tm;
}
return ret;
}
8. 网络最小费用最大流
/**** **** **** **** **** ****
网络中最小费用最大流
参数含义: np代表网络中的总节点数, v是网络节点的邻接表
cost为最后求得的最小费用, mf为求得的最大流
算法: 初始最小费用及最大流均为0,不断寻找可增广路
增广路对应的单位费用最小并且可流
修改残留网络及cost,mf. 直到无可增广路为止。
**** **** **** **** **** ****/
const int Max = 200000000;
vector<int> v[110]; //存储每个节点的邻接点
int flow[110][110]; //flow[i][j]代表由i节点到j节点的可行流量
int fcost[110][110]; //fcost[i][j]代表由i节点到j节点的单位流量费用
int ct[110]; //ct[i]代表单位容量到达i节点的最小费用
int pre[110]; //可行节点的前驱节点
void min_cost_max_flow(int np,const vector<int> * v,int & cost,int & mf)
{
int t,t1,tm,i,j;
bool out;
cost = 0,mf = 0;
while(1){
for(i = 0 ; i < np ; i++) pre[i] = -1,ct[i] = Max;
ct[0] = 0;
while(1){
out = false;
for(i = 0 ; i < np ; i++){
for(j = 0 ; j < v[i].size() ; j++){
t = v[i][j];
if(flow[i][t] > 0 && ct[i] != Max && ct[i] + fcost[i][t] < ct[t]){
out = true;
ct[t] = ct[i] + fcost[i][t];
pre[t] = i;
}
}
}
if(!out) break;
}
if(ct[np - 1] != Max){
t = np - 1;
tm = Max; //此处寻找流量最小值
while(t != 0){ //find the minimal num in the path
t1 = pre[t];
if(flow[t1][t] < tm) tm = flow[t1][t];
t = t1;
}
mf += tm; //流量增加
t = np - 1;
while(t != 0){ //change the relation
t1 = pre[t];
flow[t1][t] -= tm;
flow[t][t1] += tm;
cost += tm * fcost[t1][t]; //费用增加
t = t1;
}
}
else break;
}
}
9. 网络最大流(高度标号预流推进)
/*
函数接口: int Relabel_To_Front(int s,int d)
参数含义: s为源点,d为汇点
返回值 : 网络最大流
调用函数前的初始化工作:ver置为网络中节点的个数,c[i][j]代表节点i到
节点j的流量,vl[i]存放i与相邻的所有节点
其它全局变量均初始化为零
*/
const int VEX = 405; //网络中顶点数
const int HMAX = 810; //最大高度的定义,只要大于顶点的2倍就可以了
int f[VEX][VEX]; //流量
int c[VEX][VEX]; //边最大容量
int h[VEX]; //节点高度
int e[VEX]; //节点容量
int ver; //节点数目
vector<int> vl[VEX]; //邻接表,vl[i]存放与i相邻的节点
void Push(int u,int v) //流推进,由节点u推向v
{
int cf = c[u][v] - f[u][v]; //u,v边的容量
int d = e[u] < cf ? e[u] : cf;
f[u][v] += d;
f[v][u] = -f[u][v];
e[u] -= d;
e[v] += d;
}
void Relabel(int u) //对u重新标号
{
int i,t,cf;
int hmin = HMAX;
for(i = 0 ; i < vl[u].size() ; i++){ //寻找相邻最低点
t = vl[u][i];
cf = c[u][t] - f[u][t];
if(cf > 0 && h[u] <= h[t] && h[t] < hmin)
hmin = h[t];
}
h[u] = hmin + 1;
}
void Init_Preflow(int s) //初始化网络流,s为源点
{
int i;
int u;
h[s] = ver; //初始化高度
for(i = 0 ; i < vl[s].size() ; i++){
u = vl[s][i];
f[s][u] = c[s][u];
f[u][s] = -c[s][u];
e[u] = c[s][u];
e[s] -= c[s][u];
}
}
void Discharge(int u)
{
int i = 0;
int cf,v;
if(vl[u].size() == 0) return;
while(e[u] > 0){
if(i < vl[u].size()) {
v = vl[u][i];
cf = c[u][v] - f[u][v];
}
if(i >= vl[u].size()){
Relabel(u);
i = 0;
}
else if(cf > 0 && h[u] == h[v] + 1)
Push(u,v);
else
i++;
}
}
int Relabel_To_Front(int s,int d) //s为源点,d为汇点
{
int u,i,old_h;
list<int> l;
list<int>::iterator iter;
Init_Preflow(s);
iter = l.begin();
for(i = 0 ; i < ver ; i++){
if(i != s && i != d)
l.insert(iter,i);
}
iter = l.begin();
while(iter != l.end()){
u = *iter;
old_h = h[u];
Discharge(u);
if(h[u] > old_h){
l.erase(iter);
l.insert(l.begin(),u);
iter = l.begin();
}
iter++;
}
return e[ver - 1];
}
10. 最大团
/**** **** **** **** **** ****
- Function Name : 最大团
- Description : ZJU 1492 Maximum Clique
- 团: 指G的一个完全子图, 该子图不包含在任何其他的完全子图当中
- 最大团: 指其中包含顶点最多的团
**** **** **** **** **** ****/
#include<stdio.h>
#include<string.h>
int joint[50][50];
int Size, MAX, DP[50];
bool find;
//返回-1表示邻接顶点集已经为空集,否则返回第一个公共顶点
int reachEnd(int start, int sets[])
{
int lp;
for(lp=start; lp<Size ;lp++)
if(sets[lp]) return lp;
return -1;
}
void DFS(int Visit[], int start, int depth)
{
int loop, first, sets[50], SET[50];
memcpy(sets,Visit,Size*4);
memcpy(SET,Visit,Size*4);
if(( first=reachEnd(start,sets) ) == -1) {
if(depth > MAX) {
MAX = depth;
find = true;
}
return ;
}
while(first != -1) {
if(depth + Size - start <= MAX)//不可能找到最优解
return ;
if(depth + DP[first] <= MAX)//不可能找到最优解
return;
sets[first] = 0;
SET[first] = 0;//从邻接顶点集中清除first顶点
for(loop=first+1; loop < Size ;loop++) //合并邻接顶点集
if(SET[loop]==1 && joint[first][loop]==1) sets[loop]=1;
else sets[loop]=0;
DFS(sets,first,depth+1);
if(find) return ;
first = reachEnd(first,SET);//更新接点
}
}
int main()
{
int loop, lp, Visit[50];
while(scanf("%d",&Size)!=EOF && Size!=0) {
for(loop=0; loop < Size ;loop++)
for(lp=0; lp < Size ;lp++)
scanf("%d",joint[loop]+lp);
MAX=0;
for(loop=Size-1; loop >= 0 ;loop--) {
find=false;
memcpy(Visit, joint[loop], Size*4);
DFS(Visit, loop, 1);
DP[loop] = MAX;
}
printf("%d\n",DP[0]);
}
}
11. 最大二分图匹配(匈牙利算法)
/**** **** **** **** **** ****
- Function Name : 最大二分图匹配(匈牙利算法)
- Description : HDOJ 2063 过山车
- 二分图: 指所有顶点分成集合M和N, M或N中任意两个在同一集合中的点互不相连
- 匹配: 一组边顶点分别在两个集合中, 并且任意两条边都没有相同顶点
- 最大匹配: 所能得到的最大的边的个数
**** **** **** **** **** ****/
#include<cstdio>
#include<memory>
#include<vector>
using namespace std;
const int Max=1100;
vector< vector<int> > Bmap;
int n, m, k, nm;
int mark[Max];
bool flag[Max];
bool dfs(int pos)
{
int i, pre, tp;
for(i=0; i < Bmap[pos].size() ;i++) {
tp = Bmap[pos][i];
if( !flag[tp] ) {
flag[tp] = true;
pre = mark[tp];
mark[tp] = pos;
if(pre==-1 || dfs(pre)) return true;
mark[tp] = pre;
}
}
return false;
}
inline int Max_Match()
{
int mmax = 0, i;
for(i=1; i <= m ;i++) {
memset(flag,0,sizeof(flag));
if( dfs(i) ) mmax++;
}
return mmax;
}
int main()
{
int i, j, id, id2;
while(scanf("%d", &k)==1 && k) {
scanf("%d%d",&m, &n);
nm = n + m;
Bmap.clear(); Bmap.resize(nm+10);
memset(mark,-1,sizeof(mark));
for(j=0; j < k ;j++) {
scanf("%d %d", &id, &id2);
id2 += m;
Bmap[id].push_back(id2);
}
printf("%d\n", Max_Match());
}
}