第十一章 图论相关知识点总结

图论知识点

1、表达式树

2、最小生成树

G=(V,E),连接图G的所有点,且边集是E是子集的树称为G的生成树,而权值最小的生成树称为最小生成树。构造最小生成树,可以采用Kruskal算法。

一、Kruskal算法(求图中最小生成树算法)

Kruskal算法首先将按照边的权值,将各边按照从小到大的顺序排列,然后依次考察各边(u,v)。

情况1:u和v在同一连通分量中,那么假如(u,v)后会形成环(即首尾相连),因此不能选择。

情况2:如果u和v在不同的连通分量里,那么加入(u,v)一定是最优的。

//n个顶点,m条边,u[i],v[i]表示第i条边的两个端点,w[i]表示第i条边的权值
//排序后第i小的边的序号保存在r[i]中。 
int cmp(const int i,const int j){return w[i]<w[j];}//间接排序函数 
int find(int x){ return p[x]==x? x : p[x] = find(p[x]);}//并查集的find,并进行压缩合并。
int Kruskal()
{
	int ans = 0;
	for(int i=0;i<n;i++) p[i]=i;//初始化并查集
	for(int i=0;i<m;i++) r[i]=i;//初始化边序号
	sort(r,r+m,cmp);//给边排序
	for(int i=0;i<m;i++)
	{
		//找出当前边两个端点所在集合的序号 
		int e = r[i]; int x=find(u[e]); int y=find(v[e]);
		if(x != y){	ans+=w[e];p[x]=y;}//如果在不同的集合合并 
	} 
	return ans;//返回最小生成树的权值。 
} 


二、Dijkstra算法(求图中单源最短路)

Dijkstra算法用于求解边权为正,从单个源点出发,到所有结点的最短路。该算法同时适用于有向图和无向图。

假设起点是结点 0,它到结点i的路径长度为d[i].未标号结点的v[i]=0,已标号结点的v[i]=1。为了简单起见,用w[x][y]=INF表示边(x,y)不存在。

memset(v,0,sizof(v));
//设置d[0]=0,其余为INF. 
for(int i=0;i<n;i++) d[i] = (i==0?0:INF);
for(int i=0;i<n;i++)//循环n次 
{
	int x,m=INF;
	//选择出d值最小的点 
	for(int y=0;y<n;y++) if(!v[y]&&d[y]<=m) m = d[x=y];
	v[x]=1;//对其进行标记 
	//从点x出发更新各个点。 
	for(int y=0;y<n;y++) d[y] = min(d[y],d[x]+w[x][y]);
}

打印路径的时候,从终点出发,不断顺着d[i]+w[i][j]==d[j]边回退到起点,也可以在进行算法中,记录最短路径的父节点。


邻接表存储图

使用邻接表存储图,在稀疏的图中有空间优势。下面的代码分别采用数组和vector的方式实现邻接表建图。

//首先需要给每条边编号,first[u]保存结点u的第一条边的编号,
//next[e]表示编号为e的边的“下一条边”的编号。
//下面是使用数组建立邻接表的过程。
int n,m
int first[manx];
int u[maxm],v[maxm],w[maxm],next[maxm];
void read_graph()
{
	scanf("%d%d",&n,&m);//输出顶点数量和边的数量 
	for(int i=0;i<n;i++) first[i]=-1; 
	for(int e=0;e<m;e++) 
	{
		//输入边连接的顶点和权值 
		scanf("%d%d%d",&u[e],&v[e],&w[e]);
		//将新输入的表插入链表的首部 
		next[e] = first[u[e]];
		first[u[e]] = e;
	}
} 
版本二:采用结构题和优先队列实现Dijkstra算法
struct Edge 
{
	int from, to, dist;
	Edge(int u,int v,int d):from(u),to(v),dist(d){}
}; 
struct Dijkstra 
{
	int n,m;
	vector<Edge> edges;
	vector<int> G[maxn];
	bool done[maxn];//是否进行标记 
	int d[maxn];//s到各点的距离 
	int p[maxn];//最短路中的上一条边
	
	void init (int n)
	{
		this->n = n;
		for(int i=0;i<n;i++) G[i].clear();
		edges.clear();
	} 
	
	void AddEdge(int from,int to,int dist)
	{
		edges.push_back(Edge(from,to,dist));
		m = edges.size;
		G[from].push_back(m-1);
	}
	
};

struct HeapNode 
{
	int d,u;//d为边权值和起点 
	bool operator < (const HeapNode& rhs) const
	{
		return d<rhs.d;
	}
};

void dijkstra(int s)
{
	priority_queue<HeapNode> Q;
	for(int i=0;i<n;i++) d[i]=INF;
	d[s]=0;
	memset(done,0,sizeof(done));
	Q.push((HeapNode){0,s});
	while(!Q.empty())
	{
		HeapNode x = Q.top();Q.top();
		int u = x.u;
		if(done[x]) continue;
		for(int i=0;i<G[u].size;i++)
		{
			Edge& e = edges[G[u][i]];
			if(d[e.to]>d[u]+e.dist)
			{
				d[e.to] = d[u] + e.dist;
				p[e.to] = G[u][i];
				Q.push((HeapNode){d[e.to],e.to});
				//使用优先队列进行动态排序。 
			}
		}
	} 
}

三、Bellman-Ford 算法(图中有负权,当最短路存在时,求最短路)

代码如下:

for (int i=0;i<n;i++) d[i]=INF;
d[0]=0;
for(int k=0;k<n-1;k++)//迭代n-1次 
	for(int i=0;i<m;i++)//检查每一条边 
	{
		int x = u[i],y = v[i];
		if(d[x]<INF) d[y]= min(d[y],d[x]+w[i]);
	} 

采用队列形式实现代码:

bool bellman_ford(int s)
{
	queue<int> Q;
	memset(inq,0,sizeof(inq));
	memset(cnt,0,sizeof(cnt));
	for(int i=0;i<n;i++) d[i] = INF;
	d[s]=0;
	inq[s] = true;
	Q.push(s);
	
	while(!Q.empty())
	{
		int u =Q.front();Q.pop();
		inq[u] = false;
		for(int i=0;i<G[u].size;i++)
		{
			Edge& e=edges[G[u][i]];
			if(d[u]<INF && d[e.to] > d[u] + e.dist)
			{
				e[e.to] = d[u] + e.dist;
				p[e.to] = G[u][i];
				if(!inq[e.to])
				{
					Q.push(e.to);
					inq[e.to]= true;
					if(++cnt[e.to]>n)
						return false;
				}
			}
		}
	}
	return true;
}


三、Floyd算法(求图中每两点的距离)

初始时,d[i][i]=0,其他d值设置为无穷大。

for(int k=0;k<n;k++)  
		for(int i=0;i<n;i++)  
       	 for(int j=0;j<n;j++)  
            	if(d[i][j]<INF && d[k][j]<INF)  
               		 d[i][j]=min(d[i][j],d[i][k]+d[k][j]);  


通过将代码中最后一行更换成d[i][j] = d[i][j] || (d[i][k] && d[k][j])

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值