dijkstra算法相关(使用邻接表和优先队列两种方法)力扣题

具体dijkstra算法就不展开说了,因为太多帖子来解释了,并且这也只是我的个人总结/记录,我会把自己的思考过程写在代码的注释中。

743. 网络延迟时间(有向图)

有 n 个网络节点,标记为 1 到 n。

给你一个列表 times,表示信号经过 有向 边的传递时间。 times[i] = (ui, vi, wi),其中 ui 是源节点,vi 是目标节点, wi 是一个信号从源节点传递到目标节点的时间。

现在,从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1 。

在这里插入图片描述

1. 邻接表法

	class Solution {
    
    
public:
    // distances数组 保存:起点k 到其他点的最短距离
    // visited数组 保存:某点是否被访问过了,被访问过则设置为true
    // graph 二维数组:邻接表
    // n : 节点总数
    void dijkstra(vector<vector<long long>>& graph,vector<int>& distances,int n,int k,vector<bool>& visited){
    
    
        distances[k] = 0;// 起始点到起始点自身的距离为0

        // 这里只需要循环 n-1次
        // 原因:除k点以外只有 n-1个点,每循环一次,就更新k点到一个点的距离
        for(int i = 1;i<=n-1;i++){
    
    
            int id = 0,minDistance = INT_MAX;
            for(int j = 1;j<=n;j++){
    
    
                // 这个循环的目的:去找 距离 k点最近的点
                // 原因:可以通过 这个点,去更新 其他点到 k点的距离
                if(!visited[j] && distances[j]<minDistance){
    
    
                    minDistance = distances[j];
                    id = j;
                }
            }
            // 所有点都被访问过了 或者 所有点都不可达
            if(id == 0) return;
            visited[id] = true;

            // 得到一个中间点 id后,比较k->id->某一点的距离 和 k->某一点的距离 
            // 来更新distances数组,注意,这个数组存的是k到某一点的距离
            for(int j = 1;j<=n;j++){
    
    
                if(!visited[j] && distances[id]+graph[id][j]<distances[j]){
    
    
                    distances[j] = distances[id]+graph[id][j];
                }
            }
        }
    }

    int networkDelayTime(vector<vector<int>>& times, int n, int k) {
    
    
        vector<vector<long long>> graph(n+1,vector<long long>(n+1,INT_MAX));
        vector<int> distances(n+1,INT_MAX);
        vector<bool> visited(n+1,false);

        // 建立邻接表
        for(auto t:times){
    
    
            int u = t[0],v = t[1],w = t[2];
            graph[u][v] = w;
        }

        //dijkstra算法
        dijkstra(graph,distances,n,k,visited);

        int ans = 0;
        for(int i = 1;i<=n;i++){
    
    
            if(distances[i] == INT_MAX) return -1;
            ans = max(ans,distances[i]);
        }
        return ans;
    }
};

2. 优先队列

class Solution {
    
    
public:
    void dijktra(vector<vector<int>>& times,vector<int>& dis, int n, int k){
    
    
        dis[k] = 0;
        using Pair = pair<int,int>; // first是距离,second是目标点
        priority_queue<Pair,vector<Pair>, greater<Pair>> pq;

        pq.push({
    
    0,k});

        while(!pq.empty()){
    
    
            auto e = pq.top();
            pq.pop();
            if(e.first>dis[e.second]) continue;
            for(int i = 0;i<times.size();i++){
    
    
                if(times[i][0] == e.second){
    
    
                    int v = times[i][1];
                    // 到v点的的距离
                    int w = e.first+times[i][2];
                    if(dis[v]==-1 || dis[v]>w){
    
    
                        dis[v] = w;
                        pq.emplace(w,v);
                    }
                }
            }
        }
    }

    int networkDelayTime(vector<vector<int>>& times, int n, int k) {
    
    
        vector<int> dis(n+1,-1);

        dijktra(times,dis,n,k);

        int ans = 0;
        for(int i = 1;i<=n;i++){
    
    
            if(dis[i]==-1) return -1;
            ans = max(ans,dis[i]);
        }
        return ans;
    }
};

