C/C++中的Prim算法代码实现

在网上找到很多Prim算法的相关博客,虽然很正确,但是感觉看不懂,代码也不能完成自己需要去做的作业。之前跟着一篇博客打过一次代码,但是我看不懂了(所以痛定思痛,以后一定写注释@_@),而且测试我之后写的代码,出现了内存错误。没办法。这里记下我写代码的过程。

找不到想要博客之后,决定自己想办法,把老师的ppt看了很多遍,但是ppt上的伪代码含糊不清,有用的只有Prim的手动计算过程(这个百度上搜的到,很容易看懂,但是换成程序对于我这种不听课的人就麻烦了)。因为伪代码对自己写程序有帮助,所以我决定先弄伪代码(伪代码比较水可以不看,不过我还是放在这里)。

1.初始化visit,再加一个cont变量,记录已经访问的点的数目
2.把开始的顶点在visit里标记为已读
3.把开始的点存入V中
4.初始化lowcost和adjvex(如下标k代表这个顶点k与adjvex[k]=j,意思是在lowcost集合中lowcost[k]代表的是k到j的权值)
(lowcost第一次初始化比较简单,直接把开始顶点那一行的值全部放进去就行了)
5.从lowcost里选一个最小的权值,然后用lowcostpoint记下来,这个点需要是没被访问的点
6.把lowcostpoint放进V中,visit标记一下
7.之后对lowcost再次进行操作,对于未访问的点,与访问过的点直接的权值进行比较,把每个未访问的点的最小权值用lowcost记下,顺便更换一下adjvex,把adjvex的值变为对应的点
8.继续操作5、6、7,直到所有点被访问
9.打印V(如果这里V不是数组,是树的话返回树)

第一步,根据Prim的计算过程,需要选取一个点作为开始访问的节点(网上很多直接从0开始,这样会省去很多步骤)

void GraphMatrix::Prim(int start_index)//在GraphMatrix类中的Prim函数

第二步,在函数中,需要一个判断节点是否被访问过的数组,数组下标对应的是顶点,推荐bool值,整型的话用0或1(废话 )。这些还不够,还需要一个储存访问的节点索引的数组(这个是为了打印结果到控制台用的),一个记录权值的数组lowcost和记录最小权值对应点lowcostpoint,附带一个min,好判断最小值。如果要返回的是一个树的话,还需要一个adjvex数组。剩下的其他变量带有注释,就不一一解释了。(m_size是这个图用二维数组储存,二维数组的大小)

	int lowcost[m_size]; //储存相关顶点的最小路径
    int lowcostpoint;//记录最小权值对应顶点
    int min;//方便之后选择最小权值
    
    bool visit[m_size];//判断节点有没有被访问
    for(int i = 0;i < m_size;i ++)//初始化访问数组
        visit[i] = false;
    int cont = 0;//计数
    int adjvex[m_size];//储存相关顶点
    int V[m_size];//储存访问顺序
    

    V[cont] = start_index;//添加开始节点
    visit[start_index-1] = true;
    cont++;

    int sum = 0;//总权值

注:visit这里的下标之所以减1,是因为在节点上,我是用1开始计数,而数组是从0开始的。所以需要减小1。之后的情况也一样。

第三步初始化lowcost和adjvex,因为刚开始只有一个节点被访问,所以直接把这个点所有的分量赋值给lowcost就行。

for(int i = 0;i < m_size;i ++)
{
   
    lowcost[i] = matrix[start_index-1][i];
    adjvex[i] = start_index - 1;
}

第四步,循环m_size-1次重复的操作,每一次都需要保证有一个节点,所以可能不适用于一些特殊情况。例如
在这里插入图片描述
代码片段:

