图的基本定义(
有向图/无向图
简单图/完全图,数据结构只讨论简单图
完全图/子图
连通/连通图/连通分量/最大连通分量/最小连通分量/强连通/强连通图/强连通分量/最大强连通分量/最小强连通分量
生成树/生成森林
度/入度/出度
边的权和网
稠密图/稀疏图
路径/路径长度/回路/简单路径/简单回路/距离
有向树
邻接矩阵法:
图的存储结构 有向图 无向图
对于图,它分为顶点还有边;边就是两个顶点的邻接关系,所以说要把图这个东西储存的话,就一定要分别储存点还有边。
图一般意义分为有向图还有无向图;对于前者后者顶点存储方式相同;但是对于后者存储方式是不同的;
那么我们怎么储存呢;
对于点来说,他就是一个整形数据啥的,用一个一维数组就可以了;对于边来说用二维数组了。如下图1。左边有向 右边无向;
网呢 储存结构
我们之前说了,如果给每条边一个数值作为该边的权重:就变成了网;
那么我们该如何存储网呢?同样是利用矩阵和数组;如果该边不存在则存放无穷
举个例子:
网或者图在语言中是如何定义的?
#define MaxVertexNum 100
typedef char VertexType;
typedef int EdgeType;
typedef struct{
VertexType Vex[MaxVertexNum];
EdgeType Edge[MaxVertexNum][MaxVertexnum];
int vexnum,arcnum;
}MGraph;
当一个图相对比较稠密的时候,他适合采用上面一种邻接矩阵的方法 ,但是把,如果说这个图相对比较稀疏的话,那么采用以上算法就是相对比较浪费了,那用现在介绍的这种方法。邻接表法
采用顶点用顺序结构 而边用链式结构存储
储存结构:
#define MaxVertNum 100
typedef struct ArcNode{
int adjvex;
struct ArcNode *next;
//InfoType info;//表示权重
}ArcNode;
typedef struct VNode{
VertexType data;
ArcNode *first;
}VNode,AdjList[MaxVertexNum];
typedef struct{
Adjust vetices;//邻接表
int vexnum,arcnum;
}ALGraph;
对于连接表而言 找到出边是相对来说比较容易的 但是想找到入边来说 就是比较难的 他需要遍历整个表。
那么我们我们引申出一个新的结构
存储结构
#define MaxVertexNum 100;
typedef struct ArcNode{
int tailvex,headvex;
struct ArcNode *hlink,*tlink;
//infotype info;
}ArcNode;
typedef struct VNode{
VertexType data;
ArcNode *firststin,*firstout;
}VNode;
typedef struct{
VNode xlist[MaxVertexNum];
int vexnum,arcnum;
}GLGraph;
你看一个对于这两个储存空间,如果删除它的时候,需要遍历,而且要删除两个,这样的话会是很麻烦的。于是引用了咱们今天学习的。
#define MaxVertNum 100;
typedef struct ArcNode{
int ivex ,jvex;
struct ArcNode *ilink,*jlink;
//infotype info;
//bool mark;
}ArcNode;
typedef struct VNode{
VertexType data;
ArcNode *firstedge;
}VNode;
typedef struct{
VNode adjmulist[MaxVertexnum];
int vexnum,arcnum;
}AMLGraph;
十字链表 邻接多重表
他们都是邻接表的优化;十字链表针对的是有向图,解决了有向图中无法快速找到某一顶点入边的这一弊端。 邻接多重表解决了无向图中每一个边要用两个边表结构来存放的弊端。
Adjacent(G,x,y) 判断图G是否存在边<x,y>或(x,y).
Neighbour(G,x) 列出图G中与结点x相邻的边
InsertVertex(G,x);在图G中插入顶点x
DeleteVertex(G,x)从图G中删除顶点x;
AddEdge(G,x,y);若无向边(x,y)或者<x,y>不存在,则向图中G中添加该边
RemoveEdge(G,x,y)若无向边(x,y)或者有向边<x,y>存在,则在图G中删除该边;
FirstNeighbor(G,x);求图G中顶点X的第一点邻接点,若有则返回顶点号。若没有邻接点或图不存在x,则返回-1;
NextNeighbor(G,x,y) 假设图G中顶点y是顶点x的一个邻接点,返回除了y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1;
Get_edge_value(G,x,y);获取图G中边(x,y)或<x,y>对应的权值v;
Set_edge_value(G,x,y); 设置G中边(x,y)或<x,y>
的权值为v;
从图的某一点出发,按照某种算法,沿着图中的边,对图中所有的顶点进行一次访问(只访问一次)
它和树的层次遍历很像,但是如果又按照层次遍历的算法来 ,一定会出现错误。如下
那么怎么改善呢
算法代码如下:
bool visited[MAX_TREE_SIZE];//定义了一个bool型的辅助标记数组
void BFSTraverse(Graph G){
for(int i=0;i<G.vexnum;++i)
visited[i]=FALSE;
InitQueue(Q);
for(int i;i<G.vexnum;++i)
if(!visited[i])
BFS(G,i);
}
void BFS(Graph G,int v){
visit(v);
visited[v]=TRUE;
Enqueue(Q,v);
while(!isEmpty(Q)){
Dequeue(Q,v);
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
if(!visited[w]){
visited[w];
visited[w]=TRUE;
Enqueue(Q,w);
}
}
}
关于广度优先搜索解决最短路径问题:(不懂)
树的优先遍历
void preorder(treenode *R ){
if(R!=NULL){
visit(R);
while(R还有下一个子树T)
Preorder(T);
}
}
bool visited[MAX_VERTEX_NUM];
void DFS(Graph G,int v){
visit(v);
visited[v]=TRUE;
for(w=FirstNeighbor(G,v);w>=0;w=NextNeihbor(G,v,w))
if(!visited[w]){
DFS(G,w);
}
}
这辅助了一个函数调用栈:利用栈的栈进栈出来看目前正在执行的那个函数,调用了几层函数;
咱们看一看这是连通图用上面的算法就可以了,但是对于非连通图来说,无法遍历所有的结点;
优化:
什么是生成树?
怎么做呢?
有没有更便宜的修路方案呢?
有的话就叫做
void BFS_MIN_Distance(Graph G,int u){
for(i=0;i<G.vexnum;++i){
d[i]=MAX;
path[i]=-1;
}
d[u]=0;
visited[u]=TRUE;
Enqueue(Q,u);
while(!isEmpty(Q)){
Dequeue(Q,u);
for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
if(!vosoted[w]){
d[w]=d[u]+1;
path[w]=u;
visited[w]=TRUE;
Enqueue(Q,w);
}
}
}
代码实现:
//....准备工作,根据图的信息初始化矩阵A和path(如上图)
for(int k=0;k<n;k++){
for(int i=0;i<n;i++){
for(int j=0;j<nj++){
if(A[i][j]>A[i][k]+A[k][j]){
A[i][j]=A[i][k]+A[k][j];
path[i][j]=k;
}
}
}
}
如何寻找路径:
上面有说过D算法解决不了含有负数权的图,但是呢,Floyd 是可以的
有向无环图:若一个有向图中不存在环,则称为有向无环图,简称DAG图。
如下图
如下图右,有环路不能称为有向无环图。
刚才讲解了什么?关于有向无环图如何描述表达式。他们之间的转化
首先是如何通过表达式转化为(有向无环)图。
方法:
step1:把各个操作数不重复的排成一排。
step2:标出各个运算符的生效顺序(先后顺序有点出入无所谓)
step3:按顺序加入运算符,注意分层。
step4:从底向上逐层检查同层的运算符时候可以合体。
举个例子:
把上图转化为有向无环图:(不会看-图章节视频12)
答案如下:
咱们看下一题:
本题按照这个顺序去实现上述操作方法。再按照下述顺序操作一遍。
得到的结果是什么————自己操作,不会看视频。
得到结果如下:
你会发现同一个表达式,按照不同的顺序排序,得到的结果是不唯一的。
所以得出结论:
同一个表达式,可以用不同的图去表示
AOV网使用DAG图表示一个工程。注意A是表示一个活动。也就是顶点表示活动,有向边
定义:在图中,有一个有向无环图的顶点组成的序列。当且仅当满足下列条件时候称之为该图的一个拓扑排序。
1.每个顶点出现且出现一次。
2.若顶点A在排序中排在顶点B的前面,则在图中不存在从顶点B到顶点A的路径。
或者定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。每个AOV图都有一个或者多个拓扑排序序列。
以上是官方定义:也就是说拓扑排序就是排序而已。而且他是只能有向无环图,从前指向后的排序。排序结果可能有多种。
实现:
1.从AOV网中选择一个没有前驱(入度为0)的顶点输出。
2.从网中删除该顶点和所有以他为顶点的有向边。
重复1,2直到AOV为空为止,或者网中不存在无前驱的顶点(说明没有回路)。
那咱们看看对上述工程进行排序:下图是按照顺序从前往后的一种顺序(过程不会看视频图13)
如果图中存在回路怎么办,那么就拓扑排序不下去了,如下图
代码实现:
对于如下一个图,左边是图的结构,右边是图的储存结构。如何用代码实现如下的排序呢?
首先你需定义一个数组indegree[]表示当前各个顶点的度。定义一个输出后的排序数组print[]来记录拓扑排序。再用一个栈或者队列来按一定顺序保存和弹出相关活动获得顺序。
如下:
代码实现
bool topologicalsort(Graph G){//定义了一个bool型号的函数,看返回值返回1就是排序成功
initstack(S);
for(int i=0;i<G.vexnum;i++)
if(indegree[i]==0)
push(S,i);
push(S,i);
int count=0;//其实count表示print[]的数组序号
while(!IsEmpty(S)){
Pop(S,i);
print[count++]=i;
for(p=G.vertices[i].firstarc;p=p->nextarc){//复杂在他是一个邻接表的储存结构没有弄懂。作用是i指向的顶点入度减一,并且将入读为01的顶点压入栈。
v=p->adjvex;
if(!(--indegree[v]))
push(S,v);
}
}
if(count<G.vexnum)
return false;
else
return ture;
}
关于时间复杂度:上述使用了邻接表,几乎每一个点和边都需要遍历一遍。故邻接表的时间复杂度如下,而矩阵存储不一样。每个点都要遍历。
选一个出度为0的顶点输出。其他和上述三步法一模一样,我就不重复了。
对于上面的工程用,逆排序结果也不是唯一的。有回路也不能排序成功。下面是其中一种顺序排法。
关于代码:
题目:用逆拓扑排序解决下图:左边是图,右边是储存结构
如果使用逆邻接表(每个顶点后面链接的是入边,如上图的逆邻接表如下)邻接矩阵对时间复杂度的影响如何?
1、AOV:有向图中,用顶点表示事件,用有向边表示活动之间开始的先后顺序,则称这种有向图为AOV(Activity On Vertex)网络;AOV网络可以反应任务完成的先后顺序(拓扑排序)。
2、AOE:在实际应用中,活动除了先后关系外,还需考虑时间上的约束
在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,边上的权值表示活动的持续时间,称这样的有向图叫做边表示活动的网,简称AOE网
如图:
3、AOE网中只有一个入读为零的顶点称为始点(或源点),只有一个出度为零的顶点称为终点(或汇点)。
4、性质:
⑴ 只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始;
⑵ 只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。
5、完成一个工程所需要的最短时间是从源点到汇点的最长路径长度
关键路径:具有最长长度的路径,这里的路径长度是路径上的各权值之和
注意:关键路径不一定只有一条。
关键活动:关键路径上的活动称为关键活动。(需要满足 ee [i]=el [i] )
!! 关键路径的长度是整个工程所需要的最短工期,要缩短整个工期,必须要加快关键活动的进度
二、求关键活动所需要的参量
⑴ 事件的最早发生时间ve[k] - 从前往后,前驱结点到当前结点所需时间,取最大值。
ve[k]是指从始点开始到顶点vk的最大路径长度。这个长度决定了 所有从顶点vk发出的活动能够开工的最早时间。
⑵ 事件的最迟发生时间 vl[k] - 从后往前,后继结点的最迟发生时间-边权值,取最小值。
vl[k]是指在不推迟整个工期的前提下, 事件vk允许的最晚发生时间。
⑶ 活动的最早开始时间e [i]
若活动ai是由弧
因此,有:e[i]=ve[k]
⑷ 活动的最晚开始时间 l [i]
活动ai的 最晚开始时间是指,在不推迟整个工期的前提下, ai 必须开始的最晚时间。若ai由弧
一般等于l(i)=vl(j)-weight(vk,vj)
求解路径的步骤:
举个例子:
上面是求解路径的的方法:那咱们看一道题目来操作一哈,如下
第一步:
第二步:
第三步:
第四步:
第五步:
==》找到关键路径
1 若关键活动的耗时增加,则整个工程的工期将加长;
2 缩短 关键活动的时间,可以缩短整个工程的工期
3.当缩短到一定程度时候,关键活动可能变成非关键活动
4 可能有多条关键路径,只提高一条关键路径的关键活动速度并不能缩短整个工程的工期,只有加快那些在所有关键路径上的关键活动才能达到缩短工期的目的