1334. 阈值距离内邻居最少的城市

有 n 个城市,按从 0 到 n-1 编号。给你一个边数组 edges,其中 edges[i] = [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边,距离阈值是一个整数 distanceThreshold。

返回能通过某些路径到达其他城市数目最少、且路径距离 最大 为 distanceThreshold 的城市。如果有多个这样的城市,则返回编号最大的城市。

注意,连接城市 i 和 j 的路径的距离等于沿该路径的所有边的权重之和。

在这里插入图片描述

1. 邻接表法

class Solution {
    
    
public:
    void Dijkstra(vector<vector<long long>>& graph,vector<int>& distances,vector<bool> &visited,int n,int distanceThreshold,int start){
    
    
        // 自身到自身的距离为0
        distances[start] = 0;

        for(int i = 0;i<n-1;i++){
    
    
            int u = -1,minDis = INT_MAX;
            // 下面这个这个循环的目的:
            // 为了在 和 start 点相连的所有点中找到一个最近的点,再把它设为新的起点
            for(int j = 0;j<n;j++){
    
    
                if(!visited[j] && distances[j] <minDis){
    
    
                    u = j; // 设置新的起点
                    minDis =distances[j];
                }
            }
            // 如果所有点都访问过了 或者 不可达,直接return 即可
            if(u==-1) return;
            // 把新的起点设置为访问过
            visited[u] = true;

            // 接下来更新 start到所有点的 新的 距离
            // 为什么有新的距离?
            // 回答:因为此时我们有了一个新的点,那么从 start点开始到其他点就不止一种直连路线
            // 而是可以借助 刚刚确立好的新的点 ,比如从 start到某点 需要距离为5
            // 而start 到 u 的距离为1,u到某点的距离为2,那么就可以更新新的距离了
            for(int j = 0;j<n;j++){
    
    
                // 注意:仍然需要不去管那些已经访问过的节点
                //因为已经访问过的节点就已经是最短的距离了
                if(!visited[j] && distances[u]+graph[u][j]<distances[j]){
    
    
                    distances[j] = distances[u]+graph[u][j];
                }
            }
        }
    }

    //  使用Dijkstra算法,邻接矩阵版
    int findTheCity(int n, vector<vector<int>>& edges, int distanceThreshold) {
    
    
        vector<vector<long long>> graph(n,vector<long long>(n,INT_MAX));// 邻接矩阵
        for(auto edge:edges){
    
    
            int u = edge[0],v = edge[1],w = edge[2];
            graph[u][v] = graph[v][u] = w;
        }

        // minCount:在指定的阈值内可以 到达节点 的最少数量
        int idx = -1,minCount = INT_MAX;
        for(int i = 0;i<n;i++){
    
    
            vector<int> distances(n,INT_MAX);// 单源最短路径数组
            vector<bool> visited(n,false); // 用来记录某一节点是否被访问过
            // 调用函数可以得到从i点出发到其他各个点的最短距离
            // 这些距离被保存在distances数组中
            Dijkstra(graph,distances,visited,n,distanceThreshold,i);
            int count = 0; // 小于等于阈值的城市个数
            for(int j = 0;j<n;j++){
    
    
                if(i!=j && distances[j] <= distanceThreshold  ){
    
    
                    count++;
                }
            }
            if(count<=minCount){
    
    
                minCount = count;
                // 保存当下这个出发点
                idx = i;
            }
        }
        return idx;
    }
};

2. 优先队列

class Solution {
    
     //优先队列版
public:
    void Dijkstra(vector<vector<int>>& graph, vector<int>& distances, int n, int distanceThreshold, int start) {
    
    
        //小顶堆,按照距离dist从小到大排序,pair中first存dist
        priority_queue <pair<int, int>,vector<pair<int, int>>, greater<pair<int, int>>> q;
        distances[start] = 0;
        q.push({
    
    distances[start],start});
        while (!q.empty()) {
    
    
            pair<int, int>p = q.top();
            int u = p.second;
            q.pop();
            if (distances[u] < p.first) {
    
    
                continue;
            }
            for (int v=0; v<n; ++v) {
    
    
                if (graph[u][v] != INT_MAX && distances[v]>distances[u]+graph[u][v]) {
    
    
                    distances[v]=distances[u]+graph[u][v];
                        q.push({
    
    distances[v],v});
                }
            }
        }
    }
    int findTheCity(int n, vector<vector<int>>& edges, int distanceThreshold) {
    
    
        vector<vector<int>>graph(n,vector<int>(n,INT_MAX)); //邻接矩阵
        for (vector<int> edge : edges) {
    
    
            int u=edge[0], v=edge[1], w=edge[2];
            graph[u][v] = graph[v][u] = w;
        }
        int idx = -1, minCount = INT_MAX;
        for (int i=0; i<n; ++i) {
    
    
            vector<int>distances(n,INT_MAX); //单源最短路径数组
            Dijkstra(graph, distances, n, distanceThreshold, i);
            int count = 0; //小于等于距离阈值的城市个数
            for (int j=0; j<n; ++j) {
    
    
                if (distances[j]<=distanceThreshold && i!=j) {
    
    
                    count++;
                }
            }
            if (count <= minCount) {
    
    
                minCount = count;
                idx = i;
            }
        }
        return idx;
    }
};

1514. 概率最大的路径

给你一个由 n 个节点(下标从 0 开始)组成的无向加权图,该图由一个描述边的列表组成,其中 edges[i] = [a, b] 表示连接节点 a 和 b 的一条无向边,且该边遍历成功的概率为 succProb[i] 。

指定两个节点分别作为起点 start 和终点 end ,请你找出从起点到终点成功概率最大的路径,并返回其成功概率。

如果不存在从 start 到 end 的路径,请 返回 0 。只要答案与标准答案的误差不超过 1e-5 ,就会被视作正确答案。

在这里插入图片描述

在这题中使用领结表会超过内存限制,因此这一题需要一些小的改动,并且也不需要visited数组,在我看来,这一题的解法是dijkstra算法最优的写法。

class Solution {
    
    
public:
    struct cmp{
    
    
        bool operator()(pair<double,int> a,pair<double,int> b)const{
    
    
            return a.first<b.first;// 升序排列
        }
    };
    
    double maxProbability(int n, vector<vector<int>>& edges, vector<double>& succProb, int start_node, int end_node) {
    
    
        vector<vector<pair<double,int>>> graph(n);
        for(int i = 0;i<edges.size();i++){
    
    
            auto &e = edges[i];
            graph[e[0]].emplace_back(succProb[i],e[1]);
            graph[e[1]].emplace_back(succProb[i],e[0]);
        }

        priority_queue<pair<double,int>,vector<pair<double,int>>,cmp> que;

        vector<double> prob(n,0);

        que.emplace(1,start_node);
        prob[start_node] = 1;

        while(!que.empty()){
    
    
            auto [pr,node] = que.top();
            que.pop();
            if(pr<prob[node]) continue;

            for(auto &[prNext,nodeNext]:graph[node]){
    
    
                if(prob[nodeNext]<prNext * prob[node]){
    
    
                    prob[nodeNext] = prNext * prob[node];
                    que.emplace(prob[nodeNext],nodeNext);
                }
            }
        }
        return prob[end_node];
    }
};

最后:

其实dijkstra算法(邻接表版本)对有向图和无向图使用起来都是一样的,没有区别。我之前会想如果我把[0,3,2]中的0和3调换会不会情况不一样,答案是不会的。因为在建邻接表的时候,就把双向的距离给设置好了,如果调换顺序,不过是从另一个点出发,距离仍然一样,并且也不用担心重复访问的问题,因为使用了visited数组来区分。

猜你喜欢

转载自blog.csdn.net/weixin_47505105/article/details/132109250