定义

图是由有穷非空顶点集合和顶点之间关系——边(或者弧)的集合组成,其形式化定义为:
G=(V,E)
V={ vi | vi ∈ dataobject}
E={ (vi, vj) | vi, vj ∈V 且P(vi, vj) }
其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合,集合E中P(vi,vj)表示顶点vi和顶点vj之间有一条直接连线。

2.图的基本术语
(1)无向边、有向边
(2)无向图、有向图
(3)边、弧、弧头、弧尾
(4)无向完全图:任意两个顶点间都存在边,共有n(n-1)/2条边
(5)有向完全图:共有n(n-1)条边
(6)稠密图、稀疏图:相对而言
(7)顶点的度、入度、出度:TD(v) = ID(v) + OD(v)
(8) 权:与边或弧相关的数据信息
(9) 网图:边或弧上带权的图
(10) 路径、路径长度:路径上边的数目
(11) 回路、简单路径、简单回路:第一个顶点和最后一个顶点相同的路径成为回路或环;序列中顶点不重复出现的路径称为简单路径
(12) 子图
(13) 连通、连通图、连通分量:在无向图中,如果一个顶点vi到另一个顶点vj(i != j)有路径,则称顶点vi和vj是连通的;如果图中任意两个顶点间都有路径存在,则称该图是连通图;如果是非连通无向图,则非连通无向图的极大连通子图称为连通分量
(14) 强连通图、强连通分量:在有向图中,若图中任意一对顶点 vi和vj(i != j)均有从顶点vi到顶点vj的路径,也有从vi到vj的路径,则称该有向图为强连通图。如果是非强连通无向图,则非强连通无向图的极大连通子图称为强连通分量
(15) 生成树:连通图G的生成树,是包含G的全部n个顶点的一个极小连通子图,该极小连通子图必定包含且仅包含G的 (n-1)条边。对于生成树而言,如果在其中任意添加一条属于原图中的边,必定会产生回路。如果在生成树中减少任意一条边,则必然成为非连通的。生成树极小指连通所有点数的边数最少
(16) 生成森林:如果是一张非连通图,必然会有若干连通分量,由每个连通分量都能得到一个极小连通子图,即一个生成树。这些生成树就组成了一个非连通图的生成森林

存储结构

1. 邻接矩阵

