算法基础课:第三讲——搜索与图论

前言:

在这里插入图片描述

DFS

算法思想:

在这里插入图片描述
通过递归函数的方式,实现深度优先搜索,即对每一条路都会走到头才返回上一层,基本包括以下五个部分:
结束条件: 递归终止的条件,避免爆栈
递归顺序: 选择合适的递归顺序
进行操作: 对当前层进行操作
递归下一层: 递归下一层
恢复现场: 恢复当前层的进行的操作

例题:

AcWing 842. 排列数字

AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10;

int n;
int path[N];
bool st[N];

void dfs(int u)
{
    // 终止条件
    if (u == n)
    {
        for (int i = 0; i < n; i ++)    cout << path[i] << ' ';
        cout << endl;
        return ;
    }
    // 递归顺序
    for (int i = 1; i <= n; i ++)
        if (st[i] != true)
        {
            // 进行操作
            path[u] = i;
            st[i] = true;
            
            // 递归下一层
            dfs(u + 1);
            
            // 恢复现场
            path[u] = 0;
            st[i] = false;
        }
}

int main()
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    
    cin >> n;
    dfs(0);
    
    return 0;
}




BFS

算法思想:

在这里插入图片描述
利用队列,每次选取距离当前点最近的点进行遍历,如图所示,红色为点的遍历层次,具有最短路的性质

注意点:

  • bfs求最短路仅限制权重为1的图

模板:

void bfs()
{
	queue<> q;	// 宽搜队列
	// 起点入队
	
	while ( 队列非空 )
	{
		// 取队头
		// 弹出队头
		// 拓展队头
		// 继续入队
	}
}

例题:

AcWing 844. 走迷宫

AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

const int N = 105;
typedef pair<int, int> PII;

int n, m;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};   // 偏移量
int g[N][N]; // 存图
int d[N][N]; // 存每个点到0,0的距离
queue<PII> q;

bool check(int x, int y)
{
    return x < n && x >= 0 && y < m && y >= 0;
}

int bfs()
{
    memset(d, -1, sizeof d);    // 将所有点到起点的距离置为 -1
    q.push({0, 0});             // 将起点入队
    d[0][0] = 0;
    while(!q.empty())           // 当队列非空
    {
        auto t = q.front();     // 取队头元素
        q.pop();                // 弹出队头
        for (int i = 0; i < 4; i ++)
        {
            // 走一步可到达的点
            int x = t.first + dx[i];
            int y = t.second + dy[i];
            
            if (check(x, y) && g[x][y] != 1 && d[x][y] == -1)   // x,y没有出界,走一步的点不是墙,走一步的点并且没被搜过
            {
                d[x][y] = d[t.first][t.second] + 1;     // 记录当前这点到原点的距离
                q.push({x, y});     // 将该点入队
            }
        }
    }
    
    return d[n - 1][m - 1];
}

int main( )
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    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;
}




树与图的深度优先遍历

算法思想:

利用邻接表(拉链法) 存图,进行深度优先遍历,对遍历过的点进行标记

模板:

int dfs(int u)
{
    st[u] = true; // st[u] 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

例题:

AcWing 846. 树的重心

AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 5, M = N *2;

int h[N], e[M], ne[M], idx;
bool st[N];
int n;
int ans = N;

void add(int a, int b)      // 加边
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}


int dfs(int u)      // 返回以u为根的树的连通点的数量
{
    st[u] = true;   // 当前点被搜过
    
    int sum = 1, res = 0;  
    // sum为以u为根的树的连通点的数量
    // res为将该点删除后其余连通块点个数的最大值, 由两部分取max构成
    // 第一部分,其所有子树的sum, 第二部分,其头上的树的点个数
    
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (st[j] != true)
        {
            int s = dfs(j);     // 求子树的联通点数量
            sum += s;           // 
            res = max(res, s);  // 第一部分
        }
    }
    res = max(res, n - sum);    // 第二部分
    
    ans = min(ans, res);        // 全局sum取所有点被删除后所有res情况中的最小值
 
    return sum;
}

