Dijkstra算法求解(单源)最短路径(BFS、贪心策略算法C++)

》》》算法竞赛

/**
 * @file            
 * @author          jUicE_g2R(qq:3406291309)————彬(bin-必应)
 *						一个某双流一大学通信与信息专业大二在读	
 * 
 * @brief           一直在算法竞赛学习的路上
 * 
 * @copyright       2023.8
 * @COPYRIGHT			 原创技术笔记:转载需获得博主本人同意,且需标明转载源
 *
 * @language        C++
 * @Version         1.0还在学习中  
 */
  • UpData Log 2023.8.31 更新进行中

  • Statement0 一起进步

  • Statement1 素质三连,先赞再看

技术提升站点

贪心算法的核心:是只管当前最好(局部最优)

BFS+优先队列Dijkstra算法 在实现时都体现了这个“贪于短期”的思想

Dijkstra算法求解(单源)最短路径(BFS、贪心策略算法C++)_第1张图片

规定从 A 点开始,第一层:将 A 的两个邻居 B、C先后进入优先队列中,基于贪心思想,6(A,B) > 3(A,C),弹出 C点 ;第二层:将 C 的另外两个邻居 D、E也放入优先队列中,比较后 B 更优,依次同理。

  • BFS+优先队列=Dijkstra+优先队列

但是仍有小的区别,在BFS+优先队列中,队列存的是从起点到终点的距离

priority_queue()内元素优先级的设置

(1)基本数据类型的优先级设置

默认情况下,优先级是数值越大优先级越高

priority_queue<int> Q;						  //优先级与数值成正比(即默认情况)
priority_queue<int,vector<int>,less<int>> Q;	//less:成正比	

其中 vector (也就是第二个参数)填写的是来承载底层数据结构堆(heap)的容器,如果第一个参数是double型或char型,则此处只需要填写vector-或vector。

而第三个参数less-则是对第一个参数的比较类, less表示数字大的优先级越大,而greater表示数字小的优先级越大
因此,如果想让优先队列总是把最小的元素放在队首,只需进行如下定义:

priority_queue<char,vector,greater<char>> Q;	//greater:成反比

注:这种是坚决不能用在存储结构体的队列的!!!,亲测报错。

(2)结构体的优先级设置:重载号 ‘<’ 的使用

在这里插入图片描述

拓:关键字 友元friend

