当前位置: 首页 > news >正文

数据结构与算法:Dijkstra算法和分层图最短路

前言

这次的这些题感觉就会比前几篇要简单一点了,大多都是是背模板。

一、Dijkstra算法

Dijkstra算法是用在权值无负数的图上,用来找最短距离的算法。

1.模板——网络延迟时间

class Solution {
public:

    //邻接表建图
    vector<vector<vector<int>>>graph;

    static bool cmp(vector<int>&a,vector<int>&b)
    {
        return a[1]>b[1];
    }

    int networkDelayTime(vector<vector<int>>& times, int n, int k) {
        int m=times.size();

        build(n);

        //建图
        for(int i=0;i<m;i++)
        {
            graph[times[i][0]].push_back({times[i][1],times[i][2]});
        }

        vector<int>distance(n+1,INT_MAX);
        distance[k]=0;
        vector<bool>visited(n+1,false);

        priority_queue<vector<int>,vector<vector<int>>,decltype(&cmp)>heap(cmp);
        heap.push({k,0});

        while(!heap.empty())
        {
            int u=heap.top()[0];
            heap.pop();

            if(!visited[u])
            {
                visited[u]=true;

                for(int i=0;i<graph[u].size();i++)
                {
                    int v=graph[u][i][0];
                    int w=graph[u][i][1];

                    if(!visited[v]&&distance[u]+w<distance[v])
                    {
                        distance[v]=distance[u]+w;
                        heap.push({v,distance[v]});
                    }
                }
            }
        }

        int ans=INT_MIN;
        for(int i=1;i<=n;i++)
        {
            ans=max(ans,distance[i]);
        }
        return ans==INT_MAX?-1:ans;
    }

    void build(int n)
    {
        graph.resize(n+1);
    }
};

Dijkstra算法的过程就是,首先设置distance数组存起点到每个点的最短距离,那么为了每次求最短,所以初始时每个点都设置成无穷大。之后除了还要设置一个visited数组记录来没来过,还要借助一个以distance为排序的小根堆。

之后只要堆不为空,每次取堆顶元素,没来过就去当前节点的所有边看,如果到当前节点的距离加上边权比下一个点的距离更小,即通过这条路能把去下一个节点的距离变得更小,就更新并入堆。

2.模板——【模板】单源最短路径(标准版)

#include<bits/stdc++.h>
using namespace std;

//邻接表建图
vector<vector<vector<int>>>graph; 

static bool cmp(vector<int>&a,vector<int>&b)
{
	return a[1]>b[1];
}

void build(int n)
{
	graph.resize(n+1);
}

void solve(int n,int m,int s,vector<vector<int>>&edges)
{
	build(n);
	
	//建图
	for(int i=0;i<m;i++)
	{
		graph[edges[i][0]].push_back({edges[i][1],edges[i][2]});	
	} 
	
	vector<int>distance(n+1,INT_MAX);
	distance[s]=0;
	vector<bool>visited(n+1,false);
	
	//小根堆 
	priority_queue<vector<int>,vector<vector<int>>,decltype(&cmp)>heap(cmp);
	heap.push({s,0});
	
	while(!heap.empty())
	{
		int u=heap.top()[0];
		heap.pop();
		if(!visited[u])
		{
			visited[u]=true;
			
			for(int i=0;i<graph[u].size();i++)
			{
				int v=graph[u][i][0];
				int w=graph[u][i][1];
				
				if(!visited[v]&&distance[u]+w<distance[v])
				{
					distance[v]=distance[u]+w;
					heap.push({v,distance[v]});
				}
			}
		}
	}
	
	//输出
	for(int i=1;i<=n;i++)
	{
		cout<<distance[i]<<" ";	
	} 
}

void read()
{
	int n,m,s;
	cin>>n>>m>>s;
	vector<vector<int>>edges(m,vector<int>(3));
	for(int i=0;i<m;i++)
	{
		cin>>edges[i][0]>>edges[i][1]>>edges[i][2];
	}
	
	solve(n,m,s,edges);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	read();
	return 0;	
} 

这个题跟上个题一样。

其实观察一下就能发现,在数据结构与算法:宽度优先遍历中提到的01bfs其实就是Dijkstra的特殊情况,而当边权只有0和1时,就可以用双端队列代替小根堆。

