面试复习——数据结构(八):图(1)

本文详细介绍了图的基本概念,包括图、顶点、边等,并探讨了无向图、有向图、简单图、完全图等类型。重点讲解了图的存储结构,如邻接矩阵、邻接表、十字链表和邻接多重表,并提供了相应的代码实现。此外,还阐述了深度优先遍历(DFS)和广度优先遍历(BFS)算法,以及它们在图的遍历和应用中的作用,如路径查找、最短路径计算和拓扑排序。
摘要由CSDN通过智能技术生成

基本概念

图结构:任意两个元素之间可能存在关系,图中任意元素之间都可能相关;元素之间的关系可以是任意的
图是二元关系的扩展/Graphs are extensions of relations

关于图的术语

(graph):
顶点(vertex):表示数据元素
(edge) :表示两个顶点v和w之间存在一个关系,用顶点对<v,w>表示
G=(V, E),V是顶点集,为有穷非空集合,E是边集,为有穷集合
子图(subgraph)和生成子图(spanning subgraph)
在这里插入图片描述
在这里插入图片描述
简单图(simple graph):每条边连接两个不同的顶点(即无自环)且没有两条不同的边连接一对相同顶点(即无重边)的图
完全图(complete graph):在每对不同顶点之间恰好有一条边的简单图
多重图(multigraph):有多重边连接到同一对顶点的图

权重(weight):与图的边/弧相关的数,可以表示从一个顶点到另一个顶点的距离或耗费
带权图或网(network) :图上每个边(或弧)都附加一个权值

  • 规则网络
  • 复杂网络(Complex Network):将统计物理学思想引入图论
    随机网络、小世界网络、无尺度网络

图的分类:
按照边可以分为有向图和无向图:

  • 有向图(DG, directed graph, digraph):顶点对<v,w>的v和w之间是有序的
    在有向图中,若从顶点v到顶点w有一条有向边(directed edge)/弧(arc),那么,v称为弧尾(tail)或初始点(initial node),w称为弧头(head)或终点(terminal node)
  • 无向图(UDG, undirected graph, undigraph):顶点对<v,w>的v和w之间是无序的,即:若有(v, w)ϵE,则必有 (w, v)ϵ E

按照边/弧的数量可以分为稀疏图和稠密图:

  • 稀疏图(sparse graph):有很少边或弧的图( e < n l o g 2 ⁡ 𝑛 e<n log_2⁡𝑛 e<nlog2n) 的图,反之称为稠密图(dense graph)

路径:

  • 对无向图G=(V, E),若从顶点vi经过若干条边能到达vj,称顶点vi和vj是连通的,又称顶点vi到vj有路径
  • 对有向图G=(V, E),从顶点vi到vj有有向路径,指的是从顶点vi经过若干条有向边(弧)能到达vj
  • 路径上边或有向边(弧)的数目称为该路径的长度
    在这里插入图片描述
  • 在一条路径中,若没有重复相同的顶点,该路径称为简单路径 (simple path)
  • 第一个顶点和最后一个顶点相同的路径称为回路(circuit, 环cycle)
    在一个回路中,若除第一个与最后一个顶点之外,其余顶点不重复出现的回路称为简单回路(简单环)

连通图:

  • 无向图 G=(V, E),若∀vi,vj ∈V,vi和vj都是连通的(指从vi到vj有路径存在),则称图G是连通图(connected graph),否则称为非连通图
    • 连通分量:非连通的无向图中的极大连通子图
    • 极小连通子图(minimal connected subgraph):无向连通图的生成树
      生成:覆盖图上所有顶点
      树:连通各个顶点但无环,加边的话,出现单环,删边的话,各个顶点不连通
  • 有向图 G=(V, E),若∀vi ,vj ∈V,都有以vi为起点, vj 为终点以及以vj为起点,vi为终点的有向路径,称图G是强连通图 (strongly connected graph),否则称为非强连通图
    • 强连通分量:非强连通的有向图中的极大连通子图
    • 有向图的生成森林:由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交有向树的弧
    • 有向树是只有一个顶点的入度为0 ,其余顶点的入度均为1的有向图