打个比方,朋友之间(可以是一个朋友群)是值得信任的,所以可以对他们公开一些自己的隐私(私有成员,可以先简单的认为是私有参数

优点:解决了类与类间的数据访问(无需再通过类提供的接口间接进行)

struct node{
    int id;                 //结点号
    LL n_dis;               //该节点到初始点
	friend bool operator < (node n1,node n2){
        return n1.n_dis < n2.n_dis;}		//成正比
};

若以距离越小(路径最优)的话,需略加修改如下:

return n1.n_dis > n2.n_dis;//成反比

思索过后,会容易地发现,此处重载(降序排列)与排序函数sort中的cmp函数(升序排列)有些相似,它们接收的参数都是两个,函数内部都是returntrue或者false,事实上,这两者的作用确实是类似的只不过效果看上去似乎是“相反”的。

另一种写法是:

bool operator < (const node& bigger) const {
        return this->n_dis > bigger.n_dis;}		//成正比

(3)特殊方法

struct node{
    int id;                 //结点号
    LL n_dis;               //该节点到初始点
};
struct inverse{				   //必须要用struct封装该函数 inverse(使反比)
    bool operator () (node n1,node n2){
    	return n1.n_dis > n2.n_dis;}		//成反比
}

调用的时候只需要将第一种方法的第三个参数(less / greater)换成 函数名 inverse

priority_queue<node,vector<node>,inverse<node>> Q;		//inverse函数

实际上就是抛弃原来 priority_queue 内置的 less() / great()函数不用而自行定义函数实现用户特定功能。

最短路径

要求

给定一个图,求1到其他所有点最短路径

Input

第一行输入n(点的个数),m(边的个数)

接下来的每行输入u(边),v(边),w(边间长)

最后一行输入要到达的元素

4 6
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4

Output

无法到达输出-1,可到达输出到其他每个元素最短路径长(BFS+优先队列要求),路径详情(Dijkstra+优先队列)

11的最短距离为:0
12的最短距离为:2
13的最短距离为:4
14的最短距离为:3
//还有一空行

首先了解一下解该题的一个数据存储芝士:

vector>

vector 的这种用法有点类似于 map

map vector>
会对插入的元素按键自动排序 不会自动排序
不允许重复 允许重复

插入元素时

vector<pair<int,int>> Graph;//声明图
Graph.emplace_back(1,1);//存入
  • 代码展示
#include
using namespace std;
using LL=long long int;
using cla=pair<int,LL>;
const int MAX=3e5+2;
const LL INF=0x3f;                          //使得 条件(INF > INF+x)不成立
vector<cla> G[MAX];                         //G图G[][0]:目标点,G[][1]:边权重
vector<int> Node(MAX);                      //反向存储,配合栈正序输出最短路径
priority_queue<cla,vector<cla>,greater<cla>> Q;
LL dis[MAX];
int pace=0;
void ShortestPath(void){
    vector<bool> found(MAX,false);
    Q.emplace(1,0);                         //起点1到自己的距离为0:to=1,dis=0
    dis[1]=0;
    //found[1]=true;					  
    while(!Q.empty()){
        cla cur_node=Q.top();     Q.pop();
        int cur_id=cur_node.first;
        if(found[cur_id])       
            continue;                       //若此点已确定最短距离则跳过此点并进行下一次循环          
        found[cur_id]=true;                 //本次循环将确定该点的最短距离
        for(std::size_t i=0;i<G[cur_id].size();i++){//查找cur_node的所有邻居(G[cur_id].size()=拥有cur_id的子结点个数)
            cla son_node=G[cur_id][i];      //son_node 是 存入当前元素结点的第i个邻居 的拥有cur_id编号集合的一个子结点
            int near_id=son_node.first;     //查出该邻居的元素值
            int sonTOnear=son_node.second;  //查出两端间边的权重 
            if(found[near_id])   
                continue;                   //丢弃已经在最短路径上的结点
            if(dis[near_id] > dis[cur_id]+sonTOnear){//优化路径(贪心)
                dis[near_id]=dis[cur_id]+sonTOnear;  //更新
                Q.emplace(near_id,dis[near_id]);     //放入优先队列,看看是否有更优的解(贪心)
                Node[near_id]=cur_id;
            }
        }  
    }
}
int main(void){
    int point,edge;                         //端点数,边数
    scanf("%d %d",&point,&edge);
    memset(dis,INF,sizeof(dis));            //初始化距离为不可达,切勿赋初值为-1
    while(edge--){                          //存入两端点间(边)连接情况
        int from,to;    LL cost;
        scanf("%d %d %lld", &from, &to, &cost);
        G[from].emplace_back(to,cost);      //(只需要)单向边
    }
    ShortestPath();
    for(int i=1;i<=point;i++){
        if(dis[i]>=INF)      dis[i]=-1;
        printf("1到%d的最短距离为:%lld\n",i,dis[i]);
    }
    return 0;
}
  • 补充说明 1 :vectoremplaceemplace_back 的使用

emplace 用于一般的容器(动态数组),向尾端(无需拷贝的【注:这是比 push_back 更优的地方】)填充数据

emplace_back 用于已定义数组大小的容器,向指定下标存储填充数据

  • 补充说明 2 :

for(std::size_t i=0;i 其中的 std::size_t 类型名是专门对 .size() 得到返回值的参数的类型定义,否则会遇到warning :comparison of integers of different signs(不同符号整数的比较)

点击参考blog 中对这种warning的介绍

这种情况的发生原因是:G[cur_id].size()无符号整型

所以还有诸如的解决方法:

for (unsigned int i = 0; i<G[cur_id].size(); ++i)
for (vector<int>::size_type i = 0; i<G[cur_id].size(); ++i)
  • 补充说明 3 :可以用下面这种结构体形式存储相关信息
struct edge{                     	//用于存储用户输入的信息
    int from,to;                    //当前起点,当前终点
    LL cost;                        //边上的权值
    edge(int f,int t,LL c){from=f,to=t,cost=c;}//接收参数填充函数
} e[MAX];  */
struct node{                     	//存储最短路径
    int id;                         //结点号
    LL n_dis;                       //该节点到初始点
    node(int i,int d){id=i,n_dis=d;}
    void PrintPath(void){
        cout<< id << ":" << n_dis << " -> ";}//端点打印函数
    friend bool operator < (const Node& bigger) const{
        return n_dis > bigger.n_dis;}
} n[MAX];
priority_queue<node> Q;
  • 补充说明 4 :vector 的用法

1、对容器提前赋予数组大小最好是以这种格式定义 vector Q(大小,全体的初始值) ,这样能够直接对所在下标进行赋值: Q[index]=val;

2、备份容器操作:

vector<int> Q(MAX,0)//主容器
vector<int> save(Q)//备份容器

3、存取一般数组的子序列

int arr[]={0,1,2,3,4};
vector<int> SubArr(arr,arr+2);//arr的子序列,i范围[0,1]!!!
  • 补充说明 5 :

    我们在上述代码里只得到了 BFS+优先队列 需要的 起点1 其他点的最短距离,还有一个任务就是得出实际的最短路径

    if(dis[near_id] > dis[cur_id] + sonTOnear) 我已提前写下 Node[near_id]=cur_id; 用来存储最短路径的结点

    只需要添加一个 PrintPath 函数来进行 递归 (实质是) 实现打印最短路径上结点的功能

void PrintPath(int target){
    if(1==target){
        cout<<1;	return;
    }
    PrintPath(Node[target]);
    cout<<" -> "<<target;
    return;
}
  • 修bug时发现的非常容易犯的逻辑错误

    原来代码有一行注释掉了 //found[1]=true; (这处问题找了很久)这个 bug 就是只输出 0 ,也就只输出了起点的距离,让人误以为是循环while里卡死了。

    其实就是这一句看似严谨的初始化使得循环中的一句 if(found[cur_id]) continue; 成立,就会直接进入下一次循环,而下次循环时,Q中已经没有结点了,直接跳出循环。

    不需要这一句初始化也不会影响,因为 if(found[cur_id]) continue; 不成立后立马就有 found[cur_id]=true; 将其标记。

12-3-2 BFS与双端队列

技术提升站点

你可能感兴趣的:(C++算法,c++,算法,贪心算法,图论)