第三章的主要思想就是DFS。讲了图上的DFS操作,然后讲了各种应用。这章默认图都是用邻接矩阵存的。

  1. procedure explore(G, v)
  2. Input: G = (V, E) is a graph;
  3. Output: visited(u) is set to true for all nodes u reachable from v
  4. visited(v) = true
  5. previstit(v)
  6. for each edge(v,u) in E:
  7. if not visited(u); explore(u)
  8. postvisit(v)
  9. procedure dfs(G)
  10. for all v in V
  11. visited(v) = false
  12. for all v in V
  13. if not visited(v) explore(v)
  14. procedure previsit(v)
  15. pre[v] = clock
  16. clock = clock+1
  17. procedure postvisit(v)
  18. post[v] = clock
  19. clock = clock+1

上面的代码就是这章里面所用到的所有代码了,也是DFS的整个代码,首先对于每个点,如果没有被访问过,那么就从这个点开始进行DFS,也就是explore过程,explore过程中,会访问和当前点相连的所有点[即当前点的可达点],previst和postvisit过程只是记录每个点的pre值和post值,这个值在某些方面还是很有用的,后面会说到。这整个DFS的时间复杂度是O(V+E)的,每个点和每条边都只访问一次。

在有向图中,对边进行了一些其他的定义
Tree edges: 指向儿子节点的边
Forward edges:从一个点指向非儿子后继节点的边
Back edges: 从一个点指向其祖先节点的边
Cross edges: 既不指向后继节点也不指向祖先节点的边

对于不同的边,这些点的pre值和post值的关系如下所示
pre/post ordering for(u,v) Edge type
pre[u] pre[v] post[v] post[u] tree/forward
pre[v] pre[u] post[u] post[v] back
pre[v] post[v] pre[u] post[u] cross

接下来将的是DAG[directed acyclic graphs]也就是有向无环图。在DAG中,每个点的后继节点的post值都会比当前节点的小,每个DAG至少有一个源点,一个汇点。DAG延伸出来的就是强联通分量了。

对于有向图,对所有的对 in V,都有从u到v的路径,那么就是强联通图,这里<1,2>和<2,1>表示不同的顶点对。如果我们从一个有向图的汇联通分量中的某个点开始进行DFS搜索的话,那么最后会且仅会把整个汇联通分量的点都找出来。这样的话我们如果知道某个汇联通分量里面的一个点,那么就可以知道整个汇联通分量,现在的问题是(I)怎么知道汇联通分量里面的一个点,和(II)求得一个汇联通分量之后要怎么做?

我们知道对于汇点[把联通分量缩成一个点]来说,我们没有好的方法可以容易,轻松的求得,但是对于源点我们就有比较简单的方法可以得到。首先我们可以知道,如果一个点的post值最高的话,那么这个点一定是源点,这个可以从两方面来证明,如果C和C’是图的两个联通分量,且有一条从C到C‘的边,那么C中的所有点的post值的最大值一定回比C’中所有点的post值的最大值要大。因为,如果我们的DFS是从C中开始的话,那么开始的那个点的post值比C‘中所有点的post值都要大;如果是从C’中开始搜索的话,那么先搜索完整个C‘联通分量,然后再搜索C联通分量,这样的话,所有在C中的点的p 大专栏  Algorithms第3章及少量习题ost值比在C’中的点的post值都要大。到此得证。知道这个之后,我们就可以解决问题(I)了,我们可以把有向图反向之后,进行一次DFS,这样的话,post值最大的一定是反向之后的源点,也就是原图的汇点了。对于问题(II)那么我们可以先把找出来的联通分量去掉,然后再在剩余的点中找post值最大的,这个点一定是剩下的图中的汇点,上面的证明可以保证这一点。

所以对于寻找一个图的强联通分量来说,我们只需要对图反向,然后在反向图中进行一次DFS,得到所有点的post值,然后根据post值从大到小的顺序在原图上进行一次DFS,这样就可以得到整个图的所有联通分量了。总时间还是线性的,就相当于2次DFS所用的时间。

接下来是后面几个习题,这几个习题是在这本书的网站上找的几个习题,并不是书中所有的习题。

1.给出一个图,要标出每个点的pre/post值。
这个只需要细心一点就行来

2.对于一个给定的图,用线性的方法把图进行反向。
这个可以循环所有点v,对 in E把v加到u的出边就行了,这样我们只需要循环每个点,每条边1次,所以是线性的

3.证明,无向图中,所有点的度数和是边数的2倍
每条边对两个点共享来度数,总的来说就变成来所有点的度数和是边数的2倍

4.在无向图中,计算每个点的邻接点的数目
这个直接循环所有点就行来,对于边那么u点的邻接点和v点的邻接点都+1就行来。

5.用非递归的方法实现explore

  1. procedure explore(G, u)
  2. S = (empty stack)
  3. push(S, u)
  4. while S is not empty:
  5. v = top(S)
  6. if not visited[v]:
  7. visited[v] = true
  8. previsit(v)
  9. if there is an edge (v, w) in E with visited[w] = false:
  10. push(S, w)
  11. else: (we’re done with v, the node at the top of the stack)
  12. pop(S)
  13. postvisited(v)

