算法导论学习笔记(16)——图的基本算法

来源:互联网 发布:海康 知乎 编辑:程序博客网 时间:2024/04/25 14:11

一、图的表示:

有两种方法:

(1)邻接矩阵:在非稀疏图中,这种表示法简单高效。比如图有n个节点,矩阵大小就是NxN的M,M[i,j] 可以用来表示节点i到节点j的权等等。这种表示法使用起来相当的灵活,缺点是不管图的信息如何,其空间代价都是NxN的,开销较大。

(2)邻接表:这是专门为稀疏图所准备的,也使我最喜欢的表示方法,因为我总感觉这种表达方式尊重了图的信息,比较有感觉。首先,可以用一个向量表示各个节点,然后对于每个节点的出边,在该节点后面链接即可。实现的时候,一个vector ,一个List就搞定了,也是非常方便的。

                        查看更多精彩图片


                         (a)表示一个有5个顶点和7条边的无向图G (b)G的邻接表表示 (c)G的邻接矩阵表示

二、图的搜索

图 G=(V,E)有两种搜索方式:广度优先搜索(BFS)和深度优先搜索(DFS);以下讲的都是以邻接表作为存储结构的。

1)BFS:它首先访问初始点v, 接着访问v的所有未被访问过的邻接点v0,v1,v2,..., 然后再按照v0,v1,...的次序,访问每个顶点的所有未被访问的邻接点,依次类推,直到完全访问。我们采用队列的方式实现,先将顶层结点压入队列,而后出队,将所有与其邻接的结点入队尾,再出队队首元素,将所有未被访问的结点入队尾,再出队队首元素,如此,直到队列为空。

伪代码:

BFS(G, s)
1  for each vertex u ∈ V [G] - {s}
2       do color[u] ← WHITE
3          d[u] ← ∞
4          π[u] ← NIL
5  color[s] ← GRAY
6  d[s] ← 0
7  π[s] ← NIL
8  Q ← Ø
9  ENQUEUE(Q, s)
10  while Q ≠ Ø
11      do u ← DEQUEUE(Q)
12         for each v ∈ Adj[u]
13             do if color[v] = WHITE
14                   then color[v] ← GRAY
15                        d[v] ← d[u] + 1
16                        π[v] ← u
17                        ENQUEUE(Q, v)
18         color[u] ← BLACK

这里白色,灰色和黑色只是为了让我们理解更加容易一点,所以才加上的,比如黑色就不是需要的。原理很简单,最开始,所有的点都是白色,然后从源点开始,把它的邻接定点变成灰色,表示这些定点已经被访问,但是其字节点还没有被访问。当把所有的子节点访问完成,就把自己涂成黑色,表示即被访问了,而且其儿子节点也全部访问了。中间实现是依靠一个队列,FIFO,这样就能实现先广优先搜索了。
示意图:
查看更多精彩图片

C++代码:

#define MAXV//最大顶点个数

typedef struct ANode
{
    int end; //弧的终点
    struct ANode* next; //指向下一条弧的指针
    ArcInfo info; //与弧相关的信息
}ArcNode; //弧结点,亦即表结点

typedef struct
{
    string vertex;
}Vertex; //顶点信息

typedef struct Vnode
{
    Vertex data;
    ArcNode *firstarc;
}VNode;  //表头结点

typedef VNode AdjList[MAXV]; //定义邻接表

typedef struct
{
    AdjList adjlist;  //邻接表
    int n,e;  //顶点,边数
}ALGraph; //图的邻接表的表示

void bfs(ALGraph* alg, int v)
{
    ArcNode *p;
    int queue[MAXV],front=0,rear=0,i,w;
    cout<<v<<" ";
    visited[v]=true;

    rear=(rear+1)%MAXV;
    queue[rear]=v;
   
    while(front!=rear)
    {
        front=(front+1)%MAXV;
        w=queue[front];
        p=alg->adjlist[w].firstarc;

        while(p!=NULL)
        {
            if(!visited[p->end])
            {
                cout<<p->end<<" ";
                visited[p->end]=true;
                rear=(rear+1)%MAXV;
                queue[rear]=p->end;
            }
            p=p->next;
        }
    }
}

2)DFS:深度优先搜索遍历的过程是:从图中某个初始顶点v出发,首先访问该初始顶点v, 然后选择一个与顶点v相邻且没被访问过的顶点w为初始顶点,再从w出发进行深度优先搜索,址到与当前顶点v的所有顶点都被访问过为止。

伪代码:

DFS(G)
1  for each vertex u ∈ V [G]
2       do color[u] ← WHITE
3          π[u] ← NIL
4  time ← 0
5  for each vertex u ∈ V [G]
6       do if color[u] = WHITE
7             then DFS-VISIT(u)

DFS-VISIT(u)
1  color[u] ← GRAY     ▹White vertex u has just been discovered.
2  time ← time +1
3  d[u] ← time
4  for each v ∈ Adj[u]  ▹Explore edge(u, v).
5       do if color[v] = WHITE
6             then π[v] ← u
7                         DFS-VISIT(v)
8  color[u] BLACK      ▹ Blacken u; it is finished.
9  f [u] ← time ← time +1

深度优先搜索依靠的是递归的技术。开始时所有节点都是白色,然后依次搜索没有被访问过的节点来作为一个新的森林的根节点,直到所有的节点都被访问完。递归函数也很简单:先把自己涂成灰色,表示被访问过了,然后对于其字节点,依次递归遍历,最后把自己涂成黑色,表示该节点的子节点已经被全部遍历完成。
这中间有一个time的全局变量,他记录的是一个节点第一次被访问的时间d[u]和 该节点的子节点被访问完时的时间f[u]。也许现在你不知道这有什么作用,但是到后来的比如说拓扑排序,强连通分支等时,既可以看到它的用处了。
示意图:
查看更多精彩图片

C++代码:

void dfs(ALGraph* alg,int v)
{
    ArcNode *p;
    visited[v]=true;
    cout<<v<<" ";
    p=alg->adjlist[v].firstarc;

    while(p!=NULL)
    {
        if(!visited[p->end])   
            dfs(alg,p->end);
        p=p->next;
    }
}