NEUQ-ACM2022预备队第九周作业

第九周作业

存图方式

邻接矩阵

通过一个二维数组方式存储图的边,第一维表示起点,第二维表示终点,数组内容表示权。若两点不存在边,则通常在数组中存一个极大的数表示无穷大。
缺点:空间占用大,很多空间被浪费。

int e[MAXN][MAXN];
memset(e, 0x3f, sizeof(e));
for (int i = 0; i < m; i++)
{
    int from, to, w;
    cin >> from >> to >> w;
    e[from][to] = min(w, e[from][to]);
    e[to][from] = min(w, e[to][from]);  //无向边时
}
for (int i = 1; i <= n; i++)
{
    e[i][i] = 0;
}

邻接表

将二维数组改为动态数组(如vector),节省不存在的边的空间。如果需要存储权,则可以创建一个结构体。

struct edge
{
    int to, w;
}
vector<edge> e[MAXN];
for (int i = 0; i < m; i++)
{
    int from, to, w
    cin >> from >> to >> w;
    e[from].push_back({to, w});
    e[to].push_back({from, w});
}

链式前向星

与动态数组不同的是,链式前向星类似于链表。有一个存储第一条边的数组,存储所有起始点的第一条边,后面的边通过地址逐个连接。每次添加新边时,插入在第一条边前,使新边成为第一条边。

struct edge
{
    int to, w, next;
}
int head[MAXN];
edge e[MAXM];
int edgeCount = 0;
memset(head, 0, sizeof(head));
for (int i = 0; i < m; i++)
{
    int from, to, w
    cin >> from >> to >> w;
    e[++edgeCount].to = to;
    e[++edgeCount].w = w;
    e[edgeCount].next = head[from];
    head[from] = edgeCount;
}

求最短路径的算法

Floyd

运用了动态规划的思想。
从A到B的最短距离的计算方法:逐个将所有点(记作K点)作为间断点,A到B的最短距离是A点到K点距离+B点到K点之和的最小值。
状态转移方程:d[i, j] = min{d[i, k] + d[k, j], d[i, j]}
优点:能计算任意两点最短距离;可以计算负权值。
缺点:时间复杂度高。

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][k] + d[k][j], d[i][j]);
        }
    }
}

Dijkstra

运用了贪心的思想。
定义一个集合,将起点放入集合。从起点开始,选择与起点相连的最近的点,将其加入集合。从这个点开始,更新相连的不在集合的点的距离为 这个点的距离加两点距离 和 相连的点距离 的较小值,选择不在集合的最近的点加入集合,并再从这个点开始重复操作,直到所有点都加入集合,即可得到起点到所有点的最小距离。

struct edge
{
    int to, w, next;
}
bool vis[MAXN];
int d[MAXN];
memset(vis, 0, sizeof(vis));
memset(d, 0x3f, sizeof(d));
vis[s] = true;
dis[s] = 0;
for (int i = 1; i <= n; i++)
{
    int next = 0;
    for (int j = 1; j <= n; j++)
    {
        if (vis[e[i].to])
        {
            continue;
        }
        if (next == 0 || d[j] < d[next])
        {
            next = i;
        }
    }
    vis[next] = true;
    for (int j = head[next]; j != 0; j = e[j].next)
    {
        d[e[j].to] = min(d[next] + e[j].w, d[e[j].to]);
    }
}

优化:找到最小值的过程时间复杂度较高。可以用优先队列来优化。每次将所有相连的不在集合的点加入队列,按距离排序,下一次进行时选择队列顶部(也就是距离最小的点)进行下一次操作。

struct edge
{
    int to, w, next;
    bool operator<(const edge& e) const
    {
        return w > e.w;
    }
}
prioriy_queue<edge> q;
bool vis[MAXN];
int d[MAXN];
memset(vis, 0, sizeof(vis));
memset(d, 0x3f, sizeof(d));
q.push({s, 0, 0});
while (!q.empty())
{
    int k = q.top().to;
    q.pop();
    if (vis[k])
    {
        continue;
    }
    vis[k] = true;
    for (int i = head[k]; i != 0; i = e[k].next)
    {
        d[e[i].to] = min(e[i].w + d[k], de[e[i].to])
        if (!vis[e[i].to])
        {
            q.push({e[i].to, d[e[i].to], 0});
        }
    }
}

缺点:只能求解非负权值的问题。

第一题

题目链接

题目1

分别用dfs和bfs遍历图

思路1

题目要求遍历顺序按照序号大小,因此可以使用邻接表,然后对其排序。

代码1

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
#define MAXN 100001
#define MAXM 1000001

