最小生成树
生成树
对某一图,从图中任一顶点出发,遍历图,遍历所经过的边以及所有顶点构成一颗生成树。
因此,对于有n个顶点的图,其生成树有n个顶点,n-1条边。
边权之和称为代价,代价最小的生成树即为最小生成树。
最小生成树不一定唯一,带它们的权值相等。
通常,使用Prim算法以及Kruskal算法来求取最小生成树。
Prim算法
由顶点出发考虑。
原有顶点集V,边集E;建立最小生成树顶点集U,边集TE,初始U为空。
- (初始化)在V中任选一顶点 ,加入U.
- 若 ,在U、V - U 中各选一个顶点,使得该边权值最小,将该边加入TE,V - U 中顶点加入U.
- 重复(2.).
代码实现
const int MAX = 9999999;
const int NUM = 105;
vector<vector<int> > adjtable(NUM, vector<int>(NUM, MAX));
int Prim(vector<vector<int> > adj, int num){
//输入邻接矩阵adj以及顶点数num
int result = 0;
int* lowcost = new int[NUM];
int* visited = new int[NUM];
for(int i = 0;i<num;i++){
lowcost[i] = adj[0][i]; // 建立lowcost列表,由顶点0开始,初始化
visited[i] = 0; // 建立visited列表,初始化为0表示未访问
}
visited[0] = 1; // 从0开始,标记为1表示已访问
// 遍历各顶点
for(int i = 0;i<num;i++){
int mini = MAX; // 待选取得最小边权,初始化为MAX
int pos = 0; // 最小边权对应的在V-U中的顶点下标,即尚未加入生成树的顶点下标
for(int j = 0;j<num;j++){
if(!visited[j] && mini > lowcost[j]){
// 当顶点j未被访问,那么判断 <v, j> 边权是不是当前最小边权,
// v为已加入生成树中的某一顶点
mini = lowcost[j];
pos = j;
}
}
// 当mini未被更新,表示所有顶点已经加入生成树,完成构建
if(mini == MAX) break;
result += mini;
visited[pos] = 1; // pos标记的顶点加入生成树
// 更新lowcost,
// 若当前加入生成树的顶点与未加入顶点之间的距离小于lowcost中的值,则更新
for(int j = 0;j<num;j++){
if(!visited[j] && lowcost[j] > adj[pos][j]){
lowcost[j] = adj[pos][j];
}
}
}
return result;
}
Kruskal算法
由边出发考虑。
原有顶点集V,边集E;建立最小生成树顶点集U,边集TE,初始U为空。
-
若E未被读取完,在E中选权值最小的一条边,判断该边两顶点是否都在TE中;
若E被读取完,则完成算法.
-
若都在TE中,跳过该边,读取E中除此边以外最小的边,重复(1.);
若不都在TE中,将该边加入TE,重复(1.).
代码实现
const int NUM = 105;
struct edges{
int v1, v2;
int val;
edges(int a, int b, int c): v1(a), v2(b), val(c){}
};
vector<vector<int> > adjtable(NUM, vector<int>(NUM, 0));
vector<edges> Edges;
int visited[NUM];
int T_pre[NUM];
int find(int x){
int r = x;
while(T_pre[r] != r){
r = T_pre[r];
}
int i = x, j;
while(i!=r){
j = T_pre[i];
T_pre[i] = r;
i = j;
}
return r;
}
int join(int x, int y){
int a = find(x);
int b = find(y);
if(a!=b) T_pre[a] = b;
}
bool cmp(edges e1, edges e2){
return e1.val < e2.val;
}
int Kruskal(vector<vector<int> > adj, int num){
int result = 0;
for(int i = 0;i<num;i++){
// 初始化并查集 T_pre, T_pre记录所有已加入生成树的顶点
T_pre[i] = i;
for(int j = 0;j<num;j++){
// 初始化边集
if((!visited[i]||!visited[j]) && adj[i][j]){
edges e(i, j, adj[i][j]);
Edges.push_back(e);
}
}
}
// 边集中,按照边权排序
sort(Edges.begin(), Edges.end(), cmp);
int a, b;
for(int i = 0;i<Edges.size();i++){
a = find(Edges[i].v1);
b = find(Edges[i].v2);
// a!=b,即二者不在同一集合(生成树顶点集)中,将该边加入生成树
if(a!=b){
result += Edges[i].val;
join(Edges[i].v1, Edges[i].v2);
}
}
return result;
}
// Szp 2018/12/10