算法导论22.4拓扑排序 练习总结 (转载)

22.4-1 给出算法 TOPOLOGICAL-SORT 运行于图 22-8 上时所生成的结点次序。这里的所有假设和练习 22.3-2 一样。

ANSWER:


 

22.4-2 请给出一个线性时间的算法,算法的输入为一个有向无环图 G = (V, E) 以及两个结点 s 和 t,算法的输出是从结点 s 到结点 t 之间的简单路径的数量。例如,对于图 22-8 所示的有向无环图,从结点 p 到结点 v 一共有 4 条简单路径,分别是 pov、poryv、posryv 和 psryv。(本题仅要求计数简单路径的条数,而不要求将简单路径本身列举出来。)

ANSWER:给结点附加一个计数属性time,初始化 t.time = 1,其它的 time 均为 0 ,以 s 为源结点进行 DFS,一旦搜索到 t,则 t 马上着黑色(即不继续搜索 t 的后代)。每当结束一个结点的搜索,则该结点的 time 属性 = 该结点所指向的所有结点的 time 之和。最后 s.time 即路径条数。

如图 22-8 的 p 到 v,最后 v.time = 1, y.time = 1, r.time = 1, s.time = 1, o.time = 1, p.time。

解法二

有向无环图G=(V,E),求其点s和e之间的路径数目。此题首先要以点s开始作DFS,得到从s为起点的所有可达点的一顶DFS树,但这个路径数目需要详细参详。

定义P(x),表示点s到点x的路径数目,P(s)=1,即s到自身有一条路径,其余的所有路径数目都初始化为0。

路径从s到达点x,则必须到达x的上一层结点,假设以x为终点的上一层结点有n个,即a1,a2,...,an,由加法定律可知P(x)= P(a1) + P(a2) + ... + P(an),这是一个从顶向下的递推过程,有点类似于动态规划。

综上,我们只要获取任意一点的入邻接表(也称逆邻接表),由图G获取其转置GT,其中保存着任意一点的上一层结点。然后再从顶向下递推,正好利用拓扑排序获得的序列,因为由拓扑排序的定义可以保证结点的顺序关系,因此递推有效。以下的算法步骤:

(1) 由图G获取其转置图GT,以得到所有点的入邻接表

(2) 以点s开始作DFS,得到从点s到达点e的拓扑序列

(3) 以此拓扑序列为顺序,逐个获取P值,最终得到P(e),即s到e的路径数目

图中实线为tree edge,曲线为forward和cross edge,从p到v的路径数目,递推过程如下:

P(p) = 1

P(o) = P(p) = 1

P(s) = P(p) + P(o)= 2

P(r) = P(o) +P(s) = 3

P(y) = P(r) = 3

P(v) = P(y) +P(o) = 4

步骤(1) 代码如下

static int graph_add_edge(struct link_graph *G, int uindex, int vindex)
{
    struct link_edge *e = NULL;
    struct link_vertex *u = G->v + uindex;

    e = malloc(sizeof(struct link_edge));
    if (!e)
        return -1;
    e->vindex = vindex;
    e->type = EDGE_NONE;

    list_add(&e->node, &u->head);
    return 0;
}

struct link_graph* graph_transpos(struct link_graph *G)
{
    int i = 0;
    struct link_edge *e = NULL;
    struct link_vertex *u = NULL;
    struct link_graph *GT = NULL;

    GT = malloc(sizeof(struct link_graph));
    if (!GT)
        return NULL;
    GT->v = malloc(G->vcount * sizeof(struct link_vertex));
    if (!GT->v) {
        free(GT);
        return NULL;
    }

    GT->vcount = G->vcount;
    GT->ecount = G->ecount;
    for (i = 0;i < GT->vcount;i++) {
        u = GT->v + i;
        u->vindex = i;
        INIT_LIST_HEAD(&u->head);
    }

    for (i = 0;i < G->vcount;i++) {
        u = G->v + i;
        list_for_each_entry(e, &u->head, node) {
            graph_add_edge(GT, e->vindex, i);
        }
    }

    return GT;
}

步骤(2) 代码如下

void DFS_visit_topo(struct link_graph *G, struct link_vertex *u, int *color, struct list_head *head)
{
    struct link_edge *le = NULL;

    color[u->vindex] = COLOR_GRAY;

    list_for_each_entry(le, &u->head, node) {
        if (color[le->vindex] == COLOR_WHITE) {
            DFS_visit_topo(G, G->v + le->vindex, color, head);
            le->type = EDGE_TREE;
        }
        else if (color[le->vindex] == COLOR_GRAY) {
            le->type = EDGE_BACK;
            printf("G has cycle and cannot topology sort\n");
        }
    }

    color[u->vindex] = COLOR_BLACK;
    list_add(&u->qnode, head);
}

