第6章 图
6.1 图的相关知识
6.2 邻接矩阵 邻接表
#define MaxVertexNum 100
#define INFINITY 65535
typedef int Vertex;
typedef int WeightType;
typedef char DataType;
typedef struct ENode *PtrToENode;
struct ENode{
Vertex V1, V2;
WeightType Weight;
};
typedef PtrToENode Edge;
typedef struct GNode *PtrToGNode;
struct GNode{
int Nv;
int Ne;
WeightType G[MaxVertexNum][MaxVertexNum];
DataType Data[MaxVertexNum];
};
typedef PtrToGNode MGraph;
MGraph CreateGraph( int VertexNum ) {
Vertex V, W;
MGraph Graph;
Graph = (MGraph)malloc(sizeof(struct GNode));
Graph->Nv = VertexNum;
Graph->Ne = 0;
for (V=0; V<Graph->Nv; V++)
for (W=0; W<Graph->Nv; W++)
Graph->G[V][W] = INFINITY;
return Graph;
}
void InsertEdge( MGraph Graph, Edge E ) {
Graph->G[E->V1][E->V2] = E->Weight;
Graph->G[E->V2][E->V1] = E->Weight;
}
MGraph BuildGraph() {
MGraph Graph;
Edge E;
Vertex V;
int Nv, i;
scanf("%d", &Nv);
Graph = CreateGraph(Nv);
scanf("%d", &(Graph->Ne));
if ( Graph->Ne != 0 ) {
E = (Edge)malloc(sizeof(struct ENode));
for (i=0; i<Graph->Ne; i++) {
scanf("%d %d %d", &E->V1, &E->V2, &E->Weight);
InsertEdge( Graph, E );
}
}
for (V=0; V<Graph->Nv; V++)
scanf(" %c", &(Graph->Data[V]));
return Graph;
}
- 邻接表:存储邻接矩阵的每一行
- 方便找到某一结点的所有邻接点
- 节约稀疏图的内存空间:需要N个头指针 + 2E个结点(每个结点至少2个域)
- 方便计算无向图的度
- 对于有向图,只能计算出度,需要构造逆邻接表才方便计算入度
- 不方便检查任意一对顶点间是否存在边
#define MaxVertexNum 100
typedef int Vertex;
typedef int WeightType;
typedef char DataType;
typedef struct ENode *PtrToENode;
struct ENode{
Vertex V1, V2;
WeightType Weight;
};
typedef PtrToENode Edge;
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode{
Vertex AdjV;
WeightType Weight;
PtrToAdjVNode Next;
};
typedef struct Vnode{
PtrToAdjVNode FirstEdge;
DataType Data;
} AdjList[MaxVertexNum];
typedef struct GNode *PtrToGNode;
struct GNode{
int Nv;
int Ne;
AdjList G;
};
typedef PtrToGNode LGraph;
LGraph CreateGraph( int VertexNum ) {
Vertex V;
LGraph Graph;
Graph = (LGraph)malloc( sizeof(struct GNode) );
Graph->Nv = VertexNum;
Graph->Ne = 0;
for (V=0; V<Graph->Nv; V++)
Graph->G[V].FirstEdge = NULL;
return Graph;
}
void InsertEdge( LGraph Graph, Edge E ) {
PtrToAdjVNode NewNode;
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->AdjV = E->V2;
NewNode->Weight = E->Weight;
NewNode->Next = Graph->G[E->V1].FirstEdge;
Graph->G[E->V1].FirstEdge = NewNode;
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->AdjV = E->V1;
NewNode->Weight = E->Weight;
NewNode->Next = Graph->G[E->V2].FirstEdge;
Graph->G[E->V2].FirstEdge = NewNode;
}
LGraph BuildGraph() {
LGraph Graph;
Edge E;
Vertex V;
int Nv, i;
scanf("%d", &Nv);
Graph = CreateGraph(Nv);
scanf("%d", &(Graph->Ne));
if ( Graph->Ne != 0 ) {
E = (Edge)malloc( sizeof(struct ENode) );
for (i=0; i<Graph->Ne; i++) {
scanf("%d %d %d", &E->V1, &E->V2, &E->Weight);
InsertEdge( Graph, E );
}
}
for (V=0; V<Graph->Nv; V++)
scanf(" %c", &(Graph->G[V].Data));
return Graph;
}
6.3 图的遍历
6.3.1 深度优先搜索 DFS
伪代码描述:
void DFS ( Vertex V ) {
visited[ V ] = true;
for ( V的每一个临界点 w )
if ( !visited [ w ] )
DFS( w );
}
void Visit( Vertex V ) {
printf("正在访问顶点%d\n", V);
}
void DFS( LGraph Graph, Vertex V, void (*Visit)(Vertex) ) {
PtrToAdjVNode W;
Visit( V );
Visited[V] = true;
for( W=Graph->G[V].FirstEdge; W; W=W->Next )
if ( !Visited[W->AdjV] )
DFS( Graph, W->AdjV, Visit );
}
6.3.2 广度优先搜索 BFS
bool IsEdge( MGraph Graph, Vertex V, Vertex W )
{
return Graph->G[V][W] < INFINITY ? true : false;
}
void BFS ( MGraph Graph, Vertex S, void (*Visit)(Vertex) )
{
Queue Q;
Vertex V, W;
Q = CreateQueue( MaxSize );
Visit( S );
Visited[S] = true;
AddQ(Q, S);
while ( !IsEmpty(Q) ) {
V = DeleteQ(Q);
for( W=0; W<Graph->Nv; W++ )
if ( !Visited[W] && IsEdge(Graph, V, W) ) {
Visit( W );
Visited[W] = true;
AddQ(Q, W);
}
}
}
6.3.3 DFS 和 BFS 比较
- BFS是围绕某个点一层一层的进行遍历,借助队列的数据结构实现。
- DFS是从一个点出发一直往深处遍历,条件不符就折返,通过栈的数据结构实现。
- 在正常情况下二者的时间复杂度都是相同的O(v+e),但是在内存的使用上BFS由于要使用队列存储所有的外层节点,所以内存消耗相对较大。
- 从宏观上来看:
- 如果你已经知道解离根节点比较近,那么BFS更好
- 如果整体上每个节点的边很多,那么BFS消耗的内存会很大
- 如果一棵树很深而解很少,那么DFS可能会很慢(相反如果解很多并且都比较深的话,那么BFS就会很慢)从名字上就很容易得出,
- 如果一个问题深度无穷而广度有限,那么DFS就没法获得解,但BFS可以,反之也同理。
- 从具体应用上看:
- DFS比较适合判断图中是否有环,寻找两个节点之间的路径,有向无环图(DAG)的拓扑排序,寻找所有强连通片(SCC),无向图中寻找割点和桥等;
- BFS则比较适合判断二分图,以及用于实现寻找最小生成树(MST),如在BFS基础上的Kruskal算法。还有寻找最短路径问题(如Dijkstra算法)
6.3.4 应用实例:六度空间
算法思路:
- 对图里的每个节点,进行广度优先搜索
- 搜索过程中累计访问的节点数
- 需要记录层数,仅计算6层以内的节点数
存储结构:
- 顶点数V,边数E,首先根据 V+2E = V*V,得出
- 当 E = (V*V - V) / 2 时,邻接矩阵和邻接表的存储空间相同;(条件1)
- 当 E > (V*V - V) / 2 时,邻接矩阵更优;(条件2)
- 当 E < (V*V - V) / 2 时,邻接表更优;(条件3)
- 由于当图有V个顶点时,最大的边数 E = (V*V - V) / 2,因此
- 条件1,也就对应于 “全相连”(所有点都和除自身外所有点相连);
- 条件2,永远不可能出现;
- 条件3,只要不是“全相连”即可。
- 那么也就是说不管题目中条件(E <= 33 * V )如何,一定有邻接表更优。
伪代码:
void SixDegreesofSeparation() {
for(each V in G) {
count = BFS(V);
Output(count/N);
}
}
int BFS(Vertex V) {
visited[V] = true; count = 1;
level = 0;
last = V;
Enqueue(V, Q);
while ( !IsEmpty(Q) ) {
V = Dequeue(Q);
for( V 的每一个邻接点 W )
if( !visited[W] ) {
visited[W] = true;
Enqueue(W, Q); count++;
tail = W;
}
if ( V == last ) { level++; last = tail; }
if ( level == 6 ) break;
}
return count;
}
代码示例:
#include <iostream>
#include <queue>
#include <iomanip>
using namespace std;
#define MaxVertexNum 200
using Vertex = int;
using WeightType = int;
struct ENode {
Vertex V1, V2;
}; using Edge = struct ENode*;
struct GNode {
int Nv;
int Ne;
WeightType G[MaxVertexNum][MaxVertexNum];
}; using Graph = struct GNode*;
bool visited[MaxVertexNum];
void BuildGraph(Graph& graph) {
Edge E;
int i, j;
graph = (Graph)malloc(sizeof(GNode));
if (!graph) exit(-1);
cin >> graph->Nv >> graph->Ne;
for (i = 1; i <= graph->Nv; i++)
for (j = 1; j <= graph->Nv; j++)
graph->G[i][j] = 0;
if (graph->Ne != 0) {
E = (Edge)malloc(sizeof(ENode));
if (!E) exit(-1);
for (i = 1; i <= graph->Ne; i++) {
cin >> E->V1 >> E->V2;
graph->G[E->V1][E->V2] = 1;
graph->G[E->V2][E->V1] = 1;
}
}
}
int BFS(Graph graph, Vertex V) {
for (int i = 1; i <= graph->Nv; i++)
visited[i] = false;
int count = 1;
int level = 0;
int last = V;
int tail;
queue<int> q;
q.push(V);
visited[V] = true;
while (!q.empty()) {
V = q.front();
q.pop();
for (int w = 1; w <= graph->Nv; w++) {
if (!visited[w] && graph->G[V][w] == 1) {
visited[w] = true;
q.push(w);
++count;
tail = w;
}
}
if (V == last) {
++level;
last = tail;
}
if (level == 6) break;
}
return count;
}
int main() {
Graph graph;
BuildGraph(graph);
for (int i = 1; i <= graph->Nv; i++)
{
int count = BFS(graph, i);
cout << i << ": " << fixed << setprecision(2)
<< count * 100.0 / graph->Nv << '%' << endl;
}
}
其他相关文章 / 视频:
|