Loading

图论基础

图论

DFS & BFS

搜索的关键:用什么顺序遍历所有的方案

数据结构 空间
\(DFS\) \(stack\) \(O(h)\) 不具有最短性
\(BFS\) \(queue\) \(O(h^2)\) 具有最短性

n皇后问题

#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;
}

845. 八数码 - AcWing题库

#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\) 的点

848. 有向图的拓扑序列 - AcWing题库

#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)\)

860. 染色法判定二分图 - AcWing题库

与自己相连的点异色。

#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)\)

861. 二分图的最大匹配 - AcWing题库

#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;
}
posted @ 2022-08-08 22:38  一叶知秋`  阅读(39)  评论(0)    收藏  举报