int main()
{
    
    cin >> n;
    memset(h, -1, sizeof h);
    
    for (int i = 0; i < n - 1; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    
    dfs(1);
    
    cout << ans << endl;
    
    return 0;
}




树与图的广度优先遍历

算法思想:

利用邻接表(拉链法) 存图,进行广度优先遍历,对遍历过的点进行标记,每次对队头进行拓展后入队新点

模板:

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 (!st[j])
        {
            st[j] = true; // 表示点j已经被遍历过
            q.push(j);
        }
    }
}

例题:

AcWing 847. 图中点的层次

AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int N = 1e5 + 5, M = N * 2;

int n, m;
int h[N], e[M], ne[M], idx;
int d[N];       // 距离
queue<int> q;   // 宽搜队列

void add(int a, int b)          // 加边
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

int bfs()
{
    // 起点入队
    d[1] = 0;
    q.push(1);
    
    while (!q.empty())          // 队列非空
    {
        auto t = q.front();     // 取队头
        q.pop();                // 弹出队头
        // 拓展队头
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];           // 取值
            if (d[j] == -1)         // 没有搜过
            {
                d[j] = d[t] + 1;    // 更新距离        
                q.push(j);          // 拓展点入队
            }
        }
    }
    return d[n];
}

int main()
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    memset(d, -1, sizeof d);
    memset(h, -1, sizeof h);
    
    cin >> n >> m;
    for (int i = 0; i < m; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    cout << bfs() << endl;
    
    return 0;
}




拓扑排序

算法思想:

任何有向无环图都存在拓扑序,对于拓扑排序,每次选取入度为0的点删掉,并把以该点为起点的边删掉,将新的入度为0的点入队,重复直至排序完成,如图红色表示每个点的入度:
在这里插入图片描述

模板:

bool topsort( )
{
    int index = 0;
    for (int i = 1; i <= n; i ++)
        if (d[i] == 0)
            q.push(i);              // 入度为0的点入队
    
    while (!q.empty())              // 队列非空
    {
        auto t = q.front();         // 取队头
        q.pop();
        
        ans[index ++] = t;          // 存进ans
        
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            d[j] --;                // 删 t -> j 的边,j 的入度 - 1
            if (d[j] == 0)
                q.push(j);          // 若入度为0,则进队
        }
    }
    
    return index == n;
}

例题:

AcWing 848. 有向图的拓扑序列

AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e5 + 5;

int h[N], e[N], ne[N], idx;
int d[N];                           // 每个点的入度
int n, m;
int ans[N];                        // 存放排序
queue<int> q;                       // 宽搜队列

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

bool topsort( )
{
    int index = 0;
    for (int i = 1; i <= n; i ++)
        if (d[i] == 0)
            q.push(i);              // 入度为0的点入队
    
    while (!q.empty())              // 队列非空
    {
        auto t = q.front();         // 取队头
        q.pop();
        
        ans[index ++] = t;          // 存进ans
        
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            d[j] --;                // 删 t -> j 的边,j 的入度 - 1
            if (d[j] == 0)
                q.push(j);          // 若入度为0,则进队
        }
    }
    
    return index == n;
}

int main( )
{
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 0; i < m; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);                  // 加边
        d[b] ++;                    // 入度 + 1
    }
    
    if (topsort())
    {
        for (int i = 0; i < n; i ++)
            cout << ans[i] << ' ';
    }
    else
        puts("-1");
    
    return 0;
}





最短路问题

解题思路:

在这里插入图片描述

Dijkstra

算法思想:

朴素Dijkstra算法: dist[] 表示每个点到起点的距离
步骤一: 将第一个点加到最短路集合里,dist[1] = 0,其他点dist[i]=+∞,表示第一个点到起点的距离为0
步骤二: n次循环,找出不在集合中距离最近的点t
步骤三: 将 t 放到集合里
步骤四: 用 t 更新其他点的距离,取小值

