今天的ddl,明显感觉这周的作业轻松了不少qaq
看到我们院的大佬已经都可以去参加最强大脑了,告诉自己一定要更努力。
下一次准备写一篇文章整合一下前端的知识,然后就要开始动手刷题了
加油奥利给!
在这里非常感谢@黛西珀 给我指正了很多问题
Ps:部分内容需要点赞后查看
Prim算法介绍
1.概括
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。
2.算法简单描述
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来描述所得到的最小生成树。
图解因为课件描述比较奇怪,大家可以去上面链接里看,大佬整理的很好
我遇到的问题
1.Prim算法实现的时候我主要遇到的问题就是再加入新的节点后,因为需要更新权值信息,对于尚未有权值,已经加入树中的,和有权值的,需要分开讨论。
2.一开始实现算法的时候,只定义了权值的数组,后来发现无法确定结点之间的父母-孩子关系,我的做法是新定义的一个结构,里面同时储存权值和指向它的结点信息,当然同时定义两个数组应该也是可以的,大家可以尝试一下
3.因为大量计算涉及到权值,最好另写一个获取权值的函数。
4.最后的输出:因为不想(懒得)写树的部分算法,我选择直接输出边以及父母-孩子关系,大家有时间可以直接生成新的树,只要初始化树之后不断使用插入边的算法就可以了。
小声bb我感觉生成树之后也并没有直观多少,还不如我这么输出
代码实现
头函数和宏、结构定义
#include<stdio.h>
#include<stdlib.h>
#define MAX_VER_TEX 20//定点的最大个数
#define VertexType int
#define ERROR -1
#define OK 0
#define MAX 1000000
//typedef enum
//{ Digraph,//有向图
// Undigraph//无向图
//}GraphKind;
typedef struct weightlist{
VertexType vex;//指向这个顶点的顶点
int weight;
}WeightList;
typedef struct ArcNode{//弧结点结构
int adjvex;//本弧指向的结点的位置
struct ArcNode *nextarc;//指向下一个弧的指针
int weight;//权值
}ArcNode;
typedef struct VNode{//定点结构
VertexType data; //储存结点的信息
ArcNode* firstarc;//指向第一条弧的指针
}VNode,AdjList[MAX_VER_TEX];
typedef struct{ //图的结构
AdjList vertexs;//储存顶点的数组
int vexnum;//顶点个数
int arcnum;//边的个数
//GraphKind kind;//图的种类
}UnGraph;
无向图操作相关的子函数
int Locate(UnGraph g,VertexType elem)//定位这个结点在数组中的位置
{ int i;
for(i=0;i<g.vexnum;i++)
{ if(elem==g.vertexs[i].data)
return i;
}
return ERROR;//找不到对应元素,返回-1表示不存在
}
int CreateUnGraph(UnGraph *g)//输入图结构,图的顶点个数,边个数
{
int i;
int a,b,c,pa,pb;
ArcNode* p,*p2;
printf("输入图的有关信息,\n输入格式:图顶点个数+空格+边个数\n");
scanf("%d %d",&(g->vexnum),&(g->arcnum));
printf("下面请输入第一个顶点信息:");
for(i=0;i<g->vexnum;i++)
{ scanf("%d",&g->vertexs[i].data);
g->vertexs[i].firstarc=NULL;
if(i!=g->vexnum-1)
{ printf("请输入下一个结点的信息:");
}
}
printf("下面输入第一条边的信息,\n输入格式:起点的信息+空格+终点的信息+权值\n");
for(i=0;i<g->arcnum;i++)
{ scanf("%d %d %d",&a,&b,&c);
pa=Locate(*g,a);//起点在数组里的位置
pb=Locate(*g,b);//终点在数组里的位置
if(pa<0||pb<0)
{ printf("这个边并不存在,请重新输入\n");
i--;
continue;
}
p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=pb;
p->weight=c;
//下面把这个边信息插到起点的弧信息的第一个
p->nextarc=g->vertexs[pa].firstarc;
g->vertexs[pa].firstarc=p;
//在另一个结点的弧域中插入
p2=(ArcNode*)malloc(sizeof(ArcNode));
p2->adjvex=pa;
p2->weight=c;
//下面把这个边信息插到起点的弧信息的第一个
p2->nextarc=g->vertexs[pb].firstarc;
g->vertexs[pb].firstarc=p2;
if(i!=g->arcnum-1)
printf("请输入下一条边的信息:");
}
}
void PrintOutUnGraph(UnGraph g)
{ int i,j=1;
ArcNode *p;
printf("\n*---------分割线---------*\n");
printf("这个图共有%d个顶点%d条边:\n",g.vexnum,g.arcnum);
printf("这个图的顶点信息:\n");
for(i=0;i<g.vexnum;i++)
{ printf("该图结构的第%d个顶点的信息是%d\n",i+1,g.vertexs[i].data);
}
printf("这个图的边信息:\n");
for(i=0;i<g.vexnum;i++)
{ p=g.vertexs[i].firstarc;
while(p)
{ if(i<p->adjvex)
{
printf("第%d条边:(%d ,%d) 权值为:%d\n",j,g.vertexs[i].data,g.vertexs[p->adjvex].data,p->weight);
j++;
}
p=p->nextarc;
}
}
printf("*---------分割线---------*\n");
}
int InsertArc(UnGraph *g,int a,int b,int aweight)
{ int pa,pb;
ArcNode*p,*p2;
pa=Locate(*g,a);//起点在数组里的位置
pb=Locate(*g,b);//终点在数组里的位置
if(pa<0||pb<0)
{ printf("这个边并不存在,请重新输入\n");
return ERROR;
}
p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=pb;
p->weight=aweight;
//下面把这个边信息插到起点的弧信息的第一个
p->nextarc=g->vertexs[pa].firstarc;
g->vertexs[pa].firstarc=p;
//在另一个结点的弧域中插入
p2=(ArcNode*)malloc(sizeof(ArcNode));
p2->adjvex=pa;
p2->weight=aweight;
//下面把这个边信息插到起点的弧信息的第一个
p2->nextarc=g->vertexs[pb].firstarc;
g->vertexs[pb].firstarc=p2;
g->arcnum++;//边个数增加
return OK;
}
int DeleteArc(UnGraph *g,int a,int b)
{ int pa,pb;
ArcNode *p,*temp,*p2;
pa=Locate(*g,a);//起点在数组里的位置
pb=Locate(*g,b);//终点在数组里的位置
if(pa<0||pb<0)
return ERROR;
p=g->vertexs[pa].firstarc;
if(p->adjvex==pb)//p为头结点的情况
{ temp=p;
g->vertexs[pa].firstarc=p->nextarc;
free(temp);
}
if(p->nextarc->adjvex==pb)
{ temp=p->nextarc;
p->nextarc=temp->nextarc;
free(temp);
}
//从另一个结点删除
p2=g->vertexs[pb].firstarc;
if(p2->adjvex==pa)//p为头结点的情况
{ temp=p2;
free(temp);
g->vertexs[pb].firstarc=p2->nextarc;
}
if(p2->nextarc->adjvex==pa)
{ temp=p2->nextarc;
p2->nextarc=temp->nextarc;
free(temp);
}
g->arcnum--;//边个数减一
return OK;
}
//插入一个顶点,返回顶点在图数组里的位置,如果该顶点已经存在,或者数组已满返回-1;
int InsertVex(UnGraph *g,int adata)
{ int i;
if(g->vexnum==MAX_VER_TEX)
return ERROR;
for(i=0;i<g->vexnum;i++)
{ if(adata==g->vertexs[i].data)
return ERROR;
}
g->vertexs[g->vexnum].data=adata;
g->vertexs[g->vexnum].firstarc=NULL;
g->vexnum++;//顶点个数增加
return g->vexnum-1;
}
int Delete(ArcNode *p)//删除顶点的辅助函数:递归调用删除弧结点内容
{ if(p)
{
Delete(p->nextarc);
free(p);
return OK;
}
else
return NULL;
}
int DeleteVex(UnGraph *g,VertexType adata)
{ int qq=0;
ArcNode *p,*del,*pre;
int pdata=Locate(*g,adata);//定位结点位置
if(pdata<0)//结点不存在,返回错误信息
return ERROR;
//Delete(g->vertexs[pdata].firstarc);//删除这个结点储存的弧信息
p=g->vertexs[pdata].firstarc;
while(p)
{ g->arcnum--;
p=p->nextarc;
}
int i;
for(i=pdata;i<g->vexnum-1;i++)//数组内容移位
{ g->vertexs[i].data=g->vertexs[i+1].data;
g->vertexs[i].firstarc=g->vertexs[i+1].firstarc;//顶点信息和第一条弧的指针都移位
}
g->vertexs[g->vexnum-1].data=-1;
g->vertexs[g->vexnum-1].firstarc=NULL;
g->vexnum--;//顶点个数减1
for(i=0;i<g->vexnum;i++)
{ p=g->vertexs[i].firstarc;
while(p)
{ if(p->adjvex==pdata)
{
if(p==g->vertexs[i].firstarc)
{ del=p;
p=p->nextarc;
g->vertexs[i].firstarc=p;
pre=NULL;
free(del);
g->arcnum--;
break;
}
else
{ del=p;
p=p->nextarc;
pre->nextarc=p;
free(del);
g->arcnum--;
break;
}
}
else if(p->adjvex>pdata)
{ p->adjvex--;
}
pre=p;
p=p->nextarc;
}
}
return OK;
}
Prim算法相关函数
int GetWeight(UnGraph g,VertexType a,VertexType b )//获取权值
{ int pa,pb;
pa=Locate(g,a);
pb=Locate(g,b);
ArcNode* p;
p=g.vertexs[pa].firstarc;
while(p!=NULL)
{ if(p->adjvex==pb)
return p->weight;
else
p=p->nextarc;
}
return -1;
}
void Prim(UnGraph g,VertexType root)
{
int i,j,min=MAX,minnum;
WeightList weightlist[g.vexnum];//储存最小权值信息,-1表示没有,0表示已加入
int nowvex;//记录当前插入的哪个元素
nowvex=root;
for(i=0;i<g.vexnum;i++)
{ weightlist[i].weight=GetWeight(g,nowvex,g.vertexs[i].data);
weightlist[i].vex=nowvex;
} //初始化
weightlist[Locate(g,nowvex)].weight=0;
for(j=1;j<=g.vexnum-1;j++)
{ min=MAX;
for(i=0;i<g.vexnum;i++)
{
if(weightlist[i].weight<=min&&weightlist[i].weight>0)
{
minnum=i;//最小权值所在坐标
min=weightlist[i].weight;
}
}
//此时找到最小值位置,和当前新加入的点
printf("最小生成树上的一条边为(%d ——> %d),权值为%d\n",weightlist[minnum].vex,g.vertexs[minnum].data,weightlist[minnum].weight);
nowvex=g.vertexs[minnum].data;
weightlist[minnum].weight=0;
for(i=0;i<g.vexnum;i++)
{ if((weightlist[i].weight!=0&&GetWeight(g,nowvex,g.vertexs[i].data)!=-1)&&(weightlist[i].weight==-1||GetWeight(g,nowvex,g.vertexs[i].data)<weightlist[i].weight))
{ weightlist[i].weight=GetWeight(g,nowvex,g.vertexs[i].data);
weightlist[i].vex=nowvex;
}
}
}
}
主程序和结果检验
int main()
{ UnGraph test;
CreateUnGraph(&test);
PrintOutUnGraph(test);
Prim(test,3);
}
结果的检验我使用了作业之前的题
输入:
5 7
1
2
3
4
5
1 2 10
2 3 50
3 4 20
4 5 60
5 1 100
3 1 30
3 5 10
输出: