最小生成树之Prim算法
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。
——百度百科
最小生成树:
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
生成树中权值(代价)最小的树
问题分析:
就最小生成树问题而言,解决问题的关键是贪心算法的应用.
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
生成最小生成树有两种方案,一是按边找点,一是按点找边.Prim算法思想是按点找边,这里需要一个更新表辅助操作.
生成最小树时,不必考虑全图,每次只需取当前顶点的最小边即可,当所有顶点都取完时,所选取的边的权值和即是最小的,所生成的树也就是最小生成树.
注意:最小生成树不只一棵,但是其权值和是相同的(最小的).
Prim算法:
算法思想:
- 建立更新表与更新表的初始化
从任意一个顶点开始将其作为根节点放入顶点集合U中,更新各点到根节点的距离,将其填入lowcost中.只要lowcos不是无穷大就将其adjvex改为根节点。遍历更新表中lowcost,将lowcost最小的顶点加入顶点集合U中,并将其lowcost改为0(意为该点已经并入顶点集合U中)。 - 再从新加入点中查找新加入点与各个点的距离,如果比原更新表中的lowcost距离小,则更新表中的lowcost,并更新其adjvex为新加入点,否则不更新。遍历lowcost,将距离最小的节点加入顶点集合U中,将其lowcost改为0(意为该点已经加入顶点集合U中)。
- 重复步骤2直到所有的顶点都加入到生成树顶点集合U中为止。(更新表中所有的lowcost均为零,即遍历一遍更新表lowcost返回值为0)
prim算法的核心思想是并点与贪心策略由点找边,更新各点的边的权值,将各个顶点逐渐加入顶点集合U中,从而生成最小生成树.
Prim步骤:
本代码以邻接链表为存储结构,可以检测改图是否为强连通图.同时在强连通的前提下,求最小生成树的权值和.
//邻接表用Prim求最短生成树
#include <cstdio>
#include <iostream>
using namespace std;
#define MAX_INIT 0x3f3f3f3f// 设立无穷大
typedef int Vertex; // 用顶点下标表示顶点,为整型
/* 邻接点的定义 */
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode{
Vertex AdjV; // 邻接点下标
int weight;
PtrToAdjVNode Next; // 指向下一个邻接点的指针
};
// 邻接表表头定义
typedef struct Vnode{
PtrToAdjVNode FirstEdge; // 边表头指针
int father;
} *PAdjList, AdjList; // AdjList是邻接表类型
/* 图定义 */
typedef struct GNode *PtrToGNode;
typedef PtrToGNode LGraph; // 以邻接表方式存储的图类型
struct GNode{
int Nv; // 顶点数
int Ne; // 边数
PAdjList G; // 邻接表
};
typedef struct CloseEdge{
int adjvex;
int lostCost;
}*PCloseEdge, CloseEdge;
// 尾插法创建临接表
LGraph CreateGraph(int cityNum, int roadNum) // 创建图并且将Visited初始化为false
{
LGraph graph = (LGraph)malloc(sizeof(GNode)); // 图表的内存申请
graph -> G = (PAdjList)malloc(cityNum * sizeof(AdjList)); // 邻接表的内存申请
PAdjList tail = (PAdjList)malloc(cityNum * sizeof(AdjList));
// 变量初始化
for(int i = 0; i < cityNum; ++i) {
(graph->G + i)->FirstEdge = nullptr;}
for(int i= 0; i < cityNum; ++i) {
(tail + i)->FirstEdge = nullptr;}
for(int i = 0; i < cityNum; ++i) {
(graph ->G + i) ->father = -1;}
graph ->Nv = cityNum;
graph->Ne = roadNum;
for(int j = 0; j < roadNum; ++j) {
// 循环记录数据
int city, road, cost;
scanf("%d %d %d", &city, &road, &cost);
if(city > cityNum || city < 1)
return nullptr;
getchar();
// 建立正向出度节点
PtrToAdjVNode node1 = (PtrToAdjVNode)malloc(sizeof(GNode));
node1 ->weight = cost;
node1->AdjV = road - 1;
node1 ->Next = nullptr;
if((graph ->G + city - 1) -> FirstEdge == nullptr)
(graph -> G + city - 1) ->FirstEdge = node1;
else (tail + city - 1) ->FirstEdge ->Next = node1;
(tail + city - 1) ->FirstEdge = node1;
// 建立反向节点
PtrToAdjVNode node2 = (PtrToAdjVNode)malloc(sizeof(GNode));
node2 ->weight = cost;
node2->AdjV = city - 1;
node2 ->Next = nullptr;
if((graph ->G + road - 1) -> FirstEdge == nullptr)
(graph -> G + road - 1) ->FirstEdge = node2;
else (tail + road - 1) ->FirstEdge ->Next = node2;
(tail + road - 1) ->FirstEdge = node2;
}
return graph;
}
// 寻找该节点下是否有另一结点,存在返回其权值,不存在返回最大值
// 如果找不到说明两个顶点不是直接相连,故其间距离为无穷大
int FindCost(LGraph &Graph, int corrVex, int toVex){
PtrToAdjVNode ptemp = (Graph ->G + corrVex) ->FirstEdge;
while(ptemp){
// 遍历该边的邻接表,查找目标点
if(ptemp ->AdjV == toVex)
return ptemp->weight;
ptemp =ptemp ->Next;
}
return MAX_INIT; // 存在返回其权值,否则返回无穷大
}
// 寻找最小边的索引
int MinCost(PCloseEdge close, int cityNum){
int indexMin = -1;
int minCost = MAX_INIT; // 初始化最小边的权值为无穷大
for(int i = 0; i < cityNum; ++i){
if((close + i) ->lostCost > 0 && (close + i) ->lostCost < minCost){
// 0表示该顶点已加入最小生成树的顶点集合中。
minCost = (close + i) ->lostCost; // 寻找最小值
indexMin = i;
}
}
return indexMin;
}
// 寻找最少生成树,并返回其最少权重和
int PrimeTree ( LGraph &Graph, PCloseEdge& close, int root){
int sum = 0; // 声明最小生成树的权值和
int indexMin = 0; // 声明最小权重的下标
// 步骤1,更新表的初始化
(close + root) ->lostCost = 0; // 将根加入生成树顶点集合
(close + root) ->adjvex = root + 1; // 存储结构定义,下标为零开始存储
for(int i = 0; i < Graph ->Nv; ++i){
if(i != root)
{
(close + i) ->adjvex = -1;
(close + i) -> lostCost = FindCost(Graph, root,i);
}
}
// 步骤2
for(int i = 0; i < Graph ->Nv; ++i){
indexMin = MinCost(close, Graph ->Nv); //取最小边的索引
if(indexMin < 0){
// 如果第一颗最小生成树已经找尽,寻找第二棵树
for(int j = 1; j < Graph ->Nv; ++j)
{
if((close + j) ->adjvex < 0){
root = j;
indexMin = j;
(close + indexMin)->lostCost = 0;
break;
}
}
}
sum += (close + indexMin)->lostCost;
(close + indexMin)->lostCost= 0; //加入生成树顶点集合
(close + indexMin)->adjvex = root + 1;
for (int j = 0; j < Graph->Nv; ++j){
//加入以indexMin为索引的顶点,并更新w数组,在
int cost = FindCost(Graph, indexMin, j); //得到indexMin到j的权值
if (cost < (close + j)->lostCost) // 贪心算法,比较,并更新
(close + j)->lostCost = cost;
}
}
return sum;
}
int main()
{
LGraph graph = NULL;
int cityNum = 0;
int roadNum = 0;
scanf("%d %d", &cityNum, &roadNum);
getchar();
PCloseEdge close = (PCloseEdge)malloc( cityNum * sizeof(CloseEdge));
for(int i = 0; i < cityNum; ++i){
(close + i) ->adjvex = -1;
(close + i) ->lostCost = MAX_INIT;
}
graph = CreateGraph(cityNum, roadNum);
if(!graph){
printf(("Impossible"));
return 0;
}
int root = 0; // 默认使结构体第一个节点为第一个根
int cost = PrimeTree(graph, close, root);
int count = 0;
// 检查该图是否为强连通图
for(int i = 0; i < graph ->Nv; ++i)
{
//printf("%d\n", (close + i) ->adjvex);
if((close + i) ->adjvex != root + 1) {
++count; // 打印输出
break;
}
}
//printf("%d", cost);
if(count > 0) printf("Impossible");
else printf("%d", cost);
return 0;
}