堆优化版Dijkstra算法 :步骤二的时间复杂度经过n次循环时间复杂度为O(n²) 可以用堆来进行优化,将整个时间复杂度降为O(mlogn)

模板:

// 朴素 Dijkstra
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    // 源点入集合
    dist[1] = 0;
    
    // n次循环
    for (int i = 0; i < n; i ++)
    {
        // 找出距离最近的点
        int t = -1;
        for (int j = 1; j <= n; j ++)
            if(!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        
        // 加入集合
        st[t] = true;
        
        // 更新距离
        for (int j = 1; j <= n; j ++)
                dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    if (dist[n] == 0x3f3f3f3f)  return -1;
    else
        return dist[n];
}


// 堆优化版
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    priority_queue<PII, vector<PII>, greater<PII>> heap;    
    
    // 源点入队
    heap.push({0, 1});
    dist[1] = 0;
    
    while (!heap.empty())
    {
        // 求距离最小的点
        auto t = heap.top();
        heap.pop();
        
        int distance = t.first, index = t.second;     // 取距离和点号
        
        if (st[index]) continue;
        st[index] = true;

        // 更新以index为起点的边连接的点的距离
        for (int i = h[index]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[index] + w[i])
            {
                dist[j] = dist[index] + w[i];
                heap.push({dist[j], j});
            }
        }
        
        
    }
    if (dist[n] == 0x3f3f3f3f) return -1;
    else    return dist[n];
}

例题:

AcWing 849. Dijkstra求最短路 I
AcWing 850. Dijkstra求最短路 II

AC代码:
// 朴素 Dijkstra
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 510;

int n, m;
int g[N][N], dist[N];
bool st[N];

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    // 源点入集合
    dist[1] = 0;
    
    // n次循环
    for (int i = 0; i < n; i ++)
    {
        // 找出距离最近的点
        int t = -1;
        for (int j = 1; j <= n; j ++)
            if(!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        
        // 加入集合
        st[t] = true;
        
        // 更新距离
        for (int j = 1; j <= n; j ++)
                dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    if (dist[n] == 0x3f3f3f3f)  return -1;
    else
        return dist[n];
}

int main( )
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n >> m;
    memset(g, 0x3f, sizeof g);
    
    while (m --)                    // 加边
    {   
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c);
    }
    
    int t = dijkstra();
    
    cout << t << endl;
    
    return 0;
}


// 堆优化Dijkstra

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 2e5;

typedef pair<int, int> PII;

int n, m;
int h[N];
int w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];

void add(int a, int b, int c)
{
    w[idx] = c, e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    priority_queue<PII, vector<PII>, greater<PII>> heap;    
    
    // 源点入队
    heap.push({0, 1});
    dist[1] = 0;
    
    while (!heap.empty())
    {
        // 求距离最小的点
        auto t = heap.top();
        heap.pop();
        
        int distance = t.first, index = t.second;     // 取距离和点号
        
        if (st[index]) continue;
        st[index] = true;

        // 更新以index为起点的边连接的点的距离
        for (int i = h[index]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[index] + w[i])
            {
                dist[j] = dist[index] + w[i];
                heap.push({dist[j], j});
            }
        }
        
        
    }
    if (dist[n] == 0x3f3f3f3f) return -1;
    else    return dist[n];
}

int main()
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    memset(h, -1, sizeof h);
    
    cin >> n >> m;
    while (m --)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    
    int t = dijkstra();
    
    cout << t << endl;
    
    return 0;
}




Bellman-ford

算法思想:

循环n次 对于所有的边 a -> b 权为 w,进行松弛操作: 在原dist[b] 和dist[a] + w 中取小值赋给 dist[b]

注意点:

  • 边用结构体存储即可
  • 对于边数的限制 k 只需调整循环为k次即可,并且需要采用备份,保证每次只动一条边

