图论中的基本问题(未完)
1.最小生成树
a.kruskal 算法
在遇到最小生成树问题时,我个人偏好用kruskal算法,实在是觉得其中并查集的运用太经典了。
第一步:对所有的边从小到大排序;
第二步:依次选取n-1条不会产生回路的边;(用并查集判断添加边<i,j>是否产生回路)
模板:
代码
typedef struct
{
int i , j;
int dis;
}Node;
Node a[N*N /2]; //C(2,N)
int p[N] , rank[N];
//快排
void QuickSort(Node *arr , int left , int right)
{
int i , j;
Node x , nTemp;
if(left >= right)
return;
else
{
i = left; j = right + 1; x = arr[i];
while(1)
{
do i++; while(i < j && arr[i].dis < x.dis);
do j--; while(arr[j].dis > x.dis);
if(i > j) break;
nTemp = arr[i]; arr[i] = arr[j]; arr[j] = nTemp;
}
nTemp = arr[left]; arr[left] = arr[j]; arr[j] = nTemp;
QuickSort(arr,left,j-1);
QuickSort(arr,j+1,right);
}
}
//并查集基本操作
void MakeSet()
{
int i;
for(i = 0 ; i < N ; i++)
{
rank[i] = 0 ;
p[i] = i;
}
}
int Find(int x)
{
if(x != p[x])
{
p[x] = Find(p[x]);
}
return p[x];
}
int Union(int x , int y)
{
int a , b;
a = Find(x);
b = Find(y);
if(a == b) return 0;
if(rank[a] > rank[b])
{
p[b] = a;
}
else
{
p[a] = b;
if(rank[a] == rank[b])
rank[b]++;
}
return 1;
}
//主函数
int main(void)
{
scanf(...); //读入t条边
QuickSort(a , 0 , t - 1);
MakeSet();
cnt = sum = 0;
for(k = 0 ; k < t && cnt != n - 1 ; k++)
{
i = a[k].i; j = a[k].j;
if(Union(i ,j))
{
sum += m[i][j];
cnt += 1;
}
}
printf("%d\n", sum);
return 0;
}
b.(朴素)prim算法
从任意节点开始,依次添加最小边到集合中去,类似Dijkstra算法
模板:
代码
int Prim()
{
int i , j , k , sum , min;
//从顶点0开始
for(i = 0 ; i < n ; i++)
{
lowcost[i] = Graph[0][i];
}
lowcost[0] = 0;
for(i = 1 , sum = 0 ; i < n ; i++)
{
min = MAX;
for(j = 0 ; j < n ; j++) //这里可以用堆优化
{
if(lowcost[j] < min && lowcost[j])
{
k = j;
min = lowcost[j];
}
}
sum += lowcost[k];
lowcost[k] = 0;
for(j = 0 ; j < n ; j++)
{
if(Graph[k][j] < lowcost[j])
{
lowcost[j] = Graph[k][j];
}
}
}
return sum;
}
相关题目(比较基础):
Jungle Roads和Highways这两题直接套模板读入数据就OK了;
Eddy's picture和畅通工程再续这两题也是简单的模板套用,但是要注意的是:在求距离时不要开方(dis = (x1-x2) * (x1-x2) + (y1-y2) * (y1-y2)),只有在确定选取某条边的时候再开方,这样大大的减少开方次数(由n*(n-1)/2 降低到 n-1次,从而提高了效率);
Constructing Roads这题还是蛮有意思的,题目意思是在原有基础上(事先已经选取了若干条边)构建一棵“最小”生成树。我们可以这样思考:若原有基础中有x条边(去掉产生回路的边),那么我们只要按照原来算法取出剩余的n-1-x条边即可。
代码
#include <stdio.h>
#define N 101
typedef struct
{
int i , j;
int dis;
}Node;
Node a[50*N];
int p[N] , rank[N];
void QuickSort(Node *arr , int left , int right)
{
int i , j;
Node x , nTemp;
if(left >= right)
return;
else
{
i = left; j = right + 1; x = arr[i];
while(1)
{
do i++; while(i < j && arr[i].dis < x.dis);
do j--; while(arr[j].dis > x.dis);
if(i > j) break;
nTemp = arr[i]; arr[i] = arr[j]; arr[j] = nTemp;
}
nTemp = arr[left]; arr[left] = arr[j]; arr[j] = nTemp;
QuickSort(arr,left,j-1);
QuickSort(arr,j+1,right);
}
}
void MakeSet()
{
int i;
for(i = 0 ; i < N ; i++)
{
rank[i] = 0 ;
p[i] = i;
}
}
int Find(int x)
{
if(x != p[x])
{
p[x] = Find(p[x]);
}
return p[x];
}
int Union(int x , int y)
{
int a , b;
a = Find(x);
b = Find(y);
if(a == b) return 0;
if(rank[a] > rank[b])
{
p[b] = a;
}
else
{
p[a] = b;
if(rank[a] == rank[b])
rank[b]++;
}
return 1;
}
int main(void)
{
int i , j , k , cnt , t , n , q;
int x , y , sum;
int m[N][N];
while(scanf("%d", &n) != EOF)
{
for(i = 0 , t = 0 ; i < n ; i++)
{
for(j = 0 ; j < n ; j++)
{
scanf("%d",&m[i][j]);
if(i < j)
{
a[t].i = i;
a[t].j = j;
a[t].dis = m[i][j];
t++;
}
}
}
QuickSort(a , 0 , t - 1);
MakeSet();
scanf("%d", &q);
for(cnt = sum = i = 0 ; i < q ; i++)
{
scanf("%d%d", &x ,&y);
if(Union(x-1, y-1))
{
cnt += 1;
}
m[x-1][y-1] = m[y-1][x-1] = 0;
}
for(k = 0 ; k < t && cnt != n - 1 ; k++)
{
i = a[k].i;
j = a[k].j;
if(Union(i ,j)) //不在一个集合
{
sum += m[i][j];
cnt += 1;
}
}
printf("%d\n", sum);
}
return 0;
Slim Span这题是寻找这么一棵“最小”生成树:cost = 最大权值的边 - 最小权值的边,求cost值最小的生成树,方法就是双重循环枚举所有生成树(暴力),然后更新最小值(内循环做了一点优化)。
代码
#include <stdio.h>
#define N 101
#define Min(a,b) ((a) < (b) ? (a) : (b))
typedef struct
{
int i , j;
int dis;
}Node;
Node edge[N*50];
int p[N] , rank[N];
void QuickSort(Node *arr , int left , int right)
{
int i , j;
Node x , nTemp;
if(left >= right)
return;
else
{
i = left; j = right + 1; x = arr[i];
while(1)
{
do i++; while(i < j && arr[i].dis < x.dis);
do j--; while(arr[j].dis > x.dis);
if(i > j) break;
nTemp = arr[i]; arr[i] = arr[j]; arr[j] = nTemp;
}
nTemp = arr[left]; arr[left] = arr[j]; arr[j] = nTemp;
QuickSort(arr,left,j-1);
QuickSort(arr,j+1,right);
}
}
void MakeSet()
{
int i;
for(i = 0 ; i < N ; i++)
{
rank[i] = 0 ;
p[i] = i;
}
}
int Find(int x)
{
if(x != p[x])
{
p[x] = Find(p[x]);
}
return p[x];
}
int Union(int x , int y)
{
int a , b;
a = Find(x);
b = Find(y);
if(a == b) return 0;
if(rank[a] > rank[b])
{
p[b] = a;
}
else
{
p[a] = b;
if(rank[a] == rank[b])
rank[b]++;
}
return 1;
}
int main(void)
{
int n , m , i , j , t ,a , b;
int cnt , k , min;
while(scanf("%d%d", &n,&m) && n + m)
{
for(i = 0 ; i < m ; i++)
{
scanf("%d%d%d", &edge[i].i, &edge[i].j, &edge[i].dis);
}
if(m < n-1)
{
printf("-1\n");
}
else
{
min = 99999999;
QuickSort(edge , 0 , m - 1);
t = m - n + 2;
for(i = 0 ; i < t ; i++)
{
MakeSet();
k = edge[i].dis;
for(j = i , cnt = 0 ; j < m && cnt != n - 1; j++)
{
a = edge[j].i;
b = edge[j].j;
if(Union(a ,b))
{
if(edge[j].dis - k > min) break;
cnt += 1;
}
}
if(cnt == n - 1)
{
k = edge[j-1].dis - edge[i].dis;
min = Min(min, k);
}
}
min == 99999999 ? printf("-1\n") : printf("%d\n", min);
}
}
return 0;
}