图论.docx
- 文档编号:24442702
- 上传时间:2023-05-27
- 格式:DOCX
- 页数:43
- 大小:209.99KB
图论.docx
《图论.docx》由会员分享,可在线阅读,更多相关《图论.docx(43页珍藏版)》请在冰豆网上搜索。
图论
图
(一)基本概念
线性表:
数据间关系是线性的,每个元素只有一个前趋,一个后续,1:
1;
树:
有着明显的层次关系,每个元素只有一个前趋,但有多个后续,1:
N;
图:
数据之间的关系是任意,每个元素的前趋和后续个数是不定的,M:
N;
引入:
柯尼斯堡七桥问题,能否从A地发出,各座桥恰好通过一次,最后回到出发地A?
结论:
1736年,数学家欧拉首先解决了这个问题,由此开创了图论研究。
这事实上是欧拉图的“一笔画问题”。
答案是否定的,因为,对于每一个顶点,不论如何经过,必须有一条进路和一条出路,与每一个顶点相邻的线(关联边)必须是偶数条(除起点和终点外),而此图中所有点都只有奇数条关联边。
在后面的应用中,我们将专门讨论这个问题。
定义:
简单讲,一个图是由一些点和这些点之间的连线组成的。
严格意义讲,图是一种数据结构,定义为:
graph=(V,E)。
V是一个非空有限集合,代表顶点(结点),E代表边的集合,一般用(Vx,Vy)表示,其中,Vx,Vy属于V。
分类:
如果边是没有方向的,称为“无向图”。
表示时用一队圆括号表示,如:
(Vx,Vy),(Vy,Vx),当然这两者是等价的。
并且说边(Vx,Vy)依附于(相关联)顶点Vx和Vy。
如果边是带箭头的,则称为“有向图”,表示时用一队尖括号表示,此时
有向图中的边又称为弧。
起点称为弧头、终点称为弧尾。
相邻:
若两个结点U、V之间有一条边连接,则称这两个结点U、V是关联的。
带权图:
两点间不仅有连接关系,还标明了数量关系(距离、费用、时间等)。
图的阶:
图中结点的个数。
结点的度:
图中与结点A关联的边的数目,称为结点A的度。
入度:
在有向图中,把以结点V为终点的边的数目称为V的入度;
出度:
在有向图中,把以结点U为起点的边的数目称为U的出度;
奇点:
度数为奇数的结点;
偶点:
度数为偶数的结点;
终端结点:
在有向图中,出度为0的结点;
定理1:
图中所有结点的度数之和等于边数的2倍;
定理2:
任意一个图一定有偶数个奇点;
连通:
如果图中结点U,V之间存在一条从U通过若干条边、点到达V的通路,则称U、V是连通的。
路(径):
从一个结点出发,沿着某些边连续地移动而到达另一个指定的结点,这种依次由结点和边组成的序列,叫“路”或者“路径”。
路径长度:
路径上边的数目。
简单路径:
在一个路径上,各个结点都不相同,则称为简单路径。
回路:
起点和终点相同的路径,称为回路,或“环”。
连通图:
对于图G中的任一对不同顶点U、V,都有一条(U,V)通路,则称图G是连通的。
强连通图:
在有向图G中,每一对结点之间都有路径的图。
网络:
带权的连通图。
(二)表示方法
1、邻接矩阵表示法(顺序存储)
无向图:
A[I,J]=1当I与J两个结点相邻时
=0当I与J两个结点不相邻时,或I=J
(1=
且有:
A[I,J]=A[J,I],即邻接矩阵是对称的。
如上图(A)的邻接矩阵如下:
0111
A=1011
1100
1100
有向图:
A[I,J]=P[I,J]当I与J两个结点相邻,且权值为P[I,J]时
=0或∞当I与J两个结点不相邻时,或I=J
如上图(B)和(C)的邻接矩阵分别如下:
011∞58∞3
A=001A=5∞2∞6
00182∞104
∞∞10∞11
相应的数据结构定义如下:
constn=20;
typeadj=0..1;
graph=array[1..n,1..n]ofadj;
下面给出带权无向图的邻接矩阵建立过程:
constmax=1e5;n=10;
typegraph=array[1..n,1..n]ofreal;
varI,j,k,e:
integer;
g:
graph;
w:
real;
begin
forI:
=1tondo
forj:
=1tondo
g[I,j]:
=max;
read(e);{读入边的数目}
fork:
=1toedo
begin
read(I,j,w);{读入两个结点及权值}
g[I,j]:
=w;
g[j,I]:
=w;
end;
forI:
=1tondo
begin
forj:
=1tondowrite(g[I,j]);
writeln;
end;
end.
2、邻接表表示法(链式存储法)
以上图(A)、图(B)的邻接表分别如下,请大家自己画出图(C)的邻接表。
相应的数据结构描述为:
typegrapglist=^enode;{边表}
enode=record
adj:
1..n;{边的起点}
next:
grapglist;{指向的下条边}
end;
vnode=record{顶点表}
v:
vtype;{顶点数据类型}
link:
grapglist;{指向的下个顶点}
end;
varadjlist=array[1..n]ofvnode;{n个顶点的邻接表}
下面给出有向图结构的邻接表的建立过程:
procedurecreatelist(varg:
adjlist);
vars:
node;
begin
read(n,e);{n为顶点数,e为边数}
forI:
=1tondo{建立顶点表}
begin
read(g[I].v);
g[I].link:
=nil;
end;
fork:
=1toedo{建立边表}
begin
read(I,j);
new(s);
s^.adj:
=j;{新结点的序号为读入的终点}
s^.next:
=g[I].link;{把新结点插入到当前结点I之后}
g[I].link:
=s;
end;
end.
(三)图的遍历
1、概念:
从图中某一结点出发系统地访问图中所有结点,使每个结点恰好被访问一次,这种运算被图的遍历。
为了避免重复访问某个结点,可以设一个标志数组visited[I],未访问时值为FALSE,访问一次后就改为TRUE。
分类:
深度优先遍历和广度优先遍历。
2、深度优先遍历:
类似于树的先序遍历,从图中某个结点V0出发,访问此结点,然后依次访问从V0的未被访问的邻接点出发进行深度优先遍历,直到图中所有和V0有路径相通的结点均被访问到。
若此时图中尚有结点未被访问,则另选图中一个未被访问的结点V1作为起点,重复上述过程,直至图中所有结点都被访问到为止。
如下面两个图的深度优先遍历结果分别为:
a,b,c,d,e,g,f。
V1,V2,V4,V8,V5,V3,V6,V7。
3、深度优先遍历的递归过程如下:
{前提:
图必须是连通的,否则要多次调用本算法}
proceduredfs(i:
1..n);
begin
write(g[i].v);
visited[i]:
=true;
p:
=g[i].link;
whilep<>nildo
begin
ifnotvisited[p^.adj]thendfs(p^.adj);
p:
=p^.next;
end;
end;
4、广度优先遍历:
从图中某个结点V0出发,访问此结点,然后依次访问与V0邻接的、未被访问过的所有结点,然后再分别从这些结点出发进行广度优先遍历,直到图中所有被访问过的结点的相邻结点都被访问到。
若此时图中还有结点尚未被访问,则另选图中一个未被访问过的结点作为起点,重复上述过程,直到图中所有结点都被访问到为止。
如上面两个图的广度优先遍历结果分别为:
a,b,d,e,f,c,g。
V1,V2,V3,V4,V5,V6,V7,V8。
5、广度优先遍历的递归过程:
与深度优先不同的是,深度优先实际上是尽可能地走“顶点表”;而广度优先是尽可能沿结点的“边表”进行访问,然后再沿边表对应结点的边表进行访问,因此,有关边表的顶点需要保存(用队列,先进先出),以便进一步进行广度遍历。
Procedurebfs(i:
1..n);
Begin
Create(q);{建队列,并初始化}
Write(g[i].v);{访问Vi}
Visited[i]:
=true;{记下已访问标记}
Push(q,i);{进队列}
Whilenotempty(q)do{开始广度遍历}
Begin
i:
=pop(q);{取顶点}
p:
=g[i].link;{取边表指针}
whilep<>nildo
begin
ifnotvisited[p^.adj]thenbeginwrite(g[p^.adj].v);
visited[p^.adj]:
=true;
push(q,p^.adj);
end;
p:
=p^.next;{从边表中取下一个邻接边}
end;
end;
end;
(四)最小生成树问题
如果图G=(V,E)是一个连通的无向图,则从G的任一个顶点出发进行一次深度优先遍历或广度优先遍历便可遍历全部图。
设遍历过程中走过的边的集合为TE(G),显然T=(V,TE)是G的一个连通子图,且T本身也是一棵树(无根树),则称T是G的生成树。
上图(B)和(C)是对(A)分别进行深度和广度优先遍历得到的一种生成树。
注意,图的生成树是不唯一的。
对于一棵带权树,生成树中各边的权值之和称为这棵生成树的代价,代价最小的生成树称为“最小代价生成树”。
如何求最小代价生成树呢?
R.C.Prim提出的求最小代价生成算法是常用的一种,设图的顶点集合V共有n个顶点,则算法如下:
1、设置一个顶点的集合S1和一个边的集合TE,S1和TE的初始状态均为空集;
2、选定图中的一个顶点K,从K开始生成最小代价生成树,将K加入到集合S1;
3、重复下列操作,直到选取了n-1条边:
选取一条权值最小的边(X,Y),其中X∈S1,not(Y∈S1)。
将顶点Y加入集合S1,边(X,Y)加入集合TE。
下图给出了上图(A)的最小代价生成树的生成过程:
程序留给大家完成
(五)一笔画问题
请你设计一个程序,对给定的一个图,由计算机判断能否一笔画出,若能请打印出一笔画的先后顺序;若不能则打印“NOSOLUTION!
”。
[问题分析]
由数学知识可知:
当一个图的顶点全是偶点或仅有两个奇点时才能一笔画出,而且此图必须是连通图。
给出几个数学概念:
1.欧拉路:
在无孤立结点的图G中,若存在一条路,经过图中每条边一次且仅一次,则称此路为欧拉路。
如下图(左)可以构成一个欧拉路:
2.欧拉回路:
若存在一条路,经过图中每条边一次且仅一次,且回到原来位置,则称此路为欧拉回路。
因此,七桥问题转换成了欧拉回路问题。
如下图(右)可以构成一个欧拉路:
3.欧拉图:
存在欧拉回路的图,称为欧拉图。
4.定理1:
存在欧拉路的条件:
图是连通的,且存在0个或2个奇点。
5.定理2:
存在欧拉回路的条件:
图是连通的,且存在0个奇点。
6.哈密尔顿图:
无孤立结点的连通图,若存在一条路,经过图中每一个结点一次且仅一次,则称为哈密尔顿图。
7.哈密尔顿环:
是一条沿着图的n边环行的路径,它访问每一个结点一次且仅一次,并且返回到它的开始位置。
[算法设计]
1、建立邻接矩阵,link[i,j]:
=1或0;
2、求每个顶点的度数,存入degree[i];
3、统计奇点的个数,存入odt;
4、若无奇点,则可从任意一结点出发开始一笔画,一般start:
=1;
5、若有2个奇点,则从其中一个奇点i出发开始一笔画,start:
=i;
6、若奇点个数超过2个,则不能一笔画出。
如何画呢?
即如何打印序列呢?
细化第4、5步:
4.1设sum为图的总度数;
4.2从一个奇点(偶点)出发,打印出来,且degree[i]-1;
4.3从邻接矩阵中找到结点i的下一个邻接点j,且degree[j]-1;
4.4sum:
=sum-2;
4.5判断“sum=0”,成立就结束,否则转4.2。
[源程序]
constn=6;
link:
array[1..n,1..n]of0..1{邻接矩阵,也可定义成数组变量
=((0,1,0,0,1,1),从键盘读入,容易错}
(1,0,1,1,0,1),
(0,1,0,1,0,0),
(0,1,1,0,1,1),
(1,0,0,1,0,1),
(1,1,0,1,1,0));
vardegree:
array[1..n]ofinteger;
i,j,r,sum,odt,start,now:
integer;
begin
sum:
=0;{总度数}
odt:
=0;{奇点个数}
start:
=1;{起点,默认为第1个结点}
fori:
=1tondo{求每个结点的度,统计奇点个数,确定起点start}
begin
degree[i]:
=0;
forj:
=1tondodegree[i]:
=degree[i]+link[i,j];
sum:
=sum+degree[i];
ifodd(degree[i])thenbeginodt:
=odt+1;start:
=i;end;
end;
ifodt>2thenwriteln('nosolution!
')
elsebegin
now:
=start;{now为当前结点}
write(start);
repeat
r:
=0;repeat{找满足条件的下一个结点}
r:
=r+1
until((link[now,r]>0)and
((degree[r]>1)or((degree[r]=1)and(sum=2))));
link[now,r]:
=0;
link[r,now]:
=0;
sum:
=sum-2;{两个点均置访问标记,且总度数-2}
dec(degree[now]);
dec(degree[r]);{各点度数-1}
now:
=r;{下个起点}
write('--->',r);{输出}
untilsum=0;
end;
writeln;
readln;
end.
[输入输出样例]
将以上两图的邻接矩阵输入后,分别输出:
左:
5——>1——>2——>3——>4——>2——>6——>4——>5——>6——>1
右:
3——>2——>1——>8——>5——>4——>3——>6——>5——>7——>6——>8——>7——>2
下面给出找一个图中所有哈密尔顿环的递归回溯算法。
先假设用X[1..K]表示求的解,其中X[i]是找到的环中第i个被访问的结点,先给出已经选定X[1..K-1]生成X[K]的算法:
procedurenext(k);
varx:
array[1..n]ofinteger;
graph[1..n,1..n]ofBoolean;
k,j:
integer;
begin
x[k]:
=(x(k)+1)mod(n+1);
ifx(k)=0thenreturn;
ifgraph(x(k-1),x(k)){有边相连}
thenforj:
=1tok-1do{查与前K-1个结点是否相同}
ifx(j)=x(k)thenexit;{有则出循环}
ifj=kthen{有一个不同结点}
if(k end; 再利用下面的回溯法就可以求出所有的哈密尔顿环了: procedureHamilton(k); varx;array[1..n]ofinteger; k,n: integer; begin next(k); ifx(k)=0thenreturn; ifk=nthenprint(x,’1’) elsehamilton(k+1); end; (六)最短路径问题 在带权图G=(V,E)中,若顶点Vp,Vq是图G的两个顶点,从顶点Vp到Vq的路径长度定义为路径上各条边的权值之和。 从顶点Vp到Vq可能有多条路径,其中路径长度最短的一条路径称为顶点Vp到Vq的最短路径。 有两类最短路径问题: 一是求从某个顶点(源结点)到其它顶点(目的结点)的最短路径问题。 二是求图中每一对顶点间的最短路径。 如下例,下图是六个城市之间道路联系的示意图,连线表示两城市间有道路相通,连线旁的数字表示路程。 请编写一程序,由计算机找出从C1城到C6城之间路长最短的一条路径,输出路径序列及总长度。 link[i,j]: =0表示城市i与城市j之间没有通路,否则为权值。 基本思想为: 假设K为当前城市编号,R为下一城市编号(2<=R<=6),按下列规则进行遍历: iflink(k,r)>0且Cr没有被访问过 then记录该城市编号,k: =r。 可以用深度优先遍历,也可用广度优先遍历。 下面的程序采用后者,前者留给大家完成。 注意,广度优先遍历时,最先找到的路径未必是最短路径,而只是走过城市最少的路径而已。 所以,找到一条路径后应采用“打擂台”的思想保存最小值。 另外,按照广度优先遍历的要求,设一个pnt数组和open、closed用于队列存放之间结点。 [源程序] constmax=maxint; link: array[1..5,1..6]ofinteger=((0,4,8,0,0,0), (4,0,3,4,6,0), (8,3,0,2,2,0), (0,4,2,0,4,9), (0,6,2,4,0,4)); typefg=setof1..6; varmincost,step,open,closed,i,k,n,r: integer; path: array[1..7]of1..6;{存放最终结果} flag: array[1..100]offg;{标志数组,队列,头尾指针分别为open,closed} city,pnt: array[1..100]ofbyte;{数值数组,pnt用于队列} procedureexam;{判断最短路径和求和} varn,i,y,cost: integer; s: array[1..7]of1..6; begin y: =open; n: =0; cost: =0; whiley>0dobegininc(n);s[n]: =y;y: =pnt[y];end;{计算步长(深度)} fori: =n-1downto1do{计算路径} cost: =cost+link[city[s[i+1]],city[s[i]]]; ifcost =n-1;{记忆最短路径} mincost: =cost; fori: =1tostepdopath[i]: =city[s[i]]; end; end; procedureprint;{输出path数组和mincost} begin writeln('theshortestpath: '); write('1'); fori: =stepdownto1dowrite('->',path[i]); writeln; writeln('mincost=',mincost); end; BEGIN mincost: =max;{打擂台,赋初值} flag[1]: =[1]; city[1]: =1;{初始化,从第一个结点C1开始遍历} n: =0; pnt[1]: =0;{队列初始化} closed: =0; open: =1; repeat{直到队列空,结束程序} inc(closed); k: =city[closed]; ifk<>6thenbegin{判断有没有到达目的结点C6} forr: =2to6do{广度遍历} if(not(rinflag[closed]))and(link[k,r]>0)then begin{没访问过,则进队} inc(open); city[open]: =r; flag[open]: =flag[closed]+[r]; pnt[open]: =closed; ifr=6thenexam; end; end; untilclosed>=open; print; readln; END. [运行结果] theshortestpath: 1-2-3-5-6 mincost=13 (七)拓扑排序问题 一项大的工程可以看作是由若干个称为“活动”的子工程组成的集合,这些子工程之间必定存在一种先后关系,即某些子工程必须在其它一些工程完成之后才能开始,我们可以用有向图表示工程间关系,子工程(活动)为顶点,活动之间的先后关系为有向边,这种有向图称为顶点表示活动的网络,又称为AOV网。 在AOV网中,如果有一条从顶点Vi到Vj的路径,则说Vi是Vj的前趋,Vj是Vi的后续。 如果有弧 拓扑排序是把AOV网中所有顶点排成一个线性序列(拓扑序列),如果有弧 一个存在回路的有向图顶点是不能排成拓扑序列的,因为有回路的有向图的边体现的先后关系不是非自反的。 任何没有回路的有向图,其顶点都可以排成一个拓扑序列。 而且得到的拓扑序列是不唯一的。 如对下图进行拓扑排序可以得到多个拓扑序列,如: 02143567、01243657、02143657、01243567。 = 在这个AOV网络之中,工程1和过程2显然可以同时进行,先后无所谓。 但工程4显然要等工程1和工程2都完成以后才可进行,工程3要等到工程4完成以后才可进行(当然工程1也要完成后,显然),工程5、又要等到工程3完成以后(工程4也必须先完成,显然),工程6则要等到工程4完成后进行,工程7要等到工程3、5、6都完成后才能进行。 [数据结构]用邻接表存储AOV网: typegraphlist=^enode;{边表} enode=record adjv: 1..n; next: graphlist; end; vnode=record{顶点表} v: char; id: integer;{记录顶点的入度} link: graphlist; end; varadj: array[1..n]ofvnode; [基本算法] 1.建立一个含有n个顶点的AOV网。 2.对图中的每一个顶点i,执行下列操作: 如果顶点i没有前趋,则输出顶点i的信息,并从网中删除顶点i及由它发出的弧。 Proceduretopsort(dig: graphlist); Varn,top,i,j,k,m: integer; P: graphlist; Begin N: =dig.adj
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 图论