图及其应用

 图及其应用

衢州一中 胡承丰
一, 图的基本概念
1,图的的定义
如果数据元素集合D中的各元素之间存在任意的前驱和后继关系R,则此数据结构G=(D,R)称为图.如果将数据元素抽象为结点,元素之间的先后关系用边表 示,则图亦可以表示为G=(V,E),其中V是结点的有穷(非空)集合,E为边的集合.如果元素a是元素b的前驱,这种先后关系对应的边用(a,b)表 示,即(a,b)∈E.图可以分为无向图和有向图两种形式.
2,无向图和有向图
_⑴无向图:在图G=(V,E)中,如果对于任意的a,b∈V,当(a,b)∈E时,必有(b,a)∈E(即关系R对称),对称此图为无向图.在一无向图中用不带箭头的边
连接两个有关联的结点.在具有n个结点的无向图中,边的最大数目为:
而边数达到最大值的图称为无向完全图.
在无向图中一个结点相连的边数称为该结点的度.
图(A) V={1,2,3,4} E={(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)}
⑵ 有向图:如果对于任意的a,b∈V,当(a,b)∈E时 ,(b,a)∈E未必成立,则称此图为有向图.在有向图中,通常用带箭头的边连接两个有关联的结点(方向由前件指向后件).例如图(b)为有向图.有向图 中一个结点的后件个数称为该结点的出度,其前件个数称为该结点的入度.一个结点的入度和出度之和称为该结点的度.图中结点的最大度数称为图的度.例如下图 (b))中结点1的出度和入度分别为1,结点1和结点1度都为2.整个图的度为2.
图(B) V={1,2,3} E={,,}
3,__ 路径和连通集
在图G=(V,E)中,如果对于结点a,b,存在满足下述条件的结点序列x1……xk(k>1)
⑴ x1=a,xk=b
⑵ (xi,xi+1)∈E i=1¨k-1
则称结点序列x1=a,x2,…,xk=b为结点a到结点b的一条路径,而路径上边的数目k-1称为该路径的长度,并称结点集合{x1,…,xk}为连通集.
{V1, v2, v5, v4}
4,简单路径和回路
如 果一条路径上的结点除起点x1和终点xk可以相同外,其它结点均不相同,则称此路径为一条简单路径.图(a)中v1→v2→v3是一条简单路径, v1→v3→v4→v1→v3不是简单路径.x1=xk的简单路径称为回路(也称为环).例如图(b)中,v1→v2→v1为一条回路.
5,有根图
在一个有向图或无向图中,若存在一个结点w,它与其他结点都是连通的,则称此图为有根图,结点w即为它的根.
有根图,v1,v2,v3,v4都可以作为根
有根图,v1或v2为它的根.
6,连通图和最大连通子图
对于无向图而言,若其中任两个结点之间的连通,则称该图为连通图.一个无向图的连通分支定义为此图的最大连通子图.
左图所示的图是连通的,它的最大连通子图即为其本身.
7,强连通图和强连通分支
若对于有向图的任意两个结点vi,vj间(vi≠vj),都有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称该有向图是强连通的.有向图中强连通的最大子图称为该图的强连通分支.
上图不是强连通的,因为v3到v2不存在路径.它含有两个强连通分支
二,图的存储结构
图的相邻 矩阵表示法
相邻 矩阵是表示结点间相邻关系的 矩阵.若G=(V,E)是一个具有n个结点的图,则G的相邻 矩阵是如下定义的二维数组a,其规模为n*n
type
maxn=顶点数的上限;
var
a:array[1..maxn,1..maxn]of integer;
f:array array[1..maxn]of boolean; {顶点的访问标志序列}
上图中的图G1和图G2对应的相邻 矩阵分别为:
0 1 1 1 0
1 0 1 1 1
1 1 0 1 0
1 1 1 0 1
0 1 0 1 0
相邻 矩阵的特点:
1)无向图的相邻 矩阵是对称的,而有向图则不是.
2)相邻 矩阵方便度数的计算.用相邻 矩阵表示图:
(1)容易判定任意两个结点之间是否有边相联;
(2)并容易求得各个结点的度数.
(对于无权无向图的相邻 矩阵,第i行元素值的和就是Vi的度数;对于无权有向图的相邻 矩阵,第i行元素值的和就是Vi的出度,第i列元素值的和就是Vi的入度;)对于有权无向图的相邻 矩阵,第i行(或第i列)中元素值0的元素个数就是Vi的度数;对于有权有向图的相邻 矩阵,第i行中元素值0的元素个数就是Vi的出度;第i列元素值0的元素个数就是Vi的入度.
⑶容易计算路径的存在性.在无权有向图或无向图中,判定两个结点Vi,Vj之间是否存在长度为m的路径,只要考虑am=a*a*……*a(m个a 矩阵相乘后的乘积 矩阵)中(i,j)的元素值是否为0就行了.
(4)占用的存储单元数只与结点数有关而与边数无关.一个含n个结点的图,若其边数比n2少得多,那么其相邻 矩阵是一个稀疏 矩阵,占用许多空间来存储0(或±∝ )当然是无端浪费.
(5)存储相邻 矩阵的静态数据区仅有64kb可用空间.一旦图的存储容量超过了64kb,则非得采用动态数据类型的邻接表不可.
三,图的遍历
给出一个图G和其中任意一个结点V0,从V0出发系统地访问G中所有结点,每一个结点被访问一次,这就叫图的遍历.

