Prim算法+链式前向星(最小生成树)

本文深入探讨了Prim算法在求解最小生成树问题中的应用,详细解释了算法原理及其实现过程,对比了Kruskal算法,展示了两种贪心策略的不同之处。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前写过Kruskal算法实现最小生成树,这次补上Prim算法
上次没有介绍关于最小生成树,这次一起补上吧

连通并且没有回路的无向图被称作是树
而给定一个无向图,就可以基于无向图构造一棵树,这就是图的生成树,图的生成树可能不止一棵

当这个无向图为连通的赋权图时,在无向图的所有生成树中,必然存在一个边的权值和最小的生成树,被称作最小生成树

最小生成树的性质也再看一下

  • 最小生成树不能形成回路
  • 边的数量等于点的数量减一,即 m = n - 1

Kruskal算法和Prim算法其实都是基于贪心,不过Kruskal算法需要先对边的权值排序,而Prim算法是每次都选取当前权值最小的边

Prim算法:

1.定义 i = 0,U = 空集,从连通图 G = {V,E} 中的某一顶点u出发,选择与它关联的具有最小权值的边 (u,v),将其顶点加入到生成树的顶点集合U中
2.以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(u,v)把它的顶点加入到集合U中, i++
3.如果 i = n - 1,算法结束,否则就继续进行第二步,选取其他的边

根据前面的存图方法链式前向星就可以写出Prim算法的代码
我这里用了优先队列来优化
最小生成树模板

ll ans;
int n,m,num,cnt;
int vis[maxn],dis[maxn];
int head[maxn];
typedef pair <int,int> pii;
priority_queue<pii , vector <pii> , greater <pii> > q;
// 优先队列优化 记录边权和下一个结点 

struct node // 链式前向星 
{
    int to;
    int val;
    int next;
}E[maxn*2];

void add(int u,int v,int w)
{
    cnt++;
    E[cnt].to = v;
    E[cnt].val = w;
    E[cnt].next = head[u];
    head[u] = cnt;
}

void Prim()
{
    q.push(make_pair(0,1));
    while(!q.empty() && num < n)
    {
        int d = q.top().first;
		int u = q.top().second;
        q.pop();
        if(vis[u]) continue;
        ans += d;
        num++; // 找到一条边 
        vis[u] = 1; // 标记一下,表示我这条边已经用过 
        for(int i=head[u];i;i=E[i].next)
            if(E[i].val < dis[E[i].to]){ // 如果当前边权小,更新 
            	dis[E[i].to] = E[i].val;
				q.push(make_pair(E[i].val,E[i].to)); // 把新的边权和结点加入队列 
			}
    }
}

int main(){
	
    std::ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    for(int i=1;i<maxn;i++) dis[i] = 0x3f3f3f3f; // 初始化距离最大 
    
    cin >> n >> m;
    for(int i=0;i<m;i++)
    {
        int u,v,w;
        cin >> u >> v >> w;
        add(u,v,w);
        add(v,u,w); //无向图
    }
    Prim();
    n == num ? cout << ans << endl : cout << "orz" << endl;
    return 0;
}

当然还有一个不加优先队列优化的版本,我也放在这里了

int n,m,now,num,cnt,ans;
int vis[maxn],dis[maxn],head[maxn];
// 标记数组 最短距离 head数组 

struct Edge{ // 链式前向星 
	int to;
	int val;
	int next;
}E[maxn];

void add(int u,int v,int w) // 存图 
{
	++cnt;
	E[cnt].next = head[u];
	E[cnt].to = v;
	head[u] = cnt;
	E[cnt].val = w;
}