3.最小体力消耗路径

class Solution {
public:

    vector<int>move={-1,0,1,0,-1};

    static bool cmp(vector<int>&a,vector<int>&b)
    {
        return a[2]>b[2];
    }

    int minimumEffortPath(vector<vector<int>>& heights) {
        int n=heights.size();
        int m=heights[0].size();

        vector<vector<int>>distacne(n,vector<int>(m,INT_MAX));
        distacne[0][0]=0;
        vector<vector<bool>>visited(n,vector<bool>(m,false));

        priority_queue<vector<int>,vector<vector<int>>,decltype(&cmp)>heap(cmp);
        heap.push({0,0,0});

        while(!heap.empty())
        {
            int x=heap.top()[0];
            int y=heap.top()[1];
            int c=heap.top()[2];
            heap.pop();

            if(!visited[x][y])
            {
                //剪枝
                if(x==n-1&y==m-1)
                {
                    return c;
                }

                visited[x][y]=true;

                for(int i=0;i<4;i++)
                {
                    int nx=x+move[i];
                    int ny=y+move[i+1];

                    if(nx>=0&&nx<n&&ny>=0&&ny<m&&!visited[nx][ny])
                    {
                        int nc=max(c,abs(heights[x][y]-heights[nx][ny]));

                        if(nc<distacne[nx][ny])
                        {
                            distacne[nx][ny]=nc;
                            heap.push({nx,ny,nc});
                        }                   
                    }
                }
            }
        }
                   
        return distacne[n-1][m-1];
    }
};

这个题其实还是Dijkstra的模板题,只是计算距离的方法不同。

所以只需要统计最大的绝对差,即目前为止的绝对差和当前点和下一个点的绝对差的最大值,然后以此为去往下一个点的代价更新即可。

4.水位上升的泳池中游泳

class Solution {
public:

    vector<int>move={-1,0,1,0,-1};

    static bool cmp(vector<int>&a,vector<int>&b)
    {
        return a[2]>b[2];
    }

    int swimInWater(vector<vector<int>>& grid) {
        int n=grid.size();

        vector<vector<int>>distance(n,vector<int>(n,INT_MAX));
        distance[0][0]=grid[0][0];
        vector<vector<bool>>visited(n,vector<bool>(n,false));
        
        priority_queue<vector<int>,vector<vector<int>>,decltype(&cmp)>heap(cmp);
        heap.push({0,0,distance[0][0]});

        while(!heap.empty())
        {
            int x=heap.top()[0];
            int y=heap.top()[1];
            int t=heap.top()[2];
            heap.pop();

            if(!visited[x][y])
            {
                //剪枝
                if(x==n-1&&y==n-1)
                {
                    return t;
                }

                visited[x][y]=true;

                for(int i=0;i<4;i++)
                {
                    int nx=x+move[i];
                    int ny=y+move[i+1];

                    if(nx>=0&&nx<n&&ny>=0&&ny<n&&!visited[nx][ny])
                    {
                        int nt=max(0,grid[nx][ny]-t);

                        if(distance[x][y]+nt<distance[nx][ny])
                        {
                            distance[nx][ny]=distance[x][y]+nt;
                            heap.push({nx,ny,distance[nx][ny]});
                        }
                    }
                }
            }
        }

        return distance[n-1][n-1];
    }
};

这个题其实也是模板,不同的还是统计边权,即去往下一个点的时间为0和下一个点水升上来的时间减去来到当前点的时间取最大值。

二、分层图最短路

当最短距离需要满足其他条件时,可以将来到相同点时不同的已满足条件的情况看作不同的节点。

1.获取所有钥匙的最短路径

class Solution {
public:

    vector<int>move={-1,0,1,0,-1};

