当青训营遇上码上掘金 | 主题3:寻友之旅

118 阅读3分钟

当青训营遇上码上掘金

本文讲解「青训营 X 码上掘金」主题创作活动入营版主题3:寻友之旅的创作灵感和过程。

题目概述

小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。
步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1
公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)

请帮助小青通知小码,小青最快到达时间是多久?
输入: 两个整数 N 和 K
输出: 小青到小码家所需的最短时间(以分钟为单位)

方法介绍

这是一道典型的已知两点求最短路径问题,使用广度优先搜索算法寻找图的最短路径即可求出答案。

广度优先搜索(Breadth First Search)简称广搜或者 BFS,是遍历图存储结构的一种算法,既适用于无向图(网),也适用于有向图(网)。它的核心思想是:从初始节点开始,应用算符生成第一层节点,检查目标节点是否在这些后继节点中,若没有,再用产生式规则将所有第一层的节点逐一扩展,得到第二层节点,并逐一检查第二层节点中是否包含目标节点。若没有,再用算符逐一扩展第二层的所有节点……,如此依次扩展,检查下去,直到发现目标节点为止。即

⒈从图中的某一顶点V0开始,先访问V0;

⒉访问所有与V0相邻接的顶点V1,V2,......,Vt;

⒊依次访问与V1,V2,......,Vt相邻接的所有未曾访问过的顶点;

⒋循此以往,直至所有的顶点都被访问过为止。

这种搜索的次序体现沿层次向横向扩展的趋势,所以称之为广度优先搜索。

思路讲解

那么在这道题中,可以将小青的起始点作为初始节点V0,在判断小青未一开始就在小码家的情况下,从这里开始遍历,由于总共有两种交通方式(三种行走方式),所以每一层有三个产生式规则,分别产生V+1,V-1,2*V三个点。分别判断这三个点是否已经为小码的家(即已到达),如果均未到达,则对这三个点依次进行下一层的扩展搜索。

一直搜索直到找到小码家,由于是广度优先搜索,必为最短路径。

代码实现

这里使用了两个数据结构,一个是队列queue,用于广度优先搜索中,每一次需要将这一层的节点出队然后依次使下一层的节点入队以实现下一层的遍历;另一个是unordered_map,用于记录层数(即最短路径长),每一个节点入队时,判断其是否已经存在于map中,如果已经存在则说明已经遍历到且有更短的路径,直接跳过该节点;如果不存在则说明还未遍历到,需要记录下当前的层数(即最短路径长),由此可见这个数据结构也起到了避免重复遍历的作用。

如果遍历找到k,直接return答案,否则如果把队列中元素取完还未找到k则return-1表示找不到(正确输入情况下本题不存在这种情况)

代码如下:

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

int bfs(int n, int k)
{
    if(n == k){
        return 0;
    }

    unordered_map<int, int> m;
    queue<int> q;
    m[n] = 0;
    q.push(n);

    while(!q.empty()){
        int t = q.front();
        q.pop();

        if(t-1 == k || t+1 == k || t*2 == k){
            return m[t]+1;
        }

        if(m.find(t-1) == m.end()){
            m[t-1] = m[t] + 1;
            q.push(t-1);
        }
        if(m.find(t+1) == m.end()){
            m[t+1] = m[t] + 1;
            q.push(t+1);
        }
        if(m.find(2*t) == m.end()){
            m[2*t] = m[t] + 1;
            q.push(2*t);
        }
    }
    return -1;
}

int main()
{
    int n,k;
    cin>>n>>k;
    cout<<bfs(n,k);
    return 0;
}