This slide is based on the book:
赵静、但琦 主编《数学建模与数学实验》(第4版)
有序三元组 $G=(V,E,\Psi)$ 称为一个图, 其中
在图 $G$ 中,
若将图 $G$ 的每一条边 $e$ 都对应一个实数 $w(e)$, 则称 $w(e)$ 为该边的
记 $V(G)$ 为图 $G$ 的顶点集, $E(G)$ 为图 $G$ 的边集. 记号 $\nu=|V(G)|$ 和 $\varepsilon=\varepsilon(G)=|E(G)|$ 分别表示图 $G$ 的顶点数和边数.
在无向图中, 与顶点 $v$ 关联的边的数目(环算两次)称为 $v$ 的
在有向图中, 从顶点 $v$ 引出的边的数目称为 $v$ 的
定理. 图 $G$ 的所有顶点的度数之和是所有顶点个数的两倍. \[ \sum_{v\in V(G)}d(v)=2\varepsilon(G). \]
证明. 每增加一条边, 顶点的个数增加2. 因此所有顶点的度数之和是所有顶点个数的两倍.
推论. 任何图中奇次顶点的总数必为偶数.
证明. 设图 $G$ 中奇次顶点分别为 $v_{i_1},v_{i_2},\ldots,v_{i_k}$. 它们的度(或次数)分别为 $d_{i_1},d_{i_2},\ldots,d_{i_k}$, 这些数均为奇数. 由于 \[ d_{i_1}+d_{i_2}+\ldots+d_{i_k}+\text{剩余偶次顶点的次数之和}=2\varepsilon(G) \] 故 $k$ 是偶数.
定义. 设图 $G=(V,E,\Psi)$, $G_1=(V_1,E_1,\Psi_1)$,
https://en.wikipedia.org/wiki/Glossary_of_graph_theory_terms#subgraph
关联矩阵描述的是顶点与边的关系.
对无向图 $G$, 其关联矩阵 $M=(m_{ij})_{\nu\times\epsilon}$, 其中 \[ m_{ij}=\begin{cases} 1, & \text{若}\ v_i \text{与}\ e_j\ \text{相关联}\\ 0, & \text{若}\ v_i \text{与}\ e_j\ \text{不关联}\\ \end{cases} \]
对有向图 $G$, 其关联矩阵 $M=(m_{ij})_{\nu\times\epsilon}$, 其中 \[ m_{ij}=\begin{cases} 1, & \text{若}\ v_i \text{是}\ e_j\ \text{的起点}\\ -1, & \text{若}\ v_i \text{是}\ e_j\ \text{的终点}\\ 0, & \text{若}\ v_i \text{与}\ e_j\ \text{不关联}\\ \end{cases} \]
邻接矩阵描述的是顶点与顶点之间是否相连的关系.
对无向图 $G$, 其邻接矩阵 $A=(a_{ij})_{\nu\times\nu}$, 其中 \[ a_{ij}=\begin{cases} 1, & \text{若}\ v_i \text{与}\ v_j\ \text{相邻}\\ 0, & \text{若}\ v_i \text{与}\ v_j\ \text{不相邻}\\ \end{cases} \]
对有向图 $G=(V,E)$, 其邻接矩阵 $A=(a_{ij})_{\nu\times\nu}$, 其中 \[ a_{ij}=\begin{cases} 1, & \text{若}\ (v_i,v_j)\in E\\ 0, & \text{若}\ (v_i,v_j)\not\in E\\ \end{cases} \]
对有向赋权图 $G$, 其邻接矩阵 $A=(a_{ij})_{\nu\times\nu}$, 其中 \[ a_{ij}=\begin{cases} w_{ij}, & \text{若}\ e_{ij}=(v_i,v_j)\in E\ \text{且}\ w_{ij}\ \text{为边}\ e_{ij}\ \text{的权}\\ 0, & \text{若}\ i=j\\ \infty, & \text{若}\ (v_i,v_j)\not\in E \end{cases} \]
[Def]关于图(graph)的基本概念---路径(path),道路(trail),通路(walk)
所谓的路径(path)是指一个图 $P$, 满足
\[ V(P)=\{x_0,x_1,\ldots,x_{\ell}\},\quad E(P)=\{x_0x_1, x_1x_2,\ldots,x_{\ell-1}x_{\ell}\}. \]
这里 $x_i\neq x_j$, $\forall\ i\neq j$.
这条路径 $P$ 通常记为 $x_0x_1x_2\ldots x_{\ell}$. 顶点 $x_0$ 和 $x_{\ell}$ 是路径 $P$ 的两个端点. 而 $\ell=e(P)$ 是路径 $P$ 的长度.
我们称 $P$ 是从 $x_0$ 到 $x_{\ell}$ 的一条路径(path), 或简记为 $x_0-x_{\ell}$ path. 当然 $P$ 也是一条从 $x_{\ell}$ 到 $x_0$ 的路径, 或简记为 $x_{\ell}-x_0$ path.
有时, 我们将强调 $P$ 是从 $x_0$ 到 $x_{\ell}$ 的, 并且称 $x_0$ 为起点(或始点)(initial vertex), $x_{\ell}$ 为终点(terminal vertex).
以 $x$ 为起点的道路称为 $x$-path.
大多数路径可以视为某给定图 $G$ 的一个子图.
图 $G$ 的一条
如果通路(walk)中限定边不能重复, 顶点可以重复, 则称这条通路为
因此, 所谓的路径, 实际上就是顶点和边均不重复的通路.
若道路(trail)的两端点重合(也即是一条闭合道路 a closed trail),则称其为一个
设 $W=x_0x_1\ldots x_{\ell}$ 是一条通路(walk), 满足 $\ell\geqslant 3$, $x_0=x_{\ell}$. 并且诸 $x_i(0 < i < \ell)$ 互不相同, 也不同于 $x_0$, 则称 $W$ 是一个 圈(cycle).
为简单起见, 这个 cycle 记为 $x_1x_2\ldots x_{\ell}$. 注意到这里的记号不同于之前的, 这里 $x_1x_{\ell}$ 也是该 cycle 的一条边. 进一步的, $x_1x_2\ldots x_{\ell}$, $x_{\ell}x_{\ell-1}\ldots x_1$, $x_2x_3\ldots x_{\ell}x_1$, $x_ix_{i-1}\ldots x_1x_{\ell}x_{\ell-1}\ldots x_{i+1}$ 都指的是同一个 cycle.
记号: 符号 涵义 $P^{\ell}$ 长度为 $\ell$ 的一条路径(path) $C^{\ell}$ 长度为 $\ell$ 的一个圈 (cycle)
我们称 $C^3$ 是一个三角形(triangle), $C^4$ 是一个四边形(quadrilateral), $C^5$ 是一个五边形(pentagon), 等等.
一个 cycle 被称为是 even (或 odd) 的, 如果它的长度是 even (或 odd) 的.
给定顶点 $x,y$, 它们之间的距离 $d(x,y)$ 指连接这两点的所有路径中长度最短的那条路径的长度.
\[ d(x,y)=\min\{\text{length of } x-y\ \text{path}\} \]
若 $x,y$ 之间不存在 $x-y$ path, 则令 $d(x,y)=\infty$.
一个图称为是
设无向图 $G(V,E)$ 是连通的, 设边集 $E_1\subset E$. 在图 $G$ 中删除 $E_1$ 中所有的边后得到的子图是不连通的, 而删除了 $E_1$ 的任一真子集后得到的子图是连通图, 则称 $E_1$ 是 $G$ 的一个割边集.
若割边集仅由一条边组成, 则称这条边为
References:
Béla Bollobas, Graph Theory, An introductory course. GTM 63.
参见 http://www.atzjg.net/admin/do/view_question.php?qid=2106
% Page 90. % filename: Dijkstra.m % based on the file: road1.m on page 90. % by haifeng % Date: 2018-04 w=[ 0 2 1 8 inf inf inf inf; 2 0 inf 6 1 inf inf inf; 1 inf 0 7 inf inf 9 inf; 8 6 7 0 5 1 2 inf; inf 1 inf 5 0 3 inf 9 ; inf inf inf 1 3 0 4 6 ; inf inf 9 2 inf 4 0 3 ; inf inf inf inf 9 6 3 0 ]; n=size(w,2); % w 的列数 l=w(1,:); z=ones(1,n); % l = 0 2 1 8 Inf Inf Inf Inf % z = 1 1 1 1 1 1 1 1 % s 向量中存放的是图的各顶点标号. 注意 Matlab 中从1开始. s=[]; s(1)=1; % s(1)=1 是顶点1. u=s(1); % 第一个顶点通常记为 u_0, 即 u_0=s(1), k=1 l z u '==================' index_of_minValue=2;% 定义变量index_of_minValue(书中的 v), 循环中要使用. % 注意: 不能初始化 v=0; 否则后面的 s(k+1 )=v; k=k+1; u=s(k); 会导致 u=0; 从而在第二次循环中 % 使得 if l(i)>l(u)+w(u,i) 语句中的 l(u) 变为 l(0). Matlab 会出错: % 提示: 下标索引必须为正整数类型或逻辑类型。 while k < n for i=1:n if l(i) > l(u)+w(u,i) l(i)=l(u)+w(u,i); z(i)=u; end end ll=l; for i=1:n for j=1:k if i==s(j) ll(i)=inf; end end end minValue=inf; for i=1:n if ll(i) < minValue minValue=ll(i); index_of_minValue=i; end end s(k+1)=index_of_minValue; k=k+1; u=s(k); end l z
Floyd算法是求每对顶点之间最短路径的算法.
直接在图的带权邻接矩阵中用插入顶点的方法依次构造出 $\nu$ 个矩阵 $D^{(1)},D^{(2)},\ldots,D^{(\nu)}$, 使最后得到的矩阵 $D^{(\nu)}$ 称为图的
把带权邻接矩阵 $W$ 作为距离矩阵的初值, 即 $D^{(0)}=(d_{ij}^{(0)})_{\nu\times\nu}=W$.
在建立距离矩阵的同时可建立路径矩阵 $R=(r_{ij})_{\nu\times\nu}$, 这里 $r_{ij}$ 的含义是从 $v_i$ 到 $v_j$ 的最短路径要经过点 $r_{ij}$.
\[ R^{(0)}=(r_{ij}^{(0)})_{\nu\times\nu},\quad r_{ij}^{(0)}=j. \]
每求得一个 $D^{(k)}$ 时, 按下列方式产生相应的新的 $R^{(k)}$: \[ r_{ij}^{(k)}=\begin{cases} k,&\text{若}\ d_{ij}^{(k-1)}>d_{ik}^{(k-1)}+d_{kj}^{(k-1)},\\ r_{ij}^{(k-1)},&\text{否则}. \end{cases} \]
当 $d_{ij}^{(k-1)}>d_{ik}^{(k-1)}+d_{kj}^{(k-1)}$ 时, 显然经过 $k$ 点会缩短距离. 否则维持现状即可.
即当 $v_k$ 被插入任何两点间的最短路径时, 被记录在 $R^{(k)}$ 中, 依次求 $D^{(\nu)}$ 时求得 $R^{(\nu)}$, 可由 $R^{(\nu)}$ 来查找任何点对之间的最短路径.
若 $r_{ij}^{\nu}=p_1$, 则点 $p_1$ 是点 $i$ 到点 $j$ 的最短路径的中间点, 然后用同样的方法再分头查找, 即朝着上游、下游分头追溯. 若
则由点 $i$ 到点 $j$ 的最短路径为: \[ i,p_k,p_{k-1},\ldots,p_2,p_1,q_1,q_2,\ldots,q_m,j. \]
$D(i,j)$: 点 $i$ 到点 $j$ 的距离.
$R(i,j)$: $i$ 到 $j$ 之间的插入点.
输入带权邻接矩阵 $W$.
% page 93. % filename: floyd.m function [D,R] = floyd(A) n=size(A,1); % n 等于矩阵 A 的行数. D=A for i=1:n for j=1:n R(i,j)=j; end end R for k=1:n for i=1:n for j=1:n if D(i,k)+D(k,j) < D(i,j) D(i,j)=D(i,k)+D(k,j); R(i,j)=R(i,k); end end end k D R '-----------------------' end
---Matrix D : --------- 0 7 5 3 9 7 0 2 4 6 5 2 0 2 4 3 4 2 0 6 9 6 4 6 0 ---Matrix R : --------- 1 4 4 4 4 3 2 3 3 3 4 2 3 4 5 1 3 3 4 3 3 3 3 3 5 ----------------
关于匹配的一般性质对于二元图自然也成立. 但二元图的匹配还有其自身的一些重要性质.
设 $G=(X,Y,E)$ 是二元图, $M$ 是图 $G$ 的一个匹配, $K$ 是图 $G$ 的一个覆盖. 则 $M,K$ 分别是图 $G$ 的最大匹配、最小覆盖的充要条件是: $|M|=|K|$.
对二元图 $G=(X,Y,E)$, 有
(1) 设 $M$ 是 $G$ 中饱和 $X$ 中每个顶点的匹配. $G$ 是二元图, 因此 $X$ 中的顶点互不相邻. 又 $M$ 是匹配, 所以 $M$ 中的边互不相邻. 于是任取 $S\subset X$, 对任意 $u\in S$, 通过 $M$, 存在 $v\in Y$ 与之相邻. 而这些 $v\in N(S)$. 因此 $|N(S)|\geqslant|S|$.
反之, 假设 $\forall\ S\subset X$, 有 $|N(S)|\geqslant|S|$. 可用归纳法构造出饱和 $S$ 的一个匹配.
具体的, 参见问题2262.
如下图所示, 图 $G$ 是二元图. $M=\{(x_1,y_1), (x_2,y_2), (x_3,y_3)\}$ 是二元图 $G$ 的匹配, $K=\{x_1,x_2,x_3\}$ 是图 $G$ 的一个覆盖.
因为 $|M|=|K|=3$, 故 $M$ 和 $K$ 分别是图 $G$ 的最大匹配和最小覆盖.
又因为 $\forall\ S\subset X$, 有 $|N(S)|\geqslant |S|$, 因此存在饱和 $X$ 的所有顶点的匹配(或取 $t=3$, 用定理 4 中 (3) 的结论).
但对于 $Y$, $\exists\ S\subset Y$, 使得 $|N(S)| < |S|$. 如取 $S=\{y_1,y_2,y_3,y_4\}$, $N(S)=\{x_1,x_2,x_3\}$, 显然 $|N(S)| < |S|$, 因此不存在完美匹配.
Kruskal 算法的 idea 是: 程序管理的是一些连通子树的集合, 每次在添加新的边的时候, 检验是否形成环, 如不形成环, 则添加.
为此, 对于每条欲添加的边, 检验两个端点(不妨称之为左端点和右端点)是否已构建子树集合中的点, 分别用整数 $k_1$ 和 $k_2$ 表示其状态, $0$ 代表不在内, $1$ 代表在其中.
另一方面, 对于子树也进行标号, 记录添加的边之顶点属于哪个子树.
算法的基本思想: 最初把图的 $n$ 个顶点看作 $n$ 个分离的部分树, 每棵树有一个顶点. 算法的每一步选择可连接两分离树的边中权最小的边连接两个部分树, 合二为一, 部分树逐渐减少, 直到部分树的总边数为 $n-1$, 便得到最小生成树.
为了选取权尽可能小的边, 事先对图的边按权重由小到大排序, 得到图的边权列表(记为 $Q$), 作为算法输入.
$T$: 存放生成树的边与权的集合, 初始为 $\Phi$.
% SpanningTree_Kruskal.m % Book. page 109 %----------------------------------- clear edge=textread('net1.txt'); % net1.txt 是网络的边权文件, 每行为一条边, 第 1、2 列分别为边的两端顶点编号,第3列为边权,边权由小到大排列。 % 读取后, edge 是一个 15x3 的矩阵, 元素是 double 类型. % 1 3 1 % 2 5 1 % 4 6 1 % 1 2 2 % 4 7 2 % 5 6 3 % 7 8 3 % 6 7 4 % 4 5 5 % 2 4 6 % 6 8 6 % 3 4 7 % 1 4 8 % 3 7 9 % 5 8 9 NodeNum=max(max(edge(:,1:2))); % 网络顶点数 e1=length(edge(:,1)); % 网络边数 p=1; % p 是当前连通子树的编号 TreeNode=[edge(1,1) p edge(1,2) p]; TreeEdge=[edge(1,:)]; %当前子树的边集 for i=2:e1 if length(TreeEdge(:,1)) < NodeNum-1 %测试将当前边加入子树, 是否会形成圈 k1=0; k2=0; for j=1:length(TreeNode(:,1)) if edge(i,1)==TreeNode(j,1) k1=1; p1=TreeNode(j,2); end end for j=1:length(TreeNode) if edge(i,2)==TreeNode(j,1) k2=1; p2=TreeNode(j,2); end end % 当前边的 2 个顶点都不在已形成的子树中 if k1+k2==0 TreeEdge=[TreeEdge edge(i,:)]; p=max(TreeNode(:,2))+1; TreeNode=[TreeNode edge(i,1) p edge(i,2) p]; end % 当前边的其中一个顶点不在已形成的子树中 if k1+k2==1 TreeEdge=[TreeEdge edge(i,:)]; if k1==1 % 如果当前要加入的边的第一个顶点与TreeNode(:,1)中的相同 p=p1; TreeNode=[TreeNode edge(i,2) p]; else %p=p2; TreeNode=[TreeNode edge(i,1) p2]; end end % 当前边的 2 个顶点分别属于已形成的不同的连通子树中 if k1+k2==2 & p1 ~= p2 TreeEdge=[TreeEdge edge(i,:)]; if p1 < p2 t=find(TreeNode(:,2)==p2); TreeNode(t,2)=p1; else t=find(TreeNode(:,2)==p1); TreeNode(t,2)=p2; end end end end dlmwrite('Net1_Tree1.txt',TreeEdge, '\t') dlmwrite('Net1_Tree1_nodes.txt',TreeNode, '\t') %================================= % help max % -------- %If A is a vector, then max(A) returns the largest element of A. %If A is a matrix, then max(A) is a row vector containing the maximum value of each column. % A=magic(5) % A = % 17 24 1 8 15 % 23 5 7 14 16 % 4 6 13 20 22 % 10 12 19 21 3 % 11 18 25 2 9 % max(A) % ans = % 23 24 25 21 22
Prim 算法的 idea 是: 既然要得到权最小的生成树, 我们可以通过一条边接着一条边的生长方式构建最终所需的权最小生成树.
算法的基本思想: 以权重最小的一条边作为初始子树 $T_1$, 连接与 $T_1$ 最近的顶点得子树 $T_2$, 如此继续下去, 直到所有顶点都用到为止.
为了选取权尽可能小的边, 事先对图的边按权重由小到大排序, 得到图的边权列表(记为 $Q$), 作为算法输入.
$T$: 存放生成树的边与权的集合, 初始为 $\Phi$.
% SpanningTree_Prim.m % Book. page 112 %----------------------------------- clear edge=textread('net1.txt');%建议不要使用textread, 改用textscan %fileID=fopen('net1.txt'); %edge=textscan(fileID,); % net1.txt 是网络的边权文件, 每行为一条边, 第 1、2 列分别为边的两端顶点编号,第3列为边权,边权由小到大排列。 % 读取后, edge 是一个 15x3 的矩阵, 元素是 double 类型. % 1 3 1 % 2 5 1 % 4 6 1 % 1 2 2 % 4 7 2 % 5 6 3 % 7 8 3 % 6 7 4 % 4 5 5 % 2 4 6 % 6 8 6 % 3 4 7 % 1 4 8 % 3 7 9 % 5 8 9 NodeNum=max(max(edge(:,1:2))); % 网络顶点数 e1=length(edge(:,1)); % 网络边数 TreeNode=[edge(1,1) edge(1,2)];% 当前连通子树的顶点集 TreeEdge=[edge(1,:)]; %当前子树的边集 t1=length(TreeEdge(:,1)); % 当前连通子树的边数 while t1 < NodeNum-1 %测试当前边是否仅有一个顶点在当前连通子树里 k1=0; k2=0; i=2; while i < e1 k1=0; k2=0; for j=1:length(TreeNode) if edge(i,1)==TreeNode(j) k1=1; end end for j=1:length(TreeNode) if edge(i,2)==TreeNode(j) k2=1; end end % 当前边没有或 2 个顶点都在当前连通子树中 if k1+k2 ~=1 i=i+1; end % 当前边仅有一个顶点在当前连通子树里 if k1+k2 == 1 TreeEdge=[TreeEdge edge(i,:)]; if k1==1 TreeNode=[TreeNode edge(i,2)]; else TreeNode=[TreeNode edge(i,1)]; end edge(i,:)=[]; break end end t1=length(TreeEdge(:,1)); end dlmwrite('Net1_Tree2.txt',TreeEdge, '\t') %================================= % help max % -------- %If A is a vector, then max(A) returns the largest element of A. %If A is a matrix, then max(A) is a row vector containing the maximum value of each column. % A=magic(5) % A = % 17 24 1 8 15 % 23 5 7 14 16 % 4 6 13 20 22 % 10 12 19 21 3 % 11 18 25 2 9 % max(A) % ans = % 23 24 25 21 22