图的表示

邻接矩阵

在这里插入图片描述
不适合存储稀疏图

邻接矩阵构造

#define Maxlnt 32767     //表示极大值,即 ∞ 
#define MVNum 100        //最大顶点数 
typedef char VerTexType; //设顶点的数据类型为字符型 
typedef int ArcType;     //假设边的权值类型为整型 

typedef struct {
    VerTexType vexs[MVNum];      //顶点表 
    ArcType arcs[MVNum][MVNum];  //邻接矩阵
    int vexnum, arcnum;           //图的当前点数和边数 
}ALGraph; //Adjacency Matrix Graph 

邻接表

在这里插入图片描述

无向图的邻接表表示示例
在这里插入图片描述
在这里插入图片描述
邻接表构造

#define MaxVex 255 
#define TRUE   1  
#define FALSE  0
 
typedef char VertexType;  //顶点类型
typedef int Bool;
Bool visited[MaxVex];  //记录图中节点访问状态
 
typedef struct EdgeNode { //边表节点  
    int adjvex;    //该邻接点在顶点数组中的下标  
    struct EdgeNode *next;   //链域 指向下一个邻接点  
}EdgeNode;
 
typedef struct VertexNode { //头节点
    VertexType data;  //顶点信息
    EdgeNode *firstedge;  //边表头指针 
}VertexNode,AdjList[MaxVex]; //顶点数组   
 
typedef struct Graph{  
    AdjList adjList;  
    int numVertexes,numEdges;  //图中当前的结点数以及边数  
}Graph,*GraphAdjList;

逆邻接表

在有向图中,邻接表可以表示出顶点v的出度,而入度需要用逆邻接表表示
在这里插入图片描述

图的搜索

深度优先搜索(DFS)

用dfs求最大连通块

//定义方向数组
int dr[4]={0,-1,0,1};
int dc[4]={1,0,-1,0};
//记录是否访问过的数组
int idx[m][n];
//将要遍历的m×n的矩阵
int pic[m][n];
void dfs(int r, int c, int id){//id用于记录连通分量的个数
	if(r<0 || r>=m || c<0 || c>=n)
		return;//出界
	if(idx[r][c]>0 || pic[r][c] != '@')
		return;//不是'@'或已经访问过
	idx[r][c]=id;//连通分量编号
	for(int i=0;i<4;++i){
		dfs(r+dr[i], c+dc[i], id);
	}
}

宽度(广度)优先搜索(BFS)

用于求解无权或权值相同的图的最短路径

char mp[n][m];//1为空地,0为障碍物
int dist[maxn][maxn];//距离矩阵,记录各个点到终点的最短路长度
int sx, sy, tx, ty;//记录起止点的横纵坐标 
int dx[4] = {1, 0, 0, -1}, dy[4] = {0, -1, 1, 0};//方向数组

void bfs()
{
 	dist[tx][ty] = 0;//终点到终点距离为零
 	queue<pair<int, int> > q;//bfs用队列实现,pair记录点的横纵坐标
 	pair<int, int> t;//临时变量
 	t.first = tx;
 	t.second = ty; 
 	q.push(t);//终点入队
 	while(q.size()){//从终点开始逆方向遍历
  		t = q.front();//每次取出队头元素
  		q.pop();//队头元素出队
  		for(int i = 0; i < 4; i ++){//遍历队头元素可以往哪走 
   			int nx = t.first + dx[i];
   			int ny = t.second + dy[i];//这两句话实现了往一个方向走,想不明白可以在纸上画一下 
   			if(nx >= 0 && nx < n && ny >= 0 && ny < n  &&  dist[nx][ny] == 0  && mp[nx][ny] != '1'){//是否越界,是否走过,是否可走
    			dist[nx][ny] = dist[t.first][t.second] + 1;//满足条件则为上一个点多走一步,离终点的距离也就多1
    			q.push(make_pair(nx, ny));//将该点入队,方便下次从该点出发遍历 
   			}
  		} 
 	} 
 	cout<<dist[sx][sy];//输出起点到终点的最短距离
}

