- 图的定义: 由顶点的有穷非空集合和顶点之间边的集合组成的数据类型
- 图的表示:
G(V,E)
,G
表示一个图,V
是图G
的顶点集合,E
为图G
的边的集合 图的逻辑结构:
多对多
图的存储结构:
邻接矩阵
邻接表
十字链表
邻接多重表
- 图的一些无聊术语:
- 顶点i与j之间的边无方向,则称此边为无向边(Edge),无向边构成的图成为无向图,无序偶表示
(i,j)
- 若i到j有方向,则叫有向边,也成为弧(Arc),i叫弧尾,j叫弧头,有序偶表示:
<i,j>
- 任意两顶点都存在边的图称为(有向|无向)完全图
- 边少的图成为稀疏图,反之稠密图
- 带权的图称为网
- 以顶点v为头的弧的数目称为v的入度,对应的叫出度
- 路径:从顶点a到顶点b的一个序列叫路径
- 路径长度:路径上的边或者弧的数目
- 连通:从顶点i到顶点j有路径,则称i和j是连通的
- 连通图:图中任何两个顶点都是连通的图
- 顶点i与j之间的边无方向,则称此边为无向边(Edge),无向边构成的图成为无向图,无序偶表示
- 图的创建一般用邻接表或者邻接矩阵,下面依次给出图的邻接矩阵和邻接表的创建及两种遍历
- 图的遍历方式有两种:
深度优先遍历(DFS)
广度优先遍历(BFS)
邻接表创建并遍历图代码(粘贴即可运行):
#include <iostream>
#include <stdlib.h>
using namespace std;
#define maxSize 100
int visit[maxSize];
typedef struct EdgeNode{//边结点(其实就是尾顶点)
int adjvex;//当前顶点下标
int weight;//边的权值
struct EdgeNode *next;//指向下一个边结点的指针
}EdgeNode;
typedef struct VNode{//顶点结点
int data;//数据域
EdgeNode *firstEdg;//指向与之相邻接的边结点
}VNode;
typedef struct AGraph{//图结点
VNode adjList[maxSize];//顶点数组
int numVNode,numEdge;//顶点数和边数
}AGraph;
void createGraph(AGraph &G){//创建无向图 (因为要真正改变G,所以要传引用&)
int numVNode,numEdge,i,j,k;
EdgeNode *s,*p;
printf("请输入顶点个数和边数\n");
scanf("%d %d",&numVNode,&numEdge);
G.numEdge=numEdge;
G.numVNode=numVNode;
printf("请输入顶点\n");
for(i=0;i<numVNode;i++){//输入顶点
scanf("%d",&G.adjList[i].data);
G.adjList[i].firstEdg=NULL;
}
printf("请输入边(vi,vj)的顶点的下标,格式为:i j\n");
for(k=0;k<numEdge;k++){
scanf("%d %d",&i,&j);
s=(EdgeNode *)malloc(sizeof(EdgeNode));
s->adjvex=j;
s->next=G.adjList[i].firstEdg;
G.adjList[i].firstEdg=s;
p=(EdgeNode *)malloc(sizeof(EdgeNode));
p->adjvex=i;
p->next=G.adjList[j].firstEdg;
G.adjList[j].firstEdg=p;
}
}
void dfs(AGraph G,int i){//深度优先遍历顶点i(类似二叉树的前序遍历)
printf("%d ",G.adjList[i].data);//先访问顶点
visit[i]=1;//然后标记被访问顶点
EdgeNode *p;
p=G.adjList[i].firstEdg;
while(p!=NULL){//接着遍历被访问顶点的边表
if(visit[p->adjvex]!=1){//若顶点未访问
dfs(G,p->adjvex);//则递归调用
}
p=p->next;
}
}
void dfsTravering(AGraph G){//dfs方法中只能遍历连通图的所有顶点,而此方法则能遍历有独立顶点的图的所有顶点
for(int i=0;i<G.numVNode;i++){
visit[i]=0;//标记数组初始化
}
printf("深度优先遍历的结果为:\n");
for(int i=0;i<G.numVNode;i++){//遍历所有顶点
if(visit[i]!=1){//未访问的顶点
dfs(G,i);//从下标为0的开始访问
}
}
printf("\n");
}
void bfs(AGraph G,int v){//广度优先遍历下标为v的顶点及和其相邻接的顶点(类似二叉树的广度遍历)
int que[maxSize];//注:队列存放的都是顶点下标
int front,rear;front=rear;//初始化队头队尾指针
int i,j;
EdgeNode *p;//边结点
printf("%d ",G.adjList[v].data);//先访问结点,再标记,接着入队
visit[v]=1;
rear=(rear+1)%maxSize;//注:循环队列进出队都是先移指针再改数据
que[rear]=v;//顶点下标v入队
while(rear!=front){//循环条件:队不为空
front=(front+1)%maxSize;
i=que[front];//出队
p=G.adjList[i].firstEdg;//出队顶点指向的边结点
while(p!=NULL){//遍历出队顶点的边表
if(visit[p->adjvex]!=1){//顶点未曾访问过
printf("%d ",G.adjList[p->adjvex].data);//访问顶点
visit[p->adjvex]=1;//标记
rear=(rear+1)%maxSize;//入队
que[rear]=p->adjvex;
}
p=p->next;//边结点往下走
}
}
}
void bfsTravering(AGraph G){//广度优先遍历(当图不是连通图时需要这个方法)
for(int i=0;i<G.numVNode;i++){
visit[i]=0;//初始化标记数组
}
printf("广度优先遍历结果:\n");
for(int i=0;i<G.numVNode;i++){
if(visit[i]!=1){//不为1说明顶点未曾访问
bfs(G,i);
}
}
printf("\n");
}
int main(int argc, char** argv) {
AGraph G;
createGraph(G);
dfsTravering(G);
bfsTravering(G);
return 0;
}
/* 示例输入: 顶点数:10 边数:13 顶点:0 1 2 3 4 5 6 7 8 9 边下标: 0 2 0 1 1 4 1 3 2 5 2 3 3 4 4 7 4 6 5 7 6 9 7 8 8 9 */
邻接矩阵创建并遍历图代码(粘贴即可运行):
#include <iostream>
#define maxSize 100
#define infinity 65535
using namespace std;
bool visit[100]={false};
typedef struct {//图结点
int vertext[maxSize];//顶点数组
int edge[maxSize][maxSize]; //邻接矩阵(matrix)
int n,e;//图的顶点数和边数
}MGraph;
void createGraph(MGraph &G){//用邻接矩阵创建无向图
int i,j,k,weight;
printf("请输入顶点数和边数\n");
scanf("%d %d",&G.n,&G.e);
printf("请输入顶点:\n");
for(i=0;i<G.n;i++){
scanf("%d",&G.vertext[i]);//输入顶点
}
for(i=0;i<G.n;i++){
for(j=0;j<G.n;j++){//初始化数组
G.edge[i][j]=infinity;
}
}
printf("请输入边的顶点下标i j及边的权重w\n");
for(k=0;k<G.e;k++){
scanf("%d %d %d",&i,&j,&weight);
G.edge[i][j]=weight;
G.edge[j][i]=G.edge[i][j];//无向图的邻接矩阵是对称的
}
}
void dfs(MGraph G,int v){//深度优先遍历算法 (类似前序遍历)
int i;
printf("%d ",G.vertext[v]);//先访问
visit[v]=true;//再标记
for(i=0;i<G.n;i++){//最后遍历所有没访问过且与下标为v的顶点相邻的顶点,递归dfs
if(!visit[i]&&G.edge[v][i]!=infinity){
dfs(G,i);
}
}
}
void dfsTravering(MGraph G){//若不是连通图,则需要一个个检查顶点是否遍历过(连通图不需要,直接调用dfs方法即可)
for(int i=0;i<G.n;i++){
visit[i]=false;
}
for(int i=0;i<G.n;i++){
if(!visit[i]){
dfs(G,i);
}
}
}
void bfs(MGraph G,int v){//广度优先搜索遍历
int i,j;
int que[maxSize];
int front,rear;
front=rear=0;
printf("%d ",G.vertext[v]);
visit[v]=true;
rear=(rear+1)%maxSize;
que[rear]=v;
while(front!=rear){
front=(front+1)%maxSize;
i=que[front];
for(j=0;j<G.n;j++){
if(!visit[j]&&G.edge[i][j]!=infinity){//没有被访问且顶点之间相邻接
printf("%d ",G.vertext[j]);
visit[j]=true;
rear=(rear+1)%maxSize;
que[rear]=j;
}
}
}
}
int main(int argc, char** argv) {
MGraph G;
createGraph(G);
dfs(G,8);
dfsTravering(G);
bfs(G,5);
return 0;
}
/* 实例输入: 顶点数:10,边数:13 顶点:0 1 2 3 4 5 6 7 8 9 边顶点下标及权值: 0 2 4 0 1 3 1 4 6 1 3 5 2 5 7 2 3 8 3 4 3 4 7 4 4 6 9 5 7 6 6 9 2 7 8 5 8 9 3 */
总结
深度优先遍历4部曲(dfs):
- 先访问给定下标为v的顶点
- 再标记已访问顶点下标
- 遍历已访问顶点的边表
- 边表的顶点下标未曾被标记,则递归调用dfs(G,v)
广度优先遍历6部曲(bfs) :
- 先访问给定顶点下标v的顶点
- 再标记下标为v的顶点
- 接着将下标为v的顶点入队
- 然后遍历下标为v的顶点的边表
- 随后将边表中未曾标记的顶点依次入队
- 一个一个出队,重复执行以上步骤