int graph_topo_sort(struct link_graph *G, int s, struct list_head *thead)
{
    int i;
    int *color;
    color = malloc(sizeof(int) * G->vcount);
    for (i = 0;i < G->vcount;i++) {
        color[i] = COLOR_WHITE;
    }
    DFS_visit_topo(G, G->v + s, color, thead);

    free(color);
    return 0;
}

步骤(3) 代码如下

int graph_count_path(struct link_graph *G, int s, int end)
{
    int *P;
    struct list_head thead;
    struct link_vertex *u = NULL, *ut = NULL;
    struct link_edge *e = NULL;
    struct link_graph *GT = NULL;

    GT = graph_transpos(G);
    if (GT == NULL)
        return -1;
    P = malloc(G->vcount * sizeof(int));
    memset(P, 0, G->vcount * sizeof(int));
    P[s] = 1;

    INIT_LIST_HEAD(&thead);
    graph_topo_sort(G, s, &thead);

    printf("The topology sort from %d is below:\n", s);
    list_for_each_entry(u, &thead, qnode) {
        printf("%4d ", u->vindex);
        if (u->vindex == s)
            continue;
        ut = GT->v + u->vindex;
        list_for_each_entry(e, &ut->head, node) {
            P[u->vindex] += P[e->vindex];
        }
        if (u->vindex == end) {
            printf("\n\nThere are %d paths from %d to %d\n", P[end], s, end);
            break;
        }
    }

    link_graph_exit(GT);
    return 0;
}

22.4-3 给出一个算法来判断给定无向图 G = (V, E) 是否包含一个环路。算法运行时间应该在 O(V) 数量级,且与 |E| 无关。

ANSWER

我们都知道对于一个无向图而言,如果它能表示成一棵树,那么它一定没有回路,并且有|E|=|V|-1,如果在这个树上添加一条边,那么就构成了回路,如果在这个树中去掉一个边就成了森林(注意:如果只是限定|E|<|V|-1它不一定是森林,它当中可能存在无向连通子图)。

对于这个题目我们可以用DFS来做,每当访问当前节点的邻接表时,如果存在某个邻接的元素已被标记为访问状态,且这个元素不是当前元素的父节点,那么这个图就是存在回路的。总的时间代价是O(|E|+|V|),因为E<=|V|-1(如果|E|>|V|-1根据无向图的性质,那么这个无向图一定存在回路),所以O(|E|+|V|)=O(|V|)

22.4-4 证明或反证下述论断:如果有向图 G 包含环路,则在算法 TOPOLOGICAL-SORT(G) 所生成的结点序列里,图 G 中与所生成序列不一致的“坏”边的条数最少。

ANSWER:

 

22.4-5 在有向无环图 G =(V, E) 上执行拓扑排序还有一种办法,就是重复寻找入度为 0 的结点,输出该结点,将该结点及从其发出的边从图中删除。请解释如何在 O(V+E) 的时间内实现这种思想。如果图 G 包含环路,将会发生什么情况?

ANSWER:首先利用DFS在O(V+E)时间内记录每个结点的入度。然后在每次删除结点后更新每个结点的入度。删除结点时间为O(V),更新结点入度时间为O(E),所以总时间为O(V+E)。

如果图G包含环路,环路中的结点的入度会均大于0,都无法删除。

时间: 2024-10-04 15:51:51

算法导论22.4拓扑排序 练习总结 (转载)的相关文章

算法系列之图--拓扑排序

本文介绍使用深度先搜索对向无环图(DAG)进行拓扑排序. 对于一个有向无环图G=(V,E)来说,其拓扑排序是G中所有结点的一种线性次序,该次序满足如下条件:如果G包含边(u,v)则结点u在拓扑排序中处于结点v的前面(若图G包含一个环路则不可能排出一个线性次序).可将图中的拓扑排序看成是将图的所有结点在一条水平线上排开,图中所有边都从左指向右. 给一个拓扑图如下示: 拓扑排序算法与DFS相似,但是在拓扑排序的过程中,每个结点都是后与其临接链表里的结点而放入Stack中. 具体代码如下示: 1 #i

算法系列笔记6(有关图的算法一—搜索,拓扑排序和强连通分支)

简单概念:对于图G(V,E),通常有两种存储的数据结构,一种是邻接矩阵,此时所需要的存储空间为O(V^2):第二种是邻接表,所需要的存储空间为O(V+E).邻接表表示法存在很强的适应性,但是也有潜在的不足,当要快速的确定图中边(u,v)是否存在,只能在顶点u的邻接表中搜索v,没有更快的方法,此时就可以使用邻接矩阵,但要以占用更多的存储空间作为代价:此外当图不是加权的,采用邻接矩阵存储还有一个优势:在存储邻接矩阵的每个元素时,可以只用一个二进位,而不必用一个字的空间. 图的搜索算法 搜索一个图示有

