写在前面的话
由于上次写的太迟了,导致后面看不到题,这次熬夜肝完了。感觉就是会上课讲过的四种经典算法就可以了。
A图的最小生成树-Prim算法
题目描述
Prim算法是求解带权图的最小生成树的经典算法。其步骤如下:
E1:任取一个顶点构成U={v0};构造向量cost[0…n-1]和adj[0…n-1],cost[i]表示顶点vi到U的最短边的长度,adj[i]表示顶点vi到U的最短边在U中的邻接点的下标;其中,vi∈V-U。初始时,生成树T为空集。
E2:重复n-1次
E21:从V-U中选出cost值最小的顶点vk,将边<vk, vadj[k]>加入到生成树T中,然后将vk并入U中;
E22:修正V-U中各顶点的cost值和adj值;
本题要求根据Prim算法,求解第一步状态下的cost和adj两个向量。
输入格式
输入为邻接矩阵存储的图,第一行为正整数n(小于100),表示图中顶点个数
接下来是n行,每行为n个空格隔开的非负整数。0表示两个顶点之间没有直达边,非0表示有直达边。且该数字为对应直达边的权重。
输出格式
假设第一步选择将序号最小的0号节点并入集合U。按照样例格式输出cost和adj两个向量
输入样例 复制
7
0 10 9 13 0 0 0
10 0 0 15 7 0 12
9 0 0 4 0 3 0
13 15 4 0 0 22 23
0 7 0 0 0 0 20
0 0 3 22 0 0 32
0 12 0 23 20 32 0
输出样例 复制
0 - -
1 10 0
2 9 0
3 13 0
4 - -
5 - -
6 - -
code
#include<bits/stdc++.h>
using namespace std;
#define isw {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);}
int a[105][105];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n;
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>a[i][j];
}
}
for(int i=0;i<n;i++){
if(a[0][i]==0){
cout<<i<<" "<<"- -"<<endl;
}
else{
cout<<i<<" "<<a[0][i]<<" "<<0<<endl;
}
}
}
B: 图的最小生成树-Kruskal算法
目描述
Kruskal算法是最小生成树的经典算法,其步骤为:
E1:将所有的边按权值排序;
E2:设每个顶点为一个独立的点集,生成树T为空集;
E3:依序扫描每一条边<vi,vj>,直到已输出n-1条边:
E31:若vi、vj不在同一点集中,则将该边加入生成树T中,并合并这两个点集;否则舍弃该边;
本题要求读入带权图,对其所有边按权值排序后输出。
输入格式
输入为邻接矩阵存储的图,第一行为正整数n(小于100),表示图中顶点个数
接下来是n行,每行为n个空格隔开的非负整数。0表示两个顶点之间没有直达边,非0表示有直达边。且该数字为对应直达边的权重。
输出格式
对所有边按权重排序后输出。如果图只有1个点(即没有边),则直接输出空行。
输入样例 复制
7
0 10 9 13 0 0 0
10 0 0 15 7 0 12
9 0 0 4 0 3 0
13 15 4 0 0 22 23
0 7 0 0 0 0 20
0 0 3 22 0 0 32
0 12 0 23 20 32 0
输出样例 复制
<2,5>:3
<5,2>:3
❤️,2>:4
<2,3>:4
<4,1>:7
<1,4>:7
<2,0>:9
<0,2>:9
<1,0>:10
<0,1>:10
<1,6>:12
<6,1>:12
<0,3>:13
❤️,0>:13
<1,3>:15
❤️,1>:15
<4,6>:20
<6,4>:20
❤️,5>:22
<5,3>:22
<6,3>:23
❤️,6>:23
<5,6>:32
<6,5>:32
思路
用一个结构体存有向边的起点,终点和权值,然后选择排序
code
#include<bits/stdc++.h>
using namespace std;
#define isw {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);}
typedef pair<int,int>P;
int a[105][105];
struct edge{
int s,e;//起点和终点
int w;//权值
};
bool cmp(edge a,edge b){
return a.w<b.w;
}
void selectsort(edge a[],int n){
int m;
for(int i=0;i<n;i++){
m=i;
for(int j=i+1;j<n;j++){
if(a[j].w<a[m].w&&a[j].w!=0&&a[m].w!=0){
edge t;
t.e=a[j].e;
t.s=a[j].s;
t.w=a[j].w;
a[j].e=a[m].e,a[j].s=a[m].s,a[j].w=a[m].w;
a[m].e=t.e,a[m].s=t.s,a[m].w=t.w;
//m=j;
}
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n;
cin>>n;
if(n==1){
cout<<endl;
return 0;
}
edge g[100005];
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
int x;
cin>>x;
g[i*n+j].s=i;
g[i*n+j].e=j;
g[i*n+j].w=x;
}
//sort(g,g+n*n,cmp);
selectsort(g,n*n);
for(int i=0;i<n*n;i++){
if(g[i].w!=0){
printf("<%d,%d>:%d\n",g[i].s,g[i].e,g[i].w);
}
}
}
C: 算法7-9:最小生成树
题目描述
最小生成树问题是实际生产生活中十分重要的一类问题。假设需要在n个城市之间建立通信联络网,则连通n个城市只需要n-1条线路。这时,自然需要考虑这样一个问题,即如何在最节省经费的前提下建立这个通信网。
可以用连通网来表示n个城市以及n个城市之间可能设置的通信线路,其中网的顶点表示城市,边表示两个城市之间的线路,赋于边的权值表示相应的代价。对于n个顶点的连通网可以建立许多不同的生成树,每一棵生成树都可以是一个通信网。现在,需要选择一棵生成树,使总的耗费最小。这个问题就是构造连通网的最小代价生成树,简称最小生成树。一棵生成树的代价就是树上各边的代价之和。
而在常用的最小生成树构造算法中,普里姆(Prim)算法是一种非常常用的算法。以下是其算法的大致结构:
在本题中,读入一个无向图的邻接矩阵(即数组表示),建立无向图并按照以上描述中的算法建立最小生成树,并输出最小生成树的代价。
输入格式
输入的第一行包含一个正整数n,表示图中共有n个顶点。其中n不超过50。 以后的n行中每行有n个用空格隔开的整数,对于第i行的第j个整数,如果不为0,则表示第i个顶点和第j个顶点有直接连接且代价为相应的值,0表示没有直接连接。当i和j相等的时候,保证对应的整数为0。 输入保证邻接矩阵为对称矩阵,即输入的图一定是无向图,且保证图中只有一个连通分量。
输出格式
只有一个整数,即最小生成树的总代价。请注意行尾输出换行。
输入样例 复制
4
0 2 4 0
2 0 3 5
4 3 0 1
0 5 1 0
输出样例 复制
6
数据范围与提示
*** 提示已隐藏,点击此处可显示 ***
收起提示[-]
在本题中,需要掌握图的深度优先遍历的方法,并需要掌握无向图的连通性问题的本质。通过求出无向图的连通分量和对应的生成树,应该能够对图的连通性建立更加直观和清晰的概念。
code
#include<bits/stdc++.h>
using namespace std;
#define isw {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);}
typedef pair<int,int>P;
#define inf 0x3f3f3f3f
#define maxn 105
struct edge{
int s,e;//起点和终点
int w;//权值
};
bool cmp(edge a,edge b){
return a.w<b.w;
}
void selectsort(edge a[],int n){//选择排序
int m;
for(int i=0;i<n;i++){
m=i;
for(int j=i+1;j<n;j++){
if(a[j].w<a[m].w&&a[j].w!=0&&a[m].w!=0){
edge t;
t.e=a[j].e;
t.s=a[j].s;
t.w=a[j].w;
a[j].e=a[m].e,a[j].s=a[m].s,a[j].w=a[m].w;
a[m].e=t.e,a[m].s=t.s,a[m].w=t.w;
//m=j;
}
}
}
}
int dis[maxn];
bool vis[maxn];
int d[maxn][maxn];
int n,m;
int prim(){
memset(vis,0,sizeof vis);
memset(dis,0x3f,sizeof dis);
int res=0;
for(int i=0;i<n;i++){
int t=-1;
for(int j=0;j<n;j++){
if(!vis[j]&&(t==-1||dis[j]<dis[t])){
t=j;
}
}
if(i!=0&&dis[t]==inf)return inf;
if(i!=0)res+=dis[t];
for(int j=0;j<n;j++){
dis[j]=min(dis[j],d[j][t]);
}
vis[t]=1;
}
return res;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>d[i][j];
if(d[i][j]==0)d[i][j]=inf;//两点无直接相连的边,初始化为inf
}
}
cout<<prim()<<endl;
}
D:迪杰斯特拉最短路径算法
题目描述
在带权有向图G中,给定一个源点v,求从v到G中的其余各顶点的最短路径问题,叫做单源点的最短路径问题。
在常用的单源点最短路径算法中,迪杰斯特拉算法是最为常用的一种,是一种按照路径长度递增的次序产生最短路径的算法。
可将迪杰斯特拉算法描述如下:
设辅助向量u[0…n-1]、shortest[0…n-1]和path[0…n-1];u[i]为1表示从v0到vi的最短路径已经求出,为0表示尚未求出;shortest[i]记录目前已知的从v0到vi的较短路径的长度;path[i]记录目前已知的从v0到vi的较短路径;
初始时,设置从v0到vi的直达弧为目前已知的较短路径;
E1:初始化辅助向量u、shortest、path;
E2:循环n-1次:
E21:从M-U中选择最小的shortest[k];
E22:将vk并入U中;
E23:对M-U中的各顶点vi的已知较短路径进行修正:若P0,k+{i}的长度短于目前已知的P0,i的长度,则用P0,k+{i}取代原P0,i;
在本题中,读入一个有向图的带权邻接矩阵(即数组表示),建立有向图并按照以上描述中的算法求出源点至每一个其它顶点的最短路径长度。
输入格式
输入的第一行包含2个正整数n和s,表示图中共有n个顶点,且源点为s。其中n不超过50,s小于n。 以后的n行中每行有n个用空格隔开的整数。对于第i行的第j个整数,如果大于0,则表示第i个顶点有指向第j个顶点的有向边,且权值为对应的整数值;如果这个整数为0,则表示没有i指向j的有向边。当i和j相等的时候,保证对应的整数为0。
输出格式
只有一行,共有n-1个整数,表示源点至其它每一个顶点的最短路径长度。如果不存在从源点至相应顶点的路径,输出-1。 请注意行尾输出换行。
输入样例 复制
4 1
0 3 0 1
0 0 4 0
2 0 0 0
0 0 1 0
输出样例 复制
6 4 7
数据范围与提示
*** 提示已隐藏,点击此处可显示 ***
收起提示[-]
在本题中,需要按照题目描述中的算法完成迪杰斯特拉算法,并在计算最短路径的过程中将每个顶点是否可达记录下来,直到求出每个可达顶点的最短路径之后,算法才能够结束。 迪杰斯特拉算法的特点是按照路径长度递增的顺序,依次添加下一条长度最短的边,从而不断构造出相应顶点的最短路径。 另外需要注意的是,在本题中为了更方便的表示顶点间的不可达状态,可以使用一个十分大的值作为标记。
code
本题代码可以参考之前写过的最小生成树的代码,那里有注释
#include<bits/stdc++.h>
using namespace std;
#define isw {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);}
typedef pair<int,int>P;
#define inf 0x3f3f3f3f
#define maxn 105
struct edge{
int s,e;//起点和终点
int w;//权值
};
bool cmp(edge a,edge b){
return a.w<b.w;
}
void selectsort(edge a[],int n){//选择排序
int m;
for(int i=0;i<n;i++){
m=i;
for(int j=i+1;j<n;j++){
if(a[j].w<a[m].w&&a[j].w!=0&&a[m].w!=0){
edge t;
t.e=a[j].e;
t.s=a[j].s;
t.w=a[j].w;
a[j].e=a[m].e,a[j].s=a[m].s,a[j].w=a[m].w;
a[m].e=t.e,a[m].s=t.s,a[m].w=t.w;
//m=j;
}
}
}
}
int n,u;
int dis[maxn];
bool vis[maxn];
int d[maxn][maxn];
int p[maxn];
void Dijkstra(){
for(int i=0;i<n;i++){
dis[i]=d[u][i];
if(dis[i]==inf)p[i]=-1;
else p[i]=u;
}
vis[u]=true;
dis[u]=0;
for(int i=0;i<n;i++){
int t=inf,pos=u;
for(int j=0;j<n;j++){
if(!vis[j]&&dis[j]<t){
pos=j;
t=dis[j];
}
}
// cout<<pos<<endl;
if(pos==u)return;
vis[pos]=1;
for(int j=0;j<n;j++){
if(!vis[j]&&dis[j]>dis[pos]+d[pos][j]){
dis[j]=dis[pos]+d[pos][j];
p[j]=pos;
}
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>u;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>d[i][j];
if(!d[i][j])
d[i][j]=inf;
}
}
Dijkstra();
for(int i=0;i<n;i++){
if(i!=u){
if(dis[i]==inf){
cout<<-1<<" ";
}
else cout<<dis[i]<<" ";
}
}
cout<<endl;
}
E:弗洛伊德最短路径算法
题目描述
在带权有向图G中,求G中的任意一对顶点间的最短路径问题,也是十分常见的一种问题。
解决这个问题的一个方法是执行n次迪杰斯特拉算法,这样就可以求出每一对顶点间的最短路径,执行的时间复杂度为O(n3)。
而另一种算法是由弗洛伊德提出的,时间复杂度同样是O(n3),但算法的形式简单很多。
可以将弗洛伊德算法描述如下:
E1:初始化从vi到vj的目前已知较短路径为从vi到vj的直达弧;
E2:对每两顶点对(vi,vj)依次计算P(i,j,k),k=0…n-1,计算规则为:P(i,j,k) = min(P(i,k,k-1) + P(k,j,k-1), P(i,j,k-1))
在本题中,读入一个有向图的带权邻接矩阵(即数组表示),建立有向图并按照以上描述中的算法求出每一对顶点间的最短路径长度。
输入格式
输入的第一行包含1个正整数n,表示图中共有n个顶点。其中n不超过50。 以后的n行中每行有n个用空格隔开的整数。对于第i行的第j个整数,如果大于0,则表示第i个顶点有指向第j个顶点的有向边,且权值为对应的整数值;如果这个整数为0,则表示没有i指向j的有向边。当i和j相等的时候,保证对应的整数为0。
输出格式
共有n行,每行有n个整数,表示源点至每一个顶点的最短路径长度。如果不存在从源点至相应顶点的路径,输出-1。对于某个顶点到其本身的最短路径长度,输出0。 请在每个整数后输出一个空格,并请注意行尾输出换行。
输入样例 复制
4
0 3 0 1
0 0 4 0
2 0 0 0
0 0 1 0
输出样例 复制
0 3 2 1
6 0 4 7
2 5 0 3
3 6 1 0
数据范围与提示
*** 提示已隐藏,点击此处可显示 ***
收起提示[-]
在本题中,需要按照题目描述中的算法完成弗洛伊德算法,并在计算最短路径的过程中将每个顶点是否可达记录下来,直到求出每一对顶点的最短路径之后,算法才能够结束。 相对于迪杰斯特拉算法,弗洛伊德算法的形式更为简单。通过一个三重循环,弗洛伊德算法可以方便的求出每一对顶点间的最短距离。 另外需要注意的是,为了更方便的表示顶点间的不可达状态,可以使用一个十分大的值作为标记。而在题目描述中的算法示例使用了另外一个三维数组对其进行表示,这使原本的O(n3)时间复杂度增长到了O(n4),这也是需要自行修改的部分。
code
和DP有点类似。。。
#include<bits/stdc++.h>
using namespace std;
#define isw {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);}
typedef pair<int,int>P;
#define inf 0x3f3f3f3f
#define maxn 105
struct edge{
int s,e;//起点和终点
int w;//权值
};
bool cmp(edge a,edge b){
return a.w<b.w;
}
void selectsort(edge a[],int n){//选择排序
int m;
for(int i=0;i<n;i++){
m=i;
for(int j=i+1;j<n;j++){
if(a[j].w<a[m].w&&a[j].w!=0&&a[m].w!=0){
edge t;
t.e=a[j].e;
t.s=a[j].s;
t.w=a[j].w;
a[j].e=a[m].e,a[j].s=a[m].s,a[j].w=a[m].w;
a[m].e=t.e,a[m].s=t.s,a[m].w=t.w;
//m=j;
}
}
}
}
int n,u;
int dis[maxn][maxn];
bool vis[maxn];
int d[maxn][maxn];
int p[maxn];
void Floyd(){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
dis[i][j]=d[i][j];
}
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
dis[j][k]=min(dis[j][k],dis[j][i]+dis[i][k]);
}
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>d[i][j];
if(!d[i][j]&&i!=j)
d[i][j]=inf;
}
}
Floyd();
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(dis[i][j]==inf)cout<<-1<<" ";
else cout<<dis[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
}
F: 图的最短路径-Floyd算法输出最短路径包含的边
题目描述
在Floyd算法求解图的最短路径过程中,会记录两个顶点之间最短路径所经过的边。
如下图例子所示,本题给出Floyd算法的最后结果,请求出任意两点之间最短路径长度,以及相应的最短路径所包含的边。
输入格式
输入第一行为正整数n,表示图的节点数量(小于100)
接下来是n*n的矩阵,每个元素包含两个整数,分别是两点之间最短路径长度,和路径经过的中间节点。
最后是两个整数p和q,表示两个顶点序号。
输出格式
第一行输出从顶点p到顶点q的最短路径长度。
第二行输出该最短路径经过的边对应的顶点序列。
输入样例 复制
6
-1 -1 9 4 18 4 12 0 5 0 13 1
6 1 -1 -1 24 4 18 0 11 0 4 1
10 2 19 4 -1 -1 14 2 15 0 18 3
13 5 11 3 31 4 -1 -1 18 0 4 3
10 1 4 4 13 4 22 0 -1 -1 8 1
9 5 18 4 27 4 21 0 14 0 -1 -1
4 3
输出样例 复制
22
4 1 0 3
思路
由于是逆序找出路径,可以用栈来存储
code
#include<bits/stdc++.h>
using namespace std;
#define isw {ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);}
typedef pair<int,int>P;
#define inf 0x3f3f3f3f
#define maxn 105
P res[maxn][maxn];
int n;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
int x,y;
cin>>x>>y;
res[i][j].first=x;
res[i][j].second=y;
}
}
int p,q;
cin>>p>>q;
cout<<res[p][q].first<<endl;
stack<int>s;
s.push(q);
int t=q;
while(t!=-1){
t=res[p][t].second;
s.push(t);
}
while(s.size()){
if(s.top()!=-1)
cout<<s.top()<<" ";
s.pop();
}
cout<<endl;
}