数组表示法,用一维数组存储图中顶点信息,用二维数组(矩阵)存储图中各顶点之间的邻接关系
矩阵的元素值为:
A [ i ] [ j ] = { 1 , ( v i , v j ) 或 &lt; v i , v j &gt; 是 E 中 的 边 0 , ( v i , v j ) 或 &lt; v i , v j &gt; 不 是 E 中 的 边 A[i][j] =\begin{cases} 1, (v_i, v_j)或&lt;v_i,v_j&gt;是E中的边\\0, (v_i, v_j)或&lt;v_i,v_j&gt;不是E中的边 \end{cases} A[i][j]={1,(vi,vj)<vi,vj>E0,(vi,vj)<vi,vj>E
若G是带权图(网),则邻接矩阵可以定义为:
A [ i ] [ j ] = { w i j , ( v i , v j ) 或 &lt; v i , v j &gt; 是 E 中 的 边 ∞ , ( v i , v j ) 或 &lt; v i , v j &gt; 不 是 E 中 的 边 A[i][j] =\begin{cases} w_{ij}, (v_i, v_j)或&lt;v_i,v_j&gt;是E中的边\\\infty, (v_i, v_j)或&lt;v_i,v_j&gt;不是E中的边 \end{cases} A[i][j]={wij,(vi,vj)<vi,vj>E,(vi,vj)<vi,vj>E
在这里插入图片描述             在这里插入图片描述
在这里插入图片描述             在这里插入图片描述
邻接矩阵存储方法特点:
(1)无向图的邻接矩阵必是一个对称矩阵,存储邻接矩阵时可采用压缩存储方式,只需存放上(或下)三角矩阵的元素即可;
(2)对于无向图(网),邻接矩阵的第i行(或第i列)非零元素(或非∞元素)的个数是第i个顶点的度TD(vi)
(3) 对于有向图(网),邻接矩阵的第i行非零元素(或非∞元素)的个数是第i个顶点的出度OD(vi)
(4)对于有向图(网),邻接矩阵的第i列非零元素(或非∞元素)的个数是第i个顶点的入度ID(vi)
(5)用邻接矩阵方法存储图(网),根据顶点序号检查矩阵对应值是否非零(或非∞元素)即可确定图(网)中任意两个顶点之间是否有边相连;若想确定图中共有多少条边,则必须按行或按列对矩阵中每个元素进行检测,看是否是非零(或非∞)元素,若是则计数一次。
具体结构描述:

#define MaxVerNum 100  /*最大顶点数设为100*/
typedef char VerType;  /*顶点类型设为字符型*/         
typedef int ArcType;   /*边的权值设为整型*/     
typedef struct {
  	VerType vexs[MaxVerNum];   /*顶点表*/
    ArcType arcs[MaxVerNum][MaxVerNum];  /*邻接矩阵*/
   	int vexnum, arcnum;      /*顶点数和边数*/
}MGragh;   /*MGraph是以邻接矩阵存储的图类型*/                      

建立有向图G的邻接矩阵:

void CreateMGraph(MGraph *G){  /*建立有向图G的邻接矩阵*/
	int i,j,k,w;
    char ch;
    scanf("%d,%d",&(G->vexnum),&(G->arcnum));  /*输入顶点数和边数*/ 
    for (i=0;i<G-> vexnum;i++)  
    	scanf("%c",&(G->vexs[i]));  /*输入顶点信息*/
    for (i=0;i<G-> vexnum;i++)
       	for (j=0;j<G-> vexnum;j++)  
      		G->arcs[i][j]=0;   /*初始化邻接矩阵*/
    for (k=0;k<G-> arcnum;k++){ 
    	scanf("%d,%d",&i,&j);  /*输入每条边对应的两个顶点的序号(输入格式为:i,j)*/
	    G->arcs[i][j]=1; /*若加入G->arcs[j][i]=1,则为无向图的邻接矩阵存储建立*/
    }
}

用邻接矩阵来存储图时,算法时间复杂度只与图中顶点数n相关,与边数e无关,故比较适合于稠密图
邻接矩阵表示法空间需求一般为(n+n2)个空间,其中n代表顶点信息所占空间,n2代表邻接矩阵所占空间。
如果是建立一张带权图,则只需要将以上算法稍加改动,初始化邻接矩阵:当i=j时,令G->arcs[i][j]=0;当(i != j)时,令G->arcs[i][j]=∞;当i、j间存在边(弧)时,将G->arcs[i][j]=1;改为G->arcs[i][j]=权值即可。

2. 邻接表

邻接表(Adjacency List) 是一种将顺序存储与链式存储相结合的存储方法。
边表:为图中每个顶点都建立一个单链表,即对于图G中的每个顶点vi,将vi的所有邻接点vj都链在一个单链表里,该单链表称为顶点vi的邻接表。【链式存储结构】
顶点表:将所有顶点的邻接表表头集中放到一个一维数组中,两者一起就构成了图的邻接表结构。【顺序存储结构】
在这里插入图片描述在这里插入图片描述
带权图(网),边表中还需要保存每条边(弧)的权值:
在这里插入图片描述
在这里插入图片描述          在这里插入图片描述

#define MaxVerNum 100  /*最大顶点数为100*/
typedef struct node{   /*边表结点*/
   	int adjvex;        /*邻接点域*/           
   	struct node *nextadj;  /*指向下一个邻接点的指针域*/
   	/*若要表示边信息,则应增加一个数据域infotype info*/       
}EdgeNode;        
typedef struct vnode{   /*顶点表结点*/
   	VerType vertex;      /*顶点域*/    
    EdgeNode *firstedge;  /*边表头指针*/      
}VerNode;       
typedef VerNode AdjList[MaxVerNum];  
typedef struct{  
   	AdjList adjlist;        /*邻接表*/
    int vexnum, arcnum;     /*顶点数和边数*/
}ALGraph;             

void CreateALGraph(ALGraph *G){    /*建立有向图的邻接表*/
 	int i,j,k;
   	EdgeNode *s;   /*边表结点指针*/
   	scanf("%d,%d", &(G->vexnum), &(G->arcnum));  /*输入顶点数和边数*/ 
   	for(i = 0; i < G->vexnum; i++){  
   		scanf("%c", &(G->adjlist[i].vertex));  /*输入顶点信息*/  
	   	G->adjlist[i].firstedge = NULL;       
    }
    for(k = 0; k < G->arcnum; k++){  
      	scanf("%d,%d", &i, &j);    /*输入边的信息*/
        s = (E dgeNode*)malloc(sizeof(EdgeNode));  
	    s->adjvex = j;                      
	    s->nextadj = G->adjlist[i].firstedge;   /*使用头插法*/
	    G->adjlist[i].firstedge=s;
   	}
} 

若无向图中有n个顶点、e条边,每条边在邻接表中会出现两次,故该邻接表需n个头结点和2e个表结点。在边稀疏(e<<n(n-1)/2)的情况下,邻接表方式比邻接矩阵节省存储空间。
在无向图的邻接表中,第i个链表中的结点数量即为顶点vi的度TD(vi);在有向图中,第i个链表中的结点个数代表的是顶点vi的出度OD(vi);若想求得vi的入度ID(vi),则必须遍历整个邻接表,找到所有链表中其邻接点域adjvex的值为i的结点的个数代表的就是就是顶点vi的入度,明显效率较低。
为了便于确定顶点的入度,可以建立一张逆邻接表,即对每个顶点vi 建立一个以vi为弧头的邻接点的链表,则第i个链表中的结点个数代表的就是顶点vi的入度ID(vi)。
       在这里插入图片描述              在这里插入图片描述          在这里插入图片描述

3. 图的存储结构比较

(1) 存储表示的唯一性
当图中每个顶点的序号确定后,邻接矩阵表示法将是唯一的;而邻接表的表示法则不是唯一的,因为各边表结点的链接次序取决于建立邻接表的算法和边的输入次序。
(2) 空间复杂度
设图中顶点个数为n,边的数量为e,那么邻接矩阵的空间需求为O(n2),适合于边相对较多的稠密图;邻接表的空间需求为O(n+e),针对于边相对较少的稀疏图。
(3) 时间复杂度
求边的数目:邻接矩阵方式下必须检测整个矩阵,耗费的时间是O(n2);邻接表存储方式下只对每个边表的结点个数计数即可求得e,所耗费的时间是O(e+n),当e < n2时,采用邻接表更节省时间;
判定<vi,vj>是否是图的一条边:邻接矩阵方式下只需判定矩阵中的第i行第j列上的元素是否为零即可,时间复杂度为O(1);邻接表存储方式下需扫描第i个边表,最坏情况下时间复杂度为O(n)。

图的遍历

图的遍历是图的一种基本操作,指从图中的任一顶点出发,对图中的所有顶点访问一次且仅访问一次。
操作时要注意如下问题:
(1) 在图结构中,不能规定谁是首结点,图中可以从任意一个顶点出发来进行遍历操作;
(2) 如果是非连通图,那么从一个顶点出发,只能够访问到它所在的连通分量上的所有顶点,并不能访问完该图的所有结点,因此,遍历过程中需考虑要如何选取下一个出发点来访问图中其余的连通分量;
(3) 在图结构中,可能存在回路,那么在一个顶点被访问之后,很有可能沿回路又回到该顶点,由于不允许重复访问,需考虑如何在遍历操作中来避免此种情况的发生;
(4) 在图结构中,顶点间的关系复杂,一个顶点可以和其它多个顶点相连,当这个顶点被访问过后,如何选取下一个要访问的顶点。

1. 深度优先搜索

Depth_First Search,简记为DFS。从图中某个未被访问过的顶点v出发,首先访问该顶点,然后从v的所有未被访问的邻接点中选择某一个邻接点w访问,再从w的所有未被访问的邻接点中选择某一个邻接点x访问,依此类推,直至图中所有和v有路径相通的顶点都被访问过为止;若此时图中仍有顶点未被访问,则另选图中一个未曾被访问的顶点作为起始点,重复上述过程,直至图中所有顶点都被访问过为止。
在这里插入图片描述
原理:递归
遍历序列:
V1,V2,V4,V5
V1,V2,V4,V8
V1,V3,V6,V7

在遍历过程中一定要区分该顶点是否已被访问过,如果已被访问过就不可再次访问,解决方法为附设一访问标志数组visited[0…n-1]
从图的某一点v出发,递归地进行深度优先搜索的算法:

void DFS(Graph G, int v ){  /*从第v个点出发递归深度优先遍历图G*/
	visited[v] = TRUE;
   	Visit(v);  /*访问第一个顶点*/          
   	for(w = FirstAdjVex(G, v); w ; w = NextAdjVex(G ,v , w))
    	if (!visited[w]) DFS(G , w);   /*对v中尚未访问的邻接点w进行递归调用DFS*/         
}

以邻接表为存储结构对图G进行深度优先搜索:

void DFSAL(ALGraph *G , int j){  /*以j为出发点对邻接表存储的图G进行DFS搜索*/
   	EdgeNode *p;
    visited[j] = TRUE;   /*标记vj已访问*/             
    p = G->adjlist[j].firstedge;  /*取vj边表的头指针*/
   	while(p){     /*依次搜索vj的邻接点va,va=p->adjvex*/
   		if(!visited[p->adjvex])  /*p->adjvex为结点下标*/
	    DFSAL(G, p->adjvex);
        p = p->nextadj;          
    }
}

void DFSTraverseAL(ALGraph *G){  /*深度优先搜索以邻接表存储的图*/
	int i;
    for(i = 0; i < G->vexnum; i++)
	  	visited[i] = FALSE;      /*访问标志数组初始化*/           
    for(i = 0; i < G->vexnum; i++)
	   	if(!visited[i]) DFSAL(G, i);          
}

当用邻接矩阵来存储图的时侯,查找每个顶点的邻接点所需时间共为O(n2) ,其中n为顶点数;当以邻接表来存储图的时侯,查找每个顶点的邻接点所需时间共为O(e),其中e为无向图中边的数量或有向图中弧的数量,找到每个顶点所需时间共为O(n),其总的时间复杂度为O(n+e) 。

2. 广度优先搜索

Breadth_First Search,简记为BFS。从图中某顶点v出发,访问了顶点v后,再依次访问v的所有未曾访问过的邻接点,然后再分别从这些邻接点出发依次访问它们的所有未曾访问过的邻接点,遵循“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问的原则,至图中所有已被访问的顶点的邻接点都被访问到。若还有顶点未被访问,则另选一个未曾被访问的顶点作为新的出发点,重复上述过程,直至图中所有顶点都被访问到为止。
在这里插入图片描述
原理:非递归队列
遍历序列:
V1
      V2,V3
              V3,V4,V5
                      V4,V5,V6,V7
                              V5,V6,V7,V8

从图的某一顶点v出发,非递归地进行广度优先搜索:

void BFSTraver(Graph G , Status(*visit)(int v)){   /*按广度优先搜索非递归遍历图G,使用辅助队列Q和访问标志数组visited*/
    for(v = 0; v < G.vexnum; ++v)
        visited[v] = FALSE;      /*访问标志数组初始化*/
    Init_Que(Q);                /*置空队列Q*/
    if(!visited[v]){                  /*v尚未访问*/
       	In_Que (Q,v);             /*v入队列*/
        while (!Empty_Que (Q)){ 
            Out_Que(Q,u);     /*队头元素出队并置为u*/
            visited[u] = TRUE; 
            visit(u);   /*访问u*/
            for(w = FirstAdjVex(G,u); w; w = NextAdjVex(G,u,w))
                if(!visited[w]) In_Que(Q,w);   
        }
	}    
}
void BFSM(MGraph *G, int m){  
	int i, j;
   	c_SeqQue Q;
   	Init_SeqQue (&Q);
   	printf("visit vertex:V%c\n",G->vexs[m]); /*访问出发点Vm*/
   	visited[m] = TRUE;
   	In_SeqQue(&Q , m);                       /*出发点Vm入队列*/
   	while(!Empty_SeqQue(&Q)){  
   		i = Out_SeqQue(&Q);                    /*Vi出队列*/
       	for(j = 0;j < G->vexnum; j++)          /*依次搜索Vi的邻接点Vj*/
	 	if(G->arcs[i][j] == 1 && !visited[j]){       /*若Vj未访问*/
        	printf("visit vertex:V%c\n", G->vexs[j]);    /*访问Vj */
            visited[j] = TRUE;
   	      	In_SeqQue (&Q,j);               /*访问过的Vj入队列*/
        }
    }
} 

void BFSTraverM(MGraph *G){      /*广度优先遍历以邻接矩阵存储的图G*/
 	int i;
    for(i = 0; i < G->vexnum; i++)
	  	visited[i] = FALSE;       /*访问标志向量初始化*/
     for (i = 0; i < G->vexnum; i++)
	  	if(!visited[i]) BFSM(G, i);    /* vi未访问过,从vi开始BFS搜索*/
} 

当以邻接矩阵来存储图的时侯,每个顶点入队所需时间共为O(n),查找每个顶点的邻接点所需时间共为O(n2),其总的时间复杂度为O(n2);
当以邻接表来存储图的时侯,找到每个顶点所需时间共为O(n),查找每个顶点的邻接点所需时间共为O(e),其总的时间复杂度为O(n+e) 。
广度优先搜索和深度优先搜索不同之处在于搜索策略不同导致对顶点访问的顺序不同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值