图论:最小生成树之Prim算法

最小生成树之Prim算法

普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。
——百度百科

最小生成树:

一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
生成树中权值(代价)最小的树

问题分析:

就最小生成树问题而言,解决问题的关键是贪心算法的应用.
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
生成最小生成树有两种方案,一是按边找点,一是按点找边.Prim算法思想是按点找边,这里需要一个更新表辅助操作.
生成最小树时,不必考虑全图,每次只需取当前顶点的最小边即可,当所有顶点都取完时,所选取的边的权值和即是最小的,所生成的树也就是最小生成树.
注意:最小生成树不只一棵,但是其权值和是相同的(最小的).

Prim算法

算法思想

  1. 建立更新表与更新表的初始化
    从任意一个顶点开始将其作为根节点放入顶点集合U中,更新各点到根节点的距离,将其填入lowcost中.只要lowcos不是无穷大就将其adjvex改为根节点。遍历更新表中lowcost,将lowcost最小的顶点加入顶点集合U中,并将其lowcost改为0(意为该点已经并入顶点集合U中)。
  2. 再从新加入点中查找新加入点与各个点的距离,如果比原更新表中的lowcost距离小,则更新表中的lowcost,并更新其adjvex为新加入点,否则不更新。遍历lowcost,将距离最小的节点加入顶点集合U中,将其lowcost改为0(意为该点已经加入顶点集合U中)。
  3. 重复步骤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;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
普里姆算法Prim's Algorithm)是一种用于求解无向加权图中最小生成树的经典算法,它从一个顶点开始,每次选择与当前生成树相连的边中权重最小的一条,直到所有顶点都被包含在内。以下是用Python实现的简单版本的普里姆算法: ```python import heapq def prim(graph, start): # 初始化:设置起点到所有顶点的距离为无穷大,除了起点是0 distances = {v: float('infinity') for v in graph} distances[start] = 0 visited = set() edges = [] # 使用优先队列存储待访问的边 pq = [(0, start)] while pq: # 从优先队列中获取当前最小距离的边 current_distance, current_vertex = heapq.heappop(pq) # 如果当前节点已访问过,跳过 if current_vertex in visited: continue # 标记当前节点为已访问 visited.add(current_vertex) # 遍历当前节点的所有邻居 for neighbor, weight in graph[current_vertex].items(): # 如果经过当前节点到邻居的路径比已知的更短 new_distance = current_distance + weight if new_distance < distances[neighbor]: distances[neighbor] = new_distance # 将更优的边加入优先队列 heapq.heappush(pq, (new_distance, neighbor)) # 添加边到结果集合 edges.append((current_vertex, neighbor, weight)) return distances, edges # 示例图数据 graph = { 'A': {'B': 3, 'C': 1}, 'B': {'A': 3, 'C': 2, 'D': 4}, 'C': {'A': 1, 'B': 2, 'D': 5}, 'D': {'B': 4, 'C': 5, 'E': 6}, 'E': {'D': 6} } start = 'A' # 选择起始节点 distances, edges = prim(graph, start) print("最小生成树距离:", distances) print("最小生成树边:", edges) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值