• 图复习


    存储结构

    //图的二维数组邻接矩阵存储
    int n,e,w;    //定点数和边数 权值 
    int g[101][101];
    void  make1(){
    	cin>>n;
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j=+) g[i][j]=0x7fffffff;
    	} //初始化
    	cin>>e; 
    	int x,y;
    	for(int i=1;i<=e;i++) {
    		cin>>x>>y>>w;
    		g[x][y]=w;
    		g[y][x]=w; //这是由于无向图,所以有两句 
    	}
    } 
    
    //邻接表
    struct node{
    	int from,to,dis;
    	node(int a,int b,int c){
    		from=a;to=b;dis=c;
    	}
    }; 
    vector<node> adj[maxn];
    
    //数组模拟邻接表
    //专业名------链式前向星 
    struct Edge{
    	int next;  //下一条编的编号 
    	int to;    //这条边的去处 
    	int dis;   //边的权值 
    }edge[1001];                   //这是边 
    int head[101],num_edge;        //这是节点 
    
    void add_edge(int from,int to,int dis){  //添加一条从from到to的距离为dis的单向边! 
    	 edge[num_edge].to=to;
    	 edge[num_edge].dis=dis;
    	 edge[++num_edge].next=head[from];
    	 head[from]=num_edge; 
    }
    
    void make2(){
    	cin>>n>>e;
    	num_edge=0;
    	int x,y,d;
    	for(int i=1;i<=e;i++){
    		cin>>x>>y>>d;
    		add_edge(x,y,d);
    	}
    	for(int i=head[1];i!=0;i=edge[i].next){
    		//遍历操作 
    	}
    } 
    int main(){
    	
    return 0;
    }
    

    图的遍历

    BFS、DFS,通过图的遍历也可以找到连通块的个数

    重要概念:

    深度优先生成树

    回退边

    void dfss(int x,int depht){ //带层数的(邻接表版) 
    	vis[x]=1;
    	for(int i=1;i<=adj[x].size();i++){
    		int j=adj[x][i];
    		if(vis[j]) dfs(j,depth+1); //不需要判断是不是连接 
    	}
    }
    //广度优先 
    struct node{
    	int v; //顶点编号
    	int layer; //层号 
    };
    vector<node> ajd[MAXN];
    void bfsss(int x,int layer){
    	node start;
    	start.v=x;  //起始编号
    	start.layer=0;
    	queue<node> q;
    	q.push(start);
    	vis[x]=1;
    	while(!q.empty()){
    		node now=q.front();
    		q.pop();
    		int u=now.v;
    		for(int i=1;i<ajd[u].size();i++){
    			node j=adj[u][i]; //相连的顶点
    			j.layer=now.layer+1;
    			if(vis[j.v]==0){
    				q.push(j);
    				vis[j.v]=1;
    			} 
    			
    		}
    	} 
    }
    
    //对整张图进行遍历
    //如果图是联通的,那么只需要进行一次遍历了
    void travdfs(){
    	for(int i=1;i<=n;i++){
    		if(vis[i]==0) dfs(i);
    	}
    } 
    void travbfs(){
    	for(int i=1;i<=n;i++){
    		if(vis[i]==0) bfs(i,1);  //层数 
    	} 

    欧拉路(一笔画问题)、欧拉回路

    从图中某个点出发遍历整个图,每条边通过且通过一次。

    一、是否存在欧拉路或欧拉回路

    (1)图应该是联通图:DFS或并查集

    (2)无向图:全部都是偶点:存在欧拉回路

           有两个奇点:存在欧拉路,一个为起点,一个为终点

    (3)有向图:每个点的出度标记为1,入度标记为-1,出度+入度即为度数,

            有向图存在欧拉路:只有1个度为1(起点),1个度为-1(终点),其他都为0

            有向图存在欧拉回路:全部都为0

    二、输出欧拉回路:

    递归DFS,在后面打印或记录,但是如果数据很大,就得采用非递归形式。

    三、混合图欧拉回路问题:最大流

    http://ybt.ssoier.cn:8088/problem_show.php?pid=1341

    int n,e;
    int circuit[101],d[101][101],c[101];  //c是每个点的度,用来判断是欧拉路还是欧拉回路 
    int num;
    void find(int i){
    	for(int j=1;j<=n;j++){
    		if(d[i][j]==1){
    			d[i][j]=d[j][i]=0;  //删除这条边
    			find(j); 
    		}
    	}
    	circuit[++num]=i;  //记录路径 
    }
    //这是欧拉路(2个奇点),欧拉回路(0个奇点) 
    int main(){
    	cin>>n>>e;
    	int x,y;
    	memset(c,0,sizeof(c));
    	memset(d,0,sizeof(d));
    	for(int i=1;i<=e;i++){
    		cin>>x>>y;
    		d[x][y]=d[y][x]=1;
    		c[x]++;
    		c[y]++;
    	} 
    	int start=1;
    	for(int i=1;i<=n;i++){
    		if(c[i]%2==1) start=i; //从奇点开始 
    	}
    	num=0;
    	find(start);
    	for(int i=1;i<=num;i++) cout<<circuit[i]<<" ";
    	cout<<endl; 
    return 0;
    }
    

    哈密尔顿环:不重复的走过所有的点,并且是回路

    //哈密尔顿环:不重复的走过所有的点,并且是回路
    //能找出所有的环 
    int vi[1001],visted[1001],num[1001],g[1001][1001]; 
    int length;
    int ans[1001]; //保存答案 
    int n,m,x;
    void print(){
    	for(int i=1;i<length;i++) cout<<ans[i]<<" ";
    	cout<<ans[length]<<endl;
    }
    void dfs(int last,int i){  //上次访问的last,这次的i 
    	visted[i]=1;
    	vi[i]=1;     //标记 
    	for(int j=1;j<=num[i];j++){
    		if(g[i][j]==x&&g[i][j]!=last){
    			ans[++length]=j;
    			print();   //找到一个环,输出 
    			length--;
    			break; 
    		}
    		if(!visted[g[i][j]]) dfs(i,g[i][j]);  //遍历所有与i关联的点 
    	}
    	length--;
    	visted[i]=0;  //回溯  不标记vi[],因为vi表示是否在图中出现过 
    } 
    
    
    int main(){
    	memset(visted,0,sizeof(visted));
    	memset(vi,0,sizeof(vi));
    	cin>>n>>m;
    	int y;
    	for(int i=1;i<=m;i++){
    		cin>>x>>y;
    		g[x][++num[x]]=y;
    		g[y][++num[y]]=x;
    	}
    	for(x=1;x<=n;x++){
    		
    		if(!vi[x]) {
    		length=0;
    		dfs(0,x);
    		
    		}  //以每一个点为起点遍历,因为不是任一个点都可以遍历出环的 
    	}
    	
    return 0;
    }
    

      

    最短路径

    dijkstra:O(N^2),,单源最短路径,不能有负边.可以通过堆优化为O(nlogn+m)

    //图的结构都用邻接表写 
    //第一种:最简单的加上记录路径 
    struct node{
    	int v;
    	int dis;
    };
    vector<node> G[maxn];
    int pre[manx];  //最简单的一种pre写法(苦笑——
    //输出过程 
    int dis[maxn]={0};  //记录起点与其他各点的最短距离 
    void outputdfs(int v,int st){
    	if(v==st){
    		cout<<st<<endl;
    		return;
    	}
    		outputdfs(pre[v],st);
    		cout<<v<<" ";
    }
    void dijkstra1(int st){
    	int numnode,numedge,x,y,diss;
    	cin>>numnode>>numedge;
    	for(int i=0;i<numedge;i++){
    		cin>>x>>y>>diss;
    		node a,b;
    		a.v=x;a.dis=diss;b.v=y;b.dis=diss;
    		G[x].push_back(b);
    		G[y].push_back(a); //无向图 
    	}//以后可以直接写构造函数 
    	fill(dis,dis+maxn,INF);
    	dis[st]=0;  //
    	for(int i=0;i<numnode;i++) pre[i]=i; //初始化pre数组不要忘了!!!!
    	for(int i=0;i<numnode;i++){
    		int u=-1,numi=INF;
    		for(int j=0;j<numnode;j++){
    			if(vis[j]==0&&dis[j]<mini){
    				mini=dis[j];
    				u=j;
    			}
    		} //找点的过程 
    		if(u==-1) return; //退出标志
    		vis[u]=1;
    		//这是第一阶段
    		for(int j=0;j<G[u].size();j++){
    			//更新与找到的这个点相连的点
    			int v=G[u][j].v;
    			if(vis[v]==0&&dis[v]<dis[u]+G[u][j].dis){
    				dis[v]=dis[u]+G[u][j].dis;
    				pre[v]=u;
    			} 
    		} 
    	} 
    }
    

    有第二标尺的

    边权标尺(花费等,至于边权!=距离)

    int cost[maxn][maxn];
    int c[maxn];
    /*
    struct node{
    	int v;
    	int dis;
    };
    vector<node> G[maxn];
    int dis[maxn]={0};  */
    void dijkstra2(int st){
    	 fill(dis,dis+maxn,INF);
    	 dis[st]=0;
    	 fill(c,c+maxn,INF);
    	 c[st]=0;
    	 for(int i=0;i<numnode;i++){
    	 	int u=-1,mini=INF;
    	 	for(int j=0;j<numnode;j++){
    	 		if(vis[j]==0&&dis[j]<mini){
    	 			mini=dis[j];u=j;
    			 }
    		 }
    		 if(u==-1) return ;
    		 vis[u]=1;
    		 //以上为第一阶段 
    		 for(int j=0;j<G[u].size();j++){
    		 	int v=G[u][j].v;
    		 	if(vis[v]==0){
    		 		if(dis[v]>dis[u]+G[u][j].dis){
    		 			dis[v]=dis[u]+G[u][j].dis;
    		 			c[v]=c[u]+cost[u][v];
    				 }
    		 		else if(dis[v]==dis[u]+G[u][j].dis&&c[v]>c[u]+cost[u][v]){
    		 			c[v]=c[u]+cost[u][v]; //因为是花费,所以越小越好 
    				 }
    			 }
    			 
    		 }
    		 
    	 }
    }
    

    点权(例如资源等)越多越好 

    int weight[maxn];
    int w[maxn];
    /*
    struct node{
    	int v;
    	int dis;
    };
    vector<node> G[maxn];
    int dis[maxn]={0};  */
    void dijkstra3(int st){
    	fill(w,w+maxn,0);   //注意不同:点权的其他不等于起点的都赋值位0,边权赋值位无穷大 
    	w[st]=weight[st];
    	fill(dis,dis+maxn,INF);
    	dis[st]=0; 
    	//初始化阶段 
    	for(int i=0;i<numnode;i++){
    		int u=-1,mini=INF;
    		for(int j=0;j<numnode;j++){
    			if(vis[j]==0&&dis[j]<mini){
    				mini=dis[j];u=j;
    			}
    		}
    		if(u==-1) return ;
    		vis[u]=1;
    		//以上为第一阶段 
    		for(int j=0;j<G[u].size();j++){
    			int v=G[u][j].v;
    			if(vis[v]==0){
    				if(dis[v]>dis[u]+G[u][j].dis){
    					dis[v]=dis[u]+G[u][j].dis;
    					w[v]=w[u]+weight[v];
    				}
    				else if(dis[v]==dis[u]+G[u][j].dis&&w[v]<w[u]+weight[v]){
    					w[v]=w[u]+weight[v]
    				}
    			}
    		}	
    	}
    }
    

    路径条数

    int num[maxn]; //就多这一个数组 
    /*
    struct node{
    	int v;
    	int dis;
    };
    vector<node> G[maxn];
    int dis[maxn]={0};  */
    void dijkstra4(int st){
    	fill(num,num+maxn,0);   //与点权一样:与起点不同的都赋值为0;起点为1 
     	num[st]=1;
    	fiil(dis,dis+maxn,INF);
    	dis[st]=0;
    	for(int i=0;i<numnode;i++){
    		int u=-1,mini=INF;
    		for(int j=0;j<numnode;j++){
    			if(vis[j]==0&&mini>dis[j]){
    				mini=dis[j];u=j;
    			}
    		}
    		if(u==-1) return ;
    		vis[u]=1;
    		for(int j=0;j<G[u].size();j++){
    			int v=G[u][j].v;
    			if(vis[v]==0){
    				if(dis[v]>dis[u]+G[u][j].dis){
    					dis[v]=dis[u]+G[u][j].dis;
    					num[v]=num[u];   //继承 
    				}
    				else if(dis[v]==dis[u]+G[u][j].dis){
    					num[v]+=num[u];  //加上 
    				}
    			}
    		}
    	}
    }
    

    第二标尺不满足最优子结构时,需要改变算法,即不能在Dijkstra的算法过程中直接求出最优而是应该先求出所有的最优路径,然后选择第二标尺最优的那条路。所以采用Dijkstra+DFS的方法,Dijkstra求出所有的最优路径,DFS求出第二标尺最优的

    所以改变是pre[maxn]---vector<int> pre[maxn]‘

    vector<int> pre[maxn];
    void dijkstra5(int st){
    	fill(dis,dis+maxn,INF);
    	dis[st]=0;
    	for(int i=0;i<numnode;i++){
    		int u=-1,mini=INF;
    		for(int j=0;j<numnode;j++){
    			if(vis[j]==0&&mini>dis[j]){
    				mini=dis[j];u=j;
    			}
    		}
    		if(u==-1) return ;
    		vis[u]=1;
    		//以上为第一阶段
    		//改变的是下面的第二阶段,在记录最优路径的时候
    		for(int j=0;j<G[u].size();j++){
    			int v=G[u][j].v;
    			if(dis[v]>dis[u]+G[u][j].dis){
    				dis[v]=dis[u]+G[u][j].dis;
    				pre[v].clear();
    				//先清空
    				pre[v].push_back(u); 
    			}
    			else if(dis[v]==dis[u]+G[u][j].dis){
    				//如果距离一样 ,就压入
    				pre[v].push_back(u); 
    			}
    		} 		
    }
    } 
    
    	//接下来找出第二标尺最优的那个路径 
    	//当画出这个路径时,会发现是一颗树的结构,根节点是终点,叶子节点都是起点(所以在有些情况下需要逆序),这样走下来找到最优标尺
    	//因为有多条路径,每次决定走哪条,所以用递归搜索+回溯的方法
    	//有一点绝对要注意,因为最后的叶子节点(起点)无法自己入数组,所以需要自己碰到叶子节点是把它push进来 
    vector<int> temppath,path;  //一个用来临时存路径,一个用来存最优路径 
    int maxvalue;
    void DFS(int st,int v){
    	if(v==st){
    		temppath.push_back(v);
    		//计算这条路径上的最优路径值
    		int value=0;
    		//eg:边权值和
    		for(int i=temppath.size();i>0;i--){               //这两个例子其实都满足最优子结构,可以直接用dijkstra来解,但是这个通用模板必须记住 
    			//计算边权值和,边界时i>0;
    			int now=temppath[i],next=temppath[i-1];
    			value+=G[now][next].dis;
    		} 
    		//eg:点权值和
    		for(int i=temppath.size();i>=0;i--){
    			//计算点权值和:边界为i>=0
    			int id=temppath[i];
    			value+=weight[id]; 
    		}
    		 if(value>maxvalue){
    		 	maxvalue=value;
    		 	path=temppath;
    		 }
    		 //记录最优路径
    		 //不要忘记弹出噢!!!
    		 temppath.pop_back(); 
    		 return;
    		 //以及return噢!~ 
    	}
    	temppath.push_back(v);
    	for(int i=0;i<pre[v].size();i++){
    		DFS(st,pre[v][i]);
    	}
    	temppath.pop_back();
    	//也不要忘记弹出回溯噢!!!~ 
    }
    

    Bellman-ford:O(NM),

    对边进行遍历。不能有负权回路,但是能提示,可用循环队列。

    但是如果从原点无法到达负环的话,是不会有有影响的。

    可以处理负边权,再进行以此松弛操作既可以判断是不是存在负环 、所有的边进行操作,看能不能通过这条边来进行优化

    最短路径树:层数不超过V,源点s作为根节点,其他节点按照最短路径的节点顺序连接

    注意求路径数的时候,vector<int> pre[maxn]要改为set<int> pre[maxn]

    bool ford(int s){
    	fill(dis,dis+maxn,INF);
    	dis[s]=0;
    	//n是节点个数,因为最后是一棵树,所以边数为n-1即一共只需要n-1次循环
    	for(int i=0;i<n-1;i++){
    		for(int j=0;j<n;j++){
    			for(int z=0;z<adj[j].size();z++){
    				int v=adj[j][z].v;
    				int di=adj[j][z].dis;
    				if(di+dis[j]<dis[v]) dis[v]=dis[j]+di; 
    			}
    		}
    	}
    	//再遍历以下所有的边,看还能不能松弛 
    	for(int i=0;i<n;i++){
    		for(int j=0;j<adj[i].size();j++){
    			int v=adj[i][j].v;
    			int di=adj[i][j].dis;
    			if(dis[v]>dis[i]+di)  return 0;
    		}
    	} 
    	return 1;
    }
    //如果用ford算法求解路径的话
    //需要用
    set<int>  pre[maxn];
    int num[maxn];
    if(dis[v]>dis[j]+di){
    	dis[v]=dis[j]+di;
    	num[v]=num[j]; //直接覆盖
    	pre[v].clear(); 
    	pre[v].insert(j);
    } 
    else if(dis[v]==dis[j]+di){
    	pre[v].insert(j); //先插入
    	num[v]=0; //先付0
    	for(set<int>::iterator it=pre[v].begin();it!=pre[v].end();it++) num[v]+=num[*it]; //不是直接加*it啊 
    }
    SPFA:ford的队列实现,单源最短路径,与BFS的区别:出了队的可以再次入队。
    对ford的优化:只有最短路改变了的才可能继续改变其他的节点的最短路,所以没必要访问全部

    判断有无负环的方法是计算每个节点的入队次数,如果入队次数超过n就存在负环了

    int vis[maxn];//这是用来记录是不是在队列里面的
    int num[maxn]; //记录入队次数(如果说明不存在负环就不需要这个)
    bool spfa(int s){
    	fill(dis,dis+maxn,INF);
    	dis[s]=0;
    	vis[s]=1;
    	num[s]++; //入队次数+1
    	queue<int> q;
    	q.push(s);
    	while(!q.empty()){
    		int top=q.front();
    		q.pop();
    		vis[top]=0; //出队了
    		//接着访问这个节点的所有邻接边
    		for(int i=0;i<adj[top].size();i++){
    			int v=adj[top][i].v;
    			int diss=adj[top][i].dis;
    			if(dis[v]>dis[top]+diss) {
    				dis[v]=dis[top]+diss;
    				//先松弛,然后判断能不能入队
    				if(!vis[v]){
    					q.push(v);
    					vis[v]=1;
    					num[v]++;
    					if(num[v]>=n) return 0;
    				} 
    			}
    		} 
    	}
    	return 1; 
    } 
    

    Floyd:O(N^3),全源最短,可以处理负边权,可以判断负环

    负环判断:初始化所有的dp[i][i]=0后,如果结束时dp[i][i]<0,那么就存在负环

    //n在200以内
    //在main()函数里面先执行:
    for(int i=0;i<n;i++) dis[i][i]=0;
    //然后是函数体
    void floyd(){
    	for(int k=0;k<n;k++){
    		for(int i=0;i<n;i++){
    			for(int j=0;j<n;j++){
    				if(dis[i][k]!=INF&&dis[k][j]!=INF&&dis[i][k]+dis[k][j]<dis[i][j])
    				dis[i][j]=dis[i][k]+dis[k][j];
    			}
    		}
    }
    }  

      SPFA算法有两个优化算法 SLF 和 LLL: SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。 LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。个人觉得LLL优化每次要求平均值,不太好,为了简单,我们可以之间用c++STL里面的优先队列来进行SLF优化。

    最小生成树:在无向图中,连接所有的点,不形成环,使所有的边权值和最小的树  

    算法有:prim算法: dijkstra算法类似(dis[i]的含义不同,dijkstra中是起点,prim中是已经访问过的所有点) 稠密图时使用 O(V^2)
    kruskal算法:并查集思想,每次都找到最小的边,判断这两个边连接的点是不是在同一个集合中,如果不是就连接起来 稀疏图时使用 O(ElogE)

    struct node{
    	int v,dis;
    	node(int _v,int _dis) : v(_v),dis(_dis){}
    };
    vector<node> adj[maxn];
    int dis[maxn],vis[maxn];
    int n,m,st,ed;
    void prim(int st){
    	//树的总权值,以及当前所有的连接好了的边
    
    	 for(int i=0;i<n;i++){
    	 	int u=-1,mini=INF; //与dijkstra是不是超级像!!!!!
    		 for(int j=0;j<n;j++){
    		 	if(mini>dis[j]&&vis[j]==0) {
    		 		mini=dis[j];
    				 u=j;
    			 }
    		 } 
    		 if(u==-1) return;
    		 vis[u]=1;
    		 ans+=dis[u]; //!!!!!啊啊啊记住这个 
    		 for(int j=0;j<adj[u].size();j++){
    		 	int id=adj[u][j].v;
    		 	int diss=adj[u][j].dis;
    		 	if(!vis[id]&&dis[id]>diss){
    		 		dis[id]=diss;//!!!! 
    			 }
    		 }
    	 }
    	 
    }
    

    kruskal

    struct edge{
    	int from,to;
    	int dis;
    }E[maxn]; 
    
    int fa[maxn];
    int findfather(int x){
    	if(x!=fa[x]) return findfather(fa[x]);
    	return fa[x];
    }
    bool cmp(edge a,edge b){
    	return a.dis<b.dis;
    }
    void kruskal(int n,int m){
    	//n是顶点数,m是边数
    	for(int i=0;i<n;i++) fa[i]=i;  //先弄这个
    	fill(dis,dis+maxn,INF);
    	memset(vis,0,sizeof(vis));
    	dis[0]=0;
    	int ans=0,numedge=0; //这里才有!!总的权值和现在有了的边
    	 sort(E,E+m,cmp); //对边进行排序
    	 for(int i=0;i<m;i++){
    	 	int fa1=findfather(E[i].from);
    	 	int fa2=findfather(E[i].to);
    	 	if(fa1!=fa2){
    	 		fa[fa2]=fa1;
    	 		numedge++;
    	 		ans+=E[i].dis;
    	 		if(numedge==n-1) break; //!!!!!!!如果边数已经够了的话就可以break了 
    		 }
    	 }
    	 if(numedge!=n-1) {
    	 	cout<<"error no liantong"<<endl;
    	 	return;
    	 } 
    	  else{
    	  	cout<<ans<<endl;
    	  	return;
    	  }
    } 
    

    拓扑排序:前提条件是有向无环图(DAG),排列成为有序的。通过队列、计算入度,每次把入度为0的加入队列,然后删掉所有从这个点出发的边,每个相连的点的入度-1 

    应用:判断图是不是有向无环图 。如果队列为空时,入过队的为n,那就排列成功,不然就有环

     //下面是伪代码 
    //用邻接表实现
    struct node{
    	int v,dis;
    }; 
    vector<int> adj[maxn];
    int innode[maxn]; //节点入度
    vector<node> adj[maxn];//临界表 
    bool list(){
    	int num=0; //这个是已经有序的节点个数
    	queue<int> q;//队列
    	for(int i=0;i<n;i++){
    		if(innode[i]==0) q.push(i); //节点出度为0的,都压入 
    	} 
    	while(!q.empty()){
    		int top=q.front();
    		q.pop();
    		for(int j=0;j<adj[top].size();j++) {
    		int id=adj[top][j];
    		innode[id]--;
    		if(innode[id]==0) q.push(id);
    	}
    	adj[top].clear();//删掉所有与之相邻的边
    	num++; 
    }
    	if(num==n) return 1;
    	else return 0;
    }
    

    拓扑排序用bFS和DFS都能实现

    BFS:无前驱的顶点优先,无后继的顶点优先

    DFS:从一个入度为0的点开始DFS,递归返回的顺序就是拓扑排序(逆序),可以用stack实现

    入度为0的点:不需要特别处理,想象一个虚拟的点,单向连接到所有点,只要在主程序中把每个点轮流执行一遍DFS

    判断环:递归时发现回退边

    关键路径:

    //首先是区分事件和活动,事件是节点,活动是边,因为是求关键活动,所以是先通过求事件的最早发生和最迟发生,然后再来求活动的最早开始和最晚开始 
    //先求点,再夹边 
    //事件:  ve[i]   vl[i]
    //活动:  e[j]    l[j]
    //求事件(顶点)最早发生和最迟发生:    ve[j]=max{ve[i]+length[i->j]}  (i->j) 拓扑排序
    //                              vl[i]=min{vl[j]-length[i->j]}  (i->j) 逆拓扑排序 
    //之间的关系是,求活动(边)的最早开始和最晚开始:  e[i->j]=ve[j] (i->j)
    //                l[i->j]=ve[j]-length[i->j]
    //拓扑排序序列用stack存储,这个逆拓扑排序就不用特意去求了
    
    //求拓扑序列,顺便求ve[N]
    stack<int> toporder;
    int ve[maxn],vl[maxn];
    bool  logicalsort(){
    	//int num=0; //已经在了的点
    	//不用num了,直接判断toporder.size()就可以了 
    	queue<int> q;
    	for(int i=0;i<n;i++) if(innode[i]==0) q.push(i); 
    	//先把入度为0的点全部入队 
    	while(!q.empty()){
    		int top=q.front();
    		q.pop();
    		toporder.push(top); //拓扑序列进栈
    		 for(int i=0;i<adj[top].size();i++){
    		 	int id=adj[top][i].v;
    		 	innode[id]--; //入度-1
    			 if(innode[id]==0) q.push(id); 
    			 //边是top->id 
    			 if(ve[top]+adj[top][i].dis>ve[id]) ve[id]=ve[top]+adj[top][i].dis; 
    		 }
    	}
    	if(toporder.size()==n) return 1;
    	else return 0;
    } 
    //接下来就是求关键路径了,求出vl,然后计算出e[],l[],如果e[i]==l[i] 就是关键活动 
    int criticalpath(){
    	memset(ve,0,sizeof(ve));
    	if(logicalsort()==0) return -1;
    	//先把所有的vl[]都赋值为ve[n-1] ,然后进行逆拓扑序列求解
    	fill(vl,vl+n,ve[n-1]);
    	while(!toporder.empty()){
    		int top=toporder.top();
    		toporder.pop();
    		for(int i=0;i<adj[top].size();i++){
    			int id=adj[top][i].v;
    			//top的后继节点是id,用id的值来更新top 
    			if(vl[id]-adj[top][i].dis<vl[top]) vl[top]=vl[id]-adj[top][i].dis;
    		}
    	}
    	//遍历临界表所有的边,计算活动的e[]和l[] 
    	for(int i=0;i<n;i++){
    		for(int j=0;j<adj[i].size();j++){
    			int v=adj[i][j].v;
    			int diss=adj[i][j].dis;
    			int e=ve[v],l=vl[v]-diss;
    			if(e==l) cout<<e<<"->"<<l<<endl;
    		}
    	}
    } 
    

    动态规划实现DAG最长路

    第一种:不固定起点终点

    //用动态规划实现的:最简单
    //不固定起点和终点
    //dp[i]表示从i出发能获得的最长路:递归+记忆化(已经自动实现了字典序最小
    		//如果dp[i]表示以i结尾的:不能实现最小序 
    //记录路径:choice[]记录后继
    int dp[maxn];
    int g[maxn][maxn];
    int chioce[maxn];
    int dp(int i){
    	if(dp[i]>0) return dp[i];  //记忆化
    	for(int j=0;j<n;j++){
    		if(g[i][j]!=INF){
    			int temp=dp(j)+g[i][j];  //递归
    			if(temp>dp[i]){
    				dp[i]=temp;
    				choice[i]=j;
    			}
    		}
    	}
    	return dp[i];
    } 
    void print(int i){
    	cout<<i<<" ";
    	while(choice[i]!=-1){ //记录的就是后继
    		i=choice[i];
    		cout<<i<<" ";
    	}
    } 
    

    第二种:固定终点T

    与前一种的区别在于初始化,dp[]应该被初始化为-INF,表示不可达,但是dp[T]=0,另外设置一个vis[]数组

    int dp(int i){
    	if(vis[i]) return dp[i];
    	vis[i]=1;
    	for(int j=0;j<n;j++){
    		if(g[i][j]!=INF){
    			dp[i]=max(dp[i],dp(j)+g[i][j]);
    		}
    	}
    	return dp[i];
    } 
  • 相关阅读:
    tfrecord汇总
    python2中的编码的问题
    python multiprocessing的问题
    转载,ubuntu shell ~/.bashrc /etc/bash.bashrc
    singleton模式 Python
    目标检测 tensorflow(预训练模型)
    functools.partial 小记
    python 踩坑小计 virtualenv-site-packages
    python3.6 _tkinter module问题 python源码重新编译
    windows核心编程之网络编程入门篇
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12370182.html
Copyright © 2020-2023  润新知