将有向图中的顶点以线性方式进行排序。即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,顶点u总是在顶点v的前面。
如果存在环,那么就不可能满足u->v时u总是在v的前面了。所以必须是有向五环图(DAG)才能拓扑排序。
如果该DAG任意两个顶点都有确定的关系,拓扑排序就是唯一的。如果有这么一个唯一的拓扑排序,容易知道这样的顺序恰好能够遍历全图且每个顶点只经过一次,我们把这样的路径叫做哈密顿路径。
维护一个入度为0的集合S,每次从S中取出一个点放入存储答案的L数组,检查该点出发的所有边及连接的点,从图中移除这些边,如果连接的点在删除这条边以后入度变为0,将它加入到集合S中。
如此循环,直到集合S中没有点为止,检查图上还有没有边存在,如果有就存在环,如果没有L数组中存储的就是拓扑排序的结果。
集合S可以用栈实现。
#include
#include
#include
#include
#include
using namespace std;
int in[100100],n,m;
priority_queue<int> s;
vector<vector<int> > edge(100010);
vector<int> ans;
void Kahn(){
while(!s.empty()){
int cur=s.top();
s.pop(); ans.push_back(cur);
printf("%d ",cur);
vector<int> :: iterator it;
for(it=edge[cur].begin();it!=edge[cur].end();it++){
in[*it]--;
if(!in[*it]){
s.push(*it);
}
}
}
}
int main(){
memset(in,0,sizeof(in));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
in[y]++;
}
for(int i=1;i<=m;i++){
if(in[i]==0) {
s.push(i);
//printf("%d ",i);
}
}
Kahn();
return 0;
}
编程输入3个整数n,p,q,寻找一个由整数组成的数列(a1,a2,……,an),要求:其中任意连续p项之和为正数,任意连续q项之和为负数。0
仅一行分别表示n,p,q,之间用一个空格隔开。
只有一行,有解即输出这个数列,每个数之间用一个空格隔开。否则输出NO。
用数组S[i]代表前缀和
那么第k位数到第k+p-1位数的和就是S[k+p-1]-S[k-1]>0,可得S[k+p-1]>S[k-1]
同理S[k+q-1]-S[k-1]<0->S[k-1]>S[k+q-1]
如果S[j]>s[k]就从j->k连一条边,拓扑排序若有环则无解,否则拓扑序就是答案。
题解戳这里
#include
#include
#include
#include
#include
using namespace std;
int in[100100],n,m,clock,ou[100100];
priority_queue<int> s;
vector<vector<int> > edge(100010);
vector<int> ans;
void Kahn(){
while(!s.empty()){
int cur=s.top();
s.pop(); ans.push_back(cur);
ou[cur]=clock--;
//printf("%d ",cur);
for(int i=0;i//vector遍历范式
in[edge[cur][i]]--;
if(!in[edge[cur][i]]){
s.push(edge[cur][i]);
}
}
}
}
int main(){
memset(in,0,sizeof(in));
scanf("%d%d",&n,&m);
clock=n;
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
edge[y].push_back(x);
in[x]++;
}
for(int i=1;i<=n;i++){
if(in[i]==0) {
s.push(i);
//printf("%d ",i);
}
}
Kahn();
for(int i=1;i<=n;i++) printf("%d ",ou[i]);
return 0;
}
维护一个点集P{}存放已经加入生成树的点,维护一个边集E1{}【使用优先队列,以边权排序】存放所有与点集P中的点相连而没有加入生成树的边,维护边集E2{}存放已经加入生成树的边。
算法流程如下:
这里要转个弯,因为两种最小生成树算法(Prim&Kruskal)都是贪心算法,每次都在选取边权最小的边加入生成树,所以天然满足“最大边权最小”的条件。【证明:如果所求解不是最小生成树,必然会有一条边的边权与最小生成树不同,如果该边权比最小生成树中的小,在选择加入生成树时就会选它而不是边权更大的边,证毕。】然后求解最小生成树即可。
#include
#include
#include
#define maxn 300+20
using namespace std;
struct edge{
int l,r,w;
};
bool operator <(const edge &a,const edge &b){
return a.wint fa[maxn],n,m,u,v,c,ans=0;
edge g[20100];
int find(int a){
return fa[a] == a ? fa[a] : fa[a]=find(fa[a]);
}
void Kruskal(){
int cnt=0;
sort(g+1,g+m+1);
for(int i=1,j=0;i<=m,j1;i++){
int a=g[i].l;
int b=g[i].r;
if(find(a)!=find(b)){
ans=g[i].w;
fa[find(a)]=find(b);
j++;
}
}
return;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&c);
g[i].l=u; g[i].r=v; g[i].w=c;
}
for(int i=1;i<=n;i++) fa[i]=i;
Kruskal();
printf("%d %d",n-1,ans);
}
Prim的时间复杂度为O(n^2),n为图中顶点的个数,与边无关。所以近乎完全图的问题用Prim的效率更高,下面学习适用于稀疏图的复杂度为O(ElogE)的Kruskal算法。
将边(u,v)快排,从小到大考察加入生成树,如果u和v还没有相连(不属于同一个连通分量)就将这条边选入生成树,并将u和v所在联通分量合并,直到加入的边数为n-1为止。
关键点:边快排+并查集判断连通分量
int fa[mxn];//记录节点x在并查集中的父节点编号
int find(x){
if(fa[x]==x) return fa[x];
else fa[x]=find(fa[x]);
}
题目条件告诉我们,我们要把这个图分成K个子图,这些子图中的边权之和最小。用Kruskal添边时会有一个合并并查集的操作——每添一条边集合就会少一个,所以添N-K条边即可。
#include
#include
#include
#define maxn 20000
using namespace std;
struct edge{
int l,r,w;
};
bool operator <(const edge &a,const edge &b){
return a.wint fa[maxn],n,m,u,v,c,k,ans=0;
bool flag=false;
edge g[20000];
int find(int a){
return fa[a] == a ? fa[a] : fa[a]=find(fa[a]);
}
void Kruskal(){
int j=0;
sort(g+1,g+m+1);
for(int i=1;i<=m;i++){
int a=g[i].l;
int b=g[i].r;
if(find(a)!=find(b)){
ans+=g[i].w;
fa[find(a)]=find(b);
j++;
}
if(j>=n-k) {
flag=true;break;
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&c);
g[i].l=u; g[i].r=v; g[i].w=c;
}
for(int i=1;i<=n;i++) fa[i]=i;
Kruskal();
if(flag) printf("%d",ans);
else printf("No Answer");
return 0;
}
只有当图不存在负权回路时才有单源最短路
如果图中存在负权回路,就不可能有最短路了(越走越小…),用SPFA算法统计入队次数可以很容易判断图中是否有负权回路。方法有两种:
void Floyd(){
for(int k=1;i<=n;i++)
for(int i=1;j<=n;j++)
for(int j=1;k<=n;k++)
if(dist[i][j]>dist[i][k]+dist[k][j])
dist[i][j]=dist[i][k]+dist[k][j];
return;
}