重连通图:

  • 若从一个连通图中删去任何一个顶点及其相关联的边,它仍为一个连通图的话,则该连通图被称为重(双)连通图(biconnected graph)
  • 连通图中的某个顶点和其相关联的边被删去之后,该连通图被分割成两个或两个以上的连通分量,则称此顶点为关节点(articulation point)/割点(cut point)
  • 没有关节点的连通图为重连通图
    在这里插入图片描述
    其他:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

图的基本操作

在这里插入图片描述
在这里插入图片描述

图的存储结构

图的常用的存储结构有:数组(邻接矩阵)、邻接表、十字链表、邻接多重表
在这里插入图片描述

邻接矩阵/数组表示

对于有n个顶点的图:
用一维数组vexs[n]存储顶点信息
用二维数组A[n][n]存储顶点之间关系的信息
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//图的种类:UDG无向图,DG有向图,UDN无向网,DN有向网
#define MaxVertexNum 30
typedef enum {UDG, DG, UDN, DN} GraphKind;
typedef struct ArcCell {
	VRType adj; //顶点关系类型:w/1/0
	InfoType *info;
}ArcCell, AdjMatrix[MaxVertexNum][MaxVertexNum];
typedef struct{
    int vexnum,arcnum; 	//顶点数,边数,
    GraphKind kind; 	//图的种类
    char vexs[Max];	//存放顶点信息
    int A[Max][Max];	//存放边的信息
} MGraph;

// 构造无向图
void CreateGraph(MGraph *g) { int i,j,k;
	printf("Input: Vex Num & Arc Num & Arc Kind\n");
	scanf("%d %d %d",&g->vexnum,&g->arcnum,&k);
	switch (k) { 
		case 0:     g->kind=UDG;break;
		case 1:     g->kind=DG; break; }
	for(k=0;k<g->vexnum;k++)     
		g->vexs[k]='A'+k;
	for(i=0;i<g->vexnum;i++)
	    for(j=0;j<g->vexnum;j++)
	    	g->A[i][j] = 0; // 初始化邻接矩阵
	printf("Input: %d edges: \n",g->arcnum);
	for(k=0;k<g->arcnum;k++) {
	    scanf("%d %d",&i,&j);
	    g->A[i][j]=1;
	    if(g->kind==UDG) g->A[j][i]=1; //无向图时,邻接矩阵是对称的
    }
}

// 输出无向图
void ListGraph(MGraph *g){
	int i,j;
	for(i=0;i<g->vexnum;i++) {
    	printf("%6c---",g->vexs[i]);
    	for(j=0;j<g->vexnum;j++)
        	printf("%4d",g->A[i][j]);
    	printf("\n");
    }
}

在这里插入图片描述

邻接表表示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#define MAX_VERTEX_NUM 30
typedef char ElemType;
typedef struct node {
    int vindex; 	//邻接点在表头结点数组中的位置(下标)
    struct node *next; //指向下一个表结点
} NodeLink; // 表结点类型定义

typedef struct {
    // 图的顶点数、边数、种类标志
    int vexnum,edgenum,kind; 
    struct {
        ElemType vertex;
        NodeLink *first;// 指向第一个表结点
       } v[MAX_VERTEX_NUM];
} AGraph;

