图的广度优先搜索

来源:互联网 发布:淘宝开店品牌信息怎么写 编辑:程序博客网 时间:2024/06/07 20:59

在上一篇博文中我们介绍了图的深度优先遍历,在这一篇博文中我们将介绍图的另外一种搜索方式——图的广度优先遍历(BFS),也称为宽度优先搜索,其英文全称是Breadth First Search

图的广度优先搜索

        已知图G=(V,E)和一个源顶点s,宽度优先搜索以一种系统的方式探寻G的边,从而“发现”s所能到达的所有顶点,并计算s到所有这些顶点的距离(最少边数),该算法同时能生成一棵根为s且包括所有可达顶点的宽度优先树。对从s可达的任意顶点v,宽度优先树中从s到v的路径对应于图G中从s到v的最短路径,即包含最小边数的路径。该算法对有向图和无向图同样适用。 
        之所以称之为宽度优先算法,是因为算法自始至终一直通过已找到和未找到顶点之间的边界向外扩展,就是说,算法首先搜索和s距离为k的所有顶点,然后再去搜索和S距离为k+l的其他顶点。

与深度优先搜索的对比

深度优先搜索用栈(stack)来实现,整个过程可以想象成一个倒立的树形:
  1. 把根节点压入栈中。
  2. 每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些元素压入栈中。并把这个元素记为它下一级元素的前驱。
  3. 找到所要找的元素时结束程序。
  4. 如果遍历整个树还没有找到,结束程序。

广度优先搜索使用队列(queue)来实现,整个过程也可以看做一个倒立的树形:

  1. 把根节点放到队列的末尾。
  2. 每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。
  3. 找到所要找的元素时结束程序。
  4. 如果遍历整个树还没有找到,结束程序。

下面我们就试举一例来演示广度优先算法,类似地,广度优先算法也有两种不同的形式

邻接矩阵的广度优先搜索

二话不说贴代码
#include <stdio.h>  #include <stdlib.h>  /* 图的邻接矩阵表示法 */#define MaxVertexNum 100    /* 最大顶点数设为100 */  #define INFINITY 65535        /* ∞设为双字节无符号整数的最大值65535*/  typedef int Vertex;         /* 用顶点下标表示顶点,为整型 */typedef int WeightType;        /* 边的权值设为整型 */typedef char DataType;        /* 顶点存储的数据类型设为字符型 */int Visited[MaxVertexNum];/* 标记V已访问 *//* 边的定义 */typedef struct ENode *PtrToENode;struct ENode {Vertex V1, V2;      /* 有向边<V1, V2> */};typedef PtrToENode Edge;/* 图结点的定义 */typedef struct GNode *PtrToGNode;struct GNode {int Nv;  /* 顶点数 */int Ne;  /* 边数   */WeightType G[MaxVertexNum][MaxVertexNum]; /* 邻接矩阵 */};typedef PtrToGNode MGraph; /* 以邻接矩阵存储的图类型 */#define ERROR 0  struct Node {int Data;struct Node *Next;};struct QNode {struct Node *rear;struct Node *front;};typedef struct QNode *Queue;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] = 1;/* 若是无向图,还要插入边<V2, V1> */Graph->G[E->V2][E->V1] = 1;}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++) {int VV1, VV2;scanf("%d %d", &VV1, &VV2);E->V1 = VV1 - 1; E->V2 = VV2 - 1;/* 注意:如果权重不是整型,Weight的读入格式要改 */InsertEdge(Graph, E);}}return Graph;}void Visit(Vertex V){printf("正在访问顶点%d\n", V + 1);}int IsEmpty(Queue Q) {return(Q->front == NULL);};Queue CreateQueue() {Queue PtrQ;PtrQ = (Queue)malloc(sizeof(struct QNode));struct Node *rear;struct Node *front;rear = (Node*)malloc(sizeof(struct Node));rear = NULL;front = (Node*)malloc(sizeof(struct Node));front = NULL;PtrQ->front = front;PtrQ->rear = rear;return PtrQ;};int DeleteQ(Queue PtrQ) {struct Node *FrontCell;int FrontElem;if (IsEmpty(PtrQ)) {printf("队列空");return ERROR;}FrontCell = PtrQ->front;if (PtrQ->front == PtrQ->rear)PtrQ->front = PtrQ->rear = NULL;else {PtrQ->front = PtrQ->front->Next;}FrontElem = FrontCell->Data;free(FrontCell);return FrontElem;}void InsertQ(int item, Queue PtrQ) {struct Node *FrontCell;FrontCell = (Node*)malloc(sizeof(struct Node));FrontCell->Data = item;FrontCell->Next = NULL;if (IsEmpty(PtrQ)) {PtrQ->front = FrontCell;PtrQ->rear = FrontCell;}else {PtrQ->rear->Next = FrontCell;PtrQ->rear = FrontCell;}};/* 邻接矩阵存储的图 - 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为外部定义的常数 *//* 访问顶点S:此处可根据具体访问需要改写 */Visit(S);Visited[S] = true; /* 标记S已访问 */InsertQ(S, Q); /* 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已访问 */InsertQ(W, Q); /* W入队列 */}} /* while结束*/}int main() {MGraph Graph = CreateGraph(6);Graph->Ne = 8;Vertex a[8] = { 0,0,0,1,1,3,2,3 };Vertex b[8] = { 1,2,4,3,4,4,5,5 };/* 读入边,格式为"起点 终点 权重",插入邻接矩阵 */for (int i = 0; i<Graph->Ne; i++) {Edge E = (Edge)malloc(sizeof(struct ENode)); /* 建立边结点 */E->V1 = a[i]; E->V2 = b[i];/* 注意:如果权重不是整型,Weight的读入格式要改 */InsertEdge(Graph, E);}for (int i = 0; i < MaxVertexNum; i++) {Visited[i] = false; }BFS(Graph, 0, Visit);system("pause");}