6.某学校需要对课表进行安排,某些课必须在另外一些课的前面上。每个学生每学期选多少门课都没关系,问对于给定的所有课之间的关系,同一个人至少需要多少个学期,才能把所有的课修完。
首先我们可以用线性时间计算出每个点的入度,并把入度为0 的点加到队列currL中。然后利用如下过程可以处理剩下的问题,下面的过程中in表示每个点的入度,currL表示当前入度为0的点

  • time = 0

  • repeat until currL is empty:
  • time = time + 1
  • nextL = empty list
  • for all u in currL:
  • for all (u, w) in E
  • in[w] = in[w] - 1
  • if(in[w] is 0)
  • add w to nextL
  • currL = nextL
  • output time
  • 这样,我们得到的还是线性的时间复杂度。
    另外还想了一种为经过验证的方法[私以为是正确的],先对图跑一次DFS,然后把所有点然post值降序排列起来,然后,对排好序的点进行扫描,如果相邻的两个点u,v之间有一条边u->v的话,那么学期数就需要加1[初始化为1],这样的话,最后的数目就是所有的学期数了。

    Algorithms第3章及少量习题的更多相关文章

    1. 《Python核心编程》 第六章 序列 - 课后习题

      课后习题 6–1.字符串.string 模块中是否有一种字符串方法或者函数可以帮我鉴定一下一个字符串是否是另一个大字符串的一部分? 答:成员关系操作符(in.not in) import string ...

    2. 「学习记录」《数值分析》第二章计算实习题(Python语言)

      在假期利用Python完成了<数值分析>第二章的计算实习题,主要实现了牛顿插值法和三次样条插值,给出了自己的实现与调用Python包的实现--现在能搜到的基本上都是MATLAB版,或者是各 ...

    3. 《Python核心编程》 第五章 数字 - 课后习题

      课后习题  5-1 整形. 讲讲 Python 普通整型和长整型的区别. 答:普通整型是绝大多数现代系统都能识别的. Python的长整型类型能表达的数值仅仅与你机器支持的(虚拟)内存大小有关. 5- ...

    4. UVa第五章STL应用 习题((解题报告))具体!

      例题5--9 数据库 Database UVa 1592 #include<iostream> #include<stdio.h> #include<string.h&g ...

    5. C和指针 第十二章 结构体 习题

      12.3 重新编写12.7,使用头和尾指针分别以一个单独的指针传递给函数,而不是作为一个节点的一部分 #include <stdio.h> #include <stdlib.h> ...

    6. APUE第一章_课后习题

      /* 未完成的:1.5 不过在下文中已经给出了解答. */ 1.1 在系统上查证,除根目录外,目录.和..是不同的 ans:这个很容易,用vim打开.和..就可以看到区别. 1.2 分析程序清单1-4 ...

    7. 【原创】《算法导论》链表一章带星习题试解——附C语言实现

      原题: 双向链表中,需要三个基本数据,一个携带具体数据,一个携带指向上一环节的prev指针,一个携带指向下一环节的next指针.请改写双向链表,仅用一个指针np实现双向链表的功能.定义np为next ...

    8. 「学习记录」《数值分析》第三章计算实习题(Python语言)

      第三题暂缺,之后补充. import matplotlib.pyplot as plt import numpy as np import scipy.optimize as so import sy ...

    9. java编程思想第四版第十四章 类型信息习题

      fda dfa 第三题u package net.mindview.typeinfo.test4; import java.util.ArrayList; import java.util.Array ...

    随机推荐

    1. python中os模块的常用方法

      1.os模块:os模块在python中包含普遍的操作系统功能,下面列出了一些在os模块中比较有用的部分. os.sep可以取代操作系统特定的路径分隔符.windows下为 “\\” os.name字符 ...

    2. JavaSE--对象克隆

      当拷贝一个变量时,原始变量与拷贝变量引用同一个对象,这就是说,改变一个变量所引用的对象将会对另一个变量产生影响. 如果创建一个对象的新的 copy,他的最初状态与 original 一样,但以后将可以 ...

    3. 嵌入式linux学习笔记

      1.溢出:两个数相加,如果最高位的进位和此高位的进位不同,则产生溢出. 2.进位和溢出的概念不一样. 3.预取(取得是编译后得到的机器代码)-->译码-->执行 4.ARM的汇编指令长度是 ...

    4. Comet OJ - Contest #3 D可爱的菜菜子(线段树+线性基的合并)

      这题其实挺经典的,看到求异或最大,显然想到的是线性基,不过这怎么维护?当然区间有关的东西都可以上线段树,区间修改时记录每个点的修改量k,然后合并线性基时再加入线性基.因为线性基是求一组极大线性无关组, ...

    5. uniapp结合小程序第三方插件“WechatSI”实现语音识别功能,进而实现终端控制

      最近在用soket实现终端控制器的功能,然后就想用语音控制,这样显得更AI WechatSI在manifest.json中配置: 在vue中插入如下展示代码: <view class=" ...

    6. 11)PHP,单选框和复选框的post提交方式处理

      就是一个表单中会有input的checkbox形式,那么怎么处理,就有了问题,一般采用二维数组来处理 代码展示: <!DOCTYPE html PUBLIC "-//W3C//DTD ...

    7. Android开发学习1----AndroidStudio的安装、创建第一个Android Studio文件、Android Studio界面介绍和HelloWord!

      移动开发的工具有很多:Android Studio,eclipse,Hbuilder等,其中,现如今最火的开发工具是Android Studio,Android Studio是谷歌自己推出的一款集成开 ...

    8. mybatis 自定义类型转换器 (后台日期类型插入数据库)

      后台日期类型插入数据库 有以下几种发法: 1 调用数据库 日期字符串转日期函数 str_to_date("日期","yyyy-MM-dd HH:mm:ss") ...

    9. mpu6050的驱动的加载和测试步骤

      mpu6050的使用方法: 1.接线方式: VCC,GND,SCL,SDA,正常接法,VCC接3.3v,主要说一下AD0引脚,用来表示地址 接低电平设备地址为0x68,接高电平表示0x69 2.设备接 ...

    10. 网络类(IP、dns、网络连接类)

      一.centOS 7 设置DNS方法 使用全新的命令行工具 nmcli 来设置 1.显示当前网络连接 nmcli connection show   NAME UUID TYPE DEVICE eno ...