模板:

int bellmanford()
{
    memset(dist, 0x3f3f3f3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < k; i ++)            // k 次循环
    {
        memcpy(backup, dist, sizeof dist);    // 备份
        for (int j = 0; j < m; j ++)        // m 条边
        {
            int a = edge[j].a, b = edge[j].b, w = edge[j].w;
            dist[b] = min(dist[b], backup[a] + w);
        }
    }
    
    return dist[n];
    
}

例题:

AcWing 853. 有边数限制的最短路

AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 505, M = 10010;

int n, m, k;
int dist[N], backup[N];

struct edge{
    int a, b, w;
}edge[M];

int bellmanford()
{
    memset(dist, 0x3f3f3f3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < k; i ++)            // k 次循环
    {
        memcpy(backup, dist, sizeof dist);    // 备份
        for (int j = 0; j < m; j ++)        // m 条边
        {
            int a = edge[j].a, b = edge[j].b, w = edge[j].w;
            dist[b] = min(dist[b], backup[a] + w);
        }
    }
    
    return dist[n];
    
}

int main( )
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);

    cin >> n >> m >> k;
    for (int i = 0; i < m; i ++)    cin >> edge[i].a >> edge[i].b >> edge[i].w;
    
    int t = bellmanford();
    
    if (t > 0x3f3f3f3f / 2) cout << "impossible" << endl;
    else
        cout << t << endl;
    return 0;
}





SPFA

算法思想:

对于Bellman - ford 算法的松弛操作,我们可以发现,只有当 dist[a] 变小时,min操作才有可能变小,所以利用bfs来做优化,
步骤一: 先把起点放到队列中,只要队列不空,
步骤二: 每次取队头,进行更新操作(因为t变小了,所以t的出边到的点才有可能变小)
步骤三: 将dist[ ]变小的结点放入队列,若队列中有则不需要再加

SPFA 判断负环: 用cnt数组记录从1号点到每个点的边数,由于一共n个点,一条路最多n - 1条边,所以若cnt >= n则说明有负环

模板:

// 求最短路
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    // 初始
    queue<int> q;
    q.push(1);
    dist[1] = 0;
    st[1] =  true;
    
    while (!q.empty())
    {
        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] == false)
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return dist[n];
}

// 判断负环
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e5 + 5;

int n, m;
int h[N], e[N], w[N], ne[N], idx;
int dist[N], cnt[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 spfa()
{
    memset(dist, 0x3f, sizeof dist);
    queue<int> q;
    
    // 初始
    dist[1] = 0;
    for (int i = 1; i <= n; i ++ )
    {
        st[i] = true;
        q.push(i);
    }
    
    while (!q.empty())
    {
        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];
                cnt[j] = cnt[t] + 1;
                
                if (cnt[j] >= n) return true;
                
                if (st[j] == false)
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return false;
}

int main()
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    memset(h, -1, sizeof h);
    
    cin >> n >> m;
    for (int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    
    bool t = spfa();
    
    if (t) cout << "Yes" << endl;
    else
        cout << "No" << endl;
    
    return 0;
}

例题:

AcWing 851. spfa求最短路

AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e5 + 5;

int n, m;
int h[N], e[N], w[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 spfa()
{
    memset(dist, 0x3f, sizeof dist);
    // 初始
    queue<int> q;
    q.push(1);
    dist[1] = 0;
    st[1] =  true;
    
    while (!q.empty())
    {
        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] == false)
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return dist[n];
}

int main()
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    memset(h, -1, sizeof h);
    
    cin >> n >> m;
    for (int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    
    int t = spfa();
    
    if (t == 0x3f3f3f3f) cout << "impossible" << endl;
    else
        cout << t << endl;
    
    return 0;
}




Floyd

算法思想:

通过三重循环,每次判断是直接从i -> j的路径短,还是i ->k -> j 的路径短,选小值更新

模板:

void floyd()
{
    for (int k = 1; k <= n; k ++)
        for (int i = 1; i <= n; i ++)
            for (int j = 1; j <= n; j ++)
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}

例题:

AcWing 854. Floyd求最短路

AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 205, INF = 1e9;

int g[N][N];
int n, m, k;

void floyd()
{
    for (int k = 1; k <= n; k ++)
        for (int i = 1; i <= n; i ++)
            for (int j = 1; j <= n; j ++)
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}

int main( )
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    
    cin >> n >> m >> k;
    
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= n; j ++)
            if (i == j) g[i][j] = 0;
            else    
                g[i][j] = INF;
    
    for (int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c);
    }
    
    floyd();
    
    while (k --)
    {
        int a, b;
        cin >> a >> b;
        if (g[a][b] > INF / 2)  cout << "impossible" << endl;
        else
            cout << g[a][b] << endl;
    }
    
    return 0;
}