执行的结果如下图所示


邻接表的广度优先搜索

只需要稍稍修改之前的代码,结合队列即可
#include <stdio.h>  #include <stdlib.h>  #define MaxVertexNum 100    /* 最大顶点数设为100 */  typedef int Vertex;         /* 用顶点下标表示顶点,为整型 */typedef int WeightType;        /* 边的权值设为整型 */typedef char DataType;        /* 顶点存储的数据类型设为字符型 */int Visited[MaxVertexNum];/* 边的定义 */typedef struct ENode *PtrToENode;struct ENode {Vertex V1, V2;      /* 有向边<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; /* 以邻接表方式存储的图类型 */#define ERROR 0  struct Node {int Data;struct Node *Next;};struct QNode {struct Node *rear;struct Node *front;};typedef struct QNode *Queue;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;/* 将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;/* 将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", &E->V1, &E->V2);/* 注意:如果权重不是整型,Weight的读入格式要改 */InsertEdge(Graph, E);}}return Graph;}/* 邻接表存储的图 - DFS */void Visit(Vertex V){printf("正在访问顶点%d\n", V+1);}int IsEmpty(Queue Q) {return(Q->front == NULL);};Queue CreateQueue() {Queue PtrQ;PtrQ = (Queue)malloc(sizeof(struct QNode));struct Node *rear;struct Node *front;rear = (Node*)malloc(sizeof(struct Node));rear = NULL;front = (Node*)malloc(sizeof(struct Node));front = NULL;PtrQ->front = front;PtrQ->rear = rear;return PtrQ;};int DeleteQ(Queue PtrQ) {struct Node *FrontCell;int FrontElem;if (IsEmpty(PtrQ)) {printf("队列空");return ERROR;}FrontCell = PtrQ->front;if (PtrQ->front == PtrQ->rear)PtrQ->front = PtrQ->rear = NULL;else {PtrQ->front = PtrQ->front->Next;}FrontElem = FrontCell->Data;free(FrontCell);return FrontElem;}void InsertQ(int item, Queue PtrQ) {struct Node *FrontCell;FrontCell = (Node*)malloc(sizeof(struct Node));FrontCell->Data = item;FrontCell->Next = NULL;if (IsEmpty(PtrQ)) {PtrQ->front = FrontCell;PtrQ->rear = FrontCell;}else {PtrQ->rear->Next = FrontCell;PtrQ->rear = FrontCell;}};/* 邻接表存储的图 - BFS *//* Visited[]为全局变量,已经初始化为false */void BFS(LGraph Graph, Vertex S, void(*Visit)(Vertex)){   /* 以S为出发点对邻接矩阵存储的图Graph进行BFS搜索 */Queue Q;Vertex V;PtrToAdjVNode W;Q = CreateQueue(); /* 创建空队列, MaxSize为外部定义的常数 *//* 访问顶点S:此处可根据具体访问需要改写 */Visit(S);Visited[S] = true; /* 标记S已访问 */InsertQ(S, Q); /* S入队列 */while (!IsEmpty(Q)) {V = DeleteQ(Q);  /* 弹出V */for (W = Graph->G[V].FirstEdge; W; W = W->Next) /* 对V的每个邻接点W->AdjV */if (!Visited[W->AdjV])    /* 若W->AdjV未被访问 */{/* 访问顶点W */Visit(W->AdjV);Visited[W->AdjV] = true; /* 标记W已访问 */InsertQ(W->AdjV, Q); /* W入队列 */}} /* while结束*/}int main() {LGraph Graph = CreateGraph(6);Graph->Ne = 8;Vertex a[8] = { 0,0,0,1,1,3,2,3 };Vertex b[8] = { 1,2,4,3,4,4,5,5 };/* 读入边,格式为"起点 终点 权重",插入邻接矩阵 */for (int i = 0; i<Graph->Ne; i++) {Edge E = (Edge)malloc(sizeof(struct ENode)); /* 建立边结点 */E->V1 = a[i]; E->V2 = b[i];/* 注意:如果权重不是整型,Weight的读入格式要改 */InsertEdge(Graph, E);}for (int i = 0; i < MaxVertexNum; i++) {Visited[i] = false; }BFS(Graph, 0, Visit);system("pause");}

执行结果如下


算法复杂度

若有N个顶点、E条边,时间复杂度是

  • 用邻接表储存图,有O(N+E)
  • 用邻接矩阵储存图,有O(N^2)
1 0