    int shortestPathAllKeys(vector<string>& grid) {
        int n=grid.size();
        int m=grid[0].length();

        queue<vector<int>>node;
        int key=0;//用位信息压缩钥匙状态
        
        //初始化
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(grid[i][j]=='@')
                {
                    node.push({i,j,0});
                }
                //是钥匙
                if(grid[i][j]>='a'&&grid[i][j]<='f')
                {
                    key|=1<<(grid[i][j]-'a');
                }
            }
        }

        //初始化visited -> 考虑钥匙状态
        vector<vector<vector<bool>>>visited
        (n,vector<vector<bool>>(m,vector<bool>(key+1,false)));

        int level=0;
        while(!node.empty())
        {
            level++;
            int size=node.size();

            for(int i=0;i<size;i++)
            {
                int x=node.front()[0];
                int y=node.front()[1];
                int k=node.front()[2];
                node.pop();

                for(int j=0;j<4;j++)
                {
                    int nx=x+move[j];
                    int ny=y+move[j+1];
                    int nk=k;

                    if(nx>=0&&nx<n&&ny>=0&&ny<m&&grid[nx][ny]!='#')
                    {
                        //是锁且没钥匙
                        if(grid[nx][ny]>='A'&&grid[nx][ny]<='F'&&
                        (nk&(1<<(grid[nx][ny]-'A')))==0)
                        {
                            continue;
                        }

                        //是钥匙
                        if(grid[nx][ny]>='a'&&grid[nx][ny]<='f')
                        {
                            nk|=1<<(grid[nx][ny]-'a');
                        }

                        //剪枝
                        if(nk==key)
                        {
                            return level;
                        }

                        if(!visited[nx][ny][nk])
                        {
                            visited[nx][ny][nk]=true;
                            node.push({nx,ny,nk});
                        }
                    }
                }
            }
        }

        return -1;
    }
};

这个题除了距离还需要钥匙这个条件,再观察数据范围可以发现钥匙就六个,所以考虑用位信息来压缩已有钥匙的状态。

首先需要遍历格子找出总共的钥匙数和起点位置。由于已有钥匙的状态会看作不同节点,所以visited数组要再升一维。之后去跑宽度优先遍历,注意当有锁但没钥匙时要直接跳过不看,是钥匙的话就更新状态即可。

2.电动车游城市

class Solution {
public:

    //邻接表建图
    vector<vector<vector<int>>>graph;

    static bool cmp(vector<int>&a,vector<int>&b)
    {
        return a[2]>b[2];
    }

    int electricCarPlan(vector<vector<int>>& paths, int cnt, int start, int end, vector<int>& charge) {
        int n=charge.size();
        int m=paths.size();

        //建图 -> 无向图!
        graph.resize(n);
        for(int i=0;i<m;i++)
        {
            graph[paths[i][0]].push_back({paths[i][1],paths[i][2]});
            graph[paths[i][1]].push_back({paths[i][0],paths[i][2]});
        }

        vector<vector<int>>distance(n,vector<int>(cnt+1,INT_MAX));
        distance[start][0]=0;
        vector<vector<bool>>visited(n,vector<bool>(cnt+1,false));

        priority_queue<vector<int>,vector<vector<int>>,decltype(&cmp)>heap(cmp);
        heap.push({start,0,0});//当前点  来到当前点的电量  花费的时间

        while(!heap.empty())
        {
            int cur=heap.top()[0];
            int power=heap.top()[1];
            int time=heap.top()[2];
            heap.pop();

            if(!visited[cur][power])
            {
                //剪枝
                if(cur==end)
                {
                    return time;
                }

                visited[cur][power]=true;

                //能充电 -> 充一格
                if(power<cnt)
                {
                    if(!visited[cur][power+1]&&time+charge[cur]<distance[cur][power+1])
                    {
                        distance[cur][power+1]=time+charge[cur];
                        heap.push({cur,power+1,distance[cur][power+1]});
                    }
                }
                //不充电
                for(int i=0;i<graph[cur].size();i++)
                {
                    int next=graph[cur][i][0];
                    int restPower=power-graph[cur][i][1];
                    int nextTime=time+graph[cur][i][1];

                    if(restPower>=0&&!visited[next][restPower]&&
                    nextTime<distance[next][restPower])
                    //能到且时间更短
                    {
                        distance[next][restPower]=nextTime;
                        heap.push({next,restPower,nextTime});
                    }
                }
            }
        }

        return -1;
    }
};

这个题需要考虑的就是剩余电量,所以每来到一个点都要分充电和不充电两种情况考虑。因为不同的剩余电量属于不同状态,所以每次充电时不用讨论充几格,而是同一只充一格,剩下的留给之后去充。之后根据充电和不充电对应的时间代价去跑Dijkstra即可。

3.飞行路线

