最近在总结图的有关算法,参考了一些博客,然后自己实现了一遍,汇总如下:
概念就不多总结了
1.有向无环图的拓扑排序
基本思路:
假设我们先统计了边的对应关系邻接表edge,以及对应每个node的入度数组in,并且因为是bfs来进行,使用queue来辅助,那么按照之前说的
1.先遍历in数组,选取一个入度为0的节点进queue
2.while(!myqueue.emepty())的情况下一直进行遍历,对于当前取到的node,我们遍历相连关系数组edge,找到所有相连的node,in数组中node相应的入度-1,同时判断如果这个node的入度已经为0了,进queue
3.记住,最终我们要判断ans和node节点个数的关系,如果不相等,说明出现了环,是不可能得到一个拓扑排序序列的
#include <bits/stdc++.h>
using namespace std;
int main(){
int v,e; //分别为顶点数+输入边的个数
cout<<"number of the nodes: "<<endl;
cin>>v;
cout<<"number of the edges: "<<endl;
cin>>e;
//现在开始初始化一个map
vector<vector<int>> map(v,vector<int>(v,0));
cout<<"please input the relations of the edges: "<<endl;
int a,b; //分别表示输入的两项,并且由于是有向图,指向为map[a][b],构建这个关系
for(int i=0;i<e;++i){
cin>>a>>b;
map[a][b]=1;
}
//可以打印这个邻接矩阵来看一下
cout<<"the realations of the edges: "<<endl;
cout<<endl;
for(int i=0;i<v;++i){
for(int j=0;j<v;++j){
cout<<map[i][j]<<" ";
}
cout<<endl;
}
//统计每个节点的入度
vector<int> in(v,0);
for(int i=0;i<v;++i){
for(int j=0;j<v;++j){
if(map[i][j]){ //i->j,因此j的入度应该+1
in[j]++;
}
}
}
//queue辅助,bfs遍历,先进一个入度为0的点
queue<int> Q;
for(int i=0;i<v;++i){
if(in[i]==0){
Q.push(i);
}
}
vector<int> res; //存储最终结果
while(!Q.empty()){
int cur=Q.front();
Q.pop();
res.push_back(cur);
in[cur]=-1; //注意对应当前节点使用过了,入度设置为-1
for(int i=0;i<v;++i){
if(map[cur][i]==1){ //具有相邻关系的点我们将其入度-1
in[i]--; //入度-1
if(in[i]==0){
Q.push(i);
}
}
}
}
if(res.size()<v){ //此时说明其中有一个地方存在环了,拓扑排序是不成功的
cout<<"can not sort the nodes!"<<endl;
return 0;
}
//否则我们按序打印即可
cout<<endl;
cout<<"the result after the sort: "<<endl;
for(auto it:res){
cout<<it<<" ";
}
system("pause");
return 0;
}
最小生成树
2.Prim算法
我们的基本思路如下:
1.先进行初始化,根据输入的行与列来进行二维数组初始化,此时i==j的时候为0,i!=j的时候我们设置成一个极大值表示不连通 2.进行边的对应关系输入,进行对应两个顶点之间的权值更新 3.此时开始我们的主程序,也就是Prim的过程 (1)我们要用到两个数组,一个是visit数组表示每个点是否已经取过了,假设我们先选取一个点i作为起点,另一个是dis数组,表示当前点到各个其他点的距离 (2)此时遍历完之后我们已经找到一个距离当前i点距离最近的点j,那么我们此时将j同样进行树的生成,并且此时,我们要更新dis数组,因为此时j也是树的一部分 ,每个点距离树的距离可能变小 (3)循环往复,直到我们取到了所有点,生成了一颗最小的树
int main(){
int n,m;
cout<<"input the number of the nodes: "<<endl;
cin>>n;
cout<<"input the number of the edges: "<<endl;
cin>>m;
//此时我们根据n个顶点先初始化一个关系数组
vector<vector<int>> vec(n,vector<int>(n,INT_MAX));
for(int i=0;i<n;++i){ //权值更新,自身为0,其余为一个INT_MAX
vec[i][i]=0;
}
cout<<"input the relations of the edges: "<<endl;
//根据每条边输入的权值进行更新,注意是无向图,因此两个方向都要更新
int a,b,value;
for(int i=0;i<m;++i){ //总共m条边的对应关系
cin>>a>>b>>value;
vec[a][b]=value;
vec[b][a]=value;
}
//假设我们取第一个节点作为起始点
int sum=0; //记录生成树的权值
int cnt=0; //记录访问了多少个节点了
vector<int> dis(n,INT_MAX); //记录每个点到树的权值距离
vector<int> visit(n,0); //0表示未访问过
vector<pair<int,int>> res; //结果数组,记录每次取的节点以及对应权值
visit[0]=1; //标记为已访问
//因为选成了树的节点,因此更新一波距离
for(int i=0;i<n;++i){
dis[i]=vec[0][i];
}
res.push_back({0,dis[0]}); //输入的是节点以及对应权值
cnt++;
while(cnt!=n){
int min_dis=INT_MAX,idx=0; //目标要选取当前dis中最小的那个节点
for(int i=0;i<n;++i){
if(visit[i]==0 && dis[i]<min_dis){
min_dis=dis[i];
idx=i;
}
}
visit[idx]=1; //选定的节点进行访问并入res
res.push_back({idx,min_dis});
sum+=min_dis;
cnt++;
//因此此时idx变为一部分,因此再更新一遍权值
for(int i=0;i<n;++i){
if(visit[i]==0 && dis[i]>vec[idx][i]){
dis[i]=vec[idx][i];
}
}
}
//到目前为止完成Prim算法,我们打印一遍结果来看一下
cout<<"the result sum is: "<<sum<<endl;
cout<<"the nodes are as below: "<<endl;
for(int i=0;i<res.size();++i){
cout<<"the"<<i<<"th node is: "<<res[i].first<<" "<<"the value is: "<<res[i].second<<endl;
}
system("pause");
return 0;
}
3.Kruskal
Kruskal与Prim的不同是,之前我们是不断的每次取一个点之后都要进行距离更新,然后取距离最短的。那现在我们在开始时进行一次距离的更新,依次来进行选取,在每次取得一个点之后,我们不再进行权值的更新。此时为了我们取对应长度最小的边较为方便,考虑使用vector>>的结构来存储,分别为x,y,value 我们此时需要的辅助函数为并查集的查找以及合并
vector<int> pre; //用于并查集的查找
int find(int x){
if(pre[x]==x){
return x;
}
pre[x]=find(pre[x]); //否则的话我们进行并查集的查找
return pre[x];
}
int merge(int x,int y){
int t1=find(x);
int t2=find(y);
if(t1!=t2){ //如果两个并不是相连的,我们修改状态为相连,并返回1
pre[t2]=t1;
return 1;
}
return 0;
}
int main(){
int n,m; //同样我们要先进行对应关系初始化,总共n个节点,m条边的关系
cout<<"input the number of the nodes: "<<endl;
cin>>n;
cout<<"input the number of the edges: "<<endl;
cin>>m;
vector<pair<int,pair<int,int>>> vec; //存储对应关系
int x,y,value;
cout<<"input the relations of the edges: "<<endl;
for(int i=0;i<m;++i){
cin>>x>>y>>value;
vec.push_back({x,{y,value}});
}
//我们要根据value值进行从小到大排序
auto cmp=[](const pair<int,pair<int,int>>&a,const pair<int,pair<int,int>>& b){
return a.second.second<b.second.second;
};
sort(vec.begin(),vec.end(),cmp);
//pre数组进行初始化
for(int i=0;i<n;++i){
pre.push_back(i);
}
vector<pair<int,pair<int,int>>> res; //存储最终的结果
int sum=0; //存储最终的权值之和
int cnt=0; //记录当前访问了多少条边,到m-1的时候退出循环,完成了构建
//我们此时根据每条边来进行列举
for(int i=0;i<m;++i){
int x=vec[i].first;
int y=vec[i].second.first;
int value=vec[i].second.second;
if(merge(x,y)){
cnt++;
sum+=value;
res.push_back({x,{y,value}}); //存储对应的x,y,value
}
if(cnt==m-1){ //已经取得了m-1条边的话退出
break;
}
}
cout<<"the result sum: "<<sum<<endl;
cout<<"the select nodes are as below: "<<endl;
for(int i=0;i<res.size();++i){
cout<<"the "<<i<<"th of the edges"<<res[i].first<<"->"<<res[i].second.first<<" ";
cout<<"the value is: "<<res[i].second.second<<endl;
}
system("pause");
return 0;
}
最短路径Dijkstra
Dijkstra最短路径
迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。
####注意这里与Prim的区别,同样是计算距离,但是Prim是计算点到树的距离,因此每次求一遍过后需要更新,而Dijkstra是求点到源点的距离,因此也要更新,但仍然是更新到源点的距离
//Dijkstra算法
/*
我们这样考虑:
使用distance作为距离数组统计每个节点到源点的记录
使用s和v分别存储对应取得的节点信息,以及未被选取的节点的信息
使用flag来标记每一个节点是否进行使用过
1.先进行对应关系的建立,同样可以选择无向图,那么邻接矩阵即可
2.选取一个点i作为源点,那么我们先进行每个节点到当前源点的距离统计,选取距离最小的节点。
此后进行距离的更新,注意,在这里与Prim不同的是,我们更新的是点到源点的距离(因此新选取的点只能作为一个中间板)
3.如此往复直到选取得到i个节点完毕
*/
int main(){
int n,m;
int max_dis=9999;
cout<<"input the number of the nodes: "<<endl;
cin>>n;
cout<<"input the number of the edges: "<<endl;
cin>>m;
vector<vector<int>> vec(n,vector<int>(n,max_dis=9999));
for(int i=0;i<n;++i){
vec[i][i]=0; //自身先修改成0
}
cout<<"input the relations of the edges: "<<endl;
int a,b,value; //分别为输入的a,b以及对应的距离长度value
for(int i=0;i<m;++i){
cin>>a>>b>>value;
vec[a][b]=value;
vec[b][a]=value; //无向图距离正反都需要更新
}
//打印一下邻接矩阵看一下
cout<<"the matrix of the nodes are as below: "<<endl;
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
cout<<vec[i][j]<<" ";
}
cout<<endl;
}
//现在来进行Dijkstra的核心部分
vector<pair<int,int>> s; //已经使用的节点,分别为node和dis
vector<pair<int,int>> v; //未被使用的节点,分别为node和dis
vector<int> dis(n,max_dis); //对应的距离矩阵
vector<int> flag(n,false); //标记节点是否访问过了
//我们选取第0个点作为源点
int pos=3;
int cnt=0; //表明当前选取了几个节点
flag[pos]=true;
s.push_back({pos,0}); //作为已经访问,进入s存储
//更新距离矩阵
for(int i=0;i<n;++i){
dis[i]=vec[pos][i];
}
//现在我们找一遍dis,选取距离最短的进行记录,之后再往复进行距离的更新
for(int i=0;i<n;++i){
if(flag[i]==false){
v.push_back({i,dis[i]}); //对应点记录为未访问
}
}
cnt++;
auto cmp=[](const pair<int,int>&a ,const pair<int,int>& b){ //根据距离从小到大排序
return a.second<b.second;
};
while(cnt!=n){
sort(v.begin(),v.end(),cmp);
int cur=v[0].first; //这是我们目前能够得到的距离最小的node的坐标
cnt++;
s.push_back({cur,v[0].second}); //记录到s中,此时的距离为dis[cur]
v.erase(v.begin());
//需要进行到源点距离的更新
for(int i=0;i<n;++i){
if(dis[i]>(dis[cur]+vec[i][cur])){ //使用中间板如果距离小了,进行更新,cur作为中间板
dis[i]=dis[cur]+vec[i][cur];
for(auto it=v.begin();it!=v.end();++it){
if(it->first==i){
it->second=dis[i]; //v中的距离需要更新
break;
}
}
}
}
flag[cur]=true; //标记为已访问
}
//现在s中应该有n个节点信息,输出一下check一下
cout<<"-----------------------"<<endl;
cout<<"after the Dijkstra operation, the result is as below: "<<endl;
for(int i=0;i<s.size();++i){
cout<<"the node is: "<<s[i].first<<" "<<"the distance is: "<<s[i].second<<endl;
}
system("pause");
return 0;
}