通常有两种遍历方法:
⑴深度优先搜索dfs
⑵广度优先搜索bfs
它们对无向图和有向图都适用.我们以相邻 矩阵存储结构给出深度优先搜索和广度优先搜索的程序.
1,深度优先搜索DFS
深度优先搜索类似于树的前序遍历,是树的前序遍历的推广.其搜索过程如下:
假设初始时所有结点未曾被访问.深度优先搜索从某个结点V0出发,访问此结点.然后依次从V0的未被访问的邻接点出发深度优先遍历图,直至图中所有和V0 有路径相连的结点都被访问到.若此时图中尚有结点未被访问,则另选一个未曾访问的结点作起始点,重复上述过程,直至图中所有结点都被访问为止.换句话说, 深度优先搜索遍历图的过程是以V0为起始点,由左而右,依次访问由V0出发的每条路径.
调用一次dfs(i),可按深度优先搜索的顺序访问处理结点i所在的连通分支(或强连通分支),dfs(i)的时间复杂度为W(n2).整个图按深度优先搜索顺序遍历的过程如下:
图的深度优先应用
2,广度优先搜索(宽度优先搜索)BFS
广度优先搜索类似于树的按层次遍历的过程,其搜索过程如下:
假设从图中某结点v0出发,在访问了v0之后依次访问v0的各个未曾访问的邻接点,然后分别从这些邻接点出发按广度优先搜索的顺序遍历图,直至图中所有可 被访问的结点都被访问到.若此时图中尚有结点未被访问,则任选其中的一个作起始点,重复上述过程,直至图中所有结点都被访问到为止.换句话说,按广度优先 顺序搜索遍历图的过程是以v0为起始点,由近及远,依次访问和v0有路径相连且路径长度为1,2,3……的结点.
bfs(i)的时间复杂度为W(n2).调用一次bfs(i)可按广度优先搜索的顺序访问处理结点i所在的连通分支(或强连通分支).整个图按广度优先搜索顺序遍历的过程如下:
图的广度优先应用
1,写出图的深度优先搜索(DFS)算法和广度优先搜索(BFS)算法.
program dfsbfs(input,output);
const n=8;
var a:array[1..n,1..n]of integer;{图的邻接 矩阵}
visited,come:array[1..n]of integer;{访问标志}
queue:array[1..n]of integer;{队列}
t:array[1..n]of char;{结点信息}
i,head,tail:integer;
procedure init;
var i,j,e,k:integer;
begin
for i:=1 to n do read(t[i]);{顶点信息}
fillchar(a,sizeof(a),0);
read(e);{边数}
for k:=1 to e do{读入边的点信息,建立邻接 矩阵}
begin
read(i,j); a[i,j]:=1; a[j,i]:=1;
end;
end;
procedure dfs(i:integer);
var j:integer;
begin
write(t[i]);{输出结点信息}
visited[i]:=1;{访问标志}
for j:=1 to n do{深度优先搜索i的邻接点}
if (a[i,j]=1) and (visited[j]=0) then dfs(j);
end;
procedure bfs(i:integer);{广搜}
var j:integer;
begin
write(t[i]);
visited[i]:=1;
tail:=tail+1;{尾指针加1}
queue[tail]:=i;{入队列}
while head <=tail do{队列非空}
begin
for j:=1 to n do{搜索i的所有邻接点,如果没访问,入队列}
if (a[queue[head],j]=1 ) and (visited[j]=0) then
begin
write(t[j]);
visited[j]:=1;
tail:=tail+1;
queue[tail]:=j;
end;
head:=head+1;{出队列,访问队首元素}
end;
end;
Begin
writeln;
init;
for i:=1 to n do visited[i]:=0;
for i:=1 to n do if visited[i]=0 then dfs(i);
writeln;
head:=0;
tail:=0;
for i:=1 to n do visited[i]:=0;
for i:=1 to n do if visited[i]=0 then bfs(i);
end.
图论问题奥妙无穷
无向图的传递闭包
图的最小生成树
单源最短路
拓扑序列
关键路径
四,图的应用
(一),拓扑排序
[士兵排队]
有n个士兵(<100),编号依次为1,2,3 ....n,排队训练,现在指挥官要把这些士兵从高到矮依次排成行,但现在指挥官不能直接获得每个人的身高信息,只能获得"i比j高"这样的比较结果输 入:第一行为一个整数n,表示士兵的个数,以下若干行,每行两个数 i,j:表示i比j高.
输出:一种合法的排队序列
样例:
输入:
4
1 2
2 3
4 3
输出:
1 2 4 3
建立有向图,如果i比j高,建立一条由i指向j的有向边.J的入度加1.先找一个入度为0的结点,即没有比他高的结点输出,去掉该结点,同时把比他矮的结点的入度减1.再找入度为0的结点,依次类推.直到最后输出完毕.
1
2
3
4
const maxn=100;{拓扑排序}
var map:array[1..maxn,1..maxn]of integer;{有向图}
into:array[1..maxn]of byte;{结点的入度}
a:array[1..maxn]of integer;{拓扑序列}
i,n:integer;
f:boolean;{找到拓扑序列的标志:什么情况下无拓扑序列 }
procedure init;{建有向图}
var i,j:integer;
begin
assign(input,'tp.in');
reset(input);
fillchar(map,sizeof(map),0);
fillchar(into,sizeof(into),0);
fillchar(a,sizeof(a),0);
readln(n);
while not eof do{文件结束标志}
begin
readln(i,j);{读入士兵的信息:i比j高}
map[i,j]:=1;
inc(into[j]){统计j的入度}
end;
close(input);
end;
procedure work;
var i,j,k,t:integer;
begin
t:=0;
for i:=1 to n do
begin
j:=1;
while(j<=n)and(into[j]0) do inc(j);{寻找入度为0点的结点}
if j>n then begin f:=false;exit;end {无入度为0的结点,问题无解}
else
begin
inc(t);
a[t]:=j;
into[j]:=$ff; {赋结点入度为较大的数}
for k:=1 to n do if map[j,k]=1 then dec(into[k]);{k的入度减1}
end;
end;
end;
begin
f:=true;
init;
work;
if f then
begin
for i:=1 to n-1 do write(a[i],' ');
writeln(a[n]);
end
else writeln('no answer');
end.
拓扑排序的其他应用
1
2
3
5
4
6
7
(二),哈密顿路
邮递员在送信时,为了节省路途,自己规定:每次总是从n个村子中选择其中一个合适的村子出发,途中每个村子仅且经过一次,送完所有的信.已知各个村子的道路连通情况.请你帮邮递员选择一条合适的路线.
输入:
第一行:整数n:村子的个数.
接下来是一个n*n的0,1 矩阵,表示n个村子的连同情况,如:a[I,j]=1 ,表示第i和第j个村子之间有路可走,如果a[I,j]=0,表示他们之间无路可走.
输出:一条可行的路线
输入:
7
0 1 0 1 1 0 0
1 0 1 0 1 0 0
0 1 0 0 0 0 1
1 0 0 0 0 0 0
1 1 0 0 0 1 0
0 0 0 0 1 0 1
0 0 1 0 0 1 0
输出:
2 3 7 6 5 1 4
哈密顿路:找一条包含所有结点的简单路径
program hmdlu(input,output);
const n=10;
var a:array[1..n,1..n]of integer;
visited:array[1..n]of 0..1;
b:array[1..n] of 1..n;{记录路径上的结点的下标}
t:array[1..n]of char;{结点信息}
m,i:integer;
found:0..1;
procedure dfs(i:integer);{搜索图中的第i个点}
var j:integer;
begin
visited[i]:=1;
m:=m+1;{路径上已有的结点个数}
b[m]:=i;{记录该结点下标}
if n=m then{找到一条包含所有结点的路径}
begin
found:=1;
writeln;
for j:=1 to n do write(t[b[j]]);
halt;
end;
for j:=1 to n do
if (a[j,i]=1) and (visited[j]=0) then dfs(j);
visited[i]:=0;{取消访问标志}
m:=m-1;{路径上的结点数减1,去掉i结点}
end;
Begin
assign(input,'in.txt');
reset(input);
init;
found:=0;
m:=0;
for i:= 1 to n do
begin fillchar(visited,sizeof(visited),0);dfs(i);end;
if found=0 then writeln('no roud');
close(input);
end.
__2 ,n个客人围着一个桌子吃饭,每一个人都至少认识其他的2个客人.请设计程序求得n个人的一种坐法,使得每个人都认识他左右的客人.
输入:第一行n:人的个数.接下来有n行数,每行至少有两个数,第i行表示第i个人认识的人的编号.
输出:一种坐法,第一个人为1号.
样例:
输入:5
2 3 5
1 4 5
1 4
1 2
1 2
输出:1 3 4 2 5
(三),哈密顿回路
1,设计程序将1...n(20)放入一个环中,使任意两个相邻的数的和为素数.
哈密顿回路算法:
const maxn=10;
var a:array[1..maxn,1..maxn]of integer; {保存图的 矩阵}
visited:array[1..maxn]of 0..1;{访问标志}
b:array[1..maxn] of 1..maxn;{保存回路的结点}
n,m,i:integer;
found:0..1;
建图 :相互认识的人之间建立一条边
procedure init;
var i, k:integer;
begin
readln(n);{人树}
for i:=1 to n do{读入信息}
begin
while not eoln do{逐行读入}
begin
read(k);
a[i,k]:=1;
a[k,i]:=1;
end;
readln;
end;
end;
procedure dfs(i:integer);{搜索图中的第i个点}
var j:integer;
begin
visited[i]:=1;
m:=m+1;{路径上已有的结点个数}
b[m]:=i;{记录该结点下标}
if (n=m)and(a[b[1],b[m]]=1) then{找到一条回路}
begin
found:=1;
for j:=1 to n -1 do write(b[j] ,' ');writeln(b[n]);
halt;
end;
for j:=1 to n do
if (a[j,i]=1) and (visited[j]=0) then dfs(j);
visited[i]:=0;{取消访问标志}
m:=m-1;{路径上的结点数减1,去掉i结点}
end;
主程序
Begin
assign(input,'in.txt');
reset(input);
init;
found:=0;
m:=0;
b[1]:=1;{取第1个人作为开始}
visited[1]:=1;
dfs(1);
if found=0 then writeln(' no roud');
close(input);
end.
四, 最短路径
(一)设有n个城市,依次编号为1,2.......n,另外有一个文件保存n个城市之间的距离.当两个城市的距离等于-1时,表示这两个城市没有直接连接.求指定城市k到m的最短距离和路线.
输入文件为:input.txt,输出文件:output.txt.
输入文件格式:第一行有三个数:n k m,以下是 矩阵,表示城市之间的距离.
输出:两行,第一行k到m的最短距离.第二行为从k到m的经过城市编号.
样例:
输入文件input.txt:
5__3 5
0 40 65 –1 30
40 0 105 –1 –1
65 105 0 10 35
-1 –1 10 0 20
30 –1 35 20 0
输出:
30
3 4 5
floyd算法(任意两点之间的最短距离)
cost:array[1..m,1..m] of longint;{图的 矩阵}
a:array[1..m,1..m]of integer;{保存两点之间的最短距离}
p:array[1..m,1..m]of 0..m; {最短路线的中间结点,p[I,j]=0 表示无路, p[I,j]=k:表示i到j经过k是会使路变的更短}
计算每一对顶点间 最短路径的方法如下:
初始时:a=cost,
枚举路径上的每一个中间顶点k(1≤k≤n);然后枚举每一个顶点对(顶点i和顶点j,1≤i,j≤n).如果i顶点和j顶点间有一条途径顶点k的路径,且该路径长度在目前i顶点和j顶点间的所有条途径中最短,则修改i和j之间的最短距离.
procedure readdata;
var i,j:integer;
begin

