一、图的表示:
有两种方法:
(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;
}
}