最小生成树

解题思路:

对于稠密图采用朴素Prim算法,稀疏图采用Kruskal算法
在这里插入图片描述

Prim

算法思想:

与最短路算法不同,最小生成树的dist[] 表示该点到已经生成树的连通块的距离(不是起点),n 次循环,每次将距离连通块最小的点加入集合,并进行距离更新,若该点不是第一个将被加入集合点并且距离还为正无穷,说明有点不连通,则不存在最小生成树

注意点:

模板:

int prim()
{
    // 初始化距离
    memset(dist, 0x3f, sizeof dist);
    
    int res = 0;
    // n 次循环,加入 n 个点
    for (int i = 0; i < n; i ++)
    {
        int t = -1;
        // 选取距离连通块最小的点
        for (int j = 1; j <= n; j ++)
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        
        // 若该点不是第一个将被加入集合点并且距离还为正无穷,则图不连通不存在最小生成树
        if (i != 0 && dist[t] == INF)   return INF;
        
        // 计算最小生成树的总长度

        st[t] = true;   // 加入集合
        
        // 更新集合
        for (int j = 1; j <= n; j ++)   dist[j] = min(dist[j], g[t][j]);
        
        if (i != 0) res += dist[t];
    }
    return res;
    
}

例题:

AcWing 858. Prim算法求最小生成树

AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 505, INF = 0x3f3f3f3f;

int g[N][N];
int n, m;
int dist[N];    // 表示每个点到已经连通的块的距离
bool st[N];     // 是否在最小生成树集合中

int prim()
{
    // 初始化距离
    memset(dist, 0x3f, sizeof dist);
    
    int res = 0;
    // n 次循环,加入 n 个点
    for (int i = 0; i < n; i ++)
    {
        int t = -1;
        // 选取距离连通块最小的点
        for (int j = 1; j <= n; j ++)
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        
        // 若该点不是第一个将被加入集合点并且距离还为正无穷,则图不连通不存在最小生成树
        if (i != 0 && dist[t] == INF)   return INF;
        
        // 计算最小生成树的总长度

        st[t] = true;   // 加入集合
        
        // 更新集合
        for (int j = 1; j <= n; j ++)   dist[j] = min(dist[j], g[t][j]);
        
        if (i != 0) res += dist[t];
    }
    return res;
    
}

int main()
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    memset(g, 0x3f, sizeof g);
    for (int i = 1; i <= n; i ++)   g[i][i] = 0;
    cin >> n >> m;
    
    while(m --)     // 输入 m 条边
    {
        int a, b, c;
        cin >> a >> b >> c;
        if (a == b) continue;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    
    int t =prim();
    
    if (t == INF)   cout << "impossible" << endl;
    else
        cout << t << endl;
    
    return 0;
}




Kruskal

算法思想:

先对所有边进行排序,利用并查集,枚举每一条边,若两条边的两点不连通(不在一个集合)则进行集合合并(并查集基本操作),每次记录生成树的长度

注意点:

模板:

