图基础总结(算法导论)

1.图的基本算法

a.广、深度遍历

#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;
	}
}
b.拓扑序列

减治法,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的端点,就是死端,死端因为没有出边,所以没有后续节点,所以一定是最后输出的。

#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();
	}
}
c.强连通分量

强连通定义:任意两个顶点之间,都有互通路径(不是边!),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;
	}	
}
2.最小生成树

a.kruskal(并查集来做)

#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;    
}
b.prim
<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.每对顶点间的最短路径

a.

可以用动规来做,迪杰斯特拉可以做,表明该问题存在最优子结构,应该可以用动规来做,for【i,for【j,for【组长,for【小于组长1个规模的阵所有最优解】】】】,大约是O(n^4);也可以用for【迪杰斯特拉】来做大约是;也可以for【贝尔曼弗洛德】来做;

b.Floyd-Warshall(O(n^3)

#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;
	}
} 
c.稀疏图上的johnson算法

暂待

5.最大流

回忆时,理解不了,就去看视频,纸质一张图涵盖信息太大,理解起来会有困难的






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值