//邻接表
vector<int> e[MAXN];
//dfs和bfs标记是否访问过的数组
bool dfsvis[MAXN], bfsvis[MAXN];

//dfs
void dfs(int p)
{
    for (int i = 0; i < (int)e[p].size(); i++)
    {
        if (!dfsvis[e[p][i]])
        {
            dfsvis[e[p][i]] = true;
            cout << e[p][i] << " ";
            dfs(e[p][i]);
        }
    }
}

//bfs的队列
queue<int> q;
//bfs
void bfs(int p)
{
    while (!q.empty())
    {
        int p = q.front();
        q.pop();
        for (int i = 0; i < (int)e[p].size(); i++)
        {
            if (!bfsvis[e[p][i]])
            {
                bfsvis[e[p][i]] = true;
                cout << e[p][i] << " ";
                q.push(e[p][i]);
            }
        }
    }
}

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; i++)
    {
        int from, to;
        cin >> from >> to;
        //有向图
        e[from].push_back(to);
    }

    //对邻接表排序,保证每次访问的顺序都是从小到大
    for (int i = 1; i <= n; i++)
    {
        sort(e[i].begin(), e[i].end());
    }

    //dfs
    dfsvis[1] = true;
    cout << 1 << " ";
    dfs(1);

    cout << endl;

    //bfs
    bfsvis[1] = true;
    cout << 1 << " ";
    q.push(1);
    bfs(1);

    return 0;
}

第二题

题目链接

题目2

求一个无向图中所有点到其他点的距离之和。

思路2

由于需要求解所有点之间最短距离,可以使用Floyd算法。

代码2

#include<iostream>
#include<cstring>
using namespace std;
#define MAXN 501
#define MAXM 10001
#define MOD 998244354
int e[MAXN][MAXN];
int main()
{
    int n, m;
    cin >> n >> m;
    //使用邻接矩阵存储
    memset(e, 0x3f, sizeof(e));
    for (int i = 0; i < m; i++)
    {
        int from, to, l;
        cin >> from >> to >> l;
        e[from][to] = min(l, e[from][to]);
        //无向图
        e[to][from] = min(l, e[to][from]);
    }
    for (int i = 1; i <=n; i++)
    {
        e[i][i] = 0;
    }
    //Floyd模板
    for (int k = 1; k <= n; k++)
    {
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                e[i][j] = min(e[i][k] + e[k][j], e[i][j]);
            }
        }
    }
    //计算和并输出
    for (int i = 1; i <= n; i++)
    {
        int dis = 0;
        for (int j = 1; j <= n; j++)
        {
            dis += e[i][j];
            dis %= MOD;
        }
        cout << dis << endl;
    }
    return 0;
}

第三题

题目链接

题目3

给一个有向边的带非负权图和一个起点,求到图中其他点的距离。

思路3

由于是非负权,可以使用Dijkstra算法。

代码3

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
#define MAXN 100001
#define MAXM 200002

//使用链式前向星
//边的结构体
struct edge
{
    //下一条边、这条边的另一个端点、距离
    int next, to, d;
    //用于优先队列排序
    bool operator<(const edge& another) const
    {
        return d > another.d;
    }
};
//所有边
edge e[MAXM];
//链式前向星头、边计数(用于添加)
int head[MAXN], edgeCount = 0;
//Dijkstra的是否在集合中
bool vis[MAXN];
//存储距离
int dis[MAXN];

//添加边
//参数:起点、终点、距离
void add(int from, int to, int d)
{
    e[++edgeCount].to = to;
    e[edgeCount].next = head[from];
    e[edgeCount].d = d;
    head[from] = edgeCount;
}
//优先队列(Dijkstra的优化)
priority_queue<edge> q;

int main()
{
    int n, m, s;
    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++)
    {
        int from, to, d;
        cin >> from >> to >> d;
        add(from, to, d);
    }
    //初始化所有距离无穷远
    memset(dis, 0x3f, sizeof(dis));
    //起点距离为0
    dis[s] = 0;
    //把起点放入优先队列
    q.push({0, s, 0});
    //Dijkstra
    while (!q.empty())
    {
        //点
        int k = q.top().to;
        q.pop();
        //在集合中就不再处理
        if (vis[k])
        {
            continue;
        }
        //标记再集合中
        vis[k] = true;
        //遍历所有相连的点
        for (int i = head[k]; i != 0; i = e[i].next)
        {
            //更新距离
            int to = e[i].to;
            dis[to] = min(dis[k] + e[i].d, dis[to]);
            //放入队列
            if (!vis[to])
            {
                q.push({0, to, dis[to]});
            }
        }
    }
    
    for (int i = 1; i <= n; i++)
    {
        cout << dis[i] << ' ';
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值