图论知识点
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])