一、基本概念
连通的无圈图叫做树,记之为T ;其度为1的顶点称为叶子顶点。若图G=(V(G),E(G))和树T=(V(T),E(T)) 满足V(G) =V(T ) , E(T ) ⊂ E(G) , 则称T 是G 的生成树。图G 连通的充分必要条件为G 有生成树。一个连通图的生成树 的个数很多,用τ (G) 表示G 的生成树的个数,则有公式为:
常用的五个充要条件:
定理
(i)G=(V(G),E(G))是树当且仅当G 中任意两顶点之间有且仅有一条轨道。
(ii)G 是树当且仅当G 无圈,且|E| =|V| −1。
(iii)G 是树当且仅当G 连通,且|E| =|V| −1。
(iv)G 是树当且仅当G 连通,且∀e∈ E(G) ,G − e 不连通。
(v)G 是树当且仅当G 无圈,∀e∉ E(G) ,G + e 恰有一个圈。
二、最小生成树
问题: 欲修筑连接 n 个城市的铁路,已知i 城与 j 城之间的铁路造价为Cij ,设计一个线 路图,使总造价最低。
上述问题是在连通赋权图上求权最小的生成树。赋权图的具有最小权的生成树叫做最小生成树。
下面介绍构造最小生成树的两种常用算法。
1. prim 算法构造最小生成树
设置两个集合 P 和Q ,其中 P 用于存放G 的最小生成树中的顶点,集合Q 存放G的最小生成树中的边。令集合 P 的初值为 { P = v1 }(假设构造最小生成树时,从顶点 v1 出发),集合Q 的初值为Q = Φ 。
算法思想:从所有 p ∈ P ,v ∈V − P 的边中,选取具有最小权值的边 pv ,将顶点 v 加入集合 P 中,将边 pv 加入集合Q 中,如此不断重复,直到 P =V 时,最小生成树构造完毕,这时集合Q 中包含了最小生成树的所有边。
- 例题1
prim 算法求图 4.4 的最小生成树。 我们用
的第一、二、三行分别表示生成树边的起点、终点、权集合。
代码如下:
clc,clear
a=zeros(7);
% 矩阵的输入
a(1,2)=50; a(1,3)=60;
a(2,4)=65; a(2,5)=40;
a(3,4)=52;a(3,7)=45;
a(4,5)=50; a(4,6)=30;a(4,7)=42;
a(5,6)=70;
a=a+a';a(find(a==0))=inf;
result=[];p=1;tb=2:length(a); % 初始化变量
while length(result)~=length(a)-1 % 当检查完倒数第二列非零最小值后退出
temp=a(p,tb); % 将a中的p行tb列的元素赋给temp
temp=temp(:); % 使temp中元素变成一列
d=min(temp); % 找temp中最小的元素
[jb,kb]=find(a(p,tb)==d); % 找出最小元素在a的位置
j=p(jb(1));k=tb(kb(1)); % 行和列中有多个最小元素取第一个
result=[result,[j;k;d]]; % 将这个最小元的行位置j,列位置k,以及值d写入result
% 进行下次操作
p=[p,k];
tb(find(tb==k))=[]; % 去掉已经选出来的最小元的列
end
result
输出为:
result =
1 2 5 4 4 7
2 5 4 6 7 3
50 40 50 30 42 45
2.Kruskal 算法构造最小生成树
Kruskal 算法如下:
- 用Kruskal 算法构造上述例题1的最小生成树
用index(2xn)存放各边端点的信息,当选中某一边之后,就将此边对应的顶点序号中较大序号u 改记为此边的另一序号v ,同时把后面边中所有序号为u 的改记为v 。
此方法的几何意义是:将序号u 的这个顶点收缩到v 顶点,u 顶点不复存在。后面继续 寻查时,发现某边的两个顶点序号相同时,认为已被收缩掉,失去了被选取的资格。
代码如下:
% 核心算法就是通过边的权值从小到大排序然后去除环路来生成最小生成树
% result中的一、二、三行分别表示生成树边的起点、终点、权集合。
clc,clear
a(1,2)=50; a(1,3)=60; a(2,4)=65; a(2,5)=40;
a(3,4)=52;a(3,7)=45; a(4,5)=50; a(4,6)=30;
a(4,7)=42; a(5,6)=70;
[i,j,b]=find(a);
data=[i';j';b'];index=data(1:2,:);
loop=max(size(a))-1; % 等价于loop=length(a)-1
result=[];
while length(result)<loop % 当矩阵result长度小于length(a)-1时执行下列操作
temp=min(data(3,:)); % temp赋予最小权值
flag=find(data(3,:)==temp); % 找到最小权值
flag=flag(1);% 找到一个即可
v1=data(1,flag); % v1记录的是边的起点
v2=data(2,flag); % v2记录的是边的终点
if index(1,flag)~=index(2,flag) % 防止出现环的结构
result=[result,data(:,flag)]; % 给矩阵赋值
end
index(find(index==v2))=v1; % 当起点等于终点
data(:,flag)=[];
index(:,flag)=[]; % 将已经找到的边从中删除
end
输出为:
result =
4 2 4 3 1 4
6 5 7 7 2 5
30 40 42 45 50 50
总结
Prim算法和Kruskal算法的区别:
(i) Prim算法是直接查找,多次寻找邻边的权重最小值,而Kruskal是需要先对权重排序后查找的。
(ii) Kruskal只需一次对权重的排序就能找到最小生成树,而Prim算法需要多次对邻边排序才能找到~
(ii) 两者实现过程也不同:
Prim算法的实现过程:
首先以一个结点作为最小生成树的初始结点,然后以迭代的方式找出最小生成树中各结点权重最小的边,并加到最小生成树中。(加入之后如果产生回路了就要跳过这条边,选择下一个结点)当所有的结点都加入到最小生成树中后,就找出了这个连通图的最小生成树。
Kruskal算法的实现过程:
Kruskal算法在找最小生成树结点之前,需要对权重从小到大进行排序。将排序好的权重边依次加入到最小生成树中(如果加入时产生回路就跳过这条边,加入下一条边),当所有的结点都加入到最小生成树中后,就找到了这个连通图的最小生成树~