最小生成树

prim(普里姆算法)

贪心算法
将图分成两个集合{vi},{ui},{vi}表示已经记录的点,{ui}表示未记录的点
lowc[p]中记录了{ui}中的每一个点分别到{vi}中每一个点的最短距离
将点到点模糊处理为集合到集合

int graph[n][n];//邻接矩阵
bool vis[n];//判断是否遍历过
int lowc[n];//一个动态更新的数组,记录未遍历过的点到已遍历过的点中的最短距离
int prim(int index)
{
    int ans = 0;
    memset(vis, false, sizeof(vis));//初始表示均未遍历过
    vis[index] = true;//遍历起点
    for(int i = 1; i < n; ++i)//lowc中第一次记录index到其他各点的距离
        lowc[i] = cost[index][i];
    for(int i = 1; i < n; ++i)
    {
        int minc = INF;
        int p = -1;
        for(int j = 0; j < n; ++j)//找到lowc中的最小值,并记录其对应的点
        {
            if(!vis[j] && minc > lowc[j])
            {
                minc = lowc[j];
                p = j;
            }
        }
        if(minc == INF)//表示无法生成连通图
            return -1;
        ans += minc;
        vis[p] = true;
        for(int j = 0; j < n; ++j)//更新lowc数组,
        {//要将点p到其他点的数据加到lowc中,故只需要遍历map[p][j]即可,无需重复遍历已经遍历过的点
            if(!vis[j] && lowc[j] > graph[p][j])
                lowc[j] = cost[p][j];
        }
    }
    return ans;
}


kruskal (克鲁斯卡尔算法)

贪心+并查集

算法开始时,认为每一个点都是孤立的,分属于n个独立的连通块。
Kruskal算法将一个连通块当做一个集合。
首先将所有的边按从小到大顺序排序,然后按顺序枚举每一条边。如果这条边连接着两个不同的集合,那么就把这条边加入最小生成树,这两个不同的集合就合并成了一个集合(即选择一条最小的边,而且这条边的两个顶点分属于两个不同的集合);如果这条边连接的两个点属于同一集合,就跳过。直到选取了n-1条边为止。

typedef  struct
{
     int begin;
     int end;
     int weight;
} Edge;

/* 查找连线顶点的尾部下标 */
int Find( int *parent,  int f)
{
     while (parent[f] >  0)
        f = parent[f];

     return f;
}
/* 生成最小生成树 */
void MiniSpanTree_Kruskal(MGraph G)
{
     int i, j, n , m;
     int k =  0;
     int parent[MAXVEX]; /* 定义一数组用来判断边与边是否形成环路 */

    Edge edges[MAXEDGE]; /* 定义边集数组,edge的结构为begin,end,weight,均为整型 */

     /* 此处省略将邻接矩阵G转换为边集数组edges并按权由小到大排列的代码*/
     for (i =  0; i < G.numVertexes; i++)
        parent[i] =  0;

    cout <<  "打印最小生成树:" << endl;
     for (i =  0; i < G.numEdges; i++) /* 循环每一条边 */
    {
        n = Find(parent, edges[i].begin);
        m = Find(parent, edges[i].end);
         if (n != m) /* 假如n与m不等,说明此边没有与现有的生成树形成环路 */
        {
            parent[n] = m; /* 将此边的结尾顶点放入下标为起点的parent中。 */
             /* 表示此顶点已经在生成树集合中 */

            cout <<  "(" << edges[i].begin <<  ", " << edges[i].end <<  ") "
                 << edges[i].weight << endl;
        }
    }

}

单源最短路径

Dijkstra(迪杰斯特拉算法)

贪心

int dis[n+1];//当前各点到源点的距离
int vis[n+1];//各点到源点是否为最小值
int map[n][n];

