一、 实验题目:
制订铺设光缆的施工方案
二、主要功能介绍
1、功能描述
用Prim算法和Kruskal算法计算连接各城市光缆的最小施工方案
2、输入:
城市间的通信网络图
3、输出:
连接各城市光缆的最小施工方案及其费用
4、主要算法及原理介绍:
Prime算法:
(将顶点分为S和T两个集合,具体流程如下:初始时,所有顶点全部在S中,T为空集。从S中选择任意顶点,移动到集合T;重复以下步骤,直到所有顶点都在T中:
选择一条边( u , v , w ),使得u在点集S中,v在点集T中,且权值w最小;将这条边加入最小生成树,并将u移入点集T。)
1).输入:一个加权连通图,其中顶点集合为V,边集合为E;
2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
3).重复下列操作,直到Vnew = V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。
时间复杂度: 记顶点数v,边数e 邻接矩阵:O(v2)
Kruskal算法:
(将所有边按权值从小到大排序,依次遍历每一条边;
对于每一条边,如果在当前子图中连上之后不会形成环,则选择这条边作为最小生成树的一部分,加入子图;加入 n – 1条边后结束算法)
1).记Graph中有v个顶点,e个边
2).新建图Graphnew,Graphnew中拥有原图中相同的e个顶点,但没有边
3).将原图Graph中所有e个边按权值从小到大排序
4).循环:从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中添加这条边到图Graphnew中
时间复杂度: elog2e e为图中的边数
三、编程实现
1、代码
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
const int INF = 100000;
#define CITY_NUMBER 7 //城市数目
string name[CITY_NUMBER] = {"南昌", "景德镇", "赣州", "抚州", "鹰潭", "上饶", "萍乡"};
double city[CITY_NUMBER][CITY_NUMBER] = // 城市的临接矩阵
{
0, 43.2, INF, 28.8, INF, INF, 69,
43.2, 0, INF, INF, 33.6, INF, INF,
INF, INF, 0, 82.5, INF, INF, 68.1,
28.8, INF, 82.5, 0, 23.7, INF, 74.7,
INF, 33.6, INF, 23.7, 0, 26.1, INF,
INF, INF, INF, INF, 26.4, 0, INF,
69, INF, 68.1, 74.7, INF, INF, 0,
};
bool visited[CITY_NUMBER] = {false}; //储存城市被访问的状态
int closest[CITY_NUMBER]; // 最邻近点
double lowcost[CITY_NUMBER]; // 最临近点对应的值
int fa[CITY_NUMBER]; // kruskal 集合号存储
void bfs(int v) // 广度优先遍历 v为初始节点
{
int u, w, flag;
queue<int>Q; // 创建一个先进先出队列, 里面存放int类型
visited[v] = true;
cout << "开始广度优先遍历: " << name[v] << " --";
Q.push(v); // 初始节点入队
while(!Q.empty())
{
u = Q.front();
Q.pop();
for(w = 0; w < CITY_NUMBER; w++)
{ // 检查所有u的邻接点
if(city[u][w] != INF && !visited[w] && city[u][w] != 0) // 如果该临接点未被访问 则访问
{
cout << " --> " << name[w];
visited[w] = true;
Q.push(w);
}
}
}
cout << endl;
cout << "访问完成";
flag = 0; // 判断是否是连通图
for(w = 0; w < CITY_NUMBER; w++) // 判断是否是连通图
{
if(visited[w] != true)
{
cout << name[w] << "未被访问到" << endl;
flag = 1;
}
}
if(flag == 0)
{
cout << " 所有点均被访问到, 该图是连通网" << endl;
}
}
void init_visited() // 将visited 全置false
{
for(bool & i : visited)
{
i = false;
}
}
void init_for_prim()
{
init_visited();
for(int i = 0; i < CITY_NUMBER; i++)
{
if(i != 0)
{
lowcost[i] = city[0][i];
closest[i] = 0;
}
else
{
lowcost[i] = 0;
}
}
}
void print_prim()
{
cout << "使用prim算法生成的最小生成树的边如下: " << endl;
double distance; // 距离
double sum = 0; // 输出总距离
for(int i = 1; i < CITY_NUMBER; i++)
{
distance = city[closest[i]][i];
cout << name[closest[i]] << " 和 " << name[i] << " 距离: " << distance << endl;
sum += distance;
}
cout << "总距离为: " << sum << endl;
}
void prim()
{
init_for_prim(); // 初始化各个数组
visited[0] = true; // 初始节点为 0
for(int i = 0; i < CITY_NUMBER; i++)
{
double tmp = INF;
int flag = 0; // 判断是否找到合适的点加入集合
for(int j = 0; j < CITY_NUMBER; j++)
{
if((!visited[j]) && (lowcost[j] < tmp))
{
flag = j;
tmp = lowcost[j];
}
}
if(flag == 0)
{
break; //如果没找到合适的点 退出循环
}
visited[flag] = true;
for(int j = 0; j < CITY_NUMBER; j++) // 加入新点 更新low-cost 和 closet
{
if((!visited[j]) && (city[flag][j] < lowcost[j]))
{
lowcost[j] = city[flag][j];
closest[j] = flag;
}
}
}
print_prim();
}
struct Edge{ // 边集数组
int u, v;
double w;
}e[CITY_NUMBER * CITY_NUMBER];
bool cmp(Edge x, Edge y) //对边集排序
{
return x.w < y.w;
}
void init_for_kruskal()
{
for(int i = 0; i < CITY_NUMBER; i++) // 初始集合号为自己
{
fa[i] = i;
}
int k = 0;
for(int i = 0; i < CITY_NUMBER; i++) // 对结构体赋初值
{
for(int j = 0; j < CITY_NUMBER; j++)
{
e[k].v = i;
e[k].u = j;
e[k].w = city[i][j];
k++;
}
}
}
int merge(int a, int b) // 合并
{
int p = fa[a];
int q = fa[b];
if( p == q )
{
return 0;
}
for(int i = 0; i < CITY_NUMBER; i++) // 将所有 a 的 集合号 赋值 给 b
{
if(fa[i] == q)
{
fa[i] = p;
}
}
cout << name[a] << " 和 " << name[b] << " 距离: "<< city[a][b] << endl;
return 1;
}
void kruskal()
{
init_for_kruskal();
double ans = 0;
int n = CITY_NUMBER;
sort(e, e+CITY_NUMBER*CITY_NUMBER, cmp); // 对结构体排序 从小到大
for(int i = 0; i < CITY_NUMBER * CITY_NUMBER; i++)
{
if(merge(e[i].u, e[i].v))
{
ans += e[i].w;
n--;
if(n == 1)
{
cout << "kruskal算法计算完成, 最小距离为: " << ans << endl;
}
}
}
if(ans == 0){
cout << "kruskal算法计算失败" << endl;
}
}
int main()
{
int u = 0; // 广度优先算法初始节点, 0是南昌
bfs(u);
cout << endl;
prim();
cout << endl;
kruskal();
return 0;
}
2、运行结果截图
3、心得体会
Prim算法以及Kruskal算法分别从点和边的角度出发建立一个最小的权值树, 在coding过程中如何妥善的保存各节点和各边的状态是我觉得比较困难的地方.