广州营销型网站建设培训班,学网站建设app,做视频点播网站,网站安全管理制度建设一、定义
对于图的定义#xff0c;我们需要明确几个注意的地方:一线性表中我们把数据元素叫元素#xff0c;树中叫结点#xff0c;在途中数据元素我们则称之为顶点(Vertex)。
对于图的定义#xff0c;我们需要明确几个注意的地方:
线性表中我们把数据元素叫元素#xf…一、定义
对于图的定义我们需要明确几个注意的地方:一线性表中我们把数据元素叫元素树中叫结点在途中数据元素我们则称之为顶点(Vertex)。
对于图的定义我们需要明确几个注意的地方:
线性表中我们把数据元素叫元素树中叫结点在图中数据元素我们则称之为顶点(Vertex)。线性表可以没有数据元素称为空表,树中可以没有结点叫做空树而图结构在咱国内大部分的教材中强调顶点集合V要有穷非空。线性表中相邻的数据元素之间具有线性关系树结构中相邻两层的结点具有层次关系而图结构中任意两个顶点之间都可能有关系顶点之闻的逻辑关系用边来表示边集可以是空的。
1、各种名词的定义
无向边:
若顶点Vi到V之间的边没有方向则称这条边为无向边(Edge)用无序偶(Vi,Vj)来表示 有向边:
若从顶点Vi到Vj的边有方向则称这条边为有向边也成为弧(Arc)用有序偶Vi,Vj来表示,Vi称为弧尾Vj称为弧头。 简单图:
在图结构中,若不存在顶点到其自身的边,且同一条边不重复出现,则称这样的图为简单图。 无向完全图:
在无向图中如果任意两个顶点之间都存在边则称该图为无向完全图。含有n个顶点的无向完全图有n*(n-1)/2条边。 有向完全图:
在有向图中如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。含有n个顶点的有向完全图有n*(n-1)条边。
稀疏图和稠密图:
这里的稀疏和稠密是模糊的概念都是相对而言的通常认为边或弧数小于n*logn (n是顶点的个数的图称为稀疏图,反之称为稠密图。
有些图的边或弧带有与它相关的数字这种与图的边司弧相关的数叫做权(weight)带权的图通常称为网(Network)。 子图
假设有两个图G1(V1,E1)和G2(V2,E2)如果V2cV1E2EE1则称G2为G1的子图(Subgraph)。 2、顶点与边的关系
邻接点
对于无向图G(V,E)如果边(V1,V2)E则称顶点V1和V2互为邻接点(Adjacent) 即V1和V2相邻接。边(V1,V2)依附(incident)于顶点V1和V2或者说边(V1,V2)与顶点V1和V2相关联。
度
顶点V的度(Degree)是和V相关联的边的数目记为TD(V)如下图顶点A与B互为邻接点边(A,B)依附于顶点A与B上,顶点A的度为3。 邻接到顶点
对于有向图G(V,E)如果有V1,V2E则称顶点V1邻接到顶点V2顶点V2邻接自顶点V1。
度入度与出度
以顶点V为头的弧的数目称为V的入度(InDegree)记为ID(V),以V为尾的弧的数目称为V的出度(OutDegree),记为OD(V)因此顶点V的度为TD(V)ID(V)OD(V). 路径
无向图G(V,E)中从顶点V1到顶点V2的路径(Path)。 如果G是有向图则路径也是有向的。 下图用红线列举顶点B到顶点D的两种路径而顶点A到顶点B就不存在路径啦:
路径的长度
路径的长度是路径上的边或弧的数目。
回路或环
第一个顶点到最后一个顶点相同的路径称为回路或环(cycle)。
序列中顶点不重复出现的路径称为简单路径除了第一个顶点和最后一个顶点之外其余顶点不重复出现的回路称为简单回路或简单环。 3、连通图
在无向图G中如果从顶点V1到顶点V2有路径则称V1和V2是连通的如果对于图中任意两个顶点Vi和Vj都是连通的,则称G是连通图(ConnectedGraph) 连通分量
无向图中的极大连通子图称为连通分量。注意以下概念:
首先要是子图并且子图是要连通的;连通子图含有极大顶点数;具有极大顶点数的连通子图包含依附于这些顶点的所右边 上图的图1是一个无向非连通图。但是它有两个连通分量即图2和图3.而图4尽管是图1的子图但它却不满足连通子图的极大顶点数。因此它不是图1的无向图的连通分量。
强连通图
在有向图G中如果对于每一对Vi到Vj都存在路径则称G是强连通图。
有向图中的极大强连通子图称为有向图的强连通分量。
连通图的生成树
所谓的一个连通图的生成树是一个极小的连通子图它含有图中全部的n个顶点但只有足以构成一棵树的n-1条边。 右图就是一个生成树
有向树
如果一个有向图恰有一个顶点入度为0其余顶点的入度均为1则是一棵有向树。 二、图的存储结构
因为任意两个顶点之间都可能存在联系因此无法以数据元素在内存中的物理位置来表示元素之间的关系内存物理位置是线性的图的元素关系是平面的。
下面有几种存储结构
一邻接矩阵
1、邻接矩阵无向图
考虑到图是由顶点和边或弧两部分组成合在一起比较困难那就很自然地考虑到分为两个结构来分别存储。
顶点因为不区分大小、主次所以用一个一维数组来存储是狠不错的选择。
而边或弧由于是顶点与顶点之间的关系一维数组肯定就搞不定了那我们不妨考虑用一个二维数组来存储。 图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。 我们可以设置两个数组顶点数组为vertex[4]{vO, V1,V2,V3}边数组arc[4][41为对称矩阵(0表示不存在顶点间的边1表示顶点间存在边)。
对称矩阵:所谓对称矩阵就是n阶矩阵的元满足a[i][j]a[j][i](0i,jn)。即从矩阵的左上角到右下角的主对角线为轴右上角的元与左下角相对应的元全都是相等的。
2、邻接矩阵有向图
无向图的边构成了一个对称矩阵貌似浪费了一半的空间那如果是有向图来存放会不会把资源都利用得很好呢? 可见顶点数组vertex[4]{v0, V1,V2,V3}弧数组arc[4][4]也是一个矩阵但因为是有向图所以这个矩阵并不对称例如由V1到V0有弧得到arc[1][0]而V0到V1没有弧因此arc[0][1]0。
另外有向图是有讲究的要考虑入度和出度,顶点V1的入度为1正好是第V1列的各数之和顶点V1的出度为2正好是第V2行的各数之和。
3、邻接矩阵(网)
在图的术语中我们提到了网这个概念事实上也就是每条边上带有权的图就叫网。 这里“”表示一个计算机允许的、大于所有边上权值的值。
#includestdio.h
#includestdlib.h
#define MAXSIZE 100
//无向图的邻接矩阵
typedef struct
{char data[MAXSIZE];//顶点数组int arc[MAXSIZE][MAXSIZE];//邻接矩阵int vexnum, edgenum;//当前的定点数和边数}MGraph;
//图的邻接矩阵的创建
void CreateMgrapg(MGraph *p)
{int i, j, k;printf(输入定点数和边数);scanf(%d %d, p-vexnum,p-edgenum);//输入顶点数和边数printf(输入所有顶点的信息);for (i0; ip-vexnum;i){scanf( %c,p-data[i]);}for (i 0;i p-vexnum;i){for (j 0;jp-vexnum;j){p-arc[i][j] 0;}}for (k0; k p-edgenum; k){printf(输入第%d边的两个顶点的序号,格式i j,k1);scanf(%d %d, i, j);p-arc[i][j] 1;p-arc[j][i] 1;//去掉这个可以表示有向图}
}
void print(MGraph g)
{int i,j;for (i 0; i g.vexnum; i){for (j 0; j g.vexnum; j){printf(%-3d, g.arc[i][j]);}printf(\n);}
}
int main()
{MGraph g;CreateMgrapg(g);print(g);return 0;
}
二邻接表
1、邻接表无向图
把数组与链表结合一起来存储这种方式在图结构也适用,我们称为邻接表(AdjacencyList)。 邻接表的处理方法是这样: 图中顶点用一个一维数组存储当然,顶点也可以用单链表来存储不过数组可以较容易地读取顶点信息更加方便。图中每个顶点Vi的所有邻接点构成一个线性表,由于邻接点的个数不确定所以我们选择用单链表来存储。 2、邻接表有向图
若是有向图邻接表结构也是类似的,我们先来看把顶点当弧尾建立的邻接表,这样很容易就可以得到每个顶点的出度: 但也有时为了便于确定顶点的入度或以顶点为弧头的弧,我们可以建立一个有向图的逆邻接表: 此时我们很容易就可以算出某个顶点的入度或出度是多少,判断两顶点是否存在弧也很容易实现。
3、邻接表网
对于带权值的网图可以在边表结点定义中再增加一个数据域来存储权值即可: #include stdio.h
#include stdlib.h
#define MAXSIZE 100
//有向图邻接表的存储
typedef char VertexType;
typedef struct node{//定义边表结点int adjvex;//邻接点域struct node *next;//指向下一个邻接点域的指针域
}EdgeNode;
typedef struct vexnode{//定义顶点表结点VertexType data;//顶点域EdgeNode *firstedge;//指向第一条边结点
}VHeadeNode;
typedef struct{VHeadeNode adjlist[MAXSIZE];/*邻接表头结点数组*/int n,e;//顶点数边数
}AdjList;//有向图邻接表的创建
void GreateAGraph(AdjList *g){int i,j,k;printf(请输入图的顶点数和边数);scanf(%d %d,g-n,g-e);printf(请输入图的所有顶点信息);for(i0;ig-n;i){scanf( %c,(g-adjlist[i].data));//给图的每个结点的顶点域赋值g-adjlist[i].firstedge NULL;//首先点的边表头指针都设为空}EdgeNode *p;for(k0;kg-e;k){printf(请输入第%d条边对应的两个顶点的序号,格式i j:,k1);scanf(%d %d,i,j);p (EdgeNode*)malloc(sizeof(EdgeNode));p-adjvex j;p-next g-adjlist[i].firstedge;//头插法g-adjlist[i].firstedge p;}
}
//邻接表的输出
void printadj(AdjList *g){int i;EdgeNode *p;for(i0;ig-n;i){printf(%2d [%c],i,g-adjlist[i].data);p g-adjlist[i].firstedge;while(p!NULL){printf(--[%d],p-adjvex);p p-next;}printf(\n);}
}
int main() {AdjList G;GreateAGraph(G);//创建有向图printadj(G);return 0;
}
三十字链表有向图
结构 即弧起点的下标 弧终点的下标 入度的指针 出度的指针
蓝线出度 红线入度 十字链表的好处就是因为把邻接表和逆邻接表整合在了一起这样既容易找到以Vi为尾的弧,也容易找到以Vi为头的弧因而容易求得项点的出度和入度。
四邻接多重表
结构 其中iVex和jVex是与某条边依附的两个顶点在顶点表中的下标。iLink指向依附顶点iVex的下一条边jLink指向依附顶点jVex的下一条边。
也就是说在邻接多重表里边边表存放的是一条边而不是一个顶点。 五边集数组
边集数组是由两个一维数组构成一个是存储顶点的信息另一个是存储边的信息这个边数组每个数据元素由一条边的起点下标(begin)、终点下标(end)和权(weight)组成。 三、图的遍历
谈到图的遍历,那就复杂多了因为它的任一顶点都可以和其余的所有顶点相邻接,因此极有可能存在重复走过某个顶点或漏了某个顶点的遍历过程。
对于图的遍历如果要避免以上情况,那就需要科学地设计遍历方案通常有两种遍历次序方案:化们是深度优先遍历DFS和广度优先遍历(BFS)。
一深度优先遍历
深度优先遍历(DepthFirstSearch)也有称为深度优先搜索简称为DFS。
我们可以约定右手原则:在没有碰到重复顶点的情况下,分叉路口始终是向右手边走每路过一个顶点就做一个记号。 这里规定的就是去小的当然也可以规定先去大的
那么如何实现呢
1、邻接矩阵递归 #include stdio.h
#include stdlib.h
#define MAXSIZE 100 //最大顶点数
#define MAX_INT 32767//设置最大值
typedef struct{char vexs[MAXSIZE];//这里的数据类型根据实际情况而定int arcs[MAXSIZE][MAXSIZE];//这里的数据类型根据实际情况而定int vexnum, arcnum;//图的当前顶点数和边数
}Graph;
int get_index(char* arr,char m)
{int i 0;for(i 0; i MAXSIZE; i){if(arr[i] m)return i;}return -1;
}void CreatGraph(Graph* G)
{int i,j 0;printf(请输入顶点和边的数量:);scanf(%d%d,G-vexnum,G-arcnum);//把输入的值保存到图结构体中for(i 0; i G-vexnum; i)//初始化邻接矩阵{for(j 0; j G-vexnum; j){G-arcs[i][j] 0;}}for(i 0; i G-vexnum; i){printf(请输入每个顶点的值:);getchar();//清除键盘缓存区的回车键scanf(%c, G-vexs[i]);//把输入的值保存到顶点数组当中}for(i 0; i G-arcnum; i)//有几条边循环几次{char m,n;//用来接收两个顶点int j,k;//接收顶点的下标printf(请依次输入每一条边格式为:ab );getchar();scanf(%c%c,m,n);j get_index(G-vexs,m);//得到输入的m的值在顶点数组中的下标k get_index(G-vexs,n);//得到输入的n的值在顶点数组中的下标G-arcs[j][k] 1;//给邻接矩阵对应的位置赋权值G-arcs[k][j] 1;//因为是无向网所以是对称的给对称点赋相同值}}
//深度遍历创建的无向图
void DepthSearch(Graph G, int v, int*visited)//参数为创建的图输入的值在数组中的下标判断是否被访问过的数组
{int i 0;visited[v] 1;printf(%c, G.vexs[v]);for(i 0; i G.vexnum; i)//遍历二维数组v行的每一列{if((G.arcs[v][i] 1)(visited[i]!1))//如果有边相连而且该顶点还没有被访问过DepthSearch(G, i,visited);//递归搜索该顶点}
}
int main()
{Graph G;CreatGraph(G);char input;int visited[MAXSIZE] {0};//创建数组用来判断某一个顶点是否被访问过printf(请输入搜索的起始点:);getchar();scanf(%c,input);DepthSearch(G, get_index(G.vexs, input),visited);return 0;} 2、邻接表
代码与上面邻接矩阵也差不多只是在条件判断时有所不同这里不需要判断表结点是不是顶点的边只需要判断表结点是不是空递归具体代码如下 #include stdio.h
#include stdlib.h
#define MAXSIZE 100 //最大顶点数
#define MAX_INT 32767//设置最大值
typedef struct TableNode{//表结点int index;//结点在数组中的下标struct TableNode* nextarc;int info;//权值}TNode;typedef struct{//头结点char data;TNode* firstarc;
}HNode;typedef struct{//整个无向网的结构体HNode* head;int vexnum,arcnum;
}Gragh;
int get_index(HNode* arr,char m)
{int i 0;for(i 0; i MAXSIZE; i){if(arr[i].data m)return i;}return -1;
}void CreatGraph(Gragh* G)
{int i 0;printf(请输入顶点和边的数量:);scanf(%d%d,G-vexnum,G-arcnum);//把输入的值保存到图结构体中for(i 0; i G-vexnum; i){printf(请输入每个顶点的值:);getchar();//清除键盘缓存区的回车键scanf(%c, G-head[i].data);//把输入的值保存到顶点数组当中G-head[i].firstarc NULL;}for(i 0; i G-arcnum; i)//有几条边循环几次{char m,n;//用来接收两个顶点int j,k;//接收顶点的下标printf(请依次输入每一条边格式为:ab );getchar();scanf(%c%c,m,n);j get_index(G-head,m);//得到输入的m的值在顶点数组中的下标k get_index(G-head,n);//得到输入的n的值在顶点数组中的下标TNode* P1 malloc(sizeof(TNode));P1-index k;P1-nextarc G-head[j].firstarc;G-head[j].firstarc P1;//因为是无向图所以还要建立一条反向的边TNode* P2 malloc(sizeof(TNode));P2-index j;P2-nextarc G-head[k].firstarc;G-head[k].firstarc P2;}}
//深度遍历创建的无向图
void DepthSearch(Gragh G, int v, int*visited)//参数为创建的图输入的值在数组中的下标判断是否被访问过的数组
{visited[v] 1;printf(%c, G.head[v].data);TNode* P G.head[v].firstarc;while(P){if(!visited[P-index])DepthSearch(G, P-index, visited);//如果表结点不为空且判断数组值不为则递归搜索该结点P P-nextarc;//指针指向该结点的下一个结点}}
int main()
{Gragh G;CreatGraph(G);char input;int visited[MAXSIZE] {0};//创建数组用来判断某一个顶点是否被访问过printf(请输入搜索的起始点:);getchar();scanf(%c,input);DepthSearch(G, get_index(G.head, input),visited);return 0;}
非递归 void DFS(Graph g,int v){//采用非递归的方式实现图的深度优先遍历InitStack(s);//创建并初始化栈sPUSH(s,v);//待访问结点入栈while(!IsEmpty(s)){//栈非空栈顶元素出栈并访问同时让其所有未被访问的邻结点入栈POP(s,v);visit(v);visited[v]1;int wFirstAdjVex(g,v);while(w!-1){if(!visited[w]){PUSH(s,w);}wNextAdjVertex(g,v,w);}时间效率 实例马踏棋盘算法骑士周游问题
国际象棋的棋盘为8*8的方格棋盘现将“马”放在任意指定的方格中按照“马”走棋的规则将“马”进行移动。要求每个方格只能进入一次最终使得“马”走遍棋盘64个方格。
编写代码实现马踏棋盘的操作要求用1~64来标注“马”移动的路径看演示。
对于在n*n的棋盘上当n5且为偶数的时候以任意点作点都有解。 回溯法
一之前我们谈过回溯法,还是那句话指导思想很简单就是一条路走到黑碰壁了再回来一条路走到黑......一般和递归可以很好的搭配使用还有深度优先搜索DFS 。
哈密尔顿路径:
一图G中的哈密尔顿路径指的是经过图G中每个顶点,且只经过一次的一条轨迹。如果这条轨迹是一条闭合的路径从起点出发不重复地遍历所有点后仍能回到起始点,那么这条路径称为哈密尔顿回路
6240当前位置5173 #define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
#include time.h#define X 6
#define Y 6int chess[X][Y];//找到基于(x,y)位置下一个可走的位置int nextxy(int* x, int* y, int count)//传入xy的地址可以像全局变量一样修改变量的值。
{switch (count){case 0:if (*x 2 X - 1 *y - 1 0 chess[*x 2][*y - 1] 0){*x 2;*y - 1;return 1;}break;case 1:if (*x 2 X - 1 *y 1 Y - 1 chess[*x 2][*y 1] 0){*x 2;*y 1;return 1;}break;case 2:if (*x 1 X - 1 *y 2 Y - 1 chess[*x 1][*y 2] 0){*x 1;*y 2;return 1;}break;case 3:if (*x - 1 0 *y 2 Y - 1 chess[*x - 1][*y 2] 0){*x - 1;*y 2;return 1;}break;case 4:if (*x - 2 0 *y 1 Y - 1 chess[*x - 2][*y 1] 0){*x - 2;*y 1;return 1;}break;case 5:if (*x - 2 0 *y - 1 0 chess[*x - 2][*y - 1] 0){*x - 2;*y - 1;return 1;}break;case 6:if (*x - 1 0 *y - 2 0 chess[*x - 1][*y - 2] 0){*x - 1;*y - 2;return 1;}break;case 7:if (*x 1 X - 1 *y - 2 0 chess[*x 1][*y - 2] 0){*x 1;*y - 2;return 1;}break;}return 0;
}void print() {int i, j;for (i 0; i X; i) {for (j 0; j Y; j) {printf(%2d\t, chess[i][j]);}printf(\n);}printf(\n);
}//DFS 深度优先遍历棋盘
//(x,y)为位置坐标
//tag是标记变量每走一步tag 1
int TravelChessBoard(int x, int y, int tag) {int x1 x, y1 y, flag 0, count 0;chess[x][y] tag;if (X * Y tag) {print();return 1;}//找到马的下一个可走坐标(x1,y1),如果找到flag 1,否则为0flag nextxy(x1, y1, count);while (0 flag count 7) {count;flag nextxy(x1, y1, count);}while (flag) {if (TravelChessBoard(x1, y1, tag 1)) {return 1;}//继续找到马的下一步可走的坐标(x1,y1)...如果找到flag 1,否则为0//前面的八种情况都不行x1 x;//重新赋值是因为上面寻找的时候nextadress已经对x1,y1做改变了y1 y;//退回到前一格传入nextadress的一格(回溯1)count;flag nextxy(x1, y1, count);while (0 flag count 7) {count;flag nextxy(x1, y1, count);}}if (0 flag) {chess[x][y] 0;return 0;}
}int main() {int i, j;clock_t start, finish;start clock();for (i 0; i X; i) {for (j 0; j Y; j) {chess[i][j] 0;}}if ( TravelChessBoard(2, 0, 1)0) {printf(fail\n);}finish clock();printf(\n本次计算一共花费:%f秒\n\n, (double)(finish - start) / CLOCKS_PER_SEC);return 0;
}
emmm,吃饭去了回溯的理解DFS把8个情况可能都走一遍
二广度优先遍历
广度优先遍历(BreadthFirstSearch,又称为广度优先搜索简称BFS。 1、邻接表
#includestdlib.h
#includestdio.h
#includemalloc.h
#includestring.h
//图的邻接表类型定义
typedef char VertexType[4];
typedef char InfoPtr;
typedef int VRType;
#define MaxSize 50 //最大顶点个数
typedef enum { DG, DN, UN, UG } GraphKind; //图的类型有向图、有向网、无向图和无向网
typedef struct ArcNode {//边表结点的类型定义 int adjvex;// 弧边指向的顶点的位置InfoPtr* info;//与弧边相关的信息 struct ArcNode* nextarc;//指示下一个与该顶点相邻接的顶点
} ArcNode;
typedef struct VNode { //表头结点的类型定义 VertexType data;//用于存储顶点ArcNode* firstarc;//指示第一个与该顶点邻接的顶点
}VNode, AdjList[MaxSize];
typedef struct { //图的类型定义 AdjList vertex;int vexnum, arcnum;//图的顶点的数目与弧的数目 GraphKind kind;//图的类型
}AdjGraph;
//函数声明
int LocateVertex(AdjGraph G, VertexType v);
void CreateGraph(AdjGraph* G);
void DisplayGraph(AdjGraph G);
void DestroyGraph(AdjGraph* G);
//深度
void DFSTraverse(AdjGraph G);
void DFS(AdjGraph G, int v);
int FirstAdjVertex(AdjGraph G, VertexType v);
int NextAdjVertex(AdjGraph G, VertexType v, VertexType w);void BFSTraverse(AdjGraph G);
void CreateGraph(AdjGraph* G) {int i, j, k;VertexType v1, v2;//定义两个顶点v1和v2ArcNode* p;printf(请输入图的顶点数边数逗号分割);scanf(%d,%d, (*G).vexnum, (*G).arcnum);printf(请输入%d个顶点的值\n, G-vexnum);for (i 0; i G-vexnum; i) {scanf(%s, G-vertex[i].data);G-vertex[i].firstarc NULL;}printf(请输入弧尾和弧头以空格作为间隔\n);for (k 0; k G-arcnum; k) {scanf(%s%s, v1, v2);i LocateVertex(*G, v1);j LocateVertex(*G, v2);//j为入边i为出边 创建邻接表p (ArcNode*)malloc(sizeof(ArcNode));p-adjvex j;//邻接点域 p-info NULL;//数据域 p-nextarc G-vertex[i].firstarc;//指针域G-vertex[i].firstarc p;//i为入边j为出边 创建邻接表p (ArcNode*)malloc(sizeof(ArcNode));p-adjvex i;//邻接点域 p-info NULL;//数据域 p-nextarc G-vertex[i].firstarc;//指针域G-vertex[j].firstarc p;}(*G).kind UG;
}
//查找
int LocateVertex(AdjGraph G, VertexType v) {int i;for (i 0; i G.vexnum; i) {if (strcmp(G.vertex[i].data, v) 0) {return i;}}return -1;
}
//销毁无向图
void DestroyGraph(AdjGraph* G) {int i;ArcNode* p, * q;for (i 0; i (*G).vexnum; i) {//释放图中的边表结点 p G-vertex[i].firstarc;//p指向边表的第一个结点 if (p ! NULL) {//如果边表不为空则释放边表的结点 q p-nextarc;free(p);p q;}}(*G).vexnum 0;//将顶点的数目为0 (*G).arcnum 0;//将边的数目为0
}
//打印
void DisplayGraph(AdjGraph G) {int i;ArcNode* p;printf(%d个顶点\n, G.vexnum);for (i 0; i G.vexnum; i) {printf(%s, G.vertex[i].data);}printf(\n%d条边:\n, 2 * G.arcnum);for (i 0; i G.vexnum; i) {p G.vertex[i].firstarc;//将p指向边表的第一个结点while (p) {printf(%s-%s , G.vertex[i].data, G.vertex[p-adjvex].data);p p-nextarc;}printf(\n);}
}
int visited[MaxSize];
//打印深度优先遍历的数据
void Visit(VertexType v) {printf(%s , v);
}
//深度
void DFSTraverse(AdjGraph G) {int v;for (v 0; v G.vexnum; v) {visited[v] 0; //访问标志数组初始化为未被访问 }printf(\n深度优先遍历序列\n);for (v 0; v G.vexnum; v) {if (!visited[v]) {DFS(G, v); //对未访问的顶点v进行深度优先遍历 }}printf(\n);
}
//从顶点v出发递归深度优先遍历
void DFS(AdjGraph G, int v) {int w;visited[v] 1;//访问标志设置为已访问 Visit(G.vertex[v].data);for (w FirstAdjVertex(G, G.vertex[v].data); w 0; w NextAdjVertex(G, G.vertex[v].data, G.vertex[w].data))if (!visited[w])DFS(G, w);//递归调用DFS对v的尚未访问的序号为w的邻接顶点
}
//返回顶点v的第一个邻接顶点的序号
int FirstAdjVertex(AdjGraph G, VertexType v) {ArcNode* p;int v1;v1 LocateVertex(G, v);//v1为顶点v在图G中的序号 p G.vertex[v1].firstarc;if (p) //如果顶点v的第一个邻接点的存在返回邻接点的序号否则返回-1 return p-adjvex;elsereturn -1;
}
//返回v的相对于w的下一个临界顶点的序号
int NextAdjVertex(AdjGraph G, VertexType v, VertexType w) {ArcNode* p, * next;int v1, w1;v1 LocateVertex(G, v); //v1为顶点v在图G中的序号 w1 LocateVertex(G, w); //w1为顶点v在图G中的序号 for (next G.vertex[v1].firstarc; next;) {if (next-adjvex ! w1) {}next next-nextarc;}p next; //p指向顶点v的邻接顶点w的结点 if (!p || p-nextarc) {//如果w不存在或w是最后一个邻接点则返回-1 return -1;}else {return p-nextarc-adjvex;//返回v的相对于w的下一个邻接点的序号 }
}//广度
void BFSTraverse(AdjGraph G) {printf(\n广度优先遍历序列\n);int v, u, w, front, rear;ArcNode* p;int queue[MaxSize];//定义一个队列Q‘front rear -1;//初始化队列Qfor (v 0; v G.vexnum; v) {visited[v] 0;}v 0;visited[v] 1; //设置访问标志为1表示已经被访问过 Visit(G.vertex[v].data);rear (rear 1) % MaxSize;queue[rear] v; //v入队 while (front rear) { //如果队列不为空 front (front 1) % MaxSize;v queue[front]; //队头元素出队赋值给v p G.vertex[v].firstarc;while (p ! NULL) { //遍历序号为v的所有邻接点 if (visited[p-adjvex] 0) {//如果该顶点未被访问过 visited[p-adjvex] 1;Visit(G.vertex[p-adjvex].data);rear (rear 1) % MaxSize;queue[rear] p-adjvex;}p p-nextarc;//p指向下一个邻接点 }}
}
int main() {AdjGraph G;printf(采用邻接表创建无向图G:\n);CreateGraph(G);printf(输出无向图G:);DisplayGraph(G);DFSTraverse(G);BFSTraverse(G);DestroyGraph(G);return 1;
}这里比较好理解
就是说利用队列来帮助实现 2、邻接矩阵
#define _CRT_SECURE_NO_WARNINGS 1
#include iostream
using namespace std;
#define MVNUM 100 //最大顶点数
#define MAXINT 32767 //极大值相当于无穷大int visited[MVNUM] { 0 }; //辅助数组判断遍历过了没
int visit[MVNUM] { 0 }; //同理
typedef struct
{char vexs[MVNUM]; //顶点数据数组int arr[MVNUM][MVNUM]; //邻接矩阵int vexnum, arcnum; //现有顶点数与边数
}AMGraph;
typedef struct
{int* base; //队列数组int front; //队头的下标int rear; //队尾的下标
}sqQueue;int initGraph(AMGraph G); //初始化邻接矩阵
void showGraph(AMGraph G); //打印邻接矩阵
int locatVex(AMGraph G, char u); //定位顶点在邻接矩阵的下标
int createGraph(AMGraph G); //建立邻接矩阵
void dfsAM(AMGraph G,int i); //深度优先搜索遍历
void bfsAM(AMGraph G, int i); //广度优先搜索遍历
int initQueue(sqQueue Q); //初始化队列
int enQueue(sqQueue Q, int i); //入队
int firstVEX(AMGraph G, int u); //第一个邻接顶点
int nextVEX(AMGraph G,int w ,int u); //下一个邻接顶点int main()
{AMGraph G; initGraph(G);createGraph(G);showGraph(G);cout the result of dfs is:;dfsAM(G,0);cout endl;cout the result of bfs is:;bfsAM(G,0);
}
int initGraph(AMGraph G)
{cout please input some vexnum and arcnum! endl;cin G.vexnum G.arcnum; //输入你想要的顶点数和边数cout please input data of vex! endl;for (int i 0; i G.vexnum; i){cin G.vexs[i]; //输入顶点数据}for (int i 0; i G.vexnum; i){for (int j 0; j G.vexnum; j){G.arr[i][j] MAXINT; //邻接矩阵的初值都为无穷大} }return 1;
}
void showGraph(AMGraph G)
{for (int i 0; i G.vexnum; i){for (int j 0; j G.vexnum; j){if (G.arr[i][j] MAXINT) //无穷大弄得更好看点cout ∞ ;elsecout G.arr[i][j] ;}cout endl;}cout endl;
}int locateVex(AMGraph G, char u)
{for (int i 0; i G.vexnum; i) {if (u G.vexs[i]) //如果u的值和顶点数据匹配就返回顶点在矩阵中的下标return i;}return -1;
}int createGraph(AMGraph G)
{int i 0; int j 0;int w 0; //i,j代表顶点下标,w代表权值char v1 0; char v2 0; //v1,v2为顶点数据cout please input w of v1 to v2 in graph! endl;for (int k 0; k G.arcnum; k){cin v1 v2 w; i locateVex(G, v1); //找到v1在顶点表的下标并返回赋值给ij locateVex(G, v2);G.arr[i][j] w;G.arr[j][i] G.arr[i][j]; //无向图的矩阵是对称矩阵}cout endl;return 1;
}void dfsAM(AMGraph G, int i)
{//随机选一个顶点下标这里为0cout G.vexs[i] ; //输出0下标在顶点表的值 visited[i] 1; //辅助数组对应下标i的值置为1for (int j 0; j G.vexnum; j){if (G.arr[i][j] ! MAXINT (!visited[j])) //只要是邻接的顶点并且没有访问过{ //不然就退回也是递归结束条件dfsAM(G, j); //递归使用函数}}
}
int initQueue(sqQueue Q)
{Q.base (int *)malloc(sizeof(int) * MVNUM); //给base动态分配一个int*类型MVNUM个int大小的一维数组空间Q.front Q.rear 0; //队头和对尾下标都置为0return 1;
}
int enQueue(sqQueue Q, int i)
{if ((Q.rear 1) % MVNUM Q.front) //队满return 0;Q.base[Q.rear] i; //先赋值再加Q.rear (Q.rear 1) % MVNUM;return 1;
}int deQueue(sqQueue Q, int u)
{if (Q.rear Q.front) //队空return 0;u Q.base[Q.front]; //要删的值给u然后再加Q.front (Q.front 1) % MVNUM;return 1;
}int firstVEX(AMGraph G, int u)
{//在邻接矩阵u行0列开始遍历如果找到不等于无穷的
//并且没有访问过的就返回列的下标否则就返回无穷for (int i 0; i G.vexnum; i) {if (G.arr[u][i] ! MAXINT visit[i] 0) {return i;}}return MAXINT;
}int nextVEX(AMGraph G, int w, int u)
{//在邻接矩阵u行w1列开始遍历如果找到不等于无穷的
//并且没有访问过的就返回列的下标否则就返回无穷for (int i w 1; i G.vexnum; i){if (G.arr[u][i] ! MAXINT visit[i] 0){return i;} }return MAXINT;
}void bfsAM(AMGraph G, int i)
{//随机选一个顶点下标这里为0cout G.vexs[i] ; //输出i下标在顶点表的值 visit[i] 1; //辅助数值对应下标i的值置为1sqQueue Q; initQueue(Q);enQueue(Q, i); //i为矩阵中顶点的行下标让它入队(顶点表的下标和矩阵的列下标行下标一致本算法中说谁的下标都一样)while (Q.rear ! Q.front) //队不为空{int u 0; //接收矩阵中顶点的行下标因为是邻接矩阵deQueue(Q,u); //出队并让u接收矩阵中顶点的行下标for (int w firstVEX(G, u); w ! MAXINT; w nextVEX(G, w, u)){//注意在一次循环中u不变if (!visit[w]){cout G.vexs[w] ;visit[w] 1;enQueue(Q, w);}}}} 思路最重要吧 与DFS一致
四、最小生成树
图的生成树:图中所有顶点均由边链接在一起,但不存在回路的图。 一个图可以有许多棵生成树 无向图的生成树 最小生成树(Minimum Spanning Tree):给定一个无向网在该网所有生成树中使得各边权值之和最小的那棵生成树称为该网的最小生成树也叫最小代价生成树。
一普利姆算法
不想写了 代码如下
# include iostream
# define SIZE 20
# define NEWSIZE 20
# define maxn 100000000 //表示无限大
using namespace std;
typedef struct ArcNode { //边的结点结构类型int adjvex; //该边的终点编号int weight; //该边的权值struct ArcNode* nextarc; //指向下一条边的指针
}ArcNode;
typedef struct VexNode { //顶点结构char data;ArcNode* firstarc; //指向第一条与该顶点有关的边的指针
}VexNode;
typedef struct Graph { //邻接表结构类型VexNode* VNode; //定义邻接表int vexnum, arcnum; //顶点数和边的个数int size; //邻接表的大小
}Graph;int* Adjvex; //保存在最小生成树中的顶点
int* Lowcost; //保存不在最小生成树中的各点到最小生成树中的点的边的最小权值
int* Flag; //标注该点是否已加入最小生成树void Create_Graph(Graph G) //创建图邻接表
{cout 顶点的个数:;cin G.vexnum;G.VNode (VexNode*)malloc(SIZE * sizeof(VexNode));G.size SIZE;while (G.size G.vexnum) { //根据点的个数动态分配空间G.VNode (VexNode*)realloc(G.VNode, (G.size NEWSIZE) * sizeof(VexNode));G.size NEWSIZE;}Adjvex (int*)malloc((G.size 10) * sizeof(int));Lowcost (int*)malloc((G.size 10) * sizeof(int));Flag (int*)malloc((G.size 10) * sizeof(int));cout 请输入各顶点的名称:;for (int i 1; i G.vexnum; i) {cin G.VNode[i].data;G.VNode[i].firstarc NULL; //邻接表初始化所有单向链表均为空表}cout 请输入边的个数:;cin G.arcnum;cout 请输入各边起点和终点的编号及权重 endl;int x, y, w; //x:起始点y:终点w:权重ArcNode* p, * q;for (int i 1; i G.arcnum; i) {cin x y w;p (ArcNode*)malloc(sizeof(ArcNode)); //创建一个用于存放当前边的结点pp-nextarc NULL;p-adjvex y;p-weight w;q G.VNode[x].firstarc;//将边按顺序插入到链表末尾if (q NULL) {G.VNode[x].firstarc p;}else {while (q-nextarc ! NULL) {q q-nextarc;}q-nextarc p;}p (ArcNode*)malloc(sizeof(ArcNode));p-nextarc NULL;p-adjvex x;p-weight w;q G.VNode[y].firstarc;if (q NULL) {G.VNode[y].firstarc p;}else {while (q-nextarc ! NULL) {q q-nextarc;}q-nextarc p;}}
}void MinSpanningTree_Prim(Graph G, int v) //从顶点v出发求图的最小生成树
{for (int i 1; i G.vexnum; i) { //初始化Adjvex[i] v;Lowcost[i] maxn;Flag[i] 0;}Lowcost[v] 0; //初始点对应的权置为0Flag[v] 1; //标记初始点即将初始点加入树中cout G.VNode[v].data; //输出初始点的值int num 1; //记录目前最小生成树中的点的数目ArcNode* p;while (num G.vexnum) {p G.VNode[v].firstarc; //p为新加入树的点的第一个邻接点while (p ! NULL) { //由于新点加入更新各不在最小生成树中的点到最小生成树中点的最小距离if (!Flag[p-adjvex] Lowcost[p-adjvex] p-weight) {Lowcost[p-adjvex] p-weight;Adjvex[p-adjvex] v;}p p-nextarc;}int min maxn;for (int i 1; i G.vexnum; i) { //寻找目前到最小生成树距离最小的点该点不在最小生成树中if (!Flag[i] Lowcost[i] min) {min Lowcost[i];v i; //更新v为目前到最小生成树距离最小的点该点不在最小生成树中}}Flag[v] 1; //标记该点即将该点加入最小生成树中cout G.VNode[v].data; //输出新加入树的点的值num; //最小生成树中的点的数目1}
}int main()
{Graph G;Create_Graph(G);int sum 0;cout 最小生成树为:;MinSpanningTree_Prim(G, 1); //从1号点出发cout endl;for (int i 1; i G.vexnum; i) {cout Adjvex[i] ;}cout endl;for (int i 1; i G.vexnum; i) {cout Lowcost[i] ;sum Lowcost[i]; //求最小生成树的值}cout endl;for (int i 1; i G.vexnum; i) {cout Flag[i] ;}cout endl;cout 最小生成树的值为: sum endl;return 0;
} #include stdio.h
#include stdlib.h
#include string.h#define INF INT_MAX// 邻接矩阵表示的无向图
typedef struct {int V; // 顶点数int E; // 边数int G[100][100]; // 邻接矩阵
} Graph;// 获取边的权重
int getWeight(Graph G, int u, int v) {return G[u][v];
}// Kruskal算法求最小生成树
Graph primMST(Graph G) {int V G.V; // 顶点数int E G.E; // 边数int parent[V]; // 父节点数组int dist[V]; // 从源点到每个顶点的距离数组int i, u, v;int minCost 0; // 总代价int edgeCount 0; // 已选边的数量Graph mstEdges; // MST边集合memset(parent, -1, sizeof(parent)); // 初始化父节点为-1memset(dist, INF, sizeof(dist)); // 初始化距离为正无穷大priorityQueueNode pq; // 优先队列头结点pq.data (void*)dist[0];pq.index 0;memset(mstEdges.G, 0, sizeof(mstEdges.G)); // 将邻接矩阵清零mstEdges.V V;mstEdges.E 0;do { // 不断扩展最小生成树直到不存在增广路为止u pq.index; // 取出距离源点最近的顶点uif (u -1) break; // 如果已经没有顶点可选了跳出循环pq.index parent[u]; // 将当前顶点更新为其父节点edgeCount; // 已选边的数量加1for (i 0; i G.V; i) { // 遍历所有顶点v i; // 从当前顶点开始选择下一个顶点vif (dist[v] dist[u] G.G[u][v]) { // 如果从u到v的距离比从u到源点的距离更短则更新距离和优先级队列头结点dist[v] dist[u] G.G[u][v];pq.data (void*)dist[0];pq.index i;} else if (i ! u v ! u) { // 如果当前顶点u不是目标顶点且从u到v的距离比从u到源点的距离更短则将边的权重加入到最小生成树中并更新优先级队列头结点的位置mstEdges.G[edgeCount] getWeight(G, u, v);pq.data (void*)mstEdges.G[0];pq.index edgeCount;} else if (i u v ! u) { // 如果当前顶点u是目标顶点但从u到v的距离比从u到源点的距离更短则将边的权重加入到最小生成树中并将当前顶点更新为其父节点的值为已选边的数量减一(因为此时已经找到了一条增广路径)mstEdges.G[edgeCount] getWeight(G, u, v);parent[v] edgeCount--; // 将当前顶点的父节点设为已选边的数量减一(因为此时已经找到了一条增广路径)} else if (i ! u v u) continue; // 如果当前顶点u是目标顶点且从u到v的距离等于从u到源点的距离则不需要进行任何操作直接跳过本次循环继续下一次循环迭代}minCost mstEdges.G[mstEdges.E-1]; // 将总代价加上已选边的权重之和作为新的总代价mstEdges.E; // 将已选边的计数加一表示又选了一条边加入到最小生成树中} while (edgeCount V); // 当已选边的数量小于顶点数时继续扩展最小生成树直到不存在增广路为止 时间复杂度O()
二克鲁斯卡尔算法 #include stdio.h
#include stdlib.h
#include string.h#define INF INT_MAX// 邻接矩阵表示的无向图
typedef struct {int V; // 顶点数int E; // 边数int G[100][100]; // 邻接矩阵
} Graph;// 获取边的权重
int getWeight(Graph G, int u, int v) {return G[u][v];
}// Kruskal算法求最小生成树
Graph primMST(Graph G) {int V G.V; // 顶点数int E G.E; // 边数int parent[V]; // 父节点数组int dist[V]; // 从源点到每个顶点的距离数组int i, u, v;int minCost 0; // 总代价int edgeCount 0; // 已选边的数量Graph mstEdges; // MST边集合memset(parent, -1, sizeof(parent)); // 初始化父节点为-1memset(dist, INF, sizeof(dist)); // 初始化距离为正无穷大priorityQueueNode pq; // 优先队列头结点pq.data (void*)dist[0];pq.index 0;memset(mstEdges.G, 0, sizeof(mstEdges.G)); // 将邻接矩阵清零mstEdges.V V;mstEdges.E 0;do { // 不断扩展最小生成树直到不存在增广路为止u pq.index; // 取出距离源点最近的顶点uif (u -1) break; // 如果已经没有顶点可选了跳出循环pq.index parent[u]; // 将当前顶点更新为其父节点edgeCount; // 已选边的数量加1for (i 0; i G.V; i) { // 遍历所有顶点v i; // 从当前顶点开始选择下一个顶点vif (dist[v] dist[u] G.G[u][v]) { // 如果从u到v的距离比从u到源点的距离更短则更新距离和优先级队列头结点dist[v] dist[u] G.G[u][v];pq.data (void*)dist[0];pq.index i;} else if (i ! u v ! u) { // 如果当前顶点u不是目标顶点且从u到v的距离比从u到源点的距离更短则将边的权重加入到最小生成树中并更新优先级队列头结点的位置mstEdges.G[edgeCount] getWeight(G, u, v);pq.data (void*)mstEdges.G[0];pq.index edgeCount;} else if (i u v ! u) { // 如果当前顶点u是目标顶点但从u到v的距离比从u到源点的距离更短则将边的权重加入到最小生成树中并将当前顶点更新为其父节点的值为已选边的数量减一(因为此时已经找到了一条增广路径)mstEdges.G[edgeCount] getWeight(G, u, v);parent[v] edgeCount--; // 将当前顶点的父节点设为已选边的数量减一(因为此时已经找到了一条增广路径)} else if (i ! u v u) continue; // 如果当前顶点u是目标顶点且从u到v的距离等于从u到源点的距离则不需要进行任何操作直接跳过本次循环继续下一次循环迭代}minCost mstEdges.G[mstEdges.E-1]; // 将总代价加上已选边的权重之和作为新的总代价mstEdges.E; // 将已选边的计数加一表示又选了一条边加入到最小生成树中} while (edgeCount V); // 当已选边的数量小于顶点数时继续扩展最小生成树直到不存在增广路为止
这个
#include stdio.h
#include stdlib.h
#define VertexMax 20 //最大顶点数为20typedef char VertexType; typedef struct
{VertexType begin;VertexType end;int weight;
}Edge;//边集数组edge[]的单元 typedef struct
{VertexType Vertex[VertexMax];//顶点数组 Edge edge[VertexMax];//边集数组 int vexnum;//顶点数 int edgenum;//边数
}EdgeGraph;void CreateEdgeGraph(EdgeGraph *E)
{int i;printf(请输入顶点数和边数:\n);printf(顶点数 n); scanf(%d,E-vexnum);printf(边 数 e); scanf(%d,E-edgenum);printf(\n); //printf(\n); printf(输入顶点(无需空格隔开):); scanf(%s,E-Vertex);printf(\n\n);printf(输入边信息和权值(如AB,15):\n);for(i0;iE-edgenum;i){printf(请输入第%d边的信息:,i1);scanf( %c%c,%d,E-edge[i].begin,E-edge[i].end,E-edge[i].weight);}
}void print(EdgeGraph *E)
{int i;printf(\n-----------------------------------\n); printf( 顶点数组Vertex:);for(i0;iE-vexnum;i){printf(%c ,E-Vertex[i]);}printf(\n\n);printf( 边集数组edge:\n\n);printf(\t\tBegin End Weight\n);for(i0;iE-edgenum;i){printf(\tedge[%d] %c %c %d\n,i,E-edge[i].begin,E-edge[i].end,E-edge[i].weight);}printf(\n-----------------------------------\n);
}void sort(EdgeGraph *E)
{int i,j;Edge temp;for(i0;iE-edgenum-1;i){for(ji1;jE-edgenum;j){if(E-edge[i].weightE-edge[j].weight){tempE-edge[i];E-edge[i]E-edge[j];E-edge[j]temp;}}}
}int LocateVex(EdgeGraph *E,VertexType v)//查找元素v在一维数组 Vertex[] 中的下标并返回下标
{int i;for(i0;iE-vexnum;i){if(vE-Vertex[i]){return i; } } printf(No Such Vertex!\n);return -1;
}int FindRoot(int t,int parent[])//t接收到是结点在Vertex数组中的下标
{while(parent[t]-1)//parent-1表示没有双亲即没有根节点 {tparent[t];//逐代查找根节点 }return t;//将找到的根节点返回若没有根节点返回自身
}void MiniSpanTree_Kruskal(EdgeGraph *E)
{int i;int num;//生成边计数器当num顶点数-1 就代表最小生成树生成完毕 int root1,root2; int LocVex1,LocVex2; int parent[VertexMax];//用于查找顶点的双亲判断两个顶点间是否有共同的双亲来判断生成树是否会成环 //1.按权值从小到大排序 sort(E);print(E);//2.初始化parent数组 for(i0;iE-vexnum;i){parent[i]-1;}printf(\n 最小生成树(Kruskal):\n\n);//3.for(num0,i0;iE-edgenum;i){LocVex1LocateVex(E,E-edge[i].begin);LocVex2LocateVex(E,E-edge[i].end);root1FindRoot(LocVex1,parent);root2FindRoot(LocVex2,parent);if(root1!root2)//若不会成环则在最小生成树中构建此边 {printf(\t\t%c-%c w%d\n,E-edge[i].begin,E-edge[i].end,E-edge[i].weight);//输出此边 parent[root2]root1;//合并生成树num;if(numE-vexnum-1)//若num顶点数-1代表树生成完毕提前返回 {return;}} }}int main()
{EdgeGraph E;CreateEdgeGraph(E);MiniSpanTree_Kruskal(E);return 0;}.
代码的初始值为-1思路都一样
五、最短路径
一迪杰斯特拉算法
可以算出开始点到其他点的最短距离 顶点i第一轮第二轮第三轮第四轮第五轮第六轮1 6 V0-V1 5 V0-V2-V1 2 3 V0-V2 3 8 V0-V2-V3 6 V0-V2-V1-3 4 9 V0-V2-V1-V4 9 V0-V2-V1-V4 5 10 V0-V2-V5 10 V0-V2-V5 10 V0-V2-V5 10 V0-V2-V5 6 11 V0-V2-V1-V4-v6 11 V0-V2-V1-V4-v6 S{0,2}{0,2,1}{0,2,1,3}{0,2,1,3,4}{0,2,1,3,4,5}{0,2,1,3,4,5,6}
代码
#includestdio.h
#includestdlib.h
//最短路径算法 迪杰斯特拉 求有向图G 从某一个顶点开始 到其余各个顶点的最短路径P以及带权长度
//P为前驱顶点的下标 D为从选取的顶点V0到V顶点的最短路径长度typedef int P[200];//储存最短路径下标
typedef int D[200];//v0到v的最短路径长度和//邻接矩阵
typedef struct AdjacentMatrix
{//顶点集int Vertices[200];//边集int Edge[200][200];//顶点数 边数int numV, numE;
}AdjMatrix;void creategrahp(AdjMatrix* G)
{int n, e;//n代表顶点数 e代表边数int vi, vj;//vi vj代表边的两个顶点对int w;//表示边的权值printf(要输入的顶点数和边数\n);scanf(%d%d,n,e);G-numV n; G-numE e;//图的初始化for(int i 0; i n; i){for(int j 0; j n; j){if(i j){//一个非带权的图 0代表没有边 1代表有边//边不指向自己 即对角线为0G-Edge[i][j] 0;}else{//如果是带权的图 初始化为0或者为一个不可能的值G-Edge[i][j] 65535;}}}//将顶点存入数组for(int i 0; i G-numV; i){printf(请输入第%d个节点的信息\n,i 1);scanf(%d, G-Vertices[i]);}//输入边的信息for(int i 0; i G-numE; i){//如果输入的是顶点的值 需要从顶点集中查找对应的下标 //如果是带权图 还要输入权的信息printf(请输入边的信息Vi,Vj和权值w\n);scanf(%d%d%d,vi,vj,w);G-Edge[vi][vj] w;//如果是带权图 等于权值//如果是有向图 就不对称//如果是无向图 矩阵对称G-Edge[vj][vi] w;}
}void ShortPath_Dijkstra(AdjMatrix* G, int v0, P* p, D* d)
{int k;//记录当前最短路径的下标int final[200];//final[x] 1 表示已求得的到v0的最短路径//初始化DPFinalfor(int i 0; i G-numV; i){final[i] 0;//初始化为未知状态(*d)[i] G-Edge[v0][i];//如果v0传进的是值 寻找下标(*p)[i] -1;}final[v0] 1;(*d)[v0] 0;//自己到自己的路径为0//主循环 求每次v0到v的最短路径for(int i 1; i G-numV; i){int min 65535;//寻找与v0距离最近的顶点for(int j 0; j G-numV; j){if(final[j] ! 1 (*d)[j] min){min (*d)[j];k j;}}//把Vk加入到最短路径中final[k] 1;//修正当前最短路径的距离//以Vk作为中转更新以Vk为中心的邻接点到V0的距离for(int j 0; j G-numV; j){//如果当前找到v的顶点的路径小于原来的路径长度if(min G-Edge[k][j] (*d)[j] final[j] ! 1){//说明找到了更短的路径 修改DP(*d)[j] min G-Edge[k][j];(*p)[j] k;}}}
}int main()
{AdjMatrix* G (AdjMatrix*)malloc(sizeof(AdjMatrix));int p[200];int d[200];creategrahp(G);int v0 0;ShortPath_Dijkstra(G, v0, p, d);for (int i 1; i G-numV; i){printf(v%d - v%d:, v0, i);int j i;while (p[j] ! -1){printf(%d, p[j]);j p[j];} printf(\n);}printf(最短路径长度);for (int i 1; i G-numV; i){printf(v%d-v%d : %d\n, G-Vertices[0], G-Vertices[i], d[i]);}system(pause);return 0;
}
这里的final[]相当于S集合
时间复杂度O
二弗洛伊德算法
这个算的是所有点之间的最短长度 代码
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdbool.h
#include malloc.h
#define MVNum 100//最大顶点数
#define MaxInt 66666//表示极大值
typedef struct {char vexs[MVNum];//顶点表顶点为字符型int arcs[MVNum][MVNum];//邻接矩阵权值为整型int vexnum, arcnum;//图的当前点数和边数
}AMGraph;//定位
int LocateVex(AMGraph* G, char v) {int i;for (i 0; i G-vexnum; i) {if (G-vexs[i] v) {return i;}}return -1;
}//无向网的建立
AMGraph* CreateUDN() {int i, j, k, w;char v1, v2;AMGraph* G malloc(sizeof(AMGraph));printf(输入总顶点数边数\n);scanf(%d%d, G-vexnum, G-arcnum);getchar();//吸收换行符printf(依次输入点的信息\n);for (i 0; i G-vexnum; i) {scanf(%c, G-vexs[i]);}getchar();//吸收换行符for (i 0; i G-vexnum; i)for (j 0; j G-vexnum; j) {if (i j) {G-arcs[i][j] 0;}else {G-arcs[i][j] MaxInt;}}for (k 0; k G-arcnum; k) {printf(输入一条边依附的顶点及权值\n);scanf(%c%c, v1, v2);scanf(%d, w);getchar();//吸收换行符i LocateVex(G, v1), j LocateVex(G, v2);//确定v1、v2在顶点数组的下标G-arcs[i][j] w;//边v1,v2权值置为wG-arcs[j][i] w;//无向网对称边v2,v2权值也置为w}return G;
}//输出邻接矩阵
void print(AMGraph* G) {int i, j;printf( );for (i 0; i G-vexnum; i) {printf(%c , G-vexs[i]);}printf(\n);for (i 0; i G-vexnum; i) {printf(%c , G-vexs[i]);for (j 0; j G-vexnum; j) {if (G-arcs[i][j] MaxInt)printf(∞ );elseprintf(%d , G-arcs[i][j]);}printf(\n);}
}//弗洛伊德算法
void Floyd(AMGraph* G) {int distance[MVNum][MVNum];int i, j, k;//初始化距离矩阵for (i 0; i G-vexnum; i) {for (j 0; j G-vexnum; j) {distance[i][j] G-arcs[i][j];}}//中间节点迭代for (k 0; k G-vexnum; k) {for (i 0; i G-vexnum; i) {for (j 0; j G-vexnum; j) {if (distance[i][k] distance[k][j] distance[i][j]) {distance[i][j] distance[i][k] distance[k][j];}}}}//输出printf(每对顶点间的最短距离\n);for (i 0; i G-vexnum; i) {for (j 0; j G-vexnum; j) {if (distance[i][j] MaxInt) {printf(∞ );}else {printf(%d , distance[i][j]);}}printf(\n);}
}int main() {AMGraph* G CreateUDN();printf(该无向网邻接矩阵为\n);print(G);Floyd(G);
}
关键判断distance[i][k] distance[k][j] distance[i][j]关系
加上path
void ShortestPath_Floyd(Graph G, int** Path, int** D) //Path,D 为二维数组
{int v, w, k;for (v 0; v G.numv; v){for (w 0; w G.numv; w){D[v][w] G.edge[v][w]; //初始化 D 数组为邻接矩阵//若 v 与 w 之间有弧将 w 的前驱置为 v//若 v 与 w 之间没有弧将 w 的前驱置为 -1if (D[v][w] INFINITY){path[v][w] v;}else{path[v][w] -1;}}}for (k 0; k G.numv;k){for (v 0; v G.numv; v){for(w 0;w G.numv; w){if (D[v][w] D[v][k] D[k][w]) //如果从 v 经 k 到 w 的路径最短{D[v][w] D[v][k] D[k][w];Path[v][w] Path[v][k];}}}}//显示最短路径printf(各顶点间最短路径如下:\n); for(v 0; v G.numv; v) { for(w v1; w G.numv; w) {printf(v%d-v%d weight: %d ,v,w,D[v][w]);k Path[v][w]; //获得第一个路径的顶点下标printf( path: %d,v); //打印源点 while(k ! w) //如果路径顶点下标不是终点 {printf( - %d,k); //打印路径顶点 k Path[k][w]; //获得下一个路径顶点下标}printf( - %d\n,w); //打印终点}printf(\n);}
}六、拓扑排序这个不考
有向无环图 一AOV网
若是从i到j有一条有向路径则i是j的前驱j是i的后继若i,j 是网中有向边则i是j的直接前驱j是i的直接后继AOV网中不允许有回路因为如果有回路存在则表明某项活动以自己为先决条件显然不可能
二拓扑排序
1、在有向图中选一个没有前驱的顶点且输出 2、从图中删除该顶点和所有以他为尾的弧 3、重复上述步骤直到所有的顶点均已输出或者当图中不存在无前驱的顶点为止 比如我们要对上述图进行拓扑排序 第一次寻找的是A这一个结点 因为A没有前驱 接下来就是删除A以及以A为弧尾的弧此时就是如下图 在从中选择一个没有前驱的结点这里BCD 都可以这里我们就选择B 同样的道理 删除B以及以B为弧尾的弧此时就是如下图 同样的道理 再选一个C删除以C为弧尾的弧 再删除 D 再删除E 所有总的拓扑排序是 当然排序可能不止一种 因为可能不止一个结点在某一步骤中是无前驱的 ABCDE 有人可能会说了这个图太简单了能不能难一点的图来拓扑 如下 拓扑排序可以如下 AEBDC EABDC AEDBC EADBC
三判断一个有向图是否为有向无环图的方法是?
方法一∶深度优先遍历
若从有向图上的某个顶点u出发在DFS(u)结束之前出现一条从顶点v到u的边由于v在生成树上是u的子孙则图中必定存在包含u和v的环因此深度优先遍历可以检测一个有向图是否有环。
方法二∶拓扑排序
数组
//有向无环图的拓扑排序
#includestdio.h
#includestdlib.h
typedef struct graph
{char* vexs;//顶点数值int** arcs;//邻接矩阵int vexNum;//顶点数int arcNum;//边数
}Graph;typedef struct Node//栈的建立与存顶点下标有关
{int data;struct Node* next;
}Node;Node* initStack()
{Node* stack(Node*)malloc(sizeof(Node));stack-data0;stack-nextNULL;return stack;
}void push(Node* stack,int data)//压栈
{Node* node(Node*)malloc(sizeof(Node));node-datadata;node-nextstack-next;stack-nextnode;stack-data;
}int isEmpty(Node* stack)//判断是否为空栈
{if(stack-nextNULL){return 1;}else{return 0;}
}int pop(Node* stack)//出栈
{if(isEmpty(stack)){return -1;}else{Node* nodestack-next; int datanode-data;stack-nextnode-next;free(node);stack-data--;return data;}
}Graph* initGraph(int vexNum)//分配空间
{Graph* G (Graph*)malloc(sizeof(Graph));G - vexs (char*)malloc(sizeof(char) * vexNum);G - arcs (int**)malloc(sizeof(int*) * vexNum);for (int i 0 ; i vexNum; i) {G - arcs[i] (int*)malloc(sizeof(int) * vexNum);}G - vexNum vexNum;G - arcNum 0;return G;
}void createGraph(Graph* G, char* vexs, int* arcs)//创建图
{for (int i 0 ; i G - vexNum; i) {G - vexs[i] vexs[i];for (int j 0; j G - vexNum; j) {G - arcs[i][j] *(arcs i * G - vexNum j);if (G - arcs[i][j] ! 0)G - arcNum ;}}G - arcNum / 2;
}int* findInDegrees(Graph* G)//找出入度
{int* inDegrees(int*)malloc(sizeof(int)*G-vexNum);for(int i0;iG-vexNum;i)//初始化{inDegrees[i]0;}for(int i0;iG-vexNum;i){for(int j0;jG-vexNum;j){if(G-arcs[i][j]){inDegrees[j];}}}return inDegrees;
}void topologicalSort(Graph* G)//拓扑排序
{int index0;int* top(int*)malloc(sizeof(int)*G-vexNum);//存下标的数组int* inDegreesfindInDegrees(G);Node* stackinitStack();for(int i0;iG-vexNum;i)//入度为0的压栈{if(inDegrees[i]0){push(stack,i);}}while(!isEmpty(stack))//栈不为空循环执行入度的减法去掉输出顶点指向的下一个顶点的边{int vexindexpop(stack);//出栈的是顶点的下标top[index]vexindex;//保存顶点下标for(int j0;jG-vexNum;j){if(G-arcs[vexindex][j])//下一个顶点有入度时减去{inDegrees[j]--;if(inDegrees[j]0)//顶点入度减到0了直接入栈{push(stack,j);}}}}for(int i0;iG-vexNum;i)//依次输出入度为零的顶点{printf(%c ,G-vexs[top[i]]);}printf(\n);
}void DFS(Graph* G,int* flag,int index)//深度优先遍历
{printf(%c ,G-vexs[index]);flag[index]1;//已经访问过顶点标记为1之后不会再访问for(int i0;iG-vexNum;i){if(G-arcs[index][i]1!flag[i]){DFS(G,flag,i);}}
}int main()
{Graph* GinitGraph(6);int* flag(int*)malloc(sizeof(int)*G-vexNum);for(int i0;iG-vexNum;i)//首先赋值为0表示未访问任何顶点{flag[i]0;}int arcs[6][6]{0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,0};createGraph(G,123456,(int*)arcs);DFS(G,flag,0);printf(\n);topologicalSort(G);return 0;
}
邻接表
#include stdio.h
#include stdlib.h
#define MAXSIZE 15
#define MAXINT 10000
int get_index(int* arr, int e)//得到输出的顶点在数组中的下标
{int i 0;for(i 0; i MAXSIZE; i){if(arr[i] e)return i;}return -1;
}void TopologicalSorting(int arcs[][MAXSIZE])
{int m,n,i,j,a,b;int vertex[MAXSIZE] {0};//初始化顶点数组int visited[MAXSIZE] {0};printf(请输入顶点数和弧的数目:);scanf(%d%d,m,n);for(i 0; i m; i){printf(请输入每一个顶点:);getchar();scanf(%d, vertex[i]);}for(j 0; j n; j){printf(请输入每一条边:);getchar();scanf(%d%d,a,b);arcs[get_index(vertex,a)][get_index(vertex,b)] 1;//根据输入更新邻接矩阵}int num 0;while(num ! m)//找到全部顶点就结束{for(i 0; i m; i)//循环遍历邻接矩阵{for(j 0; j m; j){//如果找到某一列的数据全部为0说明这一列所代表的顶点是没有前驱的//这里visited[j]是用来限制某一行visited[i]为限制某一列if((arcs[j][i] 1 !visited[j]) || visited[i])break;}if(j m){printf(%d,vertex[i]);//输出没有前驱的顶点num;//找到的顶点数加visited[i] 1;//设置这个顶点为已访问}}}}int main()
{int arcs[MAXSIZE][MAXSIZE]{{0}};//邻接矩阵TopologicalSorting(arcs);return 0;
}
栈的实现
#include stdio.h
#include stdlib.h#define MAX_VER_NUM 2000
#define VertexData int
#define ERROR 0
#define OK 1typedef enum { DG, UDG, DN, UDN }GraphKind;typedef struct ArcNode{VertexData adjvex;struct ArcNode* nextarc;
}ArcNode;typedef struct VexNode {VertexData data;ArcNode* firstArc;
}VertexNode;typedef struct {VertexNode vertex[MAX_VER_NUM];int num_vex, num_arc;GraphKind kind;
}AdjList;typedef struct {int data[MAX_VER_NUM];int top;
}Stack;Stack InitStack(); //初始化栈
void PushStack(Stack* S, int v); //入栈
void PopStack(Stack* S, int* v); //出栈
int LocateVertex(AdjList A, VertexData v);//找到顶点位置
AdjList* CreateGraph(int n, int m); //创建图
void FindID(AdjList A, int indegree[MAX_VER_NUM]);//求入度
int TopoSort(AdjList A); //拓扑排序int main()
{int n, m;scanf(%d%d, n, m);AdjList *A;A CreateGraph(n, m);TopoSort(*A);return 0;
}Stack InitStack()
{ //初始化栈Stack S;S.top -1;return S;
}void PushStack(Stack *S, int v)
{ //入栈if (S-top MAX_VER_NUM - 1) {return;}S-top;S-data[S-top] v;
}void PopStack(Stack* S, int *v)
{ //出栈if (S-top -1) {return;}*v S-data[S-top];S-top--;
}int LocateVertex(AdjList A, VertexData v)
{ //找到顶点位置for (int i 0; i A.num_vex; i) {if (A.vertex[i].data v) {return i;}}return -1;
}AdjList* CreateGraph(int n, int m)
{ //创建图AdjList *A;A (AdjList*)malloc(sizeof(AdjList));if (A NULL) {return NULL;}A-kind DG;A-num_vex n;A-num_arc m;for (int i 0; i A-num_vex; i) {scanf(%d, A-vertex[i].data);//若要输入字符则要改变A-vertex[i].firstArc NULL;}VertexData v1, v2;int loc1;for (int i 0; i A-num_arc; i) {scanf(%d%d, v1, v2);//若要输入字符则要改变ArcNode* tmp;tmp (ArcNode*)malloc(sizeof(ArcNode));if (tmp NULL) {return 0;}tmp-adjvex v2;loc1 LocateVertex(*A, v1);tmp-nextarc A-vertex[loc1].firstArc;A-vertex[loc1].firstArc tmp;}return A;
}void FindID(AdjList A, int indegree[MAX_VER_NUM])
{ //求入度ArcNode* tmp;tmp (ArcNode*)malloc(sizeof(ArcNode));if (tmp NULL) {return;}for (int i 0; i A.num_vex; i) {indegree[i] 0;}int loc;for (int i 0; i A.num_vex; i) {tmp A.vertex[i].firstArc;while (tmp ! NULL) {loc LocateVertex(A, tmp-adjvex);indegree[loc];tmp tmp-nextarc;}}
}int TopoSort(AdjList A)
{ //拓扑排序Stack S;int indegree[MAX_VER_NUM];int count 0;//记录遍历顶点数若小于A.num_vex则A中有环ArcNode* tmp;tmp (ArcNode*)malloc(sizeof(ArcNode));if (tmp NULL) {return 0;}FindID(A, indegree);S InitStack();for (int i 0; i A.num_vex; i) {if (indegree[i] 0) {PushStack(S, i);}}int v, k, loc_k;while (S.top ! -1) {PopStack(S, v);printf(%d , A.vertex[v].data);//若要输入字符则要改变count;tmp A.vertex[v].firstArc;while (tmp ! NULL) {k tmp-adjvex;loc_k LocateVertex(A, k);indegree[loc_k]--;if (indegree[loc_k] 0) {PushStack(S, loc_k);}tmp tmp-nextarc;}}if (count A.num_vex) {return ERROR;}else return OK;
}队列
#include stdio.h
#include stdlib.h
#include string.h#define MAX_VERTEX_NUM 20 // 最大顶点数typedef struct {char vertex[MAX_VERTEX_NUM]; // 存放顶点信息int edge[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 存放边信息int vertex_num; // 顶点数int edge_num; // 边数
} Graph;int indegree[MAX_VERTEX_NUM]; //存放每个顶点的入度
/*这里我们不要判断某个顶点的出度在拓扑排序中我们用不到这些*///初始化顶点的入度数组
void Initindegree(Graph G){//首先初始化入度数组for(int i0;iG.vertex_num;i){indegree[i]0;}//统计每个元素对应邻接矩阵列中1的个数即可判断有几条边进入for(int i0;iG.vertex_num;i){int count0;for(int j0;jG.vertex_num;j){if(G.edge[j][i]1)count;}indegree[i]count;}
}// 拓扑排序函数
bool Topologicalsort(Graph G) {char queue[MAX_VERTEX_NUM];int front -1, rear -1; // 初始化队列front和rear都设为0// 首先把入度为0的顶点都进队列for (int i 0; i G.vertex_num; i) {if (indegree[i] 0) {queue[rear] i; // 注意这里应该存储顶点的索引而不是顶点的值}}int count 0; // 这个变量用来记录当前已输出的顶点数while (front ! rear) {int cur_vertex queue[front]; // 出队一个顶点,front原先是零现在是1printf(%c , G.vertex[cur_vertex]); // 输出顶点的值count; // 已输出的顶点数加1// 遍历以当前顶点为起点的所有边for (int i 0; i G.vertex_num; i) {if (G.edge[cur_vertex][i] 1) {indegree[i]--; // 将这些边的终点的入度减1if (indegree[i] 0) { // 如果某个终点的入度变为0就将其入队queue[rear] i;}}}}if (count G.vertex_num) { // 如果已输出的顶点数等于总顶点数说明拓扑排序成功return true;} else { // 否则说明存在环拓扑排序失败return false;}
}int main(){Graph G;G.vertex_num5;G.edge_num6;//人为构造图的顶点for(int i0;iG.vertex_num;i){G.vertex[i] Ai;}for(int i0; iG.vertex_num; i){for(int j0; jG.vertex_num; j){G.edge[i][j]0;}}//人为构造图的边G.edge[0][1]1;G.edge[0][3]1;G.edge[1][3]1;G.edge[1][2]1;G.edge[3][4]1;G.edge[2][4]1;G.edge[3][2]1;Initindegree(G);Topologicalsort(G);
}七、关键路径
1、AOE网 2、AOE网的性质
1只有在某顶点所代表的事件发生后从该顶点出发的各有向边所代表的活动才能开始
2只有在进入某顶点的各有向边所代表的活动都已结束时该顶点所代表的事件才能发生另外有些活动是可以并行进行的
3.关键路径 从源点到汇点的有向路径可能有多条所有路径中具有最大路径长度的路径称为关键路径而把关键路径上的活动称为关键活动。 完成整个工程的最短时间就是关键路径的长度若关键活动不能按时完成则整个工程的完成时间就会延长。
4.最早最晚时间 1事件vk的最早发生时间ve(k)决定了所有从vk开始的活动能够开工的最早时间
2活动ai的最早开始时间e(i)指该活动弧的起点所表示的事件的最早发生时间
3事件vk的最迟发生时间vl(k)指在不推迟整个工程完成的前提下该事件最迟必须发生的时间
4活动ai的最迟开始时间l(i)指该活动弧的终点所表示事件的最迟发生时间与该活动所需时间之差
5活动ai的时间余量d(i)l(i)-e(i)表示在不增加完成整个工程所需总时间的情况下活动ai可以拖延的时间
6若一个活动的时间余量为0则说明该活动必须要如期完成d(i)0即l(i)e(i)的活动ai是关键活动由关键活动组成的路径就是关键路径。
5、求关键路径
1.步骤
1求所有事件的最早发生时间ve()2求所有事件的最迟发生时间vl()3求所有活动的最早发生事件e()4求所有活动的最迟发生时间l()5求所有活动的时间余量d()6d(i)0的就是关键活动所有关键活动组成的路径即为关键路径。
2.举例 1.定义一维数组ve[i]并置初值0记录事件的最早发生时间、一维数组vI[i]记录事件的最晚发生时间、一维数组topo[i]记录拓扑序列的顶点序号。 2.调用拓扑排序算法使拓扑序列保存在topo中。 3.根据topo中的值按从前向后的拓扑次序依次求每个事件的最早发生时间循环n总顶点数次执行以下操作 取得拓扑序列中的顶点序号kk topo[i]; 用指针p依次指向k的每个邻接顶点取得每个邻接顶点的序号jjp-adjvex依次更新顶点j的最早发生时间ve[j]if(ve[i]ve[k] p-weight) ve[i] ve[k] p-weight 4.将每个事件的最迟发生时间vl[i]初始化为汇点的最早发生时间即vl[i]ve[n-1]。 5.根据topo中的值按从后向前的逆拓扑次序依次求每个事件的最迟发生时间循环n次执行以下操作 取得拓扑序列中的顶点序号kk topo[i]; 用指针p依次指向k的每个邻接顶点取得每个邻接顶点的序号j,jp-adjvex,依次根据 k的邻接点,更新k的最迟发生时间vI[k]if(vl[k]vl[j]-p-weight) vl[k]vl[j]-p-weight 6.判断某一活动是否为关键活动循环n次执行以下操作 对于每个顶点vi用指针p依次指向vi的每个邻接顶点取得每个邻接顶点的序号j,jp-adjvex分别计算活动vivj的最早和最晚开始时间e和l; 如果e和l相等则活动vivj为关键活动输出弧vivj。 代码
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
#include stdbool.h#define MVNUM 100typedef struct ArcNode {int adjvex;int weight;struct ArcNode* next;
}ArcNode;typedef struct VNode {char data;ArcNode* firstarc;
}VNode, AdjList[MVNUM];typedef struct {AdjList vertices;int vexnum, arcnum;
}ALGraph;
//定位
int LocateVex(ALGraph* G, char v) {int i;for (i 0; i G-vexnum; i) {if (G-vertices[i].data v) {return i;}}return -1;
}//有向网邻接表的建立
ALGraph* CreateGraph() {int i, j, k, v1, v2, w;ALGraph* G malloc(sizeof(ALGraph));printf(输入顶点数和边数:\n);scanf(%d%d, G-vexnum, G-arcnum);getchar();printf(输入顶点信息\n);for (i 0; i G-vexnum; i) {scanf(%c, G-vertices[i].data);G-vertices[i].firstarc NULL;}getchar();for (k 0; k G-arcnum; k) {printf(输入一条弧依附的起点和终点和权重\n);scanf(%c%c%d, v1, v2, w);getchar();i LocateVex(G, v1), j LocateVex(G, v2);ArcNode* p malloc(sizeof(ArcNode));p-adjvex j;p-weight w;p-next G-vertices[i].firstarc;G-vertices[i].firstarc p;}return G;
}//---------------------------------------------------------------------------
//拓扑排序
bool TopoSort(ALGraph* G, int* topo) {int indegree[MVNUM] { 0 };//记录顶点入度int i, j, v;int count 0;//记录输出的顶点数int stack[MVNUM];//定义一个简单的栈int top 0;//栈顶指针ArcNode* p NULL;//统计各顶点入度for (i 0; i G-vexnum; i) {p G-vertices[i].firstarc;while (p ! NULL) {indegree[p-adjvex];p p-next;}}//将所有入度为0的顶点入队for (i 0; i G-vexnum; i) {if (indegree[i] 0) {stack[top] i;}}//栈非空时循环while (top 0) {v stack[--top];//出栈一个顶点topo[count] v;//排序的顶点数加1并将顶点存入数组topo//遍历该顶点为起点的所有边p G-vertices[v].firstarc;while (p ! NULL) {indegree[p-adjvex]--;//入度减1//若入度为0入队if (indegree[p-adjvex] 0) {stack[top] p-adjvex;}p p-next;//指向下一条边}}//输出顶点数小于总顶点数说明有环if (count G-vexnum) {printf(该图有环拓扑排序失败。\n);return false;}return true;
}
//----------------------------------------------------------------------------
//关键路径算法
bool CriticalPath(ALGraph* G) {int topo[MVNUM];//储存拓扑序列int ve[MVNUM] { 0 };//记录每个事件最早发生时间int vl[MVNUM];//记录每个事件最晚发生时间int i, j, k, e, l;ArcNode* p NULL;if (!TopoSort(G, topo)) return false;//若拓扑排序失败则存在有向环返回false//按拓扑次序求每个事件的最早发生时间for (i 0; i G-vexnum; i) {k topo[i];//取拓扑序列中的顶点序号kp G-vertices[k].firstarc;//指针p指向顶点k的第一个邻接点//更新顶点k的所以邻接点的最早发生时间while (p ! NULL) {j p-adjvex;//j为顶点k的邻接点的序号//更新顶点j最早发生时间不理解的话找个AOE-网代入联想下if (ve[j] ve[k] p-weight) {ve[j] ve[k] p-weight;}p p-next;//指针噗指向下一邻接点}}//初始化数组Vlfor (i 0; i G-vexnum; i) {vl[i] ve[G-vexnum - 1];}//按逆拓扑次序求每个事件的最晚发生时间for (i G-vexnum - 1; i 0; i--) {k topo[i];//取拓扑序列中的顶点序号kp G-vertices[k].firstarc;//指针噗指向顶点k的第一个邻接点//根据顶点k的邻接点更新k的最晚发生时间while (p ! NULL) {j p-adjvex;//j为邻接点序号//更新顶点k的最晚发生时间if (vl[k] vl[j] - p-weight) {vl[k] vl[j] - p-weight;}p p-next;//指针p指向下一邻接点}}//判断每一活动是否为关键活动//每次循环针对以顶点i为开始点关联的所有活动for (i 0; i G-vexnum; i) {p G-vertices[i].firstarc;//指针p指向顶点i的第一个邻接点while (p ! NULL) {j p-adjvex;//j为邻接点序号e ve[i];//计算活动i,j的最早开始时间l vl[j] - p-weight;//计算活动i,j的最晚开始时间//若为关键活动则输出if (e l) {printf(%c,%c , G-vertices[i].data, G-vertices[j].data);}p p-next;//指针p指向下一邻接点}}
}int main() {ALGraph* G CreateGraph();printf(关键活动为\n);CriticalPath(G);return 0;
} 总结 关键路径算法是一个解决项目管理中最短时间问题的经典工具。在计算关键路径时我们需要对整个项目进行遍历并计算出每个活动的最早开始时间、最晚开始时间、最早完成时间以及最晚完成时间进而得到整个项目的关键路径。因此关键路径算法的时间复杂度相对较高但也有一些优化方法可以应用。
算法的时间复杂度分析对于有 n 个顶点和 e 条弧的 AOE 网而言拓扑排序的时间复杂度是 O(ne)计算事件的最晚发生时间的时间复杂度也是 O(ne)最后计算关键路径的时间复杂度还是 O(ne)所以整体的时间复杂度依然是 O(ne)。 结束结束长脑子了