for(int i = 1;i < m_size;i ++)//因为第一个点已经访问,为了防止用cont访问V越界,所以把i=0改成了i=1
{
   
    min = INF;

注:这里的INF定义如下

#define INF 65535

第六步,找到了最小的点,用之前申请的变量进行记录,把visit中对应的未访问(false)改为已访问(true),把这个节点记录在V中(所以cont的计数是在这用的)。另外,如果需要最小生成树的总权值,还需要把这个最小点的对应权值加上去。

        sum += lowcost[lowcostpoint];
        visit[lowcostpoint] = true;
        V[cont] = lowcostpoint + 1;
        cont++;

第七步,这里需要的东西有点多。主要是对lowcost的操作。在Prim算法中,很容易知道事把已经访问的节点看作一个集合(或者是点),然后再看和其他未访问顶点的连通分量。
两个集合都有未知个数的点,这里用了傻瓜方法,便于理解也便于操作。就是把visit遍历,对没有访问过的节点,把它与已经访问的节点的连通分量遍历,找到最小的,然后塞进lowcost里,已经访问过了其实操不操作已经无所谓了,但是这里还是把对应的值改为了INF。

        for(int j = 0;j < m_size;j ++)
        {
   
            if(!visit[j])
            {
   
                min = INF;
                for(int k = 0;k < m_size;k ++)//对于未访问的单个节点,寻找与被访问的节点权值最小的节点
                {
   
                    if(min > matrix[j][k] && visit[k])//需要的是已经访问的节点
                    {
   
                        min = matrix[j
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.深度优先遍历(Depth-First Traversal) 假设给定图G的初态是所有顶点均未曾访问过。在G任选一顶点v为初始出发点(源点),则深度优先遍历可定义如下:首先访问出发点v,并将其标记为已访问过;然后依次从v出发搜索v的每个邻接点w。若w未曾访问过,则以w为新的出发点继续进行深度优先遍历,直至图所有和源点v有路径相通的顶点(亦称为从源点可达的顶点)均已被访问为止。若此时图仍有未访问的顶点,则另选一个尚未访问的顶点作为新的源点重复上述过程,直至图所有顶点均已被访问为止。 2.广度优先遍历 1)从图某个顶点V0出发,并访问此顶点; 2)从V0出发,访问V0的各个未曾访问的邻接点W1,W2,…,Wk;然后,依次从W1,W2,…,Wk出发访问各自未被访问的邻接点; 3)重复步骤2,直到全部顶点都被访问为止。 3. prim算法 假设V是图顶点的集合,E是图边的集合,TE为最小生成树的边的集合,则prim算法通过以下步骤可以得到最小生成树: 1)初始化:U={u 0},TE={f}。此步骤设立一个只有结点u 0的结点集U和一个空的边集TE作为最小生成树的初始形态,在随后的算法执行,这个形态会不断的发生变化,直到得到最小生成树为止。 2)在所有u∈U,v∈V-U的边(u,v)∈E,找一条权最小的边(u 0,v 0),将此边加进集合TE,并将此边的非U顶点加入U。此步骤的功能是在边集E找一条边,要求这条边满足以下条件:首先边的两个顶点要分别在顶点集合U和V-U,其次边的权要最小。找到这条边以后,把这条边放到边集TE,并把这条边上不在U的那个顶点加入到U。这一步骤在算法应执行多次,每执行一次,集合TE和U都将发生变化,分别增加一条边和一个顶点,因此,TE和U是两个动态的集合,这一点在理解算法时要密切注意。 3)如果U=V,则算法结束;否则重复步骤2。可以把本步骤看成循环终止条件。我们可以算出当U=V时,步骤2共执行了n-1次(设n为图顶点的数目),TE也增加了n-1条边,这n-1条边就是需要求出的最小生成树的边。 4.Kruskal算法 假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林只有一棵树,也即子图含有 n-1条边为止。
算法大全(C,C++) 一、 数论算法 1.求两数的最大公约数 function gcd(a,b:integer):integer; begin if b=0 then gcd:=a else gcd:=gcd (b,a mod b); end ; 2.求两数的最小公倍数 function lcm(a,b:integer):integer; begin if a<b then swap(a,b); lcm:=a; while lcm mod b>0 do inc(lcm,a); end; 3.素数的求法 A.小范围内判断一个数是否为质数: function prime (n: integer): Boolean; var I: integer; begin for I:=2 to trunc(sqrt(n)) do if n mod I=0 then begin prime:=false; exit; end; prime:=true; end; B.判断longint范围内的数是否为素数(包含求50000以内的素数表): procedure getprime; var i,j:longint; p:array[1..50000] of boolean; begin fillchar(p,sizeof(p),true); p[1]:=false; i:=2; while i<50000 do begin if p[i] then begin j:=i*2; while j<50000 do begin p[j]:=false; inc(j,i); end; end; inc(i); end; l:=0; for i:=1 to 50000 do if p[i] then begin inc(l);pr[l]:=i; end; end;{getprime} function prime(x:longint):integer; var i:integer; begin prime:=false; for i:=1 to l do if pr[i]>=x then break else if x mod pr[i]=0 then exit; prime:=true; end;{prime} 二、图论算法 1.最小生成树 A.Prim算法: procedure prim(v0:integer); var lowcost,closest:array[1..maxn] of integer; i,j,k,min:integer; begin for i:=1 to n do begin lowcost[i]:=cost[v0,i]; closest[i]:=v0; end; for i:=1 to n-1 do begin {寻找离生成树最近的未加入顶点k} min:=maxlongint; for j:=1 to n do if (lowcost[j]<min) and (lowcost[j]<>0) then begin min:=lowcost[j]; k:=j; end; lowcost[k]:=0; {将顶点k加入生成树} {生成树增加一条新的边k到closest[k]} {修正各点的lowcost和closest值} for j:=1 to n do if cost[k,j]<lwocost[j] then begin lowcost[j]:=cost[k,j]; closest[j]:=k; end; end; end;{prim} B.Kruskal算法:(贪心) 按权值递增顺序删去图的边,若不形成回路则将此边加入最小生成树。 function find(v:integer):integer; {返回顶点v所在的集合} var i:integer; begin i:=1; while (i<=n) and (not v in vset[i]) do inc(i); if i<=n then find:=i else find:=0; end; procedure kruskal; var tot,i,j:integer; begin for i:=1 to n do vset[i]:=[i];{初始化定义n个集合,第I个集合包含一个元素I} p:=n-1; q:=1; tot:=0; {p为尚待加入的边数,q为边集指针} sort; {对所有边按权值递增排序,存于e[I],e[I].v1与e[I].v2为边I所连接的两个顶点的序号,e[I].len为第I条边的长度} while p>0 do begin i:=find(e[q].v1);j:=find(e[q].v2); if i<>j then begin inc(tot,e[q].len); vset[i]:=vset[i]+vset[j];vset[j]:=[]; dec(p); end; inc(q); end; writeln(tot); end; 2.最短路径

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值