int kruskal()
{
    // 初始化并查集
    for (int i = 1; i <= n; i ++)    p[i] = i;
    
    sort(edge, edge + m);
    
    // res 为最小生成树长度,cnt 为生成树中点的数量
    int res = 0, cnt = 0;
    
    // 枚举 m 条边
    for (int i = 0; i < m; i ++)
    {
        int a = edge[i].a, b = edge[i].b, w = edge[i].w;
        
        a = find(a), b = find(b);
        if (a != b) // 不在一个集合中
        {
            // 连通
            p[a] = b; 
            // 总和增加
            res += w;
            cnt ++;
        }
    }
    
    if (cnt < n - 1) return INF;
    else
        return res;
       
}

例题:

AcWing 859. Kruskal算法求最小生成树

AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 200005, INF = 0x3f3f3f3f;

int n, m;
int p[N];

struct edge
{
    int a, b, w;
    
    // 重载小于号
    bool operator< (const edge &W) const
    {
        return w < W.w;
    }

    
}edge[N];

int find(int x)
{
    if (p[x] != x)  p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    // 初始化并查集
    for (int i = 1; i <= n; i ++)    p[i] = i;
    
    sort(edge, edge + m);
    
    // res 为最小生成树长度,cnt 为生成树中点的数量
    int res = 0, cnt = 0;
    
    // 枚举 m 条边
    for (int i = 0; i < m; i ++)
    {
        int a = edge[i].a, b = edge[i].b, w = edge[i].w;
        
        a = find(a), b = find(b);
        if (a != b) // 不在一个集合中
        {
            // 连通
            p[a] = b; 
            // 总和增加
            res += w;
            cnt ++;
        }
    }
    
    if (cnt < n - 1) return INF;
    else
        return res;
       
}

int main()
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n >> m;
    
    for (int i = 0; i < m; i ++)    // 加边
    {
        int a, b, w;
        cin >> a >> b >>w;
        edge[i] = {a, b, w};
    }
    
    int t = kruskal();
    
    if (t == INF)   cout << "impossible" << endl;
    else
        cout << t << endl;
        
    return 0;
}




二分图

解题思路:

在这里插入图片描述







染色法判定二分图

算法思想:

有奇数环的图一定不是二分图,所以我们用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])  // 邻点未染色
        {
            if (!dfs(j, 3 - c)) return false;
        }
        else if (color[j] == color[u])  return false;
        // 邻点染色但与当前点相同
    }
    return true;
}

例题:

AcWing 860. 染色法判定二分图

AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5 + 5, M = N * 2;

int 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 ++;
}

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])  // 邻点未染色
        {
            if (!dfs(j, 3 - c)) return false;
        }
        else if (color[j] == color[u])  return false;
        // 邻点染色但与当前点相同
    }
    return true;
}

int main()
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    memset(h, -1, sizeof h);
        
    cin >> n >> m;
    while (m --)
    {
        int a, b;
        cin >> 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) cout << "Yes" << endl;
    else
        cout << "No" << endl;
    return 0;
}




匈牙利算法

算法思想:

判断二分图两部分的最大匹配,即在不同部分里的点之间只有一条边相连,使得边最多即可。
遍历左部分所有点,如图,根据 x 的出边,为他分配点 y,但若当前出边的点 y 已经被分配给了某个点 m ,则判断该点 m 是否有第二选择,若有就腾出位置,将其原有的分配让给 x,而m与p配对
在这里插入图片描述

模板:

例题:

AcWing 861. 二分图的最大匹配

AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>

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 j = e[i];
        if (!st[j])         // 该点未考虑
        {
            st[j] = true;
            if (match[j] == 0 || find(match[j]))    // 该点未分配,或可以为该点的配对找到下家
            {
                match[j] = x;
                return true;
            }
        }
    }
    return false;
}

