文章目录
- DFS
- BFS
- 树的重心
- 图中点的层次
- 拓扑排序
- 最短路算法
- 最小生成树
- 二分图
DFS
排列数字
using namespace std;
const int N = 10;
int n;
int path[N];
void dfs(int u, int state){
if(u == n){
for(int i = 0; i < n; i++) printf("%d ", path[i]);
puts("");
return;
}
for(int i = 0; i < n; i++){
if(state >> i & 1) continue;
path[u] = i + 1;
dfs(u + 1, state | (1 << i));
}
}
int main(){
scanf("%d", &n);
dfs(0, 0);
return 0;
}
n 皇后问题
用截距表示一条斜线
正对角线:
(由于存在 ),所以加上一个整数
负对角线:
解法一
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool col[N], dg[N], udg[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;
}
解法二
using namespace std;
const int N = 10;
int n;
char g[N][N];
// 列; 正对角线;副对角线
bool row[N], col[N], dg[N << 1], udg[N << 1];
void dfs(int x, int y, int s){
if(s > n) return;
if(y == n) y = 0, x++;
if(x == n){
if(s == n){
for(int i = 0; i < n; i++) puts(g[i]);
puts("");
}
return;
}
g[x][y] = '.';
dfs(x, y + 1, s); // 不放
// 放
if(!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]){
row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;
g[x][y] = 'Q';
dfs(x, y + 1, s + 1);
g[x][y] = '.';
row[x] = col[y] = dg[x + y] = udg[x - y + n] = false ;
}
}
int main(){
cin >> n;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
g[i][j] = '.';
}
}
dfs(0, 0, 0);
return 0;
}
BFS
走迷宫
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int n, m;
int g[N][N], d[N][N];
PII q[N * N];
int bfs(){
memset(d, -1, sizeof(d));
d[0][0] = 0;
int hh = 0, tt = 0;
q[0] = {0, 0};
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
while(hh <= tt){
auto t = q[hh++];
for(int i = 0; i < 4; i++){
int x = t.first + dx[i], y = t.second + dy[i];
if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1){
d[x][y] = d[t.first][t.second] + 1;
q[++tt] = {x, y};
}
}
}
return d[n - 1][m - 1];
}
int main(){
cin >> n >> m;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
cin >> g[i][j];
}
}
cout << bfs() << endl;
return 0;
}
八数码
using namespace std;
int bfs(string state){
queue<string> q;
unordered_map<string, int> d;
q.push(state);
d[state] = 0;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
string end = "12345678x";
while(q.size()){
auto t = q.front();
q.pop();
if(t == end) return d[t];
int distance = d[t];
int k = t.find('x');
int x = k / 3, y = k % 3;
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){
swap(t[a * 3 + b], t[k]);
if(!d.count(t)){
d[t] = distance + 1;
q.push(t);
}
swap(t[a * 3 + b], t[k]);
}
}
}
return -1;
}
int main(){
char s[2];
string state;
for(int i = 0; i < 9; i++){
cin >> s;
state += *s;
}
cout << bfs(state) << endl;
return 0;
}
树与图的深度优先遍历
树的重心
/*
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
*/
using namespace std;
const int N = 100010, M = N << 1;
int n;
int h[N], e[M], ne[M], idx;
int ans = N;
bool st[N];
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int dfs(int u){
st[u] = true;
int sum = 0, size = 0;
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if(!st[j]){
int s = dfs(j);
// 求得每个分支数的最大值
size = max(size, s);
// 子树的总节点数
sum += s;
}
}
// 求分支最大值和(除(当前以u为子树)的其余节点(即删除当前节点后的上面的部分))的最大值
// 即得到的是删除当前节点的其他连通块中最大的值
size = max(size, n - sum - 1);
// 求删除某节点后,(连通块值最大)的最小
// 找到重心,并得到删除重心后,连通块值最大值
ans = min(ans, size);
return sum + 1;
}
int main(){
scanf("%d", &n);
memset(h, -1, sizeof(h));
for(int i = 0; i < n - 1; i++){
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
dfs(1);
printf("%d\n", ans);
return 0;
}
树与图的广度优先遍历
图中点的层次
// 图的存储(邻接表)
// 请你求出 1 号点到 n 号点的最短距离,不能到达则输出-1
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N];
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int bfs(){
memset(d, -1, sizeof d);
int hh = 0, tt = 0;
d[1] = 0;
q[0] = 1;
while(hh <= tt){
int t = q[hh++];
for(int i = h[t]; ~i; i = ne[i]){
int j = e[i];
if(d[j] == -1){
d[j] = d[t] + 1;
q[++tt] = j;
}
}
}
return d[n];
}
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);
}
cout << bfs() << endl;
return 0;
}
拓扑排序
有向图的拓扑序列
using namespace std;
const int N = 100010;
int n, m;
// 邻接表
int h[N], e[N], ne[N], idx;
int d[N]; // 入度
int q[N]; // 队列
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 拓扑排序
bool topsort(){
int hh = 0, tt = -1;
// 入度为0的加入队列
for(int i = 1; i <= n; i++){
if(!d[i]){
q[++tt] = i;
}
}
while(hh <= tt){
int t = q[hh++];
for(int i = h[t]; i != -1; i = ne[i]){
int j = e[i];
d[j]--;
if(d[j] == 0){
q[++tt] = j;
}
}
}
return tt == n - 1;
}
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);
// 入度++
d[b]++;
}
if(!topsort()) puts("-1");
else{
for(int i = 0; i < n; i++){
printf("%d ", q[i]);
}
puts("");
}
return 0;
}
最短路算法
朴素版dijkstra 算法
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N]; // 邻接矩阵
int dist[N]; // dist数组
bool st[N]; // 标记是否访问过
int dijkstra(){
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
for(int i = 0; i < n - 1; i++){
int t = -1;
for(int j = 1; j <= n; j++){
if(!st[j] && (t == -1 || dist[j] < dist[t])){
t = j;
}
}
if(t == n) break;
st[t] = true;
for(int j = 1; j <= n; j++){
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
}
if(dist[n] == INF) return -1;
return dist[n];
}
int main(){
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof(g));
while(m--){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
// 重边时取最小的边
g[a][b] = min(g[a][b], c);
}
printf("%d\n", dijkstra());
return 0;
}
堆优化版 djikstra 算法
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10, INF = 0x3f3f3f3f;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int dijkstra(){
memset(dist, 0x3f, sizeof(dist));
// 小根堆
priority_queue<PII, vector<PII>, greater<PII>> q;
q.push({0, 1});
dist[1] = 0;
while(q.size()){
PII cur = q.top();
q.pop();
int t = cur.second;
if(st[t]) continue;
st[t] = true;
for(int i = h[t]; i != -1; i = ne[i]){
int j = e[i];
if(!st[j]){
if(dist[t] + w[i] < dist[j]){
dist[j] = dist[t] + w[i];
q.push({dist[j], j});
}
}
}
}
if(dist[n] == INF) return -1;
return dist[n];
}
int main(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof(h));
while(m--){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
cout << dijkstra() << endl;
return 0;
}
bellman-ford 算法
using namespace std;
const int N = 510, M = 10010, INF = 0x3f3f3f3f;
struct Edge{
int a, b, c;
}edges[M];
int n, m, k;
int dist[N];
int last[N];
void bellman_ford(){
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0;
// 限制路径上最多有k条边
for(int i = 0; i < k; i++){
// 防止发生串联
memcpy(last, dist, sizeof(dist));
for(int j = 0; j < m; j++){
auto e = edges[j];
dist[e.b] = min(dist[e.b], last[e.a] + e.c);
}
}
}
int main(){
scanf("%d%d%d", &n, &m, &k);
for(int i = 0; i < m; i++){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}
bellman_ford();
if(dist[n] > INF / 2) puts("impossible");
else printf("%d\n", dist[n]);
return 0;
}
SPFA 算法
最坏情况 ,一般
SPFA 求最短路
using namespace std;
const int N = 100010, INF = 0x3f3f3f3f;
int n, m;
int h[N], e[N], ne[N], w[N], idx;
bool st[N]; // 记录当前点是否在队列中
int dist[N];
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int spfa(){
memset(dist, 0x3f, sizeof dist);
queue<int> q;
q.push(1);
dist[1] = 0;
st[1] = true;
while(q.size()){
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; ~i; i = ne[i]){
int j = e[i];
if(dist[t] + w[i] < dist[j]){
dist[j] = dist[t] + w[i];
if(!st[j]) {
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
int main(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
int a, b, c;
for(int i = 0; i < m; i++){
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = spfa();
if(t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
SPFA 判断负环
using namespace std;
const int N = 2010, M = 10010, INF = 0x3f3f3f3f;
int h[N], e[M], ne[M], w[M], idx;
int n, m;
int dist[N], cnt[N]; // cnt记录其他点到当前点经过的边数
bool st[N];
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool spfa(){
// memset(dist, 0x3f, sizeof dist);
queue<int> q;
for(int i = 1; i <= n; i++) q.push(i), st[i] = true;
while(q.size()){
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; ~i; 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]) q.push(j), st[j] = true;
}
}
}
return false;
}
int main(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
int a, b, c;
for(int i = 0; i < m; i++){
scanf("%d%d%d", &a, &b, &c);
add(a, b ,c);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}
floyd 求最短路
using namespace std;
const int N = 210, INF = 0x3f3f3f3f;
int n, m, Q;
int d[N][N];
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]);
}
}
}
}
int main(){
scanf("%d%d%d", &n, &m ,&Q);
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;
}
}
int a, b, c;
for(int i = 0; i < m; i++){
scanf("%d%d%d", &a, &b, &c);
d[a][b] = min(d[a][b], c);
}
floyd();
while(Q--){
int a, b;
scanf("%d%d", &a, &b);
if(d[a][b] > INF / 2) puts("impossible");
else printf("%d\n", d[a][b]);
}
return 0;
}
最小生成树
Prim 算法求最小生成树
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int prim(){
memset(dist, 0x3f, sizeof(dist));
int res = 0;
for(int i = 0; i < n; i++){
int t = -1;
for(int j = 1; j <= n; j++){
if(!st[j] && (t == -1 || dist[j] < dist[t])){
t = j;
}
}
// 除了第一个点外,如果找到的点与集合内的点无边时,说明不连通。
if(i && dist[t] == INF) return INF;
st[t] = true;
// 为了防止环的情况自己更新自己, 所以先加
if(i) res += dist[t]; // 集合的第一个点不加dist值
for(int j = 1; j <= n; j++){
dist[j] = min(dist[j], g[t][j]);
}
}
return res;
}
int main(){
scanf("%d%d", &n, &m);
memset(g, 0x3f, sizeof(g));
while(m--){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if(t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
kruskal 算法求最小生成树
// 对边权进行排序
// 从小到大遍历边选择小的边, 并且满足边的两端点没在一个集合
using namespace std;
const int N = 100010, M = 2 * N, INF = 0x3f3f3f3f;
int n, m;
int p[N];
struct Edge{
int a, b, w;
bool operator < (const Edge & e) const{
return w < e.w;
}
}edges[M];
int find(int x){
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
int kruskal(){
int res = 0, cnt = 0;
for(int i = 0; i < m; i++){
auto t = edges[i];
int a = find(t.a), b = find(t.b);
if(a != b){
p[a] = p[b];
res += t.w;
cnt ++;
}
}
if(cnt != n - 1) return INF;
return res;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 0; i <= n; i++) p[i] = i;
int a, b, c;
for(int i = 0; i < m; i++){
scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}
sort(edges, edges + m);
int t = kruskal();
if(t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
二分图
二分图:当且仅当图中不含奇数环。
将点分为两个集合,集合内部的点没有边。
染色法判断二分图
// 二分图,不存在奇数环(边数为奇数的环)
// 无向图 - 双向边
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
int e[M], ne[M], h[N], idx;
int color[N];
int n, m, a, b;
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool dfs(int x, int c){
color[x] = c;
for(int i = h[x]; i != -1; i = ne[i]){
int j = e[i];
if(!color[j]){
// 对邻接点进行涂色
if(!dfs(j, 3 - c)) return false;
}
// 如果当前节点与邻接节点涂色相同, 则存在奇数环
if(color[x] == color[j]) return false;
}
return true;
}
int main(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof(h));
for(int i = 0; i < m; i++){
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bool flag = false;
// 对每一个连通块涂色
for(int i = 1; i <= n; i++){
if(!color[i]){
if(!dfs(i, 1)){
flag = true;
break;
}
}
}
if(flag) puts("No");
else puts("Yes");
return 0;
}
匈牙利算法(二分图的最大匹配)
,但实际运行时间一般远小于
/*
给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m 条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。
二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数
*/
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N]; // n2匹配的对应n1 match[n2] = n1;
bool st[N]; // 一轮匹配中, n2是否已经被匹配了
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 匹配过程
bool find(int x){
// 遍历男生x的心仪女生
for(int i = h[x]; i != -1; i = ne[i]){
int j = e[i];
// 如果在本轮匹配中, j 女生未被预定
if(!st[j]){
// 标记为true,代表x的候选对象
st[j] = true;
// 如果j在前面的每轮未被匹配过
// 或者匹配过了, 给j的对象能找另一个心仪对象
// 则让x的对象为j(j的对象为x)
if(!match[j] || find(match[j])){
match[j] = x;
return true;
}
}
}
// 如果遍历完心仪对象还未匹配成功,则返回false
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 res = 0;
for(int i = 1; i <= n1; i++){
memset(st, false, sizeof(st));
// 匹配成功一次, res++
if(find(i)) res++;
}
printf("%d\n", res);
return 0;
}