// 用邻接表构造无向图
void CreateGraph(AGraph *g,int n,int m)
{
	int i,e=0; NodeLink *p,*q,*s; char x,y;
	g->vexnum=n; g->kind=m;
	for(i=0;i<n;i++) {//初始化邻接表
	    g->v[i].vertex = 'A'+i; 
	    g->v[i].first=NULL;}
	printf("Input edges x-->y:");  			
	scanf("%c%c",&x,&y);
	while(x!='X' && y!='X'){ 
		e++;
	    //生成表结点并插入邻接表
	    s=(NodeLink *)malloc(sizeof(NodeLink));
	    s->vindex= y-'A';
	    if(g->v[x-'A'].first == NULL) { 
	        g->v[x-'A'].first =s;  s->next = NULL; 
	    }else {
	        p=g->v[x-'A'].first; q=p->next;
	        while (q!=NULL){
	            p=q;q=q->next;  }
	        p->next=s;s->next=q;
	    }
		if(!g->kind){ //无向图
	    	s=(NodeLink *)malloc(sizeof(NodeLink));
	    	s->vindex = x-'A';
	    	if(g->v[y-'A'].first == NULL) {
	        	g->v[y-'A'].first =s;s->next = NULL;
	        }else {
	        	p=g->v[y-'A'].first; q=p->next;
	        	while(q!=NULL) {p=q;q=q->next;}
	        	p->next =s;s->next=q;
	        }
		}
		scanf(" %c%c",&x,&y);
		}
	g->edgenum =e;
}
// 输出无向图
void ListGraph(AGraph *g) {
	int i; NodeLink *p;
	for(i=0;i<g->vexnum;i++){
	    printf("%d:%c--->",i,g->v[i].vertex);
	    p=g->v[i].first;
	    while(p) {
	        printf("%3d",p->vindex);
	        p=p->next;}
	    printf("\n");
    }
}

在这里插入图片描述
在这里插入图片描述

//确定一个顶点在v数组中的位置
int LocateVex(AGraph *g,ElemType u){
	int k;
	for(k=0;k<g->vexnum;k++)
	    if (g->v[k].vertex == u) return k;
	return -1; //图中无此顶点
}
//返回顶点x的第一个邻接顶点
int FirstAdjVex(AGraph *g, int x){
	NodeLink *p;
	p=g->v[x].first;
	if(p) return p->vindex;
	else return  -1;
}

//返回顶点x的(相对于y的)下一个邻接顶点
int NextAdjVex(AGraph *g, int x, int y){
	if (x== -1) return -1;
	NodeLink *p=g->v[x].first;
	while (p!=NULL && p->vindex !=y) p=p->next;
	if(p!=NULL && p->next!=NULL) return p->next->vindex;
	return -1;
}

在这里插入图片描述

(有向图)十字链表

在这里插入图片描述
在这里插入图片描述

#define MAX_VERTEX_NUM  30 //最大顶点数
typedef char ElemType;
typedef struct ArcBox {
    int  tailvex, headvex;//尾结点和头结点在图中的位置
    //InfoType    info;   	// 与弧相关的信息 如权值
    struct ArcBox *hlink,
                  	*tlink; 	//分别链接弧头相同和弧尾相同的弧
} ArcNode; //弧结点
typedef struct VexNode {
    ElemType  data;    	// 顶点信息
    ArcBox  *firstin,
	    *firstout; 	//分别指向该顶点第一条入弧和出弧
} VexNode;//顶点结点
typedef struct {
    int vexnum, arcnum;
    VexNode  xlist[MAX_VERTEX_NUM];
} OLGraph;

// 用十字链表构建有向图
void CreateGraph(OLGraph *g) { 
	// G.kind = DG
	int i,j,k; char v1,v2; struct ArcBox *p;
	scanf("%d %d",&g->vexnum, &g->arcnum);
	for(i=0; i<g->vexnum; ++i) { //构造表头向量
	         g->xlist[i].data='A'+i;    //设置顶点值
	         g->xlist[i].firstin = g->xlist[i].firstout = NULL;
	         }
	for(k=0; k<g->arcnum; ++k) { //输入各弧并构造十字链表
	        scanf(" %c%c",&v1, &v2); //输入一条弧的始点和终点
	        i=LocateVex(g,v1); j=LocateVex(g,v2); //确定v1和v2在g中位置
	        p=(ArcBox *) malloc (sizeof (ArcBox)); //假定有足够空间
	        p->tailvex=i; p->headvex=j;
	        p->hlink=g->xlist[j].firstin; p->tlink=g->xlist[j].firstout;
	        g->xlist[j].firstin = g->xlist[i].firstout = p; //在入弧和出弧链头插入
	        }
	 return; 
}