算法学习 - 图的拓扑排序

拓扑排序 拓扑排序是对有向无圈图的顶点的一种排序,使得如果存在一条从Vi到Vj的路径,那么排序中Vj一定出现在Vi后面. 所以假如图里面有圈就不可能完成排序的. 第一种方法 一种简单的办法就是在排序算法中,先找到任意一个没有入边的顶点,然后显示该顶点,并把它和它的边一起从图里删掉.依次类推到最后. 入度(indegree): 顶点v的入度为,所有指向顶点v的变数(u, v). 出度(outdegree): 顶点v的出度为,顶点v所发出的边数(v, u). 下面写下这种方法的伪代码,因为这个的时间

[算法小练][图][拓扑排序+深度优先搜索] 平板涂色问题

说在前面 本题是一道经典题目,多做经典题目可以节省很多学习时间,比如本题就包含了许多知识:回溯+剪枝+拓扑排序+深度优先搜索.[动态规划方法另作讨论] 关键代码 题: CE数码公司开发了一种名为自动涂色机(APM)的产品.它能用预定的颜色给一块由不同尺寸且互不覆盖的矩形构成的平板涂色. 为了涂色,APM需要使用一组刷子.每个刷子涂一种不同的颜色C.APM拿起一把有颜色C的刷子,并给所有颜色为C且符合下面限制的矩形涂色: 为了避免颜料渗漏使颜色混合,一个矩形只能在所有紧靠它上方的矩形涂色后,才能涂

算法详解之拓扑排序

名词解释 ·(点的)度:对于无向图,和某个点相连的边条数 ·入度:对于有向图,终点是该点的边条数 ·出度:对于有向图,起点是该点的边条数 ·(两点间)路径:从起点点依次沿着边移动到下一个点,直到终点所经过的点和/或边若未有向图要求只能从边的起点移动到边的终点 ·圈:从一个点出发到自己的路径,常常被称作环 ·有向无环图(DAG):不含有环的有向图 拓扑排序 ·和数组的排序没什么关系 ·对DAG的顶点进行排序,结果要求 每个顶点出现且仅出现一次 对于顶点对(u,v),若排序后u在v前,则不存在v到u

算法导论22.3深度优先搜索 练习总结 (转载)

22.3-1 画一个 3*3 的网格,行和列的抬头分别标记为白色.灰色和黑色,对于每个表单元 (i, j),请指出对有向图进行深度优先搜索的过程中,是否可能存在一条边,链接一个颜色为 i 的结点和一个颜色为 j 的结点.对于每种可能的边,指明该种边的类型.另外,请针对无向图的深度优先搜索再制作一张这样的网格. ANSWER:   22.3-2 给出深度优先搜索算法在图 22-6 上的运行过程.假定深度优先搜索算法的第 5~7 行的 for 循环是以字母表顺序依次处理每个结点,假定每条邻接链表皆以

[一周一算法]算法导论学习之计数排序

计数排序是一种线性时间的排序,同时也是一种非比较排序 代码如下: 1 void CountingSort(int *data, int k, int num) // A ~ data[], B ~ aimArray[], C ~ tempArray[] 2 { 3 int *aimArray = new int[num]; 4 int *tempArray = new int[k + 1]; 5 for (int i = 0; i <= k; i++) 6 tempArray[i] = 0; 7

算法导论22章基本的图算法 思考题总结 (转载)

22-1 (以广度优先搜索来对图的边进行分类)深度优先搜索将图中的边分类为树边.后向边.前向边和横向边.广度优先搜索也可以用来进行这种分类.具体来说,广度优先搜索将从源结点可以到达的边划分为同样的4种类型. a.证明在对无向图进行的广度优先搜索中,下面的性质成立: 1.不存在后向边,也不存在前向边. 2.对于每条树边(u, v),我们有v.d = u.d + 1. 3.对于每条横向边(u, v),我么有v.d = u.d 或 v.d = u.d + 1. b.证明在对有向图进行广度优先搜索时,下

算法导论01 几种排序以及其时间复杂度01

冒泡排序 #include<stdio.h> int main() { int a[10],i,j,t; for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0;i<10;i++) { for(j=i+1;j<10;j++) { if(a[i]>a[j]) { t=a[i]; a[i]=a[j]; a[j]=t; } } } for(i=0;i<10;i++) printf("%3d"