void Dijkstra(int u)
{
	memset(vis,0,sizeof(vis)); //初始化 
	for(int t=1;t<=n;t++)
	{
		dis[t]=map[u][t];
	}
	vis[u]=1;
	for(int t=1;t<n;t++)
	{//第一次循环到点1只有一条边的且距离最短的点,该点到1的距离一定最短,不可能再通过其他点作为中转点对距离进行松弛操作

		int minn=Inf; //当前未确定最短路径的点,到源点的最小距离大小
		int temp;
		for(int i=1;i<=n;i++)//从上一次循环更新的dis数组中找到最小值作为新的中转点
		{
			if(!vis[i]&&dis[i]<minn)
			{
				minn=dis[i];
				temp=i;
			}
		}
         //每轮迭代都可以确定一个点到源点的最短距离,因此只要迭代n-1轮
		vis[temp]=1;
		//通过已经确定的点,对dis数组进行更新
		//(未确定的点通过点u,缩小了到源点的距离)
		for(int i=1;i<=n;i++)//通过中转点更新dis数组
		{//该循环只根据最新找到的中转点对到其他点的距离进行更新,因为之前找到的点已经更新过距离,没有必要重复计算

			if(map[temp][i]+dis[temp]<dis[i])
			{
				dis[i]=map[temp][i]+dis[temp];
			}
		}
	}
	
}

多元最短路径

floyd(弗洛伊德算法)

插点法的动态规划
计算任意两点间的最短路径
从最开始的只允许经过1号顶点进行中转,接下来只允许1,2进行中转。。。。。允许经过1~n 号所有的顶点进行中转,求任意两点的最短路径。

for(int k = 1 ; k <= n ; k ++)//最外层循环遍历顶点
{
        for(int i = 1 ; i <= n ; i ++)
        {
                for(int j = 1 ; j <= n ; j ++)
                {
                        if(e[i][j] > e[i][k] + e[k][j])
                                e[i][j] = e[i][k] + e[k][j];
                 }
        }
}

拓扑排序

”有向无环图"(Directed Acyclic Graph DAG)中
率先输出入度为零的点

void TopologicalSort(){
	queue<int>q;
    vector<int>edge[n];//记录每一个顶点的指向顶点有些哪些
    for(int i=0;i<n;i++)  //n  节点的总数
        if(in[i]==0) 
        	q.push(i);  //将入度为0的点入队列
    vector<int>ans;   //ans 为拓扑序列
    while(!q.empty())
    {
        int p=q.front(); 
        q.pop(); // 选一个入度为0的点,出队列
        ans.push_back(p);
        for(int i=0;i<edge[p].size();i++)//遍历所有p的指向顶点
        {
            int y=edge[p][i];
            in[y]--;
            if(in[y]==0)
                q.push(y);  
        }
    }
    if(ans.size()==n)   
    {
        for(int i=0;i<ans.size();i++)
            printf( "%d ",ans[i] );
        printf("\n");
    }
    else printf("No Answer!\n");   //  ans 中的长度与n不相等,就说明含环,无拓扑排序
}

求AOV网络的关键路径

AOV(Activity On Vertex)是一个有向图G
V(G)表示活动
E(G)表示领先关系
关键活动:延迟会影响整个工期的活动
关键路径:由关键活动构成的从开始到完成的路径,即最长路径

Earliest(v):

v的最早完成时间
是起点到点v的最长路径长度
表示所有v的前提任务都完成以后v才可以进行
Earliest(w)=max{ Earlist(vi)+edge(vi,w) }

Latest(v):

在不延迟整个工期下v的最晚完成时间
从终点逆向递推
Latest(v)=min{ latest(wi)-edge(v,wi) }

Delay(v):

Delay(v)=Latest(v)-Earliest(v)
Delay(v)=0的活动就是关键活动
由关键活动构成的最长路径叫关键路径

关键路径描述及代码参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值