int Prim()
{
	now = 1;
    for(int i=2;i<=n;i++) dis[i] = 0x3f3f3f3f; // 初始化距离为最大 
    for(int i=head[1];i!=0;i=E[i].next) // 从1顶点开始 
        dis[E[i].to] = min(E[i].val,dis[E[i].to]); // 找边权最小点 
    while(num < n-1){
        int minn = 0x3f3f3f3f;
        vis[now] = 1; // 第一个点已经确定,标记一下 
        for(int i=1;i<=n;i++){
            if(minn > dis[i] && !vis[i]){
                minn = dis[i];
                now = i;
            }
        }
        ans += minn;
        num++; // 边加进去 
        for(int i=head[now];i!=0;i=E[i].next){ // 当前起点为now 
            if(dis[E[i].to]>E[i].val && !vis[E[i].to])
                dis[E[i].to] = E[i].val;
        }
    }
    return ans;
}

int main(){
	
	io >> n >> m;
	for(int i=0;i<m;i++){
		int u,v,w;
		io >> u >> v >> w;
		add(u,v,w);
		add(v,u,w); // 无向图 
	}
	printf("%d\n",Prim());
	return 0;
}
### 关于Prim算法中的数据输入方式 在Prim算法中,通常会处理带权无向图来构建最小生成树。为了实现这一目标,需要一种合适的方式来表示图的结构及其边权重。常见的图表示方法有邻接矩阵和邻接表两种形式。 #### 邻接矩阵表示法 邻接矩阵是一种二维数组的形式用于存储图的信息。对于一个具有 \(n\) 个顶点的图,可以创建一个大小为 \(n \times n\) 的矩阵 `graph` 来记录每一对顶点之间的连接关系及对应的权重。如果两个顶点之间存在一条边,则该位置存入相应的权重;如果没有直接相连,则可以用无穷大(如 INT_MAX 或者一个非常大的数)或者零来标记[^1]。 例如,在C语言中定义如下: ```c #define INF 99999 int graph[V][V]; ``` 这里假设常量 V 表示节点数量的最大值,并初始化整个 matrix 数组为INF代表初始状态下任意两点间均不存在路径[^2]。 接着通过读取具体边信息填充此matrix, 假设文件里给出的是三元组(u,v,w),分别对应起点u终点v还有它们间的距离w: ```c for (i = 0; i < edges_count; ++i){ scanf("%d %d %d", &start_vertex[i], &end_vertex[i], &weight[i]); graph[start_vertex[i]][end_vertex[i]] = weight[i]; graph[end_vertex[i]][start_vertex[i]] = weight[i]; // 如果是无向图的话还需要加上这句 } ``` 上述代码片段展示了如何基于标准输入流获取边的相关参数并将这些数值填入预先准备好的邻接矩阵当中去[^3]。 #### 邻接列表表示法 另一种常用的方法就是采用链式前向星或者是vector容器构成的邻接列表表达图形。这种方法更加节省空间尤其当稀疏图的时候显得尤为重要。每一个结点关联着一系列指向其他相邻节点指针连同其上的代价一同保存下来形成所谓的邻居清单。 下面是一个简单的例子展示怎样利用STL vector建立这样的结构: ```cpp #include <bits/stdc++.h> using namespace std; struct Edge { int to; int cost; }; const int MAXN = 1e5 + 7; vector<Edge> adjList[MAXN]; // 添加边到邻接表中 void addEdge(int u,int v ,int w ){ adjList[u].push_back({v,w}); adjList[v].push_back({u,w}); // 对于无向图而言也需要反方向加一次 } int main(){ ios::sync_with_stdio(false); cin.tie(0); int nodes,edges; cin>>nodes>>edges; while(edges--){ int a,b,cost; cin >>a>>b>>cost; addEdge(a,b,cost); } return 0; } ``` 以上程序段落先声明了一个最大容量足够容纳所有可能存在的定点数目上限的大规模静态数组adjList[][],其中每个元素都是动态增长型别的vector对象专门用来装载那些隶属于当前索引编号所指示的那个特定顶点的所有直系子代们各自的资料详情包括但不限于他们自己的身份识别号码to以及彼此沟通所需支付的价格成本cost等等属性特征值不一而足. 无论是哪种表现手法最终目的皆是为了方便后续执行prim核心逻辑运算过程能够顺利取得必要的拓扑资讯而已.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你数过天上的星星吗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值