int main( )
{
    cin.tie(0), cout.tie(0);
    ios::sync_with_stdio(false);
    
    memset(h, -1, sizeof h);
    
    cin >> n1 >> n2 >> m;
    while (m --)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    int res = 0;
    for (int i = 1; i <= n1; i ++)
    {
        memset(st, false, sizeof st);
        if (find(i))    res ++;
    }
    
    cout << res << endl;
    return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 广度优先搜索是一种图的遍历算法,它从图的某个顶点开始遍历,先访问该顶点,然后依次访问该顶点的所有邻接点,再依次访问邻接点的邻接点,直到遍历完所有可达的顶点为止。广度优先搜索通常使用队列来实现,每次访问一个顶点时,将其所有未访问的邻接点加入队列中,然后从队列中取出一个顶点进行访问,直到队列为空为止。广度优先搜索可以用于寻找图中的最短路径,也可以用于检测图是否连通,或者寻找图中的环等问题。 ### 回答2: 广度优先搜索(BFS)是一种图的遍历算法,其遍历规则是:从某个定点开始,先访问它的所有邻居节点,然后对于每个邻居节点再访问它们的邻居节点,依此类推,直到遍历完整个图。BFS使用队列来存储待访问的节点,并在队列中按照先进先出(FIFO)的原则进行访问,确保遍历结果是按照最短路径的顺序得到的。在任何时刻,队列中所有已访问过的节点都必须被一个标记,避免重复遍历。 BFS算法可以用于寻找无权图中两个顶点之间最短路径的问题。具体实现过程如下:从起始节点开始,扩展其所有邻居节点,将它们加入队列,并在它们的数据结构中记录下与它相邻的“父节点”。然后从队列中取出下一个节点,重复此过程,直到找到目标节点或者队列为空。如果目标节点被访问过,可以通过跟踪每个节点的父节点信息反向回溯得到最短路径。 需要注意的是,由于BFS需要维护一个队列,且图中节点访问过与否的状态需要用额外的标记进行管理,因此其时间复杂度为O(n+m),其中n为节点数,m为边数。BFS空间复杂度为O(n),主要是队列的存储空间。此外,当图的规模较大时,BFS可能会因为占用大量内存而无法使用,需要使用更高效的算法来解决问题。 总之,BFS是一种简单而有效的图的遍历算法。它可以被广泛应用于各种场景中,如建模地图网络、社交网络、计算机网络等,并且具有简单易懂、实现容易、算法复杂度较低等优点。 ### 回答3: 广度优先搜索(BFS)是一种常用的图遍历算法,其核心思想是“一层一层地遍历”,即先访问起始顶点的邻接顶点,然后再访问邻接顶点的邻接顶点,以此类推,直到图中所有顶点都被访问过为止。广度优先搜索可以用于图的遍历、最短路径等问题。 广度优先搜索算法流程如下: 1. 创建一个队列,将起始顶点加入队列; 2. 标记起始顶点已被访问; 3. 循环执行以下步骤,直到队列为空: a. 出队一个顶点,并访问该顶点; b. 遍历该顶点的所有邻接顶点,如果邻接顶点未被访问,则标记其已被访问,并将其加入队列中; 4. 如果图中还有未被访问的顶点,从未访问的顶点中选取一个作为起始顶点重复上述步骤。 广度优先搜索是一种较为简单且易于实现的算法,其时间复杂度为O(V+E),其中V为图中顶点的数目,E为图中边的数目。因此,该算法适用于较小规模的图。 除了广度优先搜索外,还有一种常用的图遍历算法是深度优先搜索(DFS),其核心思想是“一条路走到黑”,即从起始顶点出发,沿着路径一直往下走直到走到末端,然后返回上一层继续遍历。DFS也可以用于图的遍历、最短路径等问题。与BFS不同的是,DFS使用的是栈而非队列来存储待访问的顶点,其时间复杂度也为O(V+E)。 总之,广度优先搜索和深度优先搜索是两种常用的图遍历算法,它们在不同的场景下具有不同的优势和应用。因此,我们需要针对具体的问题来选择合适的算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值