#include<bits/stdc++.h>
using namespace std;

static bool cmp(vector<int>&a,vector<int>&b)
{
	return a[2]>b[2];
}

//邻接表建图
vector<vector<vector<int>>>graph;

int solve(int n,int m,int k,int s,int t,vector<vector<int>>&edges)
{
	//建图 -> 无向图! 
	graph.resize(n);
	for(int i=0;i<m;i++)
	{
		graph[edges[i][0]].push_back({edges[i][1],edges[i][2]});
		graph[edges[i][1]].push_back({edges[i][0],edges[i][2]});	
	} 
	
	vector<vector<int>>distance(n,vector<int>(k+1,INT_MAX));
	distance[s][0]=0;
	vector<vector<bool>>visited(n,vector<bool>(k+1,false));
	
	priority_queue<vector<int>,vector<vector<int>>,decltype(&cmp)>heap(cmp); 
	heap.push({s,0,0});
	
	while(!heap.empty())
	{
		int cur=heap.top()[0];
		int use=heap.top()[1];
		int cost=heap.top()[2];
		heap.pop();
		
		if(!visited[cur][use])
		{
			if(cur==t)
			{
				return cost;
			}
			
			visited[cur][use]=true; 
			
			for(int i=0;i<graph[cur].size();i++)
			{
				int next=graph[cur][i][0];
				
				//能用 -> 用一次 
				int nextUse=use+1;
				int nextCost=0;
				if(use<k&&!visited[next][nextUse]&&nextCost+distance[cur][use]<distance[next][nextUse])
				{
					distance[next][nextUse]=nextCost+distance[cur][use];
					heap.push({next,nextUse,distance[next][nextUse]});
				}
				
				//不用
				nextUse=use;
				nextCost=graph[cur][i][1];
				if(!visited[next][nextUse]&&nextCost+distance[cur][use]<distance[next][nextUse])
				{
					distance[next][nextUse]=nextCost+distance[cur][use];
					heap.push({next,nextUse,distance[next][nextUse]});
				}
			}
		}
	}
	
	return -1;
}

void read()
{
	int n,m,k;
	cin>>n>>m>>k;
	int s,t;
	cin>>s>>t;
	vector<vector<int>>edges(m,vector<int>(3));
	for(int i=0;i<m;i++)
	{
		cin>>edges[i][0]>>edges[i][1]>>edges[i][2];
	}
	
	cout<<solve(n,m,k,s,t,edges);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	read();
	return 0;
}

 这个题就是讨论用和不用两种情况即可。

总结

其实这几道题归根结底还是模板,重点是要通过分析题目想到可以用Dijkstra和分层图最短路解决。

END

相关文章:

  • 技术赋能与创新实践:基于低代码平台的高性能应用开发
  • YoloV8训练和平精英人物检测模型
  • Vue动态绑定:文本框、单选按钮、下拉列表、多选按钮
  • Java运行时的堆、栈和方法区
  • 参考apollo3 FLASH样例改写实际应用的接口函数(带磨损均衡处理)
  • 导入 Excel 规则批量修改或删除 PDF 文档内容
  • 【网络】手机PUSH消息发送自建IM通道实现方案
  • 【Pandas】pandas Series to_csv
  • C++学习:六个月从基础到就业——C++基础语法回顾:指针与引用基础
  • 5款视觉OCR开源模型
  • WELL健康建筑认证是什么?
  • 2025年渗透测试面试题总结-某 长亭(题目+回答)
  • [M模拟] lc2711. 对角线上不同值的数量差(对角线遍历+前后缀分解)
  • Python条件处理,新手入门到精通
  • 【系统架构设计师】软件质量管理
  • 常见电子元器件介绍
  • Ollama Embedding模型运行与使用
  • Bluetooth Beacons的介绍和技术实现
  • 基于动态 FOF(基金中的基金)策略的基金交易推荐系统的设计与实现思路
  • 【QT】 布局器
  • 做网站需要准备什么东西/济南竞价托管公司
  • 威海环翠疫情最新消息/seo一般包括哪些内容
  • 做网站需要留什么/本地广告推广平台哪个好
  • 凡科的模板做网站/品牌广告语
  • 北京高端网站建设公司/网络口碑营销名词解释
  • 传奇手游大型网站/搜索引擎营销推广方案