六度空间理论:
跟任意一个陌生人认识,但可能朋友的朋友有一个共同的朋友,通过不超过6个人即可。
什么是图(Graph)
表示多对多的关系
包含:
- 一组顶点:通常用V(Vetex)表示顶点集合
- 一组边:通常用E(Edge)表示边的集合
(1)边是顶点对:(v,w)∈E,其中v,w∈V
(2)有向边<v,w>表示从v指向w的边(单行线)
(3)不考虑重边和自回路
抽象数据类型定义
-
类型名称:图(Graph)
-
数据对象集:G(V,E)由一个非空的有限顶点集合V和一个有限边集合E组成。
-
操作集:对应于任意图G∈Graph,以及v∈V,e∈E
(1)Create()
(2)InsertVetex
(3)InsertEdge
(4)DFS
(5)BFS
(6)ShortestPath
(7)MST -
常见术语
(1)无向图
(2)有向图
(3)网络(带权重)
怎么在程序中表示一个图(一)
-
邻接矩阵G[N][N]------N个顶点从0到N-1编号
G[i][j]
=1 若<vi,vj>是G中的边
=0 否则
-
无向图的存储怎么可以节省一半的空间?
用长度为N(N+1)/2的1维数组A存储{G00,G10,G11,…,Gn-10,…,Gn-1n-1},则Gij在A对应的下标是
(i*(i+1)/2+j)
等差数列求和
-
对于网络只要把G[i][j]的值定义为边<vi,vj>的权重即可。
-
问题:vi,vj之间若没有边该怎么表示
-
好处?
(1)直观、简单、好理解
(2)方便检查任意一对顶点间是否存在边
(3)方便找任一顶点的所有“邻接点”(有边直接相连的顶点)
(4)方便计算任意顶点的“度”(从该点发出的边数为“出度”,指向该店的边数为“入度”)
a)无向图:对应行(或列)非0元素的个数
b)有向图:对应行非0元素的个数是“出度”;对应列非0元素的个数是“入度” -
坏处?
(1)浪费空间——存稀疏图(点很多而边很少)有大量无效的元素(但对于稠密图,特别是完全图,还是很合算的)
(2)浪费时间——统计稀疏图一共多少条边
(1)有N个顶点的无向完全图图有N(N-1)/2条边
(2)用一维数组G[ ]存储有4个顶点的无向图如下:G[ ] = { 0, 1, 0, 1, 1, 0, 0, 0, 1, 0 }则顶点2和顶点0之间是有边的。index=2(2+1)/2+0,G[index]=1
/* 图的邻接矩阵表示法 */
#define MaxVertexNum 100 /* 最大顶点数设为100 */
#define INFINITY 65535 /* ∞设为双字节无符号整数的最大值65535*/
typedef int Vertex; /* 用顶点下标表示顶点,为整型 */
typedef int WeightType; /* 边的权值设为整型 */
typedef char DataType; /* 顶点存储的数据类型设为字符型 */
/* 边的定义 */
typedef struct ENode *PtrToENode;
struct ENode{
Vertex V1, V2; /* 有向边<V1, V2> */
WeightType Weight; /* 权重 */
};
typedef PtrToENode Edge;
/* 图结点的定义 */
typedef struct GNode *PtrToGNode;
struct GNode{
int Nv; /* 顶点数 */
int Ne; /* 边数 */
WeightType G[MaxVertexNum][MaxVertexNum]; /* 邻接矩阵 */
DataType Data[MaxVertexNum]; /* 存顶点的数据 */
/* 注意:很多情况下,顶点无数据,此时Data[]可以不用出现 */
};
typedef PtrToGNode MGraph; /* 以邻接矩阵存储的图类型 */
MGraph CreateGraph( int VertexNum )
{ /* 初始化一个有VertexNum个顶点但没有边的图 */
Vertex V, W;
MGraph Graph;
Graph = (MGraph)malloc(sizeof(struct GNode)); /* 建立图 */
Graph->Nv = VertexNum;
Graph->Ne = 0;
/* 初始化邻接矩阵 */
/* 注意:这里默认顶点编号从0开始,到(Graph->Nv - 1) */
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 )
{
/* 插入边 <V1, V2> */
Graph->G[E->V1][E->V2] = E->Weight;
/* 若是无向图,还要插入边<V2, V1> */
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); /* 初始化有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);
/* 注意:如果权重不是整型,Weight的读入格式要改 */
InsertEdge( Graph, E );
}
}
/* 如果顶点有数据的话,读入数据 */
for (V=0; V<Graph->Nv; V++)
scanf(" %c", &(Graph->Data[V]));
return Graph;
}
怎么在程序中表示一个图(二)
- 邻接表:G[N]为指针数组,对应矩阵每行一个链表,只存非0元素
对于网络,结构中要增加权重的域
每个元素要保存数据和一个指针,空间大小相当于两个整数。【一定要够稀疏才合算】
- 邻接表
(1)方便找任意顶点的所有“邻接点”
(2)节省稀疏图的空间
a)需要N个头指针+2E个结点(每个结点至少2个域)E小于N(N-1)/4才省空间
(3)方便计算任意顶点的“度”?
a)对无向图:是
b)对有向图:只能计算“出度”;需要构造“逆邻接表”(存指向自己的边)来方便计算“入度”
(4)不方便检查任意一对顶点间是否存在边
用邻接表表示有N个顶点,E条边的图,则遍历图中所有的边的时间复杂度是O(N+E)
/* 图的邻接表表示法 */
#define MaxVertexNum 100 /* 最大顶点数设为100 */
typedef int Vertex; /* 用顶点下标表示顶点,为整型 */
typedef int WeightType; /* 边的权值设为整型 */
typedef char DataType; /* 顶点存储的数据类型设为字符型 */
/* 边的定义 */
typedef struct ENode *PtrToENode;
struct ENode{
Vertex V1, V2; /* 有向边<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; /* 存顶点的数据 */
/* 注意:很多情况下,顶点无数据,此时Data可以不用出现 */
} AdjList[MaxVertexNum]; /* AdjList是邻接表类型 */
/* 图结点的定义 */
typedef struct GNode *PtrToGNode;
struct GNode{
int Nv; /* 顶点数 */
int Ne; /* 边数 */
AdjList G; /* 邻接表 */
};
typedef PtrToGNode LGraph; /* 以邻接表方式存储的图类型 */
LGraph CreateGraph( int VertexNum )
{ /* 初始化一个有VertexNum个顶点但没有边的图 */
Vertex V;
LGraph Graph;
Graph = (LGraph)malloc( sizeof(struct GNode) ); /* 建立图 */
Graph->Nv = VertexNum;
Graph->Ne = 0;
/* 初始化邻接表头指针 */
/* 注意:这里默认顶点编号从0开始,到(Graph->Nv - 1) */
for (V=0; V<Graph->Nv; V++)
Graph->G[V].FirstEdge = NULL;
return Graph;
}
void InsertEdge( LGraph Graph, Edge E )
{
PtrToAdjVNode NewNode;
/* 插入边 <V1, V2> */
/* 为V2建立新的邻接点 */
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->AdjV = E->V2;
NewNode->Weight = E->Weight;
/* 将V2插入V1的表头 */
NewNode->Next = Graph->G[E->V1].FirstEdge;
Graph->G[E->V1].FirstEdge = NewNode;
/* 若是无向图,还要插入边 <V2, V1> */
/* 为V1建立新的邻接点 */
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->AdjV = E->V1;
NewNode->Weight = E->Weight;
/* 将V1插入V2的表头 */
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); /* 初始化有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);
/* 注意:如果权重不是整型,Weight的读入格式要改 */
InsertEdge( Graph, E );
}
}
/* 如果顶点有数据的话,读入数据 */
for (V=0; V<Graph->Nv; V++)
scanf(" %c", &(Graph->G[V].Data));
return Graph;
}
图的遍历
深度优先搜索(Depth First Search,DFS)
若有N个顶点、E条边,时间复杂度是
(1)用邻接表存储图,有O(N+E)
(2)用邻接矩阵存储图,有O(N2)
广度优先搜索( breadth First Search,BFS)
若有N个顶点、E条边,时间复杂度是
(1)用邻接表存储图,有O(N+E)
(2)用邻接矩阵存储图,有O(N2)
为什么需要两种遍历?
但入口换在最右下角,DSF更有优势
BFS研究最近的路时比较好; dfs和暴力搜索类似,研究出口的路比较快,但不是最优
图不连通怎么办?
无向图:
有向图:
(1)具有N(>0)个顶点的无向图至多有N个连通分量
(2)具有N(>0)个顶点的无向图至少有1个连通分量
(3)如果从无向图的任一定点出发进行一次深度优先搜索可访问所有的顶点,则该图锑锭时连通图。
/* 邻接表存储的图 - DFS */
void Visit( Vertex V )
{
printf("正在访问顶点%d\n", V);
}
/* Visited[]为全局变量,已经初始化为false */
void DFS( LGraph Graph, Vertex V, void (*Visit)(Vertex) )
{ /* 以V为出发点对邻接表存储的图Graph进行DFS搜索 */
PtrToAdjVNode W;
Visit( V ); /* 访问第V个顶点 */
Visited[V] = true; /* 标记V已访问 */
for( W=Graph->G[V].FirstEdge; W; W=W->Next ) /* 对V的每个邻接点W->AdjV */
if ( !Visited[W->AdjV] ) /* 若W->AdjV未被访问 */
DFS( Graph, W->AdjV, Visit ); /* 则递归访问之 */
}
/* 邻接矩阵存储的图 - BFS */
/* IsEdge(Graph, V, W)检查<V, W>是否图Graph中的一条边,即W是否V的邻接点。 */
/* 此函数根据图的不同类型要做不同的实现,关键取决于对不存在的边的表示方法。*/
/* 例如对有权图, 如果不存在的边被初始化为INFINITY, 则函数实现如下: */
bool IsEdge( MGraph Graph, Vertex V, Vertex W )
{
return Graph->G[V][W]<INFINITY ? true : false;
}
/* Visited[]为全局变量,已经初始化为false */
void BFS ( MGraph Graph, Vertex S, void (*Visit)(Vertex) )
{ /* 以S为出发点对邻接矩阵存储的图Graph进行BFS搜索 */
Queue Q;
Vertex V, W;
Q = CreateQueue( MaxSize ); /* 创建空队列, MaxSize为外部定义的常数 */
/* 访问顶点S:此处可根据具体访问需要改写 */
Visit( S );
Visited[S] = true; /* 标记S已访问 */
AddQ(Q, S); /* S入队列 */
while ( !IsEmpty(Q) ) {
V = DeleteQ(Q); /* 弹出V */
for( W=0; W<Graph->Nv; W++ ) /* 对图中的每个顶点W */
/* 若W是V的邻接点并且未访问过 */
if ( !Visited[W] && IsEdge(Graph, V, W) ) {
/* 访问顶点W */
Visit( W );
Visited[W] = true; /* 标记W已访问 */
AddQ(Q, W); /* W入队列 */
}
} /* while结束*/
}
拯救007
06-图1 列出连通集 (25 分)
给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N−1编号。进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。
输入格式:
输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。随后E行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。
输出格式:
按照"{ v1 v2 … vk}"的格式,每行输出一个连通集。先输出DFS的结果,再输出BFS的结果。
输入样例:
8 6
0 7
0 1
2 0
4 1
2 4
3 5
输出样例:
{ 0 1 4 2 7 }
{ 3 5 }
{ 6 }
{ 0 1 2 7 4 }
{ 3 5 }
{ 6 }
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
#define MaxVertexNum 11
#define INFINITY 65535
typedef int Vertex;
typedef int WeightType;
typedef char DataType;
//边
typedef struct ENode *PtrToENode;
struct ENode {
Vertex V1, V2;
};
typedef PtrToENode Edge;
//邻接点
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode {
Vertex AdjV;//邻接点下标
PtrToAdjVNode Next;//指向下一个邻接点的指针
};
//顶点表头结点
typedef struct Vnode {
PtrToAdjVNode FirstEdge;/* 边表头指针 */
} AdjList[MaxVertexNum]; /* AdjList是邻接表类型 */
//图结点的定义
typedef struct GNode *PtrToGNode;
struct GNode {
int Nv;//顶点数
int Ne;//边数
AdjList G;
};
typedef PtrToGNode LGraph;
LGraph CreateGraph(int VertexNum)
{
Vertex V;
LGraph Graph;
Graph = new GNode;
Graph->Nv = VertexNum;
Graph->Ne = 0;
for (V = 0; V < Graph->Nv; V++){
Graph->G[V].FirstEdge = new AdjVNode;
Graph->G[V].FirstEdge->Next = NULL;
}
return Graph;
}
void InsertEdge(LGraph Graph, Edge E) {
if (E->V1 > E->V2)
swap(E->V1, E->V2);
PtrToAdjVNode NewNode;
NewNode = new AdjVNode;
NewNode->AdjV = E->V2;
PtrToAdjVNode tmp = Graph->G[E->V1].FirstEdge;
while (tmp->Next!=NULL){
if (tmp->Next->AdjV > E->V2)
break;
tmp = tmp->Next;
}
NewNode->Next = tmp->Next;
tmp->Next = NewNode;
NewNode = new AdjVNode;
NewNode->AdjV = E->V1;
tmp = Graph->G[E->V2].FirstEdge;
while (tmp->Next != NULL) {
if (tmp->Next->AdjV > E->V1)
break;
tmp = tmp->Next;
}
NewNode->Next = tmp->Next;
tmp->Next = NewNode;
}
LGraph BuildGraph()
{
LGraph Graph;
Edge E;
int Nv, i;
cin >> Nv;
Graph = CreateGraph(Nv);
cin >> Graph->Ne;
if (Graph->Ne != 0) {
E = new ENode;
for (i = 0; i < Graph->Ne; ++i) {
cin >> E->V1 >> E->V2;
InsertEdge(Graph, E);
}
}
return Graph;
}
bool Visited[MaxVertexNum] = { false };
void DFS(const LGraph &g, Vertex v,vector<Vertex> &visit) {
PtrToAdjVNode w = g->G[v].FirstEdge;
if (Visited[v] != true) {
visit.push_back(v);
Visited[v] = true;
}
while (w->Next != NULL) {
w = w->Next;
if (Visited[w->AdjV]!=true) {
DFS(g, w->AdjV, visit);
}
}
}
void BFS(const LGraph &g, Vertex v, vector<Vertex> &visit) {
queue<Vertex> q;
q.push(v);
while (!q.empty()) {
Vertex temp = q.front();
q.pop();
if (Visited[temp] != true) {
visit.push_back(temp);
Visited[temp] = true;
}
PtrToAdjVNode w = g->G[temp].FirstEdge;
while (w->Next != NULL) {
w = w->Next;
if (Visited[w->AdjV] != true) {
q.push(w->AdjV);
}
}
}
}
void print(const vector<vector<Vertex>>&visit) {
for (int i = 0; i < visit.size(); i++)
{
cout << "{";
for (int j = 0; j < visit[i].size(); j++)
{
cout << " " << visit[i][j];
}
cout << " }";
if (i != visit.size() - 1)
cout << endl;
}
}
void show(const LGraph &g)
{
for (int i = 0; i < MaxVertexNum; i++){
Visited[i] = false;
}
vector<vector<Vertex>> res;
int num = g->Nv;
for (int i = 0; i < num; i++){
if (Visited[i] != false)
continue;
vector<Vertex> visit;
DFS(g, i, visit);
res.push_back(visit);
}
for (int i = 0; i < MaxVertexNum; i++){
Visited[i] = false;
}
for (int i = 0; i < num; i++) {
if (Visited[i] != false)
continue;
vector<Vertex> visit;
BFS(g, i, visit);
res.push_back(visit);
}
print(res);
}
int main()
{
LGraph Graph = BuildGraph();
show(Graph);
return 0;
}