(无向图)邻接多重表

在这里插入图片描述
在这里插入图片描述

#define MAX_VERTEX_NUM  30  //最大顶点数
typedef  emnu {unvisited , visited}  VisitIf;
typedef struct Ebox {
	VisitIf  mark;    //访问标记
	int  ivex, jvex; //该边依附的两个结点在图中的位置
	//分别指向依附于这两个顶点的下一条边
	struct EBox  *ilink, *jlink;
	InfoType    info  ;       //与边相关的信息, 如权值
}EBox ;    //边结点
typedef struct VexBox { 
	VertexType  data;     //顶点信息
	EBox  *firsedge ;      //指向依附于该顶点的第一条边
}VerBox;    //顶点结点
typedef struct {
	int vexnum,edgenum; //无向图的顶点数和边数
	VerBox adjmulist[MAX_VERTEX_NUM]; 
}AMGraph ;

访问图的顶点:深度优先遍历,广度优先遍历

图的遍历:从图的某一顶点出发,访问图中的其余顶点,每个顶点仅被访问一次
可以采用数据结构是(正)邻接链表,算法采用深度优先搜索和广度优先搜索

#define MAX_VERTEX_NUM 30
typedef char ElemType;
typedef struct node {
    int vindex; 	//邻接点在表头结点数组中的位置(下标)
    struct node *next; //指向下一个表结点
} NodeLink; // 表结点类型定义

typedef struct {
    // 图的顶点数、边数、种类标志
    int vexnum, edgenum, kind; 
    struct {
        ElemType vertex;
        NodeLink *first;// 指向第一个表结点
       } v[MAX_VERTEX_NUM];
} AGraph;

深度优先搜索 Depth First Search DFS

在这里插入图片描述
在这里插入图片描述

// DFS的递归实现
int visited[MAX_VERTEX_NUM]; //访问标志数组 
//Status (* VisitFunc)(int v); //函数变量
//从第x个顶点出发递归地深度优先遍历图g
void DFS(AGraph *g,int x) {
	NodeLink *p;
	visited[x]=1; printf("%3c",g->v[x].vertex);
	//VisitFunc(v); //访问第v个顶点
	p=g->v[x].first;
	while(p){
	    if(!visited[p->vindex]) //对x的尚未访问的邻接顶点
			DFS(g,p->vindex);
	    p=p->next; 
	}
}

//对图G作深度优先遍历
void DFSGraph(AGraph *g){
	int i;
	for(i=0;i<g->vexnum;i++)     
		visited[i]=0; //访问标志数组初始化
	for(i=0;i<g->vexnum;i++)
	    	if(!visited[i]) DFS(g,i);
} 

广度优先搜索 Breadth First Search BFS

在这里插入图片描述
在这里插入图片描述
BFS递归实现

void BFS(AGraph *G, Status (*Visit)(int v )) {
	//使用辅助队列Q,保存已访问过的顶点
	//使用访问标志数组visited,用于标记图中顶点是否被访问过
	LinkedQueue Q;
	QElemType v,w,u; 
	for (v=0; v<G->vexnum; ++v) visited[v] = FALSE;
	InitQueue(&Q); //置空的辅助队列Q 
	for (v=0; v<G->vexnum; ++v) 
		if (!visited[v]) {// v尚未访问
			visited[v] = TRUE;  Visit(v); // 访问v 
			EnQueue(&Q, v); // v入队列
			while (!IsQueueEmpty(&Q)) { 
			  DeQueue(&Q,&u); //队头元素出队并置为u 
			  for (w=FirstAdjVex(G, u); w>=0; 
					w=NextAdjVex(G, u, w)) 
			          if (!visited[w]) { 
				// u的尚未访问的邻接顶点w入队列Q 
				visited[w] = TRUE; 
				Visit(w); 
				EnQueue(&Q, w); 
			          }//if 
			}//while
	  }//if 
} // BFS

