9.4 关系的闭包

9.4 关系的闭包

闭包的定义:

关系R对于性质P的闭包,是加入最小数量的序偶,使得R恰好符合性质P所得到的集合
R的闭包R1具有如下3个特点:
①. R1 包含 R
②. R1具有性质P
③. 如果R2具有性质P且R2包含R, 那么R2包含R1

就R的有向图而言:

  1. 找其自反闭包(reflexive closure)

    添加循环/闭环

  2. 找其对称闭包(symmetric closure)

    沿相反方向添加弧线(箭头)

  3. 找其传递闭包(transitive closure)

    如果a到b连通, 那么就添加从a到b的弧线(箭头)

自反闭包(reflexive closure)
定理:R是定义在A上的关系,那么R的自反闭包r(R) = R∪△
如何获得?

①. 在R的有向图的所有顶点上添加闭环
②. 令R的邻接矩阵的对角线上全为1

对称闭包(symmetric closure)

定理①:R是定义在A上的关系,那么R的对称闭包s(R) = R∪R-1
NOTE: R-1 = {(b, a) | (a, b) ∈ R}
NOTE: R-1的邻接矩阵是R的邻接矩阵的转置,
即: MRT = MR-1

定理②:R是对称的,当且仅当 R = R-1
NOTE:在对称关系的有向图中,用无向的边来代替弧线(箭头)

路径(Paths)

假设R为定义在A上的关系,则R从a到b,长度为n的路径可表示为以a为起始点,b为终点的一个有限序列π:
a, x1, x2, ..., xn-1, b;
其中,满足:a R x1, x1 R x2, ..., xn-1 R b

例:

一些重要定义:

  • 环(cycle):

一条起始点和终点为相同顶点的路径称为:环(cycle)

  • Rn:

x Rn y表示,在R中存在一条或多条从x到y的路径

  • 连通关系(connectivity relation) R*

R*包含的序偶对(a, b), 其中在R中至少存在一条从a到b的路径

例:

一些重要定理:

  1. 如果R是定义在A上的关系,那么有:

    其中,⊙表示矩阵布尔乘法

证明:

  1. 当n>=2时,有:

证明:科学归纳法(略)

连通关系(The connectivity relation)

准备

  • 路径的合成

令:
π1: a, x1, x2, … , xn-1, b
π2: b, y1, y2, … , ym-1, c
则π1与π2合成后的路径为:
π2 o π1:a, x1, x2, … , xn-1, b, y1, y2, … , ym-, c

NOTE THE ORDER!!!(注意顺序)

  • 传递闭包(Transitive closure)

①. 关系R的传递闭包是包含R的最小的传递关系。

②. R是传递的, 当且仅当,对于任何的n,均有Rn ⊆ R(结论来自9.1)

③. 如果传递闭包存在一条从x到y的路径,那么一定有从x直接到y的弧线(箭头)

  • 传递闭包里有用的一些结论:

①. If A ⊆ B and C ⊆ B, then (A∪C) ⊆ B.

②. If R ⊆ S and T ⊆ U then (RoT) ⊆ (SoU).

推论: If R ⊆ S then Rn ⊆ Sn

③. 如果R是传递的,那么Rn也是传递的
只需证明:(Rn)2 = (R2)n ⊂ Rn

④. 如果对于j>k, 有Rk = Rj, 那么对于某些n>=j, 有Rj+m = Rn
除了Rj之外,我们无法得到任何新的关系

  • 一个重要定理:
  • R为定义在A上的一个关系,那么R的闭包就等于R*

PROOF:我们必须证明,R

1). 是一个传递关系
2). 包含R
3). 是包含R的最小的传递关系

Proof of Part 1):

假设(x, y)和(y, z)都在R*中,只需证(x, z)也在R*
由R*定义知,一定存在m,n,使得(x, y)和(y, z)分别在Rm和Rn
又由复合定理,知:(x, z) ∈ RnoRm = Rm+n ⊆ R*
因此,R*是传递的

Proof of Part 2):

显然.

Proof of Part 3):

最重要的结论:

  • 如果集合A的维数 = n,即|A|=n, 那么对于定义在A上的关系R,有:

  • 等价命题:对于k<=n<=m,有1)和2)同时成立

  • 1). Rm ⊆ Rk
  • 2). (a, b) ⊆ Rm → (a, b) ⊆ Rk

证明其实就是去掉环,此处略

沃舍尔算法(Warshall’s Algorithm)

需要知道:内部顶点(Interior vertices)

大致方法:

