图-广度优先搜索
- [1. 图是什么](#1. 图是什么)
- [2. 广度优先搜索(BFS)](#2. 广度优先搜索(BFS))
图和数的区别:树中除根节点没有前驱外,其余的每个结点只有唯一的一个前驱(双亲)结点,每个结点可以有多个子结点。图中任意两个结点之间都可能有直接关系,一个结点的前驱和后继的数目是没有限制的。
1. 图是什么
图由节点(node)和边(edge)组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。
Rama是Alex的邻居。Adit不是Alex的邻居,因为他们不直接相连。但Adit既是Rama的邻居,又是Tom的邻居。
1.1 基本概念
图G是由集合V和E构成的二元组,记作 G=(V,E),其中V是图中顶点的非空有限集合,E是图中边的有限集合。
- 有向图。若图中的每条边都有方向,则称为有向图。有向边也称为弧,弧线的起点为弧尾,终点为弧头。方向不一样,即使路径相同也是不同的弧。
- 无向图。若图中的每条边都没有方向,则称为无向图。
- 完全图。若一个无向图具有n个顶点,而每一个顶点与其他n-1个顶点之间都有边则称为无向完全图。含有n个顶点的无向完全图共有 n(n-1)/2 条边;有n个顶点的有向完全图中弧的数目为 n(n-l),即任意两个不同顶点之间都存在方向相反的两条弧。
- 度、出度和入度。度是指关联于该顶点的边的数目,记作 D(v)。若 G为有向图,顶点的度表示该顶点的入度和出度之和。顶点的入度是以该顶点为终点的有向边的数目,而顶点的出度指以该顶点为起点的有向边的数目,分别记为ID(v)和 OD(v)。
- 路径。从一个结点到另一个结点经过的边的有序集合称为路径。路径的长度是路径上边或弧的数目。第一个定点和最后一个顶点相同的路径称为回路或环。若一条路径除起点和终点相同外,其他的结点都不相同,这种路径称为一条简单路径。
- 子图;其中一个图是另一个图的一部分。
- 连通图。从一个结点到另一个结点之间有路径,则称这两个结点时连通的。如果无向图中任意两个结点都是连通的,则称为连通图。
- 强连通图。在有向图中,如果任意两个结点都存在路劲,则称为强连通图。
- 网:边或弧具有权值的图称为网。
图中的结点之间不存在顺序关系。
1.2 存储结构
图的两种存储结构:邻接矩阵和邻接表。
邻接矩阵
邻接矩阵表示法是利用一个矩阵来表示图中顶点之间的关系。对于具有n个顶点的图G=(V,E)来说,其邻接矩阵是一个n阶方阵,且满足:
A [ i ] [ j ] = { 1 , 若( v i , v j )或 < v i , v j > 是 E 中的边 0 , 若( v i , v j )或 < v i , v j > 不是 E 中的边 A[i][j]=\begin{cases} 1, &若(v_i,v_j)或<v_i,v_j>是E中的边\\ 0, &若(v_i,v_j)或<v_i,v_j>不是E中的边\\ \end{cases} A[i][j]={1,0,若(vi,vj)或<vi,vj>是E中的边若(vi,vj)或<vi,vj>不是E中的边
由邻接矩阵的定义可知,无向图的邻接矩阵是对称的,而有向图的邻接矩阵则不一定具有该性质。无向图结点的度为相对应行或列上1的数目,有向图结点的出度为相对应行上1的数目,有向图结点的入度为相对应列上1的数目。
网(权重图)的邻接矩阵定义:
A [ i ] [ j ] = { W i j , 若( v i , v j )或 < v i , v j > 是 E 中的边 ∞ , 若( v i , v j )或 < v i , v j > 不是 E 中的边 A[i][j]=\begin{cases} W_{ij}, &若(v_i,v_j)或<v_i,v_j>是E中的边\\ \infty, &若(v_i,v_j)或<v_i,v_j>不是E中的边\\ \end{cases} A[i][j]={Wij,∞,若(vi,vj)或<vi,vj>是E中的边若(vi,vj)或<vi,vj>不是E中的边
- 数据类型定义
cpp
#define MaxN 50 //图中顶点的最大数量
typedef int AdjMatrix[MaxN][MaxN] //邻接矩阵
或
typedef double AdjMatrix[MaxN][MaxN]
typedef struct{
int Vnum,Enum;
AdiMatrix Arcs;
}Graph;
邻接表
邻接链表是为图的每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶 v i v_i vi的边(对于有向图是以 v i v_i vi为尾的弧)。邻接链表中的结点有表结点和表头结点两种类型。
- adjvex:指示与顶点 v i v_i vi邻接的顶点的序号;
- nextarc:指示下一条边或弧的结点。
- info:存储和边或弧有关的信息,如权值等
- data:存储顶点 v i v_i vi的名或其他有关信息。
- firstarc:指示链表中的第一个结点。
数据类型:
cpp
#define MaxN 50 //图中顶点的最大数量
typedef struct ArcNode{ //邻接链表的表结点
int adjvex; //邻接顶点编号
double weight; //边上权重值
struct ArcNode *nextarc; //下一个邻接顶点的指针
}EdgeNode;
typedef struct VNode{ //邻接链表的头结点
char data; //顶点表示的数据,以一个字符表示
struct ArcNode *firstarc; //指示第一条依附于该顶点的指针
}AdjList[MaxN];
typedef struct{
int Vnum, Enum; //图中实际的顶点和边数目
AdjList Vertices;
}Graph;
实例:
2. 广度优先搜索(BFS)
广度优先搜索:解决最短路径的算法,是一种用于图的查找算法。在查找的过程需要按添加顺序进行检查,因此利用队列来实现,利用图来表示关系,利用矩阵/散列表来存储关系。
-
第一类问题:从节点A出发,有前往节点B的路径吗?
-
第二类问题:从节点A出发,前往节点B的哪条路径最短?
在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系。一度关系在二度关系之前加入查找名单。
实例1:在人际圈中找人(采用Python中散列表实现)
python
graph = {}
graph["you"] = ["alice", "bob", "claire"]
graph["bob"] = ["anuj", "peggy"]
graph["alice"] = ["peggy"]
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []
graph["peggy"] = []
graph["thom"] = [] //目标人物
graph["jonny"] = []
def search(name):
search_queue = deque()
search_queue += graph[name]
searched = [] //用于记录检查过的人
while search_queue:
person = search_queue.popleft()
if not person in searched: //没有检查过才检查
if person_is_seller(person):
print person + " is a mango seller!"
return True
else:
search_queue += graph[person]
searched.append(person) //将此人标记过检查
return False
实例2:编写国际跳棋AI,计算最少走多少步就可获胜。
实例3:编写拼写检查器,计算最少编辑多少个地方就可将错拼的单词改成正确的单词,如将READED改为READER需要编辑一个地方。
- 运行时间:广度优先搜索的运行时间为O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数,E为边数。