BFS非递归实现:

void BFS(AGraph *g,int x) {
	// 用一个数组q作辅助队列, q[0..front)存放的是访问过的顶点, 
	// q[front..rear)存放的是已访问顶点的相邻点,是马上要访问的顶点
	int q[MAX_VERTEX_NUM],front,rear,i; 
	NodeLink *p;
	front=rear=0; q[rear++]=x;
	while(front != rear) //队列非空
	{ //顶点出队列,并访问它
	    x=q[front++]; printf("%c->",g->v[x].vertex); visited[x]=1;
	    p=g->v[x].first;
	    while(p!=NULL) {
	        for(i=0;i<rear;i++) //判邻接点是否在数组q中
	            if(p->vindex == q[i]) break;
	        if(i >=rear)//邻接点未被访问且不在队列中,则入队列
	            q[rear++]=p->vindex;
	        p=p->next; // 找x的下一个邻接点
	    }
	 }
}
void BFSGraph(AGraph *g){
	int i;
	for(i=0;i<g->vexnum;i++)
	    visited[i]=0;
	for(i=0;i<g->vexnum;i++)
	    if(!visited[i]) BFS(g,i);
}

排序(有向)图的顶点:DAG/AOV网的拓扑排序

图遍历的应用

顶点之间的可达性检测
顶点之间的路径求解
顶点之间的最短距离
拓扑排序

图的连通性检测
连通图的生成树,非连通图的生成森林
有向图的强连通分量
重连通图/连通图关节点的判定
欧拉回路

求从一顶点到另一顶点的一条简单路径

在这里插入图片描述

void DFSPath( int v, int s, char *PATH)  {
 	//从第v个顶点出发递归地深度优先遍历图G,
	//求得一条从v到s的简单路径,并记录在PATH中  
	visited[v] = TRUE;   // 访问第 v 个顶点
	Append(PATH, getVertex(v));   // 第v个顶点加入路径
	for (w=FirstAdjVex(v);  w!=0&&!found;w=NextAdjVex(v) )
		if (w=s) { found = TRUE;  Append(PATH, w); }
		else if (!visited[w])  DFSearch(w, s, PATH);
	if (!found)  Delete (PATH, v); // 从路径上删除顶点 v
}

求两个顶点之间的一条最短路径

在这里插入图片描述
为了记录路径(通过prior指针记录路径)

  • 将链队列的结点改为“双链”结点:结点中包含next 和prior两个指针;
  • 修改入队列的操作:插入新的队尾结点时,令其prior域的指针指向刚刚出队列的结点,即当前的队头指针所指结点;
  • 修改出队列的操作:出队列时,仅移动队头指针,而不将队头结点从链表中删除
typedef  DuLinkList QueuePtr; 
void InitQueue(LinkQueue& Q) {
  Q.front = Q.rear = new QNode;
  Q.front->next = Q.rear->next = NULL;
}
void EnQueue( LinkQueue& Q, QelemType e ) {
  p =  new QNode;
  p->data = e;  p->next = NULL;
  p->prior = Q.front;
  Q.rear->next = p;  Q.rear = p;
}
void DeQueue( LinkQueue& Q, QelemType& e ) {
  Q.front = Q.front->next;  e = Q.front->data
}

拓扑排序(图的顶点排序)——仅限于有向无环图

在这里插入图片描述

AOV网

AOV网(Activity On Vertex Network):在有向图中,用顶点表示活动,用有向边表示活动之间的优先关系
DAG/AOV网的拓扑排序:构造顶点的一个拓扑线性序列(v’1,v’2, ⋯,v’n),使得该线性序列不仅保持原来有向图中顶点之间的优先关系,而且对原图中没有优先关系的顶点之间也建立一种(人为的)优先关系
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值