数据结构实例精讲图的应用.docx
《数据结构实例精讲图的应用.docx》由会员分享,可在线阅读,更多相关《数据结构实例精讲图的应用.docx(53页珍藏版)》请在冰豆网上搜索。
数据结构实例精讲图的应用
数据结构实例精讲:
图的应用
图是一种较线性表和树更为复杂的数据结构。
在图形结构中,结点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关。
图的应用极为广泛,例如,最小生成树、拓扑排序、关键路径、最短路径就是图的基本应用。
6.3图的应用
6.3.1最小生成树
在含有n个顶点的连通网中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称这棵连通子图为连通网的最小代价生成树(MinimumCostSpanningTree,MCSTorMST),简称为最小生成树。
【实例6-11】最小生成树。
如图6-5(a)所示的赋权图表示某六个城市及在它们之间直接架设一些通信线路的预算造价(单位:
万元)。
由于在6个城市间构建通信网只需架设5条线路,工程队面临的问题是架设哪几条线路能使总的工程费用最低?
试给出一个设计方案,使得各城市之间既能够相互通信又使总造价最小。
(1)问题分析。
构造最小生成树的算法有很多种,其中大多数的算法都利用了最小生成树的一个性质,简称为MST性质:
假设N=(V,E)是一个连通网络,U是顶点集V的一个非空子集,若存在顶点u∈U和顶点v∈(V–U)的边(u,v)是一条具有最小权值的边,则必存在G的一棵最小生成树包括这条边(u,v)。
普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法是两个采用贪心策略、利用MST性质构造最小生成树的算法。
普里姆算法的基本思想是:
首先选取网N=(V,E)中任意一个顶点u作为生成树的根加入到顶点集U(U是已落在生成树上的顶点的集合),之后不断在尚未落在生成树上的顶点集V-U中选取顶点v,添加到生成树中,直到U=V为止。
选取的顶点v应满足下列条件:
它和生成树上的顶点之间的边上的权值是在联接这两类顶点(U和V-U两个顶点集)的所有边中权值属最小。
设N=(V,E)是一个有n个顶点的连通网,用T=(U,TE)表示要构造的最小生成树,其中U为顶点集合,TE为边的集合,则Prim算法的具体实现步骤为:
1)初始化:
令U={Ø},TE={Ø}。
从V中取出一个顶点
放入生成树的顶点集U中,作为第一个顶点,此时T=({
},{φ})。
2)从u∈U,v∈(V–U)的边(u,v)中找一条权值最小的边(u*,v*),将其加入到TE中,并将v*添加到U中。
3)重复步骤2),直至U=V为止。
此时集合TE中必有n-1条边,T即为所要构造的最小生成树。
按普里姆算法构造一棵最小生成树的过程如图6-5所示。
图6-5普里姆算法构造最小生成树的过程
克鲁斯卡尔算法的基本思想为:
为使生成树上总的权值之和达到最小,则应使每一条边上的权值尽可能地小,自然应从权值最小的边选起,直至选出n-1条互不构成回路的权值最小边为止。
Kruskal算法的具体作法是:
设N=(V,E)是一个有n个顶点的连通网,令最小生成树的初始状态为只有n个顶点而无任何边的非连通图T=(V,{Ø}},图中每个顶点自成一个连通分量。
依次选择E中的最小权值的边,若该边依附于T中的两个不同的连通分量,则将此边加入到T中;否则,舍去此边(因为生成树中不允许有回路)而选择下一条代价最小的边。
以此类推,直到T中所有顶点都在同一连通分量上为止。
这时的T就是N的一棵最小生成树。
按克鲁斯卡尔算法构造一棵最小生成树的过程如图6-6所示。
图6-6克鲁斯卡尔算法构造最小生成树的过程
(2)源程序。
#include
#include
usingnamespacestd;
#defineMAX_VERTEX_NUM20//最大顶点个数
#defineMAX1000//最大值∞
typedefstructArcell
{
doubleadj;
}Arcell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedefstruct
{
charvexs[MAX_VERTEX_NUM];//顶点数组,每个顶点用一个字符表示
AdjMatrixarcs;//邻接矩阵
intvexnum,arcnum;//图的当前顶点数和弧数
}MGraph;
structPnode//用于普利姆算法
{
charadjvex;//顶点
doublelowcost;//权值
};
structKnode//用于克鲁斯卡尔算法中存储一条边及其对应的2个顶点
{
charch1;//顶点1
charch2;//顶点2
doublevalue;//权值
};
intLocateVex(MGraphG,charch)//确定顶点ch在图G.vexs中的位置
{
for(inti=0;i{
if(G.vexs[i]==ch)
returni;
}
return-1;
}
voidCreateUDG(MGraph&G,Knodedgevalue[])//构造无向加权图的邻接矩阵
{
inti,j,k;
cout<<"请输入图中顶点个数和边/弧的条数:
";
cin>>G.vexnum>>G.arcnum;
cout<<"请依次输入"<";
for(i=0;icin>>G.vexs[i];
for(i=0;i{
for(j=0;j{
G.arcs[i][j].adj=MAX;
}
}
cout<<"请依次输入"<"<for(k=0;k{
cin>>dgevalue[k].ch1>>dgevalue[k].ch2>>dgevalue[k].value;
i=LocateVex(G,dgevalue[k].ch1);
j=LocateVex(G,dgevalue[k].ch2);
G.arcs[i][j].adj=dgevalue[k].value;
G.arcs[j][i].adj=G.arcs[i][j].adj;
}
}
intMinimum(MGraphG,Pnodeclosedge[])//求辅助数组closedge中权值最小的边
{
inti,j;
doublek=MAX;
for(i=0;i{
if(closedge[i].lowcost!
=0&&closedge[i].lowcost{
k=closedge[i].lowcost;
j=i;
}
}
returnj;
}
voidMiniSpanTree_PRIM(MGraphG,charu)//普利姆算法求最小生成树
{
inti,j,k;
Pnodeclosedge[MAX_VERTEX_NUM];
//记录顶点集U到V-U的代价最小的边的辅助数组
k=LocateVex(G,u);
for(j=0;j{
if(j!
=k)
{
closedge[j].adjvex=u;
closedge[j].lowcost=G.arcs[k][j].adj;
}
}
closedge[k].lowcost=0;//初始,U={u}
for(i=1;i{
k=Minimum(G,closedge);//求出T的下一个顶点:
第k顶点
cout<<"("<cout<closedge[k].lowcost=0;//第k顶点并入U集
for(j=0;j{
if(G.arcs[k][j].adj{
closedge[j].adjvex=G.vexs[k];
closedge[j].lowcost=G.arcs[k][j].adj;
}
}
}
}
voidMiniSpanTree_KRSL(MGraphG,Knodedgevalue[])//克鲁斯卡尔算法
{
intp1,p2,i,j;
intbj[MAX_VERTEX_NUM];//标记数组
Knodetemp;
for(i=0;ibj[i]=i;
for(i=0;i{
for(j=i;j{
if(dgevalue[i].value>dgevalue[j].value)
{
temp=dgevalue[i];dgevalue[i]=dgevalue[j];
dgevalue[j]=temp;
}
}
}
for(i=0;i{
p1=bj[LocateVex(G,dgevalue[i].ch1)];
p2=bj[LocateVex(G,dgevalue[i].ch2)];
if(p1!
=p2)
{
cout<<"("<cout<for(j=0;j{
if(bj[j]==p2)
bj[j]=p1;
}
}
}
}
intmain()
{
inti,j;
MGraphG;
charu;
Knodedgevalue[MAX_VERTEX_NUM];
CreateUDG(G,dgevalue);
cout<<"图的邻接矩阵为:
"<for(i=0;i{
for(j=0;jif(G.arcs[i][j].adj!
=MAX)
cout<else
cout<cout<}
cout<<"=============普里姆算法===============\n";
cout<<"请输入起始顶点:
";
cin>>u;
cout<<"构成最小代价生成树的边集为:
\n";
MiniSpanTree_PRIM(G,u);
cout<<"============克鲁斯卡尔算法=============\n";
cout<<"构成最小代价生成树的边集为:
\n";
MiniSpanTree_KRSL(G,dgevalue);
return0;
}
6.3.2拓扑排序
由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序(TopologicalSort)。
例如,一个参加计算机专业自修的人必须系统地学习一系列的课程,如表6-1所示。
整个培训过程就是一项工程,每门课程的学习就是一项活动,一门课程可能以其它若干门课程为先修基础,而它本身又可能是另一些课程的先修基础。
各门课程之间的先修关系可以用如图6-7所示的活动顶点网络表示。
活动顶点网络,即AOV(ActivityOnVertex)网,是一个有向图,在图中顶点表示活动,弧表示活动之间的优先制约关系。
在AOV网中不允许出现有向环,否则意味着某项子工程的开始以本身的完成为先决条件,显然这说明这个工程的施工设计图存在问题。
而如果这个有向图表示的是数据流图的话,则表明存在着一个死循环。
表6-1计算机专业自修课程表
课程编号
课程名称
先修课程
C1
程序设计基础
无
C2
离散数学
C9
C3
数据结构
C1、C2
C4
微机原理
C5
C5
计算机系统结构
C3
C6
操作系统
C3、C5
C7
数据库应用
C3
C8
编译原理
C3、C1
C9
高等数学
无
C10
线性代数
C9
C11
数值分析
C1、C9、C10
C12
计算机网络
C5、C6、C7
图6-7表示课程之间优先关系的有向图
对AOV网进行拓扑排序的步骤如下:
(1)在AOV网中选择一个没有前驱的顶点并输出。
(2)从AOV网中删除该顶点以及从它出发的弧。
(3)重复以上两步直至AOV网变空(即已输出所有顶点)或者剩余子图中不存在无前驱的顶点。
后一种情况则说明该AOV网中含有向环。
例如,对如图6-7所示的AOV网,可以得到以下两个拓扑有序序列:
C9、C10、C2、C1、C11、C3、C8、C7、C5、C6、C4、C12
和C1、C8、C9、C2、C10、C11、C3、C5、C4、C6、C7、C12。
【实例6-12】拓扑排序。
编写一个函数,对有向图G进行拓扑排序。
(1)问题分析。
采用邻接表作为有向图的存储结构。
先计算图中每个顶点的入度,并保存到数组indegree中。
入度为零的顶点就是没有前驱的顶点,删除顶点及以它为尾的弧的操作,可以采用弧头顶点的入度减1来实现。
为了避免重复检测入度为零的顶点,可另设一栈暂存所有入度为零的顶点。
(2)函数程序代码。
voidFindInDegree(ALGraphG,intindegree[])//求顶点的入度
{
inti;
ArcNode*p;
for(i=0;iindegree[i]=0;
for(i=0;i{
p=G.vertices[i].firstarc;
while(p)
{
indegree[p->adjvex]++;
p=p->nextarc;
}
}
}
voidTopologicalSort(ALGraphG)//若G无回路,则输出G的顶点的一个拓扑序列
{
inti,k,count,indegree[MAX_VERTEX_NUM];
SqStackS;
ArcNode*p;
FindInDegree(G,indegree);//对各顶点求入度indegree[0..vernum-1]
InitStack(S);//初始化栈
for(i=0;iif(!
indegree[i])
Push(S,i);
count=0;//输出顶点计数
while(!
StackEmpty(S))
{
i=GetTop(S);Pop(S);
cout<++count;
for(p=G.vertices[i].firstarc;p;p=p->nextarc)
{
k=p->adjvex;//对i号顶点的每个邻接点的入度减1
if(!
(--indegree[k]))//若入度减为0,则入栈
Push(S,k);
}
}
if(countcout<<"此有向图有回路!
"<else
cout<<"为一个拓扑序列。
"<}
(3)函数应用的示例源程序。
#include
usingnamespacestd;
#defineMAX_VERTEX_NUM20//最大顶点个数
typedefintInfoType;
typedefcharVertexType[5];//顶点信息是一个最多4个字符的字符串
typedefenum{DG,DN,UDG,UDN}GraphKind;//{有向图,有向网,无向图,无向网}
typedefstructArcNode{
intadjvex;//该弧所指向的顶点的位置
structArcNode*nextarc;//指向下一条弧的指针
InfoType*info;//该弧相关信息的指针
}ArcNode;
typedefstructVNode{
VertexTypedata;//顶点信息
ArcNode*firstarc;//指向第一条依附该顶点的弧
}AdjList[MAX_VERTEX_NUM];
typedefstruct{
AdjListvertices;
intvexnum,arcnum;//图的当前顶点数和弧数
GraphKindkind;//图的种类标志
}ALGraph;
intLocateVex(ALGraphG,VertexTypeu)
//若图G中存在顶点u,则返回该顶点在图中位置;否则,返回-1
{
inti;
for(i=0;iif(strcmp(u,G.vertices[i].data)==0)
returni;
return-1;
}
voidCreateDG(ALGraph&G)
{
inti,j,k;
VertexTypeva,vb;
ArcNode*p;
cout<<"请输入有向图G的顶点数、弧的条数:
";
cin>>G.vexnum>>G.arcnum;
cout<<"请输入"<";
for(i=0;i{
cin>>G.vertices[i].data;
G.vertices[i].firstarc=NULL;
}
cout<<"请输入"<顶点1顶点2):
"<for(k=0;k{
cin>>va>>vb;
i=LocateVex(G,va);
j=LocateVex(G,vb);
p=newArcNode;
p->adjvex=j;
p->info=NULL;
p->nextarc=G.vertices[i].firstarc;//插在表头
G.vertices[i].firstarc=p;
}
G.kind=DG;
}
voidDisplayGraph(ALGraphG)
{
inti;
ArcNode*p;
cout<";
for(i=0;icout<cout<"<for(i=0;i{
p=G.vertices[i].firstarc;
while(p)
{
cout<adjvex].data<<"";
p=p->nextarc;
}
cout<}
}
voidDestroyGraph(ALGraph&G)//销毁图G
{
inti;
ArcNode*p,*q;
G.vexnum=0;
G.arcnum=0;
for(i=0;i{
p=G.vertices[i].firstarc;
while(p)
{
q=p->nextarc;
deletep;
p=q;
}
}
}
#defineMAXNUM100//假定预分配的栈空间最多为100个元素
typedefintElemType;//假定栈元素的数据类型为整型
typedefstruct{
ElemTypedata[MAXNUM];
inttop;
}SqStack;
voidInitStack(SqStack&S)//置栈空
{
S.top=-1;
}
boolStackEmpty(SqStackS)//判栈空
{
returnS.top==-1;
}
voidPush(SqStack&S,ElemTypex)//入栈
{
if(S.top==MAXNUM-1)
{
cout<<"栈上溢"<return;
}
S.data[++S.top]=x;//栈顶指针加1后将x入栈
}
voidPop(SqStack&S)//出栈,不返回栈顶元素
{
if(StackEmpty(S))
{
cout<<"栈为空"<return;
}
S.top--;//将栈顶指针减1
}
ElemTypeGetTop(SqStackS)//取栈顶元素
{
if(StackEmpty(S))
{
cout<<"栈为空"<exit
(1);
}
returnS.data[S.top];
}
voidFindInDegree(ALGraphG,intindegree[])//求顶点的入度
{
inti;
ArcNode*p;
for(i=0;iindegree[i]=0;
for(i=0;i{
p=G.vertices[i].firstarc;
while(p)
{
indegree[p->adjvex]++;
p=p->nextarc;
}
}
}
voidTopologicalSort(ALGra