1.图的基本算法
a.广、深度遍历
b.拓扑序列#include<iostream> #include<vector> #include<queue> #include<cstdlib> using namespace std; //邻接矩阵存储 bool random(int s,int e) { return s+rand()%(e-s); } void breadthFirstSearch(const vector< vector<bool> > &gmap,vector<bool> &isvis,int sp) { if(isvis[sp]) { return; } queue<int> q; q.push(sp); isvis[sp]=true; while(!q.empty()) { sp=q.front(); q.pop(); cout<<sp<<" "; for(int i=0;i<gmap.size();i++) { if(gmap[sp][i]&&!isvis[i]) { q.push(i); isvis[i]=true; } } } } void depthFirstSearch(const vector< vector<bool> > &gmap,vector<bool> &isvis,int sp) { if(isvis[sp]) { return; } cout<<sp<<" "; isvis[sp]=true; for(int i=0;i<gmap.size();i++) { if(gmap[sp][i]&&!isvis[i]) { depthFirstSearch(gmap,isvis,i); } } } int main() { int num; cin>>num; vector< vector<bool> > gmap; vector<bool> isvis1; for(int i=0;i<num;i++) { vector<bool> temp; for(int j=0;j<num;j++) { temp.push_back(false); } gmap.push_back(temp); isvis1.push_back(false); } for(int i=0;i<num;i++) { for(int j=0;j<num;j++) { if(i!=j) { bool temp=random(0,3); gmap[i][j]=temp; } } gmap[i][5]=false;//为测试效果,弄张不连通的图出来,num>=6 gmap[5][i]=false; gmap[i][3]=false; gmap[3][i]=false; gmap[5][3]=true; gmap[3][5]=true; } for(int i=0;i<num;i++) { for(int j=0;j<num;j++) { cout<<gmap[i][j]<<" "; } cout<<endl; } vector<bool> isvis; cout<<"广度"<<endl; isvis=isvis1; for(int i=0;i<num;i++) { cout<<"childmap<"<<i<<">:"; breadthFirstSearch(gmap,isvis,i); cout<<endl; } cout<<"深度"<<endl; isvis=isvis1; for(int i=0;i<num;i++) { cout<<"childmap<"<<i<<">:"; depthFirstSearch(gmap,isvis,i); cout<<endl; } }
减治法,visit一个顶点,减去该顶点和他所有的边。输出入度为0的节点,入度为0的节点没有前驱节点,所以肯定得先输出
#include<iostream> #include<vector> #include<queue> using namespace std; const int num=5; int main() { vector< vector<bool> > gmap; vector<bool> isvis; vector<int> f; for(int i=0;i<num;i++) { vector<bool> temp; for(int j=0;j<num;j++) { temp.push_back(false); } gmap.push_back(temp); f.push_back(0); isvis.push_back(false); } gmap[0][1]=true; gmap[0][3]=true; gmap[1][2]=true; gmap[1][3]=true; gmap[2][4]=true; gmap[3][2]=true; gmap[3][4]=true; for(int i=0;i<num;i++) { for(int j=0;j<num;j++) { if(gmap[i][j]) { f[j]++;//各顶点入度统计 } } } for(int z=0;z<num;z++) for(int i=0;i<num;i++) { if(f[i]==0&&!isvis[i]) { isvis[i]=true; cout<<i<<" "; for(int j=0;j<num;j++) { if(gmap[i][j]) { f[j]--; } } } } }
深度优先遍历来做拓扑序列
深度遍历一次,并记下该端变为死端的顺序,这个顺序反过来就是拓扑序列
【1【2【3【5,5】3】【4,4】2】1】,各顶点变成死端的顺序为(5,3,4,2,1),所以拓扑序列为(1,2,4,3,5)
遍历边1->2,1的出度减1。出度为0的端点,就是死端,死端因为没有出边,所以没有后续节点,所以一定是最后输出的。
c.强连通分量#include<iostream> #include<vector> #include<stack> using namespace std; const int num=5; void DFS(vector< vector<bool> > &g,vector<bool> &isvis,int sp,stack<int> &s) { if(isvis[sp]) { return; } isvis[sp]=true; for(int i=0;i<num;i++) { if(g[sp][i]&&!isvis[i]) { DFS(g,isvis,i,s); } } s.push(sp);//sp的出边,出完了,出度减为0,出度为0后,没有后续节点了,压栈 } int main() { vector< vector<bool> > g; vector<bool> isvis; stack<int> s; for(int i=0;i<num;i++) { vector<bool> temp; for(int j=0;j<num;j++) { temp.push_back(false); } g.push_back(temp); isvis.push_back(false); } g[0][1]=true; g[0][3]=true; g[1][2]=true; g[1][3]=true; g[2][4]=true; g[3][2]=true; g[3][4]=true; for(int i=0;i<num;i++) { DFS(g,isvis,i,s); } while(!s.empty()) { cout<<s.top()<<" "; s.pop(); } }
2.最小生成树强连通定义:任意两个顶点之间,都有互通路径(不是边!),v有路径到w,w也有路径到v。
Kosaraju_Algorithm:(不会证明)
• step1:对原图G进行深度优先遍历,记录每个节点的离开时间。(得到G图的伪拓扑序列)
• step2:选择具有最晚离开时间的顶点,对反图GT进行遍历,删除能够遍历到的顶点,这些顶点构成一个强连通分量。
• step3:如果还有顶点没有删除,继续step2,否则算法结束。
#include<iostream> #include<vector> #include<stack> using namespace std; void DFS(const vector< vector<bool> > &g,vector<bool> &isvis,int sp,stack<int> &s) { if(isvis[sp]) { return; } isvis[sp]=true; for(int i=0;i<g.size();i++) { if(g[sp][i]&&!isvis[i]) { DFS(g,isvis,i,s); } } s.push(sp); } void DFS2(const vector< vector<bool> > &g,vector<bool> &isvis,int sp) { if(isvis[sp]) { return; } isvis[sp]=true; cout<<sp<<" "; for(int i=0;i<g.size();i++) { if(g[sp][i]&&!isvis[i]) { DFS2(g,isvis,i); } } } int main() { int num=8; vector< vector<bool> >g; vector<bool> isvis; stack<int> s; //输入图 for(int i=0;i<num;i++) { vector<bool> temp1; for(int j=0;j<num;j++) { temp1.push_back(false); } g.push_back(temp1); isvis.push_back(false); } g[0][1]=true;g[1][2]=true;g[1][4]=true;g[1][5]=true;g[2][3]=true; g[2][6]=true;g[3][2]=true;g[3][7]=true;g[4][0]=true;g[4][5]=true; g[5][6]=true;g[6][5]=true;g[7][6]=true;g[7][3]=true; //求伪拓扑序列 DFS(g,isvis,0,s); //求逆图 for(int i=0;i<num;i++) { for(int j=0;j<num;j++) { g[i][j]=false; } isvis[i]=false; } g[1][0]=true;g[2][1]=true;g[4][1]=true;g[5][1]=true;g[3][2]=true; g[6][2]=true;g[2][3]=true;g[7][3]=true;g[0][4]=true;g[5][4]=true; g[6][5]=true;g[5][6]=true;g[6][7]=true;g[3][7]=true; //遍历 while(!s.empty()) { int sp=s.top(); s.pop(); cout<<"storage chlidmap<"<<">:"; DFS2(g,isvis,sp); cout<<endl; } }
a.kruskal(并查集来做)
b.prim#include<iostream> #include<queue> using namespace std; #define Maxv 100+5 struct Node { int v2; int v1; int len; }; struct cmp { bool operator()(Node a,Node b) { return a.len>b.len; } }; int dis[Maxv][Maxv];//dis[i][j]等于0时表示不连通 ,不等于1时表示边权值 int fa[Maxv];//father,并查集 int Getfa(int i)//查找根节点的函数 { if(fa[i]!=i)//如果不是根节点 fa[i]=Getfa(fa[i]);//找根节点 return fa[i];//返回节点i所在集合的根节点 } int main() { int sum;//最小生成树代价 priority_queue<Node,vector<Node>,cmp> Q;//声明小顶堆,返回最小数 int vn;//图中的顶点个数 int i; int j; cin>>vn; //输入图 for(i=1;i<=vn;i++) { for(j=1;j<=vn;j++) { cin>>dis[i][j]; } } for(i=1;i<=vn;i++) { fa[i]=i;//并查集,father,一开始有vn个节点,就有vn个集合 } while(!Q.empty()) { Q.pop(); } //把每条边压入堆中 for(i=1;i<vn;i++) { for(j=i+1;j<=vn;j++) { if(dis[i][j])//如果边权值不为0,即顶点之间有边,压入该边 { Node e; e.v1=i; e.v2=j; e.len=dis[i][j]; Q.push(e); } } } sum=0; while(Q.size()!=0) { Node e; e=Q.top(); Q.pop(); if(Getfa(e.v1)!=Getfa(e.v2))//若两个顶点不属于同一个点集,表示该边不是回路;也即两个节点的根节点是否相同 { //根节点不同,则为不同集合,不构成回路 sum=sum+e.len; fa[Getfa(e.v2)]=Getfa(e.v1);//把e.v1的根节点作为e.v2的根节点的爹,也即合并两个集合 } } cout<<sum; return 0; }
<pre name="code" class="cpp">/************************************************************************************************************************** Prim最小生成树:1.从顶点0开始搜素最小权边 2.搜索到最短边之后,将另一个顶点u加入点集,将最短边值加入ret(总边权值) 3.判断从顶点u出发的边map[u][j](j为未加入点集的点),是否小于原先点集的点到点j的距离,如果是就替换掉 4.prim是遍历顶点,所以邻接矩阵比较合适 ***************************************************************************************************************************/ #define MaxN 101 int n,ret; int map[MaxN][MaxN]; void prim() { int closet[MaxN];//该点是否加入点集,1表示加入,0表示不加入 int dist[MaxN];//点集到各个离散点的距离 int i,j; for(i=0;i<n;i++) { closet[i]=0;//初始化全未加入 dist[i]=map[0][i];//初始化待搜索边集为顶点0的边集 } closet[0]=1;//顶点0,加入点集 int u,min; for(i=1;i<n;i++)//共n-1条边需要加入边集(直到所有点加入点集) { min=100001; for(j=1;j<n;j++)//搜索待选边集最小边权边 { if(!closet[j]&&dist[j]<min&&dist[j]>0)//点j未加入点集,点j与顶点之间的有边,且边权小于min { u=j; min=dist[j]; } } closet[u]=1;//顶点u加入点集 ret=ret+min;//总边权值 for(j=1;j<n;j++) { if(!closet[j]&&map[u][j]<dist[j])//将点u纳入点集后,需要更新点集到各个未纳入点集的点的距离 { dist[j]=map[u][j];//更新点集到离散点的距离 } } } }
3.单源最短路径
a.bellman-ford
支持负边权,但不支持负权回路
负权回路:
在一个图里每条边都有一个权值(有正有负)如果存在一个环(从某个点出发又回到自己的路径),而且这个环上所有权值之和是负数,那这就是一个负权环,也叫负权回路存在负权回路的图是不能求两点间最短路的, 因为只要在负权回路上不断兜圈子,所得的最短路长度可以任意小。
1.数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为无穷大, Distant[s]为0;
2.以下操作循环执行至多n-1次,n为顶点数:
对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;
3.为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。
#include<iostream> #include<vector> using namespace std; const int max_int=200;//最大值199,200及以上视为无穷大 struct Eage { int u; int v; int cost; }; bool bellmanFord(int num,vector<int> &d,const vector<Eage> e) { int nume=e.size(); //松弛 for(int i=0;i<num;i++) { for(int j=0;j<nume;j++) { if(d[e[j].v]>d[e[j].u]+d[e[j].cost]) { d[e[j].v]=d[e[j].u]+d[e[j].cost]; } } } //检测负权回路 for(int i=0;i<nume;i++) { if(d[e[i].v]>d[e[i].u]+d[e[i].cost]) { return false; } } return true; } void initDE(vector<Eage> &e,vector<int> &d,int nodenum,int eagenum,int sp) { for(int i=0;i<nodenum;i++) { d[i]=max_int; } d[sp]=0; for(int i=0;i<eagenum;i++) { Eage tempe; cin>>tempe.u>>tempe.v>>tempe.cost; e.push_back(tempe); if(tempe.u==sp) { d[tempe.v]=tempe.cost; } } } int main() { int nodenum; int eagenum; int sp; vector<int> d; vector<Eage> e; cin>>nodenum>>eagenum>>sp; initDE(e,d,nodenum,eagenum,sp); if(bellmanFord(nodenum,d,e)) { for(int i=0;i<nodenum;i++) { cout<<d[i]<<" "; } } return 0; }
b.Dijkstra
迪杰斯特拉不支持负边权
#include<iostream> #include<vector> #include<climits> using namespace std; int getMin(int dist[5],bool vt[5]) { int min=INT_MAX; int mark; for(int i=0;i<5;i++) { if(!vt[i]&&min>dist[i]&&dist[i]>0) { min=dist[i]; mark=i; } } return mark; } void dijkstra(int dis[5][5],bool vt[5],int dist[5],int v) { int mark; vt[v]=true; for(int i=0;i<5;i++) { dist[i]=dis[v][i]; cout<<dist[i]<<"\t"; } cout<<endl; for(int j=1;j<5;j++) { mark=getMin(dist,vt); vt[mark]=true; for(int i=0;i<5;i++) { if(!vt[i]&&((dist[i]>0&&dist[mark]+dis[mark][i]<dist[i]&&dis[mark][i]>0)||(dist[i]<0&&dis[mark][i]>0&&dis[mark][i]>0))) { dist[i]=dist[mark]+dis[mark][i]; } cout<<dist[i]<<"\t"; } cout<<endl; } } int main() { int dis[5][5]={{0,10,-1,30,100},{-1,0,50,-1,-1},{-1,-1,0,-1,10},{-1,-1,20,0,60},{-1,-1,-1,-1,0}}; bool vt[5]={false}; int dist[5]={-1}; int v; cin>>v; dijkstra(dis,vt,dist,v); cout<<"the distance is:"<<endl; for(int i=0;i<5;i++) { cout<<dist[i]<<"\t"; } }
c.有向无环图最短路径
1.求有向无环图的拓扑序列
2.松弛
#include<iostream> #include<vector> #include<stack> using namespace std; const int num=6; const int max_int=999; void DFS(const vector< vector<int> > &g,vector<bool> &isvis,int sp,stack<int> &s) { if(isvis[sp]) { return; } isvis[sp]=true; int num=g.size(); for(int i=0;i<num;i++) { if(g[sp][i]>=0&&!isvis[i]) { DFS(g,isvis,i,s); } } s.push(sp); } void initGID(vector< vector<int> > &g,vector<bool> &isvis,vector<int> &d) { for(int i=0;i<num;i++) { vector<int> tempg; for(int j=0;j<num;j++) { tempg.push_back(max_int);//不可达 } g.push_back(tempg); isvis.push_back(false); d.push_back(max_int); } d[0]=0; g[0][1]=3;g[0][2]=2;g[1][3]=2;g[1][4]=3; g[2][3]=4;g[2][5]=3;g[3][5]=2;g[4][5]=1; } void dagSP(const vector< vector<int> > &g,vector<int> &d,stack<int> &s,vector<int> &path) { while(!s.empty()) { path.push_back(s.top()); s.pop(); } //松弛 for(int i=1;i<num;i++) { for(int j=i-1;j>=0;j--) { if(d[path[i]]>d[path[j]]+g[path[j]][path[i]]) { d[path[i]]=d[path[j]]+g[path[j]][path[i]]; } } } } int main() { vector< vector<int> > g; stack<int> s; vector<bool> isvis; vector<int> d; vector<int> path; initGID(g,isvis,d); //获取有向无环图的拓扑序列,如果点u到点v有路径,那么在拓扑序列中,点u一定先于点v DFS(g,isvis,0,s); dagSP(g,d,s,path); for(int i=0;i<num;i++) { cout<<path[i]<<": "<<d[path[i]]<<endl; } }
4.每对顶点间的最短路径
5.最大流a.
可以用动规来做,迪杰斯特拉可以做,表明该问题存在最优子结构,应该可以用动规来做,for【i,for【j,for【组长,for【小于组长1个规模的阵所有最优解】】】】,大约是O(n^4);也可以用for【迪杰斯特拉】来做大约是;也可以for【贝尔曼弗洛德】来做;
b.Floyd-Warshall(O(n^3))
c.稀疏图上的johnson算法#include<iostream> #include<vector> using namespace std; int num; const int max_int=999; void initGD(vector< vector<int> > &d) { for(int j=0;j<num;j++) { vector<int> tempd; for(int k=0;k<num;k++) { tempd.push_back(max_int); } d.push_back(tempd); } for(int i=0;i<num;i++) { for(int j=0;j<num;j++) { int temp; cin>>temp; d[i][j]=temp; } } cout<<"***************"<<endl; } void floydWarshall(vector< vector<int> > &d) { for(int k=0;k<num;k++)//依次加入顶点i { for(int i=0;i<num;i++)//加入顶点i后各个d[]的表现 { for(int j=0;j<num;j++) { if(d[i][j]>d[i][k]+d[k][j]&&d[i][k]!=max_int&&d[k][j]!=max_int) { d[i][j]=d[i][k]+d[k][j]; } } } } } int main() { vector< vector<int> > d; cin>>num; initGD(d); floydWarshall(d); for(int i=0;i<num;i++) { for(int j=0;j<num;j++) { cout<<d[i][j]<<" "; } cout<<endl; } }
暂待
回忆时,理解不了,就去看视频,纸质一张图涵盖信息太大,理解起来会有困难的