图的创建、广度优先搜索、深度优先搜索
2016-04-13 14:14
357 查看
1、图的概述
图是一种较线性表和树更加复杂的数据结构。在线性表中,数据元素之间仅有线性关系,每个元素只有一个直接前驱和直接后继;在树形结构中元素之间有着明显的层次关系,并且每一层上的元素可能和下层的多个元素相关,而只能和上层中的一个元素相关;而在图结构中,元素之间的关系是任意的,,图中任意一个节点都可能和其它节点相关。
图中的基本概念:图中的元素通常称为顶点(vertex),V是图中顶点的有穷集合;VR是两个顶点之间的关系的
集合,若<u,v>∈VR,则<u,v>表示从u到v的一条弧,且称u为弧尾,v为弧头,此时的图称为有向图;若<u,v>∈VR必有<v,u>∈VR,即VR是对称的,则以无序对(u,v)代替这两个有序对,表示u和v之间的一条边,此时的图称为无向图。如下图所示。在无向图G中,如果从顶点v到顶点w有路径,则称v和w是连通的,如果对于图中任意两个顶点都是连通的,则称G是连通图,如下图所示。
图的存储结构:图在计算机中有两种存储结构,邻接链表和邻接矩阵。对于上图一所示的无向图的邻接链表和邻
接矩阵为:
如果我们采用邻接链表的方式存储一张图,那么我们需要构造顶点及弧,对于顶点我们要存储顶点的数据域及其
指向的第一条弧,对于弧我们需要存储弧的权、下一条弧、弧所连接的下一个顶点下标。因此顶点和边的结构体为:
/*边的数据结构*/ typedef struct arc_node { int position;//存储边的另一个顶点下标 int weight;//边的权重 struct arc_node *next;//指向下一条边 }ANode,*pArc; /*顶点的数据结构*/ typedef struct vertex_node { ElemType data; pArc first_arc;//指向第一条弧 }VNode, *pVNode; /*图的数据结构*/ typedef struct graphic_node { int vertex_num;//顶点数量 int arc_num;//边的数量 pVNode vertex[MAX_VERTEX];//用来存放顶点的数组 }Graphic, *pGNode;
2、图的创建
在本文中,我们创建的图是无向图并采用邻接链表的方式来存储图,输入顶点信息和弧的信息,使用动态内存分配构造顶点节点和弧节点,从而构造一个邻接链表。
void create_graphic(pGNode g,int direction)//direction = 0表示创建的是无向图,非0值是有向图 { cout << "输入顶点数" << endl; cin >> g->vertex_num; cout << "输入边数" << endl; cin >> g->arc_num; int i; cout << "输入" << g->vertex_num << "个顶点" << endl; for (i = 0; i < g->vertex_num;i++) { g->vertex[i] = (pVNode)malloc(sizeof(VNode)); cin >> (g->vertex[i]->data); g->vertex[i]->first_arc = NULL; } cout << "输入" << g->arc_num << "个边和边的权重(例如:输入0 1 20表示下标为0和下标为1的顶点有一条边且权重为20)" << endl; for (i = 0; i < g->arc_num; i++) { int x, y,w; cin >> x >> y>>w; pArc temp1 = (pArc)malloc(sizeof(ANode)); temp1->position = y; temp1->weight = w; /*将边加入到链接链表中*/ temp1->next = g->vertex[x]->first_arc; g->vertex[x]->first_arc = temp1; if (direction == 0)//说明是无向图 { pArc temp2 = (pArc)malloc(sizeof(ANode)); temp2->position = x; temp2->weight = w; temp2->next = g->vertex[y]->first_arc; g->vertex[y]->first_arc = temp2; } } }
3、图的广度优先搜索
广度优先搜索是最简单的图搜索算法之一,也是许多重要的图算法原型。给定图G=(V,E)和一个可以识别的源节点s,该算法能够系统性地探索从源节点s到达的所有节点。广度优先搜索之所以如此得名,是因为该算法始终将未发现节点和已发现节点之间的边界沿其广度方向扩展,也就是说访问完所有的距离源节点s为k的节点之后,再去访问距离源节点为k+1的节点。广度优先搜索将会生成以源节点s为根的一棵“广度优先搜索树”。该算法类似于树的按层次遍历。例如对图一(源节点为a)进行广度优先搜索棵可得到:
基本思想:从源节点s开始访问,遍历所有与源节点s相连接的顶点,并依次将这些顶点放入到队列中,然后从队
列中取顶点(同时把顶点标记为黑色,意味着这些顶点已访问,初始化时所有顶点被标记为白色,表示未被访问。也就是说通过涂色来鉴别节点的状态),并判断所有与此顶点相连接的其它顶点,如果其它顶点没有被访问则将它们添加到队列中,否则不添加到队列中,直到队列为空为止。
void bfs(Graphic g, int source)//给定图g和起始遍历位置 { int visit[MAX_VERTEX] = { WHITE };//初始化为白色,说明所有顶点没有被访问,访问之后变为黑色 Queue q; init_queue(&q); insert_queue(&q, g.vertex[source]); visit[source] = BLACK; /*如果队列不为空则从队列中取出元素*/ while ( !isempty_queue(q) ) { pVNode temp; delete_queue(&q, &temp); /*打印输出该节点数据*/ cout << temp->data << " "; /*如果节点所连接的其它节点没有被访问,则加入到队列中*/ pArc x = temp->first_arc; while (x != NULL) { int position = x->position; if (visit[position] == WHITE) { insert_queue(&q, g.vertex[position]); visit[position] = BLACK; } x = x->next; } } }
4、图的深度优先搜索
深度优先搜索,顾名思义,就是在搜素的过程中在图中尽可能地“深入”。深度优先搜索总是从最近才发现的节点v的出发边进行探索,直到v节点的所有出发边都被发现为止,然后回溯到v节点的前驱节点(v节点是通过该节点才被发现的),再搜索前驱节点的出发边。该过程一直持续到从源节点可以到达的所有节点被访问为止。如果还存在未被访问的节点,则从这些节点中任选一个做为源节点,并重复同样的搜索过程,直到图中所有节点都被访问。和广度优先搜索一样,对被访问的节点标记为黑色。但是和广度优先搜索不一样的是,广度优先搜索生成一棵广度优先搜索树,而深度优先搜索可能会生成多棵树。深度优先搜索类似于树的先序遍历。图一的生成的深度优先搜索树及遍历次序如下图所示:
void dfs(Graphic g) { for (int i = 0; i < g.vertex_num;i++) { if (visit[i] == WHITE) dfs_visit(g, i); } } void dfs_visit(Graphic g,int source) { if (visit[source] == BLACK) return; pVNode x = g.vertex[source]; cout << x->data; visit[source] = BLACK; pArc y = x->first_arc; while (y != NULL) { dfs_visit(g, y->position); y = y->next; } }
5、测试代码
#include <iostream> /*图的存储结构:邻接链表、邻接矩阵*/ /******************图的数据结构***************/ typedef char ElemType; const int WHITE = 0; const int BLACK = 1; const int MAX_VERTEX = 30; const int MAX_ARC = 900; /*边的数据结构*/ typedef struct arc_node { int position;//存储边的另一个顶点下标 int weight;//边的权重 struct arc_node *next;//指向下一条边 }ANode, *pArc; /*顶点的数据结构*/ typedef struct vertex_node { ElemType data; pArc first_arc;//指向第一条弧 }VNode, *pVNode; /*图的数据结构*/ typedef struct graphic_node { int vertex_num;//顶点数量 int arc_num;//边的数量 pVNode vertex[MAX_VERTEX];//用来存放顶点的数组 }Graphic, *pGNode; void create_graphic(pGNode g, int direction);//创建图,direction=0无向图,=1有向图 /******************图的遍历***************/ /*1、广度优先搜索(类似于树的按层次遍历) 基本思想:在已发现节点和未发现节点的边界上,沿其广度方向向外扩展,也就是说访问完距离源节点 为k的节点再去访问距离为k+1的节点 */ void bfs(Graphic g, int source);//给定图g和起始遍历位置 /*2、深度优先搜索(类似于树的先根遍历) 基本思想:只要可能就在图中尽量深入,总是对最近才发现的节点v沿其出发边进行探索 直到该节点的所有出发边都被发现为止,搜索则回溯到v节点的前驱节点 */ void dfs(Graphic g); void dfs_visit(Graphic g, int source); int visit[MAX_VERTEX] = { WHITE };//用来记录节点是否被访问 /*辅助数据结构:队列*/ struct queue_node { pVNode data; struct queue_node *next; }; typedef struct queue { struct queue_node *front; struct queue_node *tail; }Queue; /*要想修改内容必须传指针,要想修改内容指向的值可以不传指针*/ void init_queue(Queue *q); bool isempty_queue(Queue q); void insert_queue(Queue *q, pVNode e); bool delete_queue(Queue *q, pVNode *e);//当删除最后一个元素的时候一定要修改尾部指针 using namespace std; int main() { Graphic undirec_g, direc_g; cout << "*****************创建一个无向图*************" << endl; create_graphic(&undirec_g, 0); cout <<endl<< "广度优先遍历:" << endl; bfs(undirec_g, 0); cout << endl << "深度优先遍历:" << endl; dfs(undirec_g); cout << endl << "*****************创建一个有向图*************" << endl; create_graphic(&direc_g, 1); cout << endl << "广度优先遍历:" << endl; bfs(direc_g, 0); cout << endl << "深度优先遍历:" << endl; dfs(direc_g); cout << endl; } void create_graphic(pGNode g, int direction)//direction = 0表示创建的是无向图,非0值是有向图 { cout << "输入顶点数" << endl; cin >> g->vertex_num; cout << "输入边数" << endl; cin >> g->arc_num; int i; cout << "输入" << g->vertex_num << "个顶点" << endl; for (i = 0; i < g->vertex_num; i++) { g->vertex[i] = (pVNode)malloc(sizeof(VNode)); cin >> (g->vertex[i]->data); g->vertex[i]->first_arc = NULL; } cout << "输入" << g->arc_num << "个边和边的权重(例如:输入0 1 20表示下标为0和下标为1的顶点有一条边且权重为20)" << endl; for (i = 0; i < g->arc_num; i++) { int x, y, w; cin >> x >> y >> w; pArc temp1 = (pArc)malloc(sizeof(ANode)); temp1->position = y; temp1->weight = w; /*将边加入到链接链表中*/ temp1->next = g->vertex[x]->first_arc; g->vertex[x]->first_arc = temp1; if (direction == 0)//说明是无向图 { pArc temp2 = (pArc)malloc(sizeof(ANode)); temp2->position = x; temp2->weight = w; temp2->next = g->vertex[y]->first_arc; g->vertex[y]->first_arc = temp2; } } } void bfs(Graphic g, int source)//给定图g和起始遍历位置 { int visit[MAX_VERTEX] = { WHITE };//初始化为白色,说明所有顶点没有被访问,访问之后变为黑色 Queue q; init_queue(&q); insert_queue(&q, g.vertex[source]); visit[source] = BLACK; /*如果队列不为空则从队列中取出元素*/ while (!isempty_queue(q)) { pVNode temp; delete_queue(&q, &temp); /*打印输出该节点数据*/ cout << temp->data << " "; /*如果节点所连接的其它节点没有被访问,则加入到队列中*/ pArc x = temp->first_arc; while (x != NULL) { int position = x->position; if (visit[position] == WHITE) { insert_queue(&q, g.vertex[position]); visit[position] = BLACK; } x = x->next; } } } void dfs(Graphic g) { for (int i = 0; i < g.vertex_num; i++) visit[i] = WHITE;//初始化为白色,意味着没有被访问 for (int i = 0; i < g.vertex_num; i++) { if (visit[i] == WHITE) dfs_visit(g, i); } } void dfs_visit(Graphic g, int source) { if (visit[source] == BLACK) return; pVNode x = g.vertex[source]; cout << x->data; visit[source] = BLACK; pArc y = x->first_arc; while (y != NULL) { dfs_visit(g, y->position); y = y->next; } } void init_queue(Queue *q) { queue_node *temp = (queue_node *)malloc(sizeof(queue_node)); temp->next = NULL; q->front = q->tail = temp; } bool isempty_queue(Queue q) { if (q.front->next == NULL) return true; return false; } void insert_queue(Queue *q, pVNode e) { /*动态分配一个队列节点*/ queue_node *x = (queue_node *)malloc(sizeof(queue_node)); x->data = e; x->next = NULL; /*将节点插入到队列的尾部*/ q->tail->next = x; q->tail = x; } bool delete_queue(Queue *q, pVNode *e) { if (isempty_queue(*q)) return false; queue_node *temp = q->front->next; *e = temp->data; q->front->next = temp->next; if (temp->next == NULL)//到了队尾 q->tail = q->front; free(temp); return true; }
相关文章推荐
- java多线程入门学习(二)
- 如斯·如诗
- Mysql命令
- 一切又如从前
- 利用NPOI组件操作Excel-数据库与EXCEl之间的操作
- 学习播
- 专门写给yuki同学
- java double类型运算问题
- Java仿12306图片验证码
- android数据库里的query
- Spring和ThreadLocal
- 如何移动CleanMyMac激活码到另一台Mac上
- Let's Encrypt 正式发布,已经保护 380 万个域名
- hadoop文件夹上传问题
- 表单验证插件jQuery.validate 介绍+快速入门案例
- 从网上整理了一些关于比较时间大小和请求网络时间的代码,在这里展示一下
- C语言中的static函数,C++中的静态成员和静态成员函数
- 排序
- 第六周作业----测试自动化工具
- 庸人