①. 将n个节点赋予顺序为{a1, a2,…, an}, 并定义Wk = [tij]表示存在从第i个节点到第j个节点且仅通过内部节点{a1, a2,…, ak}这k个节点(这些节点可选可不选,但除此之外的节点都不能选)的路径连通,并记为“1”, 否则记为“0”
②. W0 = 初始邻接矩阵MR
③. 利用Wk-1来计算Wk
④. 得连通矩阵MR* = Wn

计算具体Wk的做法:

对于Wk中的tab
①. 如果Wk-1的sab == ‘1’,即仅利用{a1, a2,…, ak-1}这k-1个点(当然包括第a和第b个顶点)就能使a连通到b,那么Wk的tab直接 = ‘1’
②. 如果Wk-1的sab == ‘0’, 那么要使仅用{a1, a2,…, ak}这n个点从a连通到b, 则:
只需存在一个m(1 <= m <= k-1),使得在Wk-1中,有:sam == “1”并且smb == “1”(在Wk-1中,节点a到m连通,节点m到b连通, 那么在Wk中,节点a到b连通)

例:

核心代码:

for(int k = 1; k <= N; k++)
{
    for(int i = 1; i <= N; i++)
    {
        for(int j = 1; j <= N; j++)
        {
            if(W[k-1][i][j]=='1')
            {
                W[k][i][j] = 1;
            }
            else if(W[k-1][i][k]=='1'&&W[k-1][k][j]=='1')
            {
                W[k][i][j] = 1;
            }
        }
    }   
}

时间复杂度:O(n^3)
空间复杂度:O(n^3)

其实由于W[k]只与W[k-1]有关,可以只用二维数组来表示W[k-1],然后更新W[k]的时候直接覆盖到这个数组即可将空间复杂度压缩成O(n^2)

实战:Cow Contest

思路:只需求以这些牛组成有向图的传递闭包(用连通矩阵表示),再判断每个节点的入度+出度是否等于n-1,来判断每头牛能否确认排名

AC代码:

#include <stdio.h>

int main(void)
{
    int n, m, a, b; //n头牛, m个关系
    scanf("%d %d",&n,&m);
    int W[n+1][n+1];
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            W[i][j] = 0;
    for(int i = 1; i <= m; i++)
    {
        scanf("%d %d",&a,&b);
        W[a][b] = 1;  //初始赋值W[0]
    }
    //这里即只需用一个二维数组表示W[k-1],然后将W[k]覆盖到W[k-1]这个二维数组上,使得空间复杂度为:O(n^2)
    for(int k = 1; k <= n; k++)
    {
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                //if(W[i][j]==1) // 代表第一种情况,完全可以省去
                //  W[i][j] = 1;
                if(W[i][j]==0)  //代表第二种情况
                {
                    if(W[i][k]==1&&W[k][j]==1)
                        W[i][j] = 1;
                }
            }
        }
    }
    int sumdu = 0, ans = 0;
    for(int i = 1; i <= n; i++)  //判断每个点的入度和出度之和是否为n-1
    {
        sumdu = 0;
        for(int j = 1; j <= n; j++)
        {
            if(W[i][j]==1||W[j][i]==1)
                sumdu++;
        }
        if(sumdu==n-1)
            ans++;
    }
    printf("%d\n",ans);
    return 0;
}

OTHERS

解决最短路径问题有几个常用的算法:

    1. dijkstra算法,最经典的单源最短路径算法
    1. bellman-ford算法,允许负权边的单源最短路径算法
    1. spfa,其实是bellman-ford+队列优化,其实和bfs的关系更密一点
    1. floyd算法,经典的多源最短路径算法

而Warshall算法和flody算法最具异曲同工之妙:

flody算法:用Wk的tab表示点a到点b只用{a1, a2,…, ak}这k个点和a与b所能达到的最短路径值,和Warshall算法一样,用W[k-1]来计算W[k],而Wn即为所有两点之间(即多源)最短路径的答案

  • flody算法是用来求图的多源最短路径的算法,复杂度也为O(n^3)
  • 将连通的边权定为有限值(如1),不连通的边权定为∞,便可以用flody算法求所有点的连通性(如i, j两个节点的最短路径为有限值即连通)
  • Warshall算法和flody算法都能用动态规划的想法来理解:动态规划深刻理解flody
  • 为什么 Dijkstra 不能提出 floyd 算法?因为他的名字是 ijk 而不是 kij

猜你喜欢

转载自www.cnblogs.com/SpicyArticle/p/11544570.html
9.4