最小生成树
(一) Prim
算法思路:
首先建立邻接矩阵,这邻接矩阵用文件读取的方式读取。然后建立一个distance辅助数组,该数组是记录以生长好的生成树到其余顶点的距离(这数组非常类似于Dijkstra算法的辅助数组)。先是设定从标号为0 的节点开始生成生成树。然后
1. 从distance数组中找到weight最小的那一个节点。然后记录下这条边。
2. 接着将这边记录下,再更新distance数组的信息。
将上面1.2.步骤循环vexnum– 1次。
源代码:
#include<stdio.h>
#include<string.h>
#define INF 100
#define MAXVEX 100
typedef struct edge
{
int node0, node1;
int weight;
}edge; //用来储存生成树的数据结构
typedef struct distance
{
int node;
int weight;
}distance; //辅助数组 储存到其他顶点的最小距离 很类似Dijkstra算法中的辅助数组
void read(int m[][MAXVEX], int *vexnum, int *arcnum); //读取邻接矩阵
int min(distance d[],int vexnum); //找到的 d[] 权重最小的
int main(void)
{
int vexnum,arcnum;
intm[MAXVEX][MAXVEX];
read(m, &vexnum,&arcnum);
distanced[vexnum];
for(int i = 0; i< vexnum; i++) //distance初始化 以 节点0 来生最小生成树
{
d[i].node = 0;
d[i].weight =m[0][i];
}
edge e[vexnum -1];
for(int v = 0; v< vexnum - 1; v++)
{
int k;
k = min(d,vexnum);
e[v].node0 =d[k].node;
e[v].node1 = k;
e[v].weight =d[k].weight; //记录下新的树枝
for(int i = 0;i < vexnum; i++) //更新distance (因为有新节点的加入 可能会对distance产生影响)
{
if(m[k][i]< d[i].weight)
{
d[i].node = k; //记录下新起点
d[i].weight =m[k][i];
}
}
}
for(int i = 0; i< vexnum - 1; i++) //打印
printf("<%d, %d> %d \n", e[i].node0, e[i].node1, e[i].weight);
}
int min(distance d[], int vexnum)
{
int min_weight =INF;
int next_vex;
for(int i = 0; i< vexnum; i++)
{
if(d[i].weight< min_weight && d[i].weight != 0)
{
min_weight= d[i].weight;
next_vex =i;
}
}
return next_vex;
}
void read(int m[][MAXVEX], int *vexnum, int *arcnum)
{
FILE *fp;
fp =fopen("D:\\test.txt", "r");
fscanf(fp,"%d %d", vexnum, arcnum); //文件读取节点数和边数
for(int i = 0; i< *vexnum; i++) //邻接矩阵的文件读取
{
for(int j = 0;j < *vexnum; j++)
{
fscanf(fp,"%d", &m[i][j]);
}
}
fclose(fp);
}
程序示意图
//文件中的100代表的是INF 就是图中这两个顶点不相邻
(二)Kruskal
算法思路:这个算法图需要用边数组这数据结构来存储(具体定义在源代码中)。接着以便的权重为关键字,将边从小到大排序(这代码用的时堆排序)。接着从权重最小的边开始选取边,并对顶点建立并查集。用并查集来判断所选的边是否有圈。重复选取vexnum– 1次。
源代码:
#include<stdio.h>
#define INF 100
typedef struct edge
{
int node0, node1;
int weight;
int flag = 0;
}edge;
int find_father(int f[], int t); //并查集
void sift(edge a[], int k, int m); //堆排序
void heapsort(edge a[], int n); //堆排序
int main(void)
{
int vexnum;
int edgenum;
FILE *fp;
fp =fopen("D:\\kruskal.txt", "r");
fscanf(fp, "%d %d", &vexnum, &edgenum);
edge e[edgenum];
int set_[vexnum];//并查集
for(int i = 0; i< vexnum; i++)
set_[i] = i;
for(int i = 0; i< edgenum; i++)
{
fscanf(fp,"%d %d %d", &e[i].node0, &e[i].node1, &e[i].weight);
}
fclose(fp);
heapsort(e,edgenum);
for(int i = 0; i< edgenum; i++)
{
if(find_father(set_, e[i].node0) != find_father(set_, e[i].node1))
{
set_[find_father(set_, e[i].node1)] = set_[e[i].node0];
e[i].flag = 1;
}
}
for(int i = 0; i< edgenum; i++)
if(e[i].flag)printf("<%d, %d> %d\n", e[i].node0, e[i].node1, e[i].weight);
}
void sift(edge a[], int k, int m) //这里有一个前提就是 k+1 到 m 是堆 这才能保证调用这函数后k 到m是堆
{
edge temp;
temp = a[k];
int i, j;
i = k; j = 2 *i;
int flag = 0;
while(j <= m&& !flag)
{
if((j + 1)<= m && a[j + 1].weight > a[j].weight) j++;
if(temp.weight < a[j].weight) {a[i] = a[j]; i = j; j *= 2;}
else flag =1;
}
a[i] = temp;
}
void heapsort(edge a[], int n)
{
int temp;
temp = n / 2; // temp的位置是最后一结点的双亲
for(int i = temp;i >= 0; i--) sift(a, i, n);
edge t;
for(int i = n; i>= 1; i--)
{
t = a[i]; a[i]= a[0]; a[0] = t; //交换
sift(a, 0, i -1);
}
}
int find_father(int f[], int t) //并查集的经典写法 注意这时间复杂度是相当的小的,
{ //因为这在查找的同时也在不断的修改其数据的逻辑关系
return (f[t] == t)? t : f[t] = find_father(f, f[t]);//也就是提高下次的查找性能
}
程序示意图:
//文件数据依次是顶点数,边数。 然后是每条边的起点、终点和权重。