目录
1、邻接矩阵
对于无向图中的边ab,存储两条有向边a->b, b->a。
因此我们可以只考虑有向图的存储。
(1) 邻接矩阵:g[a][b] 存储边a->b
(2) 邻接表:
// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;
// 添加一条边a->b,链表操作:插入元素
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
// 初始化
idx = 0;
memset(h, -1, sizeof h);
2、DFS
时间复杂度 O(n + m),n表示点数,m表示边数
void dfs(int u)
{
if (u == n)// 当走到第n个位置,说明我们把所有位置填满了
{
//输出结果
for (int i = 0; i < n; i ++) printf("%d ", path[i]);
puts("");
return;
}
for (int i = 1; i<= n; i ++)//枚举当前位置能填哪些数
if (!st[i])//如果该数没被用过
{
path[u] = i;//将i放到当前位置
st[i] = true;//记录i已被用过
dfs(u + 1);//递归到下一层
//当dfs结束之后,一条遍历完所以需要恢复现场
st[i] = false;
}
}
3、BFS
时间复杂度 O(n + m),n表示点数,m表示边数
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();//出队
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!s[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);//入队
}
}
}
4、拓扑排序
时间复杂度 O(n + m),n表示点数,m表示边数
bool topsort()
{
int hh = 0, tt = -1;
//d[i] 存储点i的入度
for (int i = 1; i <= n; i ++)
if (!d[i])
q[++ tt] = i;//入队
while (hh <= tt)
{
int t = q[hh ++];
//枚举t的所有出边
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
d[j] --;//删除j
if (d[j] == 0) q[++ tt] = j;//删除完将j插入队列
}
}
return tt == n - 1;//如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
}
4、Dijkstra
朴素做法:
优化:
//朴素版
typedef pair<int, int> PII;
int n; // 点的数量
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定
// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1}); // first存储距离,second存储节点编号
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
//优化版
int n; // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储每个点到1号点的最短距离
bool st[N]; // 存储每个点是否在队列中
// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while (q.size())
{
auto t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入
{
q.push(j);
st[j] = true;
}
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
5、队列优化的Bellman-Ford算法
时间复杂度 平均情况下O(m),最坏情况下 O(nm),n表示点数,m表示边数
int dist[N], backup[N];//遍历之前备份dist
struct Edge
{
int a, b, w;
}edges[M];
int bellman_ford()
{
//初始化
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;//第一个点初始化为0
for (int i = 0; i < k; i ++)
{
memcpy(backup, dist, sizeof dist);//存上一次迭代结果
for (int j = 0; j < m; j ++)
{//遍历所有边
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b], backup[a] + w);//只用上一次结果来更新这次的距离,避免出现串联
}
}
if (dist[n] > 0x3f3f3f3f / 2) return -1;//0x3f3f3f3f / 2
return dist[n];
}
6、spfa
int h[N], w[N], e[N], ne[N], idx;
int dist[N];//dist[N]狄杰斯特拉的距离,表示从1号点到其他点的最短距离是多少。
bool st[N];//st[]每个点最短路是否确定
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int spfa()
{
//1、初始化
memset(dist, 0x3f, sizeof dist);//初始化
dist[1] = 0;//第一个点标记0
queue<int> q;//定义队列
q.push(1);//第一个点入队
st[1] = true;//标记1号点,防止重复存储
while (q.size())//while不空
{
int t = q.front();//取队头
q.pop();//删除队头
st[t] = false;//t不在队列
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];//取出当前点
if(dist[j] > dist[t] + w[i])//如果大于就更新
{
dist[j] = dist[t] + w[i];
if (!st[j])//如果j不在队列,才加进去
{
q.push(j);
st[j] = true;
}
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
判断负环
int h[N], w[N], e[N], ne[N], idx;
//cnt[N]存边数
int dist[N], cnt[N];//dist[N]狄杰斯特拉的距离,表示从1号点到其他点的最短距离是多少。
bool st[N];//st[]每个点最短路是否确定
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int spfa()
{
queue<int> q;
//判断是否存在负环,需要把所有点放在队列里面
for (int i = 1; i <= n; i ++)
{
st[i] = true;
q.push(i);//把所有点放进来
}
while (q.size())//while不空
{
int t = q.front();//取队头
q.pop();//删除队头
st[t] = false;//t不在队列
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];//取出当前点
if(dist[j] > dist[t] + w[i])//如果大于就更新
{
dist[j] = dist[t] + w[i];
//更新边
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (!st[j])//如果j不在队列,才加进去
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
7、Floyd算法
时间复杂度是 O(n^3), n表示点数
初始化:
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
8、prim最小生成树
时间复杂度是 O(n^2+m), n 表示点数,m 表示边数
//稠密图用邻接矩阵来存
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int prim()
{
//把所有距离初始化正无穷
memset(dist, 0x3f, sizeof dist);//初始化成一个很大的数
int res = 0;//存最小生成树所有边长度之和
//n次迭代
for(int i = 0; i < n; i ++)
{
int t = -1;
//找所有集合外边中最小的
for (int j = 1; j <= n; j ++)
//在集合外,并且如果t == -1表示当前没有找到任何点,dist[t]距离 > dist[j]
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;//将t更新j
if (i && dist[t] == INF) return INF;//当前图不连通,不存在最小生成树
if (i) res += dist[t];//只要不是第一个点,就加上这条边
//将t更新到集合的距离
for (int j = 1; j <= n; j ++) dist[j] = min(dist[j], g[t][j]);
st[t] = true;//标记t已访问过
}
return res;
}
9、Kruskal-最小生成树
int n, m; // n是点数,m是边数
int p[N]; // 并查集的父节点数组
struct Edge // 存储边
{
int a, b, w;
bool operator< (const Edge &W)const
{
return w < W.w;
}
}edges[M];
int find(int x) // 并查集核心操作
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + m);
for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集
int res = 0, cnt = 0;
for (int i = 0; i < m; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b) // 如果两个连通块不连通,则将这两个连通块合并
{
p[a] = b;
res += w;
cnt ++ ;
}
}
if (cnt < n - 1) return INF;
return res;
}
10、染色法判别二分图
时间复杂度是 O(n+m), n 表示点数,m 表示边数
int h[N], e[M], ne[M], idx;//深度优先遍历用链表来存
int color[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
//dfs一遍
bool dfs(int u, int c)
{
//记录当前点的颜色
color[u] = c;
//遍历当前点零点
for (int i = h[u]; i != -1; i = ne[i])
{
//记录当前点编号
int j = e[i];
if (!color[j])//如果没有染色,则染成另外一种颜色 3 - c
{//3 - c: 把1变成2或者把2变成1
if(!dfs(j, 3 - c)) return false;
}
//当前j已经染了颜色,如果和当前颜色不同则矛盾
else if (color[j] == c) return false;//两条边颜色不一样
}
return true;
}
12、匈牙利算法
int h[N], e[M], ne[M], idx;
int match[N];//右边对应的点
bool st[N];//判断点是否重复
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int find(int x)
{
//1、枚举第x个男生所有看上女生的集合
for (int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];//j表示当前集合点的编号
if (!st[j]) //如果当前女生没有考虑过
{
st[j] = true;
//如果当前女生没有被匹配任何男生,或者虽然匹配了男生,但是可以为男生找到下家
if (match[j] == 0 || find(match[j]))
{
match[j] = x;//当前妹子可以匹配这个男生
return true;
}
}
}
return false;
}