readln(n,k,m);
for i:=1 to n do
for j:=1 to n do
begin
read(cost[i,j]);
if (cost[i,j]=0)or(cost[i,j]=-1)
then begin
cost[i,j]:=maxint;
p[i,j]:=0;
end
a[i,j]:=cost[i,j];
end;
end;
procedure floyed;
var i,j,k:integer;
begin
for k:=1 to n do {依次枚举途中经过的点}
for i:=1 to n do {修改i和j点之间的距离}
for j:=1 to n do
begin
if a[i,k]+a[k,j]
最小更小,就修改}
begin
a[i,j]:=a[i,k]+a[k,j];
p[i,j]:=k;{记下当前点}
end;
end;
end;
procedure path(i,j:integer);{依次输出I,j 最短路径的中间结点}
var k:integer;
begin
k:=p[i,j];
if k0 then
begin
path(i,k);
write(k);
path(k,j);
end;
end;
BEGIN
assign(input,'fin.txt');
reset(input);
read(n,x,y);
readdata;
floyed;
writeln(a[x,y]);
if p[x,y]=0 then writeln('no round')
else
begin
write(x);
path(x,y);
write(y);
end;
close(input);
END.
(二)设有n个城市,依次编号为1,2.......n,另外有一个文件保存n个城市之间的距离.当两个城市的距离等于-1时,表示这两个城市没有直接连接.求指定城市k到m的最短距离和路线.
输入文件为:input.txt,输出文件:output.txt.
输入文件格式:第一行有三个数:n k ,以下是 矩阵,表示城市之间的距离.
输出:一行,结点k到其他结点的最短距离.样例:
输入文件input.txt:
5__3
0 40 65 –1 30
40 0 105 –1 –1
65 105 0 10 35
-1 –1 10 0 20
30 –1 35 20 0
方法二:Dijkstra算法(单源 最短路径算法)
var cost:array[1..m,1..m] of longint;{图中两点距离}
a:array[1..m]of integer;{到源点的最短距离}
n,k,i,j:integer;
mark:array[1..m] of boolean;{分组标记}
方法:把结点分为两组:
第一组:一开始是原点k.
第二组:为确定踞k 最短路径的点.
按最短距离从小到大依次加到第一组,每加一个点j,都要重新修改第二组的最小距离,看是否经过j点,距离减小了.
{读入信息}
procedure readdata;
var i,j:integer;
begin
readln(n,k);
for i:=1 to n do
for j:=1 to n do
read(cost[i,j]);
end;
procedure dijkstra;
var i,j,min,minj,temp:integer;
begin
fillchar(mark,sizeof(mark),false);
for i:=1 to n do a[i]:=maxint;{各点初始化最大距离}
a[k]:=0;{到本身距离为0}
for i:=1 to n-1 do {枚举除原点外的其他n-1个点}
begin
min:=maxint;
for j:=1 to n do {找距k最小的点}
if (not mark[j]) and (a[j]0) then
begin
temp:=a[minj]+cost[minj,j];
if temp end;
end;
end;
BEGIN{主程序}
readdata;
dijkstra;
for i:=1 to n-1 do write(a[i],' ');
writeln(a[n]);
END.
请同学们考虑:
1,如何求第2 最短路径
2,如何求第3 最短路径
五,欧拉回路
排列方案
将2n个0和2n个1排成一圈.从任一位置开始,每次按逆时针方向以长度为n+1的单位数二进制数.要求给出一种排法,用上面方法产生出的2n+1个二进制数互不相同.当n=2时,有4个0和4个1,排列如下图:
从位置a开始,逆时针方向取三个数000;然后从位置b开始取三个数001,接着取010,101,011,111,110,100,共8个互不相同的二进制数.
【输入】n (1≤n≤100);
【输出】2n+1位二进制数(排列方案不唯一,但一定能组成2n+1个互不相同的n+1位二进制数)
算法分析
以n位二进制数排列之间的重叠部分为点,以01排列为边,构成有向图.例如n=2
从该图中找一条欧拉回路(不重复地遍历图中的每一条边),边上的01序列构成问题的解.设vt[i]—十进制数i的访问标志(0≤i≤2n-1).
首先vt清零,然后从i=0开始,按照上述规则计算,直至vt[k]=2为止.
var
n,i:integer;
mx,nw:longint; {mx=2n;nw为n位二进制数对应的十进制数}
vt:array[0..10500] of byte; {访问序列}
begin
readln(n); {输入0和1的个数}
mx:=1; {mx=2n }
for i:=1 to n do mx:=mx*2;
nw:=0;i:=0; {从0开始访问}
while vt[nw]<2 do {若十进制数nw 未访问,则增加一条边}
begin
inc(vt[nw]);
if vt[nw]=1 {增加一条权为1的边}
then begin write(1); nw:=(nw*2+1)mod mx; end
else begin write(0); nw:=(nw*2)mod mx; end; {增加一条权为0的边}
i:=(i+1)mod 70; {每行01数的上限为70}
if i=0 then writeln;
end;{while}
end.{main}
六,计算无向图的传递闭包
无向图的传递闭包主要用于计算图的连通性和图中满足条件的连通分支.借鉴传递闭包的思想,可计算每一对顶点间的 最短路径.
⑴判别任两个顶点之间是否有路
【例题】计算连通性问题
输入一张无向图,指出该图中哪些顶点对之间有路.
输入:
n (顶点数,1≤n≤20)
e (边数1≤e≤210)
以下e行,每行为有边连接的一对顶点
输出:
k行,每行两个数,为存在通路的顶点对序号i,j(i
var
link,longlink:array[1..20,1..20] of boolean;{ 无向图和无向图的传递闭包.其中 }
我 们递推产生longlink(0),longlink(1),…longlink(n).在递推过程中,路径长度的'+'运算和比较大小的运算用相应的逻 辑运算符'and'和'or'代替.对于i,j和k=1¨n,如果图中结点i至结点j间存在通路且通路上所有结点的序号均属于{1¨k},则定义 longlinkij(k)=1;否则longlinkij(k)=0.
longlinkij(k)=longlinkij(k-1)or(longlinkik(k-1)andlonglinkkj(k-1))
传递闭包longlink的计算过程如下:
longlink←link;
for k←1 to n do {枚举中间顶点}
for i←1 to n do{枚举所有顶点对}
for j←1 to n do {计算顶点i和顶点j的连通情况}
longlink[i,j]←longlink[i,j]or(longlink[i,k]andlonglink[k,j]);
主程序
fillchar(longlink,sizeof(longlink),0);{传递闭包初始化}
fillchar(link,sizeof(link),0); {无向图初始化}
readln(n);readln(e); {读顶点数和边数}
for k←1 to e do {输入无向图的信息}
begin
readln(i,j);link[i,j]←true;link[j,i]←true;
end;{for}
计算传递闭包longlink;
for i←1 to n-1 do {输出所有存在通路的顶点对}
for j←i+1 to n do if longlink[i,j] then 输出i和j;
(2)计算有向无圈图的根
输入一个有向无圈图DAG,计算和输出DAG的根r(即r到其他任何顶点都有一条路.若图中没有根,则输出"not found").
输入:
顶点数n和边数e
以下为e行,每行为一条有向边的两个端点
输出:
根r或者"not found"
算法分析

const mx=100;{顶点数的上限}
var
n,e,i,j,k,a,b:integer;{ 顶点数n,边数e}
g:array[1..mx,1..mx]of boolean;{传递闭包}
bn:boolean;{根存在标志}
1,输入信息,计算传递闭包
readln(f,n,e);{输入顶点数和边数}
fillchar(g,sizeof(g),0);{ 有向无圈图初始化}
for i:=1 to e do{输入信息,构造传递闭包的初始值}
begin readln(f,a,b);g[a,b]:=true end;
for k:=1 to n do{计算传递闭包}
for i:=1 to n do
for j:=1 to n do
if g[i,j] or g[i,k] and g[k,j]then g[i,j]:=true;
2,计算DAG的根
然后枚举每一个顶点.根据传递闭包的信息,若当前顶点至其它所有顶点有路,则判定该顶点即为根.若没有一个顶点可作为DAG的根,则输出失败信息
for i:=1 to n do{枚举每一个可能的根}
begin
bn:=true;{设定I至其他所有顶点有路的标志}
for j:=1 to n do{若I至任一个顶点无路,则返回bn为false}
if (ij) and not g[i,j]
then begin bn:=false; break end;
if bn then break{若I至其他所有顶点有路,则输出根i}
end;
if bn then writeln(i)
else writeln('not found'){若不存在根,则输出失败信息}
_
(3)寻找满足条件的连通分支
在一张顶点带权的无向图中,计算含顶点数最多的一个连通分支和顶点的权和最大的连通分支
输入:
n (顶点数,1≤n≤20)
以下n行,其中第i行为顶点i的权
e (边数1≤e≤210)
以下e行,每行为有边连接的一对顶点
输出:
含顶点数最多的一个连通分支
顶点的权和最大的连通分支
通过longlink计算出每个顶点所在的连通分支,然后在所有可能的连通分支中找出满足条件的解.至于计算连通分支的顶点方案也不难,只要分别从连通分支中任选一个代表顶点,由此出发,通过深度优先搜索即可得到顶点方案.设
best,besti—含顶点数最多的连通分支中的顶点数和代表顶点;
max,maxk——顶点的权和最大的连通分支的顶点权和和代表顶点
计算best,besti和max,maxk的过程如下:
输入顶点带权的无向图的信息,计算传递闭包longlink;
for i←1 to n do {枚举每一个顶点}
begin
k←0;s←0;
for j←1 to n do {计算顶点i所在连通分支中的顶点总数k和顶点的权和s}
if longlink[i,j] then
begin inc(k);inc(s,顶点j的权);end;
if k>best{若k为目前最大,则记入best,i作为代表顶点记入besti}
then begin best←k;besti←i;end;
if s>max{若s为目前最大,则记入max,i作为代表顶点记入maxk}
then begin max←s;maxk←i;end;
if k=n then break;{若整个图为连通图,则退出}
end;{for}
dfs(besti);{从顶点besti出发,深度优先搜索含顶点数最多的连通分支}
dfs(maxk);{从顶点maxki出发,深度优先搜索顶点的权和最大的连通分支}
(4)计算每一对顶点间的 最短路径(floyd算法)
_ 现有一张城市地图,图中的顶点为城市,有向边代表两个城市间的连通关系,边上的权即为距离.现在的问题是,为每一对可达的城市间设计一条公共汽车线路,要求线路的长度在所有可能的方案里是最短的.
输入:
n (城市数,1≤n≤20)
e (有向边数1≤e≤210)
以下e行,每行为边(i,j)和该边的距离wij(1≤i,j≤n)
输出:
k行,每行为一条公共汽车线路
在枚举途径某中间顶点k的任两个顶点对i和j时,将顶点i和顶点j中间加入顶点k后是否连通的判断,改为顶点i途径顶点k至顶点j的路径是否为顶点i至顶点j的 最短路径(1≤i,j,k≤n).
显然三重循环即可计算出任一对顶点间的 最短路径.设
n—有向图的结点个数;
path— 最短路径集合.其中path[i,j]为vi至vj的最短路上vj的 前趋结点序号(1≤i,j≤n);
adj— 最短路径 矩阵.初始时为有向图的相邻 矩阵
用类似传递闭包的计算方法反复对adj 矩阵进行运算,最后使得adj成为存储每一对顶点间的 最短路径矩阵
Var
adj:array[1¨n,1¨n] of real;
path:array[1¨n,1¨n] of 0¨n;
计算每一对顶点间 最短路径的方法如下:
adj 矩阵的每一个元素初始化为∞;
for i←1 to n do{初始时adj为有向图的相邻 矩阵,path存储边信息}
for j←1 to n do
if wij0 then begin adj[i,j]←wij;path[i,j]←i;end{then}
else path[i,j]←0;
for k←1 to n do {枚举每一个中间顶点}
for i←1 to n do {枚举每一个顶点对}
for j←1 to n do
if adj[i,k]+adj[k,j]
then begin
adj[i,j]←adj[i,k]+adj[k,j];path[i,j]←path[k,j];
end,{then}
矩阵path可推知任一结点对i,j之间的 最短路径方案
Procedure print(i,j);
begin
if i=j then 输出i
else if path[i,j]=0
then 输出结点i与结点j之间不存在通路
else begin
print(i,path[i,j]);{递归i顶点至j顶点的 前趋顶点间的 最短路径}
输出j;
end;{else}
end;{print}
由此得出主程序
距离 矩阵w初始化为0;
输入城市地图信息(顶点数,边数和距离 矩阵w);
计算每一对顶点间 最短路径矩阵path;
for i←1 to n do {枚举每一个顶点对}
for j←1 to n do
if path[i,j]0{若顶点i可达顶点j,则输出 最短路径方案}
then begin print(i,j);writeln;end;{then}

(5)计算DAG中的最长路
输入一个有向无圈图DAG,计算和输出DAG中最长路的长度
输入:
顶点数n和边数e
以下为e行,每行为一条有向边的两个端点和边权
输出:
最长路的长度
算法分析

const
max=100;{顶点数的上限}
maxint=10000;
var
n,e,i,j,k,a,b,ans:integer;{顶点数n,边数e }
g:array[1..max,1..max]of integer;{g[I,j]为顶点I至顶点j的路径长度}
1,输入有向无圈图的信息
首先输入有向无圈图的信息.若顶点I至顶点j有边,则g[I,j]设为wij;否则g[I,j]设为-∞.
readln(f,n,e);{输入输入顶点数和边数}
fillchar(g,sizeof(g),0);{最长路径长度 矩阵初始化
for i:=1 to e do{输入边信息,构造最长路径长度 矩阵的初始值}
begin readln(f,a,b, wab); g[a,b]:= wab end;
for i:=1 to n do{将最长路径长度 矩阵中无边的元素设为-∞}
for j:=1 to n do
if (ij)and(g[i,j]=0) then g[i,j]:=-maxint;
2,按照计算传递闭包的思想计算每一对顶点间的最长路,找出最长路径长度
for k:=1 to n do{计算最长路径长度 矩阵}
for i:=1 to n do
for j:=1 to n do
if g[i,k]+g[k,j]>g[i,j]
then g[i,j]:=g[i,k]+g[k,j];
ans:=0;{ 最长路径长度初始化}
for i:=1 to n do{枚举所有可能的顶点对,将其中的最大g值找出来}
for j:=1 to n do
if g[i,j]>ans then ans:=g[i,j];{调整最长路径长度}
writeln(ans){输出最长路径长度}
(6),计算带权有向图的中心
输入一个带权有向图G,计算和输出G的中心v(v是G的一个顶点,v的偏心距定义为
输入:
顶点数n和边数e
以下为e行,每行为一条有向边的两个端点和边权
输出:
G的中心v
算法分析
const mx=100;
var
n,e,i,j,k,a,b,ans:integer;{ 顶点数为n,边数为e,中心为ans}
w,bt,b0:real;{bt为所有顶点偏心距的最小值,b0为当前顶点的偏心距}
g:array[1..mx,1..mx]of real;{最短路长 矩阵.初始时
g[i,i]=0,g[I,j]=
计算后,g[i,j]存储顶点I至顶点j的最短路长}
1,输入信息,构造最短路长 矩阵g
readln(f,n,e);{输入顶点数和边数}
for i:=1 to n do{输入信息,构造最短路长 矩阵g的初始值}
for j:=1 to n do
if ij then g[i,j]:=maxint
else g[i,j]:=0;
for i:=1 to e do
begin readln(f,a,b,w);g[a,b]:=w;end;
for k:=1 to n do{计算任一对顶点间的最短路长}
for i:=1 to n do
for j:=1 to n do
ifg[i,k]+g[k,j]b0 then b0:=g[i,j];{计算顶点I的偏心距}
if b0
then begin bt:=b0; ans:=i end
end;
writeln(ans){输出图的中心}
_
(7),图的最小生成树(prim算法)
对于一张图进行深度优先搜索或宽度优先搜索,可生成一棵深度优先搜索树或宽度优先搜索树.搜索的出发点不同,生成树的形态亦不同.在一张有权连通图中,如何寻找一棵各边权的总和为最小的生成树,就是本章节所要讨论的问题.
现有一张城市地图,图中的顶点为城市,无向边代表两个城市间的连通关系,边上的权为公路造价.在分析了这张图后可以发现,任一对城市都是连通的.现在的问题是,要用公路把所有城市联系起来,如何设计可使得工程的总造价最少.
输入:
n (城市数,1≤n≤20)
e (有向边数1≤e≤210)
以下e行,每行为边(i,j)和该边的距离wij(1≤i,j≤n)
输出:
n-1行,每行为两个城市的序号,表明这两个城市间建一条公共汽车线路.

W—带权连通图的相邻 矩阵.
t—存储相邻 矩阵W的下三角元素.其中t[ +j]存储W[i,j](j≤i).t数组的长度为 .在计算过程中若vi在生成树中,则t[ ]=1;若(i,j)边在生成树,则t[ +j]取负.
min—当前待扩展边的最小权.
我 们从任意结点开始(不妨设为vn)构造最小生成树:首先把这个结点包括进生成树里,然后在那些其一个端点已在生成树里,另一端点还未在生成树里的所有边中 找出权最小的一条边,并把这条边,包括不在生成树的另一端点包括进生成树,….依次类推,直至将所有结点都包括进生成树为止.这种尽量找最小权的边扩展的 策略即为贪心法.
readln(n); {输入城市数}
for i←1 to do t[i]←∞ ;
for i←1 to n do t[ ]←0;{所有结点未包括进生成树}
for i←1 to 边数e do {输入相邻 矩阵的下三角元素}
begin
读第i条边和该边的权wxy;
if x>y then t[ ]←wxy
else t[ ]←wxy;
end{for}
t[ ]←1; {vn进入最小生成树}
for k←2 to n do {顺序构造最小生成树的n-1条边}
begin
min←∞;
for i←1 to n do {搜索那些其一个端点已在生成树里,另一端点还未在生成树里的边}
if t[ ]=1 {若vi在生成树中}
then begin
for j←1 to n do

if t[ ]=0 {若vj不在生成树中}
then begin
if j
else l← +i;
if t[l] begin min←t[l];p←l;q←j;end;{then}
end;{then}
end;{then}
t[ ]←1;t[p]←-t[p];{vq和相连的权最小的边el加入最小生成树}
end;{for}
for i←2 to n do
for j←1 to i-1 do
if t[ +j ]0 then cp:=1 else cp:=-1;
end;{cp}
function dist(a,b:integer):longint;{ 计算第a条机器蛇和第b条机器蛇间的距离,若ab之间有屏蔽,则距离设为无穷大 }
var
i:integer;
begin
dist:=oo;
for i:=1 to m do { 如果a到b穿过第i个屏蔽,则返回无穷大 }
if (cp(w1[i],w2[i],s[a])*cp(w1[i],w2[i],s[b])=-1) and
(cp(s[a],s[b],w1[i])*cp(s[a],s[b],w2[i])=-1) then exit;
dist:=sqr(s[a].x-s[b].x)+sqr(s[a].y-s[b].y);
end;{ dist }
begin
read(n);{ 读入数据 }
for i:=1 to n do with s[i] do read(x,y);
read(m);
for i:=1 to m do read(w1[i].x,w1[i].y,w2[i].x,w2[i].y);
{用Prim算法求最小生成树 }
fillchar(ba,sizeof(ba),0); {所有机器蛇未访问}
for i:=2 to n do d[i]:=oo; {最短边长序列初始化}
d[1]:=0 ;ans:=0; {从机器蛇1出发,通信网的最短长度初始化}
for i:=1 to n do begin {访问n条机器蛇}
min:=oo;{在所有未访问的机器蛇中寻找与已访问的机器蛇相连且具有最短边长的机器蛇k}
for j:=1 to n do if not ba[j] and (d[j]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值