第六章 图

一、图的存储及基本操作

邻接矩阵法

#define MaxVertexNum 100 //顶点数目的最大值
typedef char VertexType;//顶点的数据类型
typedef int EdgeType;//带权图中边上权值的数据类型
typedef struct{
	VertexType Vex[MaxVertexNum];//顶点表
	EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
	int vexnum,arcnum; //图的当前顶点数和弧数
}MGraph;

邻接表法

#define MaxVertexNum 100
typedef struct ArcNode{ //边表结点
	int adjvex;//该弧指向的顶点的位置
	struct ArcNode *next;//指向下一条弧的指针
	//InfoType info;//网的边权值
}ArcNode;

typedef struct VNode{ //顶点表结点
	VertexType data;
	ArcNode *first;
}VNode,AdjList[MaxVertexNum];

typedef struct{
	AdjList vertices;//邻接表
	int vexnum,arcnum;
}ALGraph;//邻接表存储的图

二、图的遍历

广度优先搜索BFS

广度优先遍历

bool visited[MAX_VERTEX_NUM];

void BFSTraverse(Graph G)
{
	for(int i = 0;i < G.vexnum;i++)
	{
		visited[i] = false;
	}
	InitQueue(Q);
	//以上为初始化
	for(int i = 0;i < G.vexnum;i++) //BFS遍历整个图
	{
		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)) //出队后不再访问该结点,直接遍历其邻接点
		//NextNeighbor(G,v,w)表示图G中v的除w外的下一个邻接点
		{
			if(!visited[w])//邻接点未访问过,则直接访问并入队
			{
				visit(w);
				visited[w] = true;
				EnQueue(Q,w);
			}
		}
	}
}

广度优先搜索求非带权图的单源最短路

void BFS_MIN_Distance(Graph G,int u)//从u出发的最短路径
{
	//d[i]表示从u到i的最短路径长度
	for(int i = 0;i < G.vexnum;i++)
	{
		d[i] = INF;//初始化
		visited[i] = false;
	}
	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))//w为u尚未访问的邻接点
		if(!visited[w])
		{
			visited[w] = true;
			d[w] = d[u] + 1;
			EnQueue(Q,w);
		}
	}
}

深度优先搜索DFS

bool visited[MAX_VERTEX_NUM];
void DFSTraverse(Graph G)
{
	for(int i = 0;i < G.vexnum;i++)
	{
		visited[i] = false;
	}
	for(int i = 0;i < G.vexnum;i++)
	{
		if(!visited[i])
		{
			DFS(G,i);
		}
	}
}

void DFS(Graph G,int u)
{
	visit(u);
	visited[u] = true;
	for(w = FirstNeighbor(G,u);w >= 0;w = NextNeighbor(G,u,w))
	{
		if(!visited[w])
		{
			DFS(G,w);
		}
	}
}

三、图的应用

最小生成树

1.Prim算法 O(n^2) 适用于点少边多的图

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 505,INF = 0x3f3f3f3f;
int n,m;
int g[N][N];
int dist[N];//表示点到集合中某点的距离
bool st[N];

int prim()
{
    int res = 0;
    memset(dist,INF,sizeof dist);
    for(int i=0;i<n;i++) //循环n次,把所有点都加入到集合中
    {
        int t = -1;
        for(int j=1;j<=n;j++) //找到集合外距离集合最短的点
        {
            if(!st[j] && (t == -1 || dist[j] < dist[t])) //j在集合外,且未找到任何一个点或j距离较小
            {
                t = j;
            }
        }
        if(i && dist[t] == INF) return INF; //不是第一个点且当前距离最近的点的距离都是无穷,
        //说明图不连通
        if(i) res += dist[t];//只要不是第一条边(无穷),就把t的距离加入到答案去
        
        for(int j=1;j<=n;j++)//用当前新加入的点更新其它集合外的点与集合的距离
        {
            dist[j] = min(dist[j],g[t][j]);
        }
        st[t] = true; //标志着当前点加入到集合中
    }
    return res;
}

int main()
{
    cin>>n>>m;
    memset(g,INF,sizeof g);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b] = g[b][a] = min(c,g[a][b]);//无向图
    }
    int t = prim();
    if(t == INF) puts("impossible");
    else cout<<t<<endl;
    return 0;
}

2.Kruskal算法 O(mlogm) 适用于边少点多的图

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 2e5+20,INF = 0x3f3f3f3f;
int p[M];
int n,m,a,b,c;
struct Edge
{
    int a;
    int b;
    int w;
}edges[M];

int find(int a) //查找a点的集合的祖宗
{
    if(a!=p[a]) p[a] = find(p[a]);
    return p[a];
}
bool cmp(struct Edge a,struct Edge b)
{
    return a.w < b.w;
}

void kruskal()
{
    int cnt = 0,res = 0;
    sort(edges+1,edges+m+1,cmp);//对每个边进行升序排序
    for(int i=1;i<=n;i++) //初始化所有集合
    {
        p[i] = i;
    }
    for(int i=1;i<=m;i++)
    {
        int a = edges[i].a;
        int b = edges[i].b;
        int w = edges[i].w;
        a = find(a),b = find(b); //若不在一个集合,则合并
        if(a!=b)
        {
            p[b] = a;
            cnt++;
            res += w;
        }
    }
    
    if(cnt < n-1) puts("impossible");//合并次数小于n-1,说明图不连通
    else cout<<res<<endl;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edges[i] = {a,b,c};
    }
    
    kruskal();

    return 0;
}

最短路

单源最短路 Dijkstra算法 O(n^2)

多源最短路 Floyd算法 O(n^3)

四、易错总结

  • 只要连通图中没有权值相同的边,则其最小生成树唯一
  • 当连通图中存在权值相同的边时,最小生成树不一定唯一
  • 深度优先遍历、拓扑排序、求关键路径可以判断一个有向图是否有环
  • 若一个有向图的顶点不能排成一个拓扑序列,则判定该有向图含有顶点数大于1的强连通分量,不一定是强连通图(如单顶点的图:强连通且存在拓扑序列)
  • 带权图的最小生成树中,某条边的权值可能超过未选边的权值(若错误,则意味着最小生成树一定是权值最小的n-1条边)
  • AOE网中,要缩短工期,必须将所有的关键路径都缩短,即每个关键路径的时间都缩短
  • 上三角邻接矩阵对应的图的拓扑序列必存在但可能不唯一
  • 单个顶点本身可以是强连通的,可以作为一个强连通分量
  • 邻接矩阵主对角元素全为0,无通路的结点元素为无穷
  • 关键路径用顶点序列表示,eg.(v1,v3,v5)
  • 根据工程优先关系及其时间画出AOE图时,最后应把无后继的结点指向时间跨度最长的结点,表明工期的结束
  • Dijkstra算法能给出一棵生成树,但不能给出最小生成树
  • 求有向图的强连通分量的数目的方法类似于拓扑排序:当某个顶点只有出边而没有入边时,其它顶点无法到达这个顶点,不可能于其他顶点构成强连通分量(此时单独构成强连通分量)
posted @   安河桥北i  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示