这里是第二部分
上一部分是BFS与DFS
如何利用STL
这部分的内容因为是在边写最短路全篇时边加的,所以知识点会有点零碎。
(后期可能也会有补充)
vector
头文件
#include< vector >
用法很简单,
若a是一个vector,
a.size() //可以读取它的大小,
a.resize() //改变大小,
a.push_back() //向尾部添加元素,
a.pop_back() //删除最后一个元素
a.clear() //清空
a.empty() //是否为空
vector< int >a; // 一个长度可变的数组
vector<vector< int> >a;//一个长度可变的二维数组
vector<int>a[10005];//10005个长度可变的数组
并且vector支持对二维数组的随机访问和每一个长度可变的数组的单独操作。
pair
Pair C++内置的结构体
pair<int, int>a
简单来说就是a结构体有 int 类型的变量first 和int类型的变量second。
a.first
a.second
目前求最短路只需要知道这些
至此,STL知识结束。
存图的方法
存图方法的概述
前面讲到
写最短路问题时要考虑:
1.是否有权
2.是有向图还是无向图
3.是否有负环
存图时就需要考虑这三点。
存图的常见方法有三种:
1. 邻接矩阵
2. 邻接表
3. 前向星
所以存图又可以分成(3*3)种情况
要了解存图的基本情况,从无向无权的图开始展开讲解。
给一张小镇的图,有信息每个地方的编号,哪些地方连着,一般是这样输入。
7 6 // 顶点数是n==7和边数m==6.下面m行表示m条边
1 2 //编号为1和编号为2的地点相邻
2 4
1 3
2 5
3 7
3 6
那么如何存这张小镇的地图呢?
示例解析:
7个顶点 编号从1到7
6条边
1点和2点 相连,2点和4点相连 等等。
邻接矩阵
即简单的二维矩阵
对于该题来说:可以画出这样的邻接矩阵。
int g[maxn][maxn]; // g[u][v]==w 表示u到v有一条权重为w的有向边
无权 无向图时
g[u][v] // g[u][v]==1时 表示u到v有一条有向边
// g[u][v]==0时 无法从u点走到v点
//并在读边时,用g[u][v]=g[v][u]=1; 表示u点与v点有一条无向边
code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1e4;
int g[maxn][maxn]; // g[u][v]==1 表示u到v有一条有向边
int n,m;
void AdjacencyMatrix(){
int u,v;
memset(g,0,sizeof(g));//初始化 无边
for(int i = 0;i < m;i++){ // 读入边
scanf("%d %d",&u,&v);
g[u][v] = g[v][u] = 1;//因为是无向图 所以用俩个有向边表示无向边
}
}
int main()
{
cin>>n>>m;
AdjacencyMatrix();
return 0;
}
有权的处理方法
就是把
for(int i = 0;i < m;i++){ // 读入边
scanf("%d %d",&u,&v);
g[u][v] = g[v][u] = 1;//因为是无向图 所以用俩个有向边表示无向边
}
这部分中的g[u][v] = g[v][u] = 1;改成 g[u][v] = g[v][u] = w;
g[u][v] = w // w != 0时 表示u到v有一条权重为w的有向边
// w == 0时 无法从u点走到v点
//并在读边时,用g[u][v]=g[v][u]=w; 表示u点与v点有一条无向边
code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1e4;
int g[maxn][maxn]; // g[u][v]==w 表示u到v有一条权重为w有向边
int n,m;
void AdjacencyMatrix(){
int u,v,w;//u,v表示点的编号 w表示边的权重
memset(g,0,sizeof(g));//初始化 无边
for(int i = 0;i < m;i++){ // 读入边
scanf("%d %d %d",&u,&v,&w);
g[u][v] = g[v][u] = w;//因为是无向图 所以用俩个有向边表示无向边
}
}
int main()
{
cin>>n>>m;
AdjacencyMatrix();
return 0;
}
有向的处理方式
就是在读边时,正常得读边就行
g[u][v] // g[u][v]==1时 表示u到v有一条有向边
// g[u][v]==0时 无法从u点走到v点
//并在读边时,用g[u][v] =1; 表示u点与v点有一条无向边
code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1e4;
int g[maxn][maxn]; // g[u][v]==w 表示u到v有一条权重为w有向边
int n,m;
void AdjacencyMatrix(){
int u,v,w;//u,v表示点的编号 w表示边的权重
memset(g,0,sizeof(g));//初始化 无边
for(int i = 0;i < m;i++){ // 读入边
scanf("%d %d",&u,&v,&w);
g[u][v] = 1;//因为是有向图 g[u][v]==1 表示u到v有一条有向边
}
}
int main()
{
cin>>n>>m;
AdjacencyMatrix();
return 0;
}
邻接表
对于该题,图可以这样画出:
我们也可以这样来表示每个点的关系:
对于
1 : 2、3
2: 1、4、5
3: 1、6、7
4: 2
5: 2
6: 3
7: 3
即:
对于1点 可以到达2点和3点;
对于2点可以到达1点4点5点;
…
由这些数据,我们发现了:
对于每一个点,都关联着不一定数量的其他点。
这不就正像一个vector< int >a。
a是某一个点
所以存图时,
可以选择用vector< int >a[maxn];
或者vector<vector< int > >a;
无权无向时
vector<int>g[maxn];//g[u] == v 表示u到v有一条有向边
CODE:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 1e4;
vector<int>g[maxn];//g[u] == v 表示u到v有一条有向边
int n,m;
void adjacencyList(){
int u,v,w;//u,v表示点的编号 w表示边的权重
for(int i = 0;i < m;i++){
scanf("%d %d",&u,&v);
g[u].push_back(v);//把v存入编号为u的vector数组,表示u到v有一条有向边。
g[v].push_back(u);//由于是无向边,用俩条有向边表示无向边
}
}
int main(int argc, char const *argv[])
{
cin>>n>>m;
adjacencyList();
return 0;
}
有权时的处理
typedef pair<int,int>pii;//pii.first表示v,pii.second表示u到v的w权重
vector<pii>g[maxn];//g[u]表示u 到 g[u].first == v 有一个有向边; g[u].second == w 表示该有向边的权重
CODE:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 1e4;
typedef pair<int,int>pii;//pii.first表示v,pii.second表示u到v的w权重
vector<pii>g[maxn];//g[u]表示u 到 g[u].first == v 有一个有向边; g[u].second == w 表示该有向边的权重
int n,m;
void adjacencyList(){
int u,v,w;//u,v表示点的编号 w表示边的权重
for(int i = 0;i < m;i++){
scanf("%d %d %d",&u,&v,&w);
pii now(v,w);//把v存到now结构体的first,w存到now结构体的second。
g[u].push_back(now);//把now结构体存入编号为u的vector数组
}
}
int main(int argc, char const *argv[])
{
cin>>n>>m;
adjacencyList();
return 0;
}
有向时的处理
vector<int>g[maxn];//g[u] == v 表示u到v有一条有向边
CODE:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 1e4;
vector<int>g[maxn];//g[u] == v 表示u到v有一条有向边
int n,m;
void adjacencyList(){
int u,v,w;//u,v表示点的编号 w表示边的权重
for(int i = 0;i < m;i++){
scanf("%d %d",&u,&v);
g[u].push_back(v);//把v存入编号为u的vector数组,表示u到v有一条有向边。
}
}
int main(int argc, char const *argv[])
{
cin>>n>>m;
adjacencyList();
return 0;
}
前向星
(据某大佬的讲述,前向星能做的邻接表大概都能做)
但这也是一种存图的思路,可以拓宽一下思维方式:
我们知道,邻接表存图,是用一个点当作一个vector不定长数组里面存其他点
那么用前向星,就是用链表 来存边的信息
int nxt[maxn],head[maxn],to[maxn];
nxt[i] 存编号为i的边的下一个点
head[i] 存起点i的第一条边的编号
to[i] 存编号为i的边的终点。
CODE:
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1e4;
int nxt[maxn],head[maxn],to[maxn];//nxt[i] 存编号为i的边的下一个点、head[i] 存起点i的第一条边的编号、to[i] 存编号为i的边的终点。
int n,m;
int cnt = -1;//表示边的编号 就一直++得去;
void add(int u,int v){
nxt[++cnt] = head[u];//把u的第一条边存入 当前边 0 的后继(在nxt中先存的是-1 然后是对应边的编号)
head[u] = cnt;//把编号为 0 的边存入 u的第一条边
to[cnt] = v;//表示编号为cnt的边的后继点是v;
}
void read(int u){
for(int i = head[u];i!=-1;i = nxt[i]){//链表遍历
int v = to[i];//to[i]存的就是那条边的后继点
}
}
int main(int argc, char const *argv[])
{
memset(head,-1,sizeof(head));//做记号,表示-1时 无边或边被全部读完
cin>>n>>m;
int u,v;//u,v表示点的编号 w表示边的权重
for(int i = 0;i < m;i++){
scanf("%d %d",&u,&v);
}
return 0;
}
总结
(邻接矩阵只适用于没有重边(或重边可以忽略)的情况,一般情况下不用考虑)
- 邻接矩阵
空间复杂度高:为O(n^2)
查询一条边的存在情况快: O(1)
遍历一个点的所有出边 :O(n)
遍历整个图 :O(n^2)
- 邻接表
邻接表基本可以应对所有情况(除了要快速查询一条边要用邻接矩阵)
空间复杂度低 :为O(m) m为边数
查询某条边 : O(d+(u))即这个点的边数 ,如果边有排序,则通过二分就是O(log d+(u)) log边数的事件。
遍历一个点的所有出边 :O(d+(u))即该点的边数
遍历整个图 :O(n+m) 事件为点的个数加边数。
- 前向星
与邻接表基本一致。
优点是边有编号(在更厉害点的算法中有用)
求最短路推荐用邻接表存图(足够应付大部分情况)。
以上就是这篇文章的全部内容。如果看到这里不如点赞评论支持一下啦!
下一篇文章,就是重头戏——常见的最短路算法
预告 *)
拓扑排序、SPFA、Dijkstra、Bellman-ford、floy(多源最短路中的情况