图论基础
图论
DFS & BFS
搜索的关键:用什么顺序遍历所有的方案
| 数据结构 | 空间 | ||
|---|---|---|---|
| \(DFS\) | \(stack\) | \(O(h)\) | 不具有最短性 |
| \(BFS\) | \(queue\) | \(O(h^2)\) | 具有最短性 |
#include <iostream>
using namespace std;
const int N = 10;
bool col[N], dg[N], udg[N]; // 用来标记列和两条斜线
char g[N][N];
int n;
void dfs(int u)
{
if (u == n)
{
for (int i = 0; i < n; i++)
puts(g[i]);
puts("");
return;
}
for (int i = 0; i < n; i++)
{
if (!col[i] && !dg[u + i] && !udg[n + u - i])
{
g[u][i] = 'Q';
col[i] = dg[u + i] = udg[n + u - i] = true;
dfs(u + 1);
col[i] = dg[u + i] = udg[n + u - i] = false;
g[u][i] = '.';
}
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
g[i][j] = '.';
dfs(0);
return 0;
}
#include <iostream>
#include <unordered_map>
#include <queue>
using namespace std;
int bfs(string start)
{
unordered_map<string, int> d;
queue<string> q;
string end = "12345678x"; // 存储最终状态
d[start] = 0;
q.push(start);
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
while (q.size())
{
auto t = q.front();
q.pop();
if (t == end)
return d[t];
// 变换过程
int k = t.find('x');
int x = k / 3, y = k % 3; // 记录 x 的坐标
// 尝试向 4 个方向变换
for (int i = 0; i < 4; i++)
{
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < 3 && b >= 0 && b < 3)
{
int dis = d[t];
swap(t[k], t[a * 3 + b]);
if (!d.count(t)) {
d[t] = dis + 1;
q.push(t);
}
swap(t[k], t[a * 3 + b]);
}
}
}
return -1;
}
int main()
{
char op[2];
string str;
for (int i = 0; i < 9; i++)
{
scanf("%s", op);
str += *op;
}
cout << bfs(str) << endl;
return 0;
}
图的存储
邻接矩阵
适用与稠密图
int g[N][N]
邻接表
\(N\) 个单链表,使用稠密图
// h[]记录表头,e[]存储节点值 ne[]存储节点 next 指针 w[]存储由 h[] 指向 e[]的权值
int h[N], e[M], ne[M], w[M], idx;
// 添加节点
void add(int a, int b, int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
// 遍历某节点的所有边
for (int i = h[t]; i != -1; i = ne[i])
{
}
memset(h, -1, sizeof(h)); // 一定要将头节点设为 -1
拓扑排序
拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。
每次将入度为 \(0\) 的点入队。
一个有向无环图,一定至少存在一个入度为 \(0\) 的点
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1000010;
int e[N], ne[N], h[N], idx;
int q[N], d[N];
int n, m;
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
bool topsort()
{
int hh = 0, tt = -1;
for (int i = 1; i <= n; i++)
if (d[i] == 0)
q[++tt] = i;
while (hh <= tt)
{
int t = q[hh++];
for (int i = h[t]; i != -1; i = ne[i])
{
int u = e[i];
if (--d[u] == 0)
q[++tt] = u;
}
}
return tt == n - 1;
}
int main()
{
memset(h, -1, sizeof(h));
scanf("%d%d", &n, &m);
while (m--)
{
int a, b;
scanf("%d %d",&a, &b);
add(a, b);
d[b]++;
}
if (topsort())
for (int i = 0; i < n; i++)
printf("%d ", q[i]);
else
puts("-1");
return 0;
}
最短路

朴素Dijkstra
\(O(n^2 + m)\)
每次找到离源点最近的点,看能否用这个点更新其他的点。
bool st[N]; // 每个点的状态
int dis[N], g[N][N];
int dijkstra()
{
memset(dis, 0x3f, sizeof(dis));
dis[1] = 0;
for (int i = 1; i <= n; i++)
{
int u = -1;
for (int j = 1; j <= n; j++)
if (!st[j] && (u == -1 || dis[j] < dis[u]))
u = j;
st[u] = true;
// 用 u 更新其他点的距离
for (int v = 1; v <= n; v++)
dis[v] = min(dis[v], dis[u] + g[u][v]);
}
if (dis[n] != 0x3f3f3f3f)
return dis[n];
else
return -1;
}
堆优化版Dijkstra
\(O(mlogn)\)
用优先队列维护到源点最近的点的集合。
typedef pair<int, int> P;
int n, m, dis[N];
int h[N], e[N], ne[N], w[N], idx;
bool st[N];
int dijkstra()
{
memset(dis, 0x3f, sizeof(h));
dis[1] = 0;
priority_queue<P, vector<P>, greater<P>> q;
q.push({0, 1});
while (q.size())
{
auto t = q.top();
q.pop();
int u = t.second;
if (st[u])
continue;
st[u] = true;
for (int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if (dis[v] > dis[u] + w[i])
{
dis[v] = dis[u] + w[i];
q.push({dis[v], v});
}
}
}
if (dis[n] != 0x3f3f3f3f)
return dis[n];
else
return -1;
}
bellman-ford
int n, m; // n表示点数,m表示边数
int dist[N]; // dist[x]存储1到x的最短路距离
struct Edge // 边,a表示出点,b表示入点,w表示边的权重
{
int a, b, w;
}edges[M];
// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < m; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
if (dist[b] > dist[a] + w)
dist[b] = dist[a] + w;
}
}
if (dist[n] > 0x3f3f3f3f / 2) return -1;
return dist[n];
}
// 经过 k 次边的最短距离
void bellman_ford()
{
memset(dis, 0x3f, sizeof(dis));
dis[1] = 0;
for (int i = 0; i < k; i++)
{
memcpy(backup, dis, sizeof(dis));
for (int j = 0; j < m; j++)
{
auto e = edges[j];
dis[e.b] = min(dis[e.b], backup[e.a] + e.w);
}
}
}
SPFA
bellman-ford 做了很多无用的松弛操作。只用上一次被松弛的点才可能引起下一次的松弛操作。
用队列来维护可能引起松弛操作的点。
void spfa()
{
memset(dis, 0x3f, sizeof dis);
queue<int> q;
q.push(1);
dis[1] = 0;
while (q.size())
{
auto t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int v = e[i];
if (dis[v] > dis[t] + w[i])
{
dis[v] = dis[t] + w[i];
if (!st[v])
{
q.push(v);
st[v] = true;
}
}
}
}
if (dis[n] > 0x3f3f3f3f / 2)
puts("impossible");
else
printf("%d\n", dis[n]);
}
SPFA判断负环
\(n\) 个点从 \(1\) 到 \(n\) 的最短距离最多经过 \(n - 1\) 条边,如果超出了说明有负环。
bool spfa()
{
queue<int> que;
// 有可能存在不能从 1 到达的负环,所以把所有点放进队列
for (int i = 1; i <= n; i++)
{
st[i] = true;
que.push(i);
}
while (!que.empty())
{
int t = que.front();
que.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int u = e[i];
if (dis[u] > dis[t] + w[i])
{
dis[u] = dis[t] + w[i];
cnt[u] = cnt[t] + 1;
if (cnt[u] >= n)
return true;
if (!st[u])
{
que.push(u);
st[u] = true;
}
}
}
}
return false;
}
Floyd
求多源最短路。
三重循环,每次看看能否通过 \(k\) 这个点使 \(i\) 到 \(j\) 的距离更新。
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]);
}
最小生成树
prim
\(O(n^2)\)
int prim()
{
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
int res = 0;
for (int i = 0; i < n; i++)
{
int u = -1;
for (int j = 1; j <= n; j++)
if (!st[j] && (u == -1 || dis[j] < dis[u]))
u = j;
if (i && dis[u] == INF) return INF;
st[u] = true;
res += dis[u];
for (int v = 1; v <= n; v++)
if (!st[v])
dis[v] = min(dis[v], g[u][v]);
}
return res;
}
Kruskal
将边的距离从小到大排序,枚举所有边,如果不在集合就加入集合。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
struct edge {
int a, b, w;
bool operator< (const edge &W) const
{
return w < W.w;
}
}edges[N];
int n, m;
int p[N];
int find(int x)
{
return p[x] == x ? x : p[x] = find(p[x]);
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
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++)
{
auto e = edges[i];
int a = find(e.a), b = find(e.b);
if (a != b)
{
cnt++;
p[a] = b;
res += e.w;
}
}
if (cnt < n - 1)
puts("impossible");
else
printf("%d\n", res);
return 0;
}
二分图
染色法判断二分图
\(O(n + m)\)
与自己相连的点异色。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100010, M =200010;
int e[M], ne[M], h[N], idx;
int n, m;
int color[N];
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
bool dfs(int u, int c)
{
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if (!color[v])
{
if (!dfs(v, 3 - c))
return false;
}
else if (color[v] == c)
return false;
}
return true;
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1,sizeof (h));
for (int i = 0; i < m; i++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
bool flag = true;
for (int i = 1; i <= n; i++)
{
if (!color[i])
{
if (!dfs(i, 1)) //
{
flag = false;
break;
}
}
}
if (flag)
puts("Yes");
else
puts("No");
return 0;
}
匈牙利算法
求二分图的最大匹配
\(O(nm)\) 实际远小于 \(O(nm)\)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, M = 100010;
int n1, n2 , m;
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++;
}
bool find(int x)
{
for (int i = h[x]; i != -1; i = ne[i])
{
int u = e[i];
if (!st[u])
{
st[u] = true;
if (!match[u] || find(match[u]))
{
match[u] = x;
return true;
}
}
}
return false;
}
int main()
{
scanf("%d%d%d", &n1, &n2, &m);
memset(h, -1, sizeof (h));
while (m--)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
int cnt = 0;
for (int i = 1; i <= n1; i++)
{
memset(st, false, sizeof(st));
if (find(i))
cnt++;
}
printf("%d\n", cnt);
return 0;
}

浙公网安备 33010602011771号