Мы используем cookies
все статьи

Часть 1. Как навигационные приложения прокладывают кратчайший путь?

Кто-нибудь задумывался, каким образом современные навигационные приложения по типу 2GIS, Google Maps или же Яндекс.Карты прокладывают маршруты из точки A в точку B? При этом такие приложения пытают ...

Кто-нибудь задумывался, каким образом современные навигационные приложения по типу 2GIS, Google Maps или же Яндекс.Карты прокладывают маршруты из точки A в точку B? При этом такие приложения пытаются выбрать для вас наиболее кратчайший маршрут. В этой статье я расскажу основные алгоритмы, на которых базируются все эти приложения при построении каких либо маршрутов. И так, начнем. 

В большинстве своем навигационные системы для построения маршрутов используют такую структуру данных, как “граф”. Коротко говоря, Граф - это структура данных, описывающая отношения между объектами с помощью узлов и ребер. Узел может быть напрямую соединен с несколькими другими узлами. Эти узлы называются соседями. Существуют два вида графа. Это направленный и ненаправленный. 

В направленном графе отношения действуют только в одну сторону. В данном примере узел B является соседом А, но узел А не является соседом для узла B. В ненаправленном графе стрелок нет, и каждый из узлов является соседом по отношению друг к другу. В принципе, это все, что нужно знать о графах. 

И так, давайте продолжим. Предположим, что у нас есть два узла: A и B, пусть это будет наш Дом и Работа, соответственно. Допустим, вы находитесь Дома и хотите добраться до Работы. Вы намереваетесь доехать на автобусе с минимальным количеством пересадок. Давайте попробуем визуализировать полученную информацию в виде графа. Возможны следующие варианты: 

Задача такого типа является задачей кратчайшего пути. Часто требуется найти некий кратчайший путь: путь к дому вашего друга, путь к победе в шахматной партии (за наименьшее количество ходов) и т.д. Алгоритм для решения задачи поиска кратчайшего пути называется - поиском в ширину. 

В нашем случае кратчайший путь определяется наименьшим количеством пересадок. Возможно ли добраться до узла B без пересадок? Увы, нет. Без пересадок мы доберемся только до узлов C и D. 

А с одной пересадкой? Да, маршрут с одной пересадкой выглядит следующим образом: 

Будем считать, что узлы до которых можно добраться за 1 ход - это связи первого уровня, узлы до которых можно добраться за 2 хода - связи второго уровня и т.д. 

Связи первого уровня предпочтительнее связей второго уровня, связи второго уровня предпочтительнее связей третьего уровня и т.д. Отсюда следует, что поиск по узлам второго уровня не должен производиться, пока мы не обойдем все узлы первого уровня и не убедимся, что за 1 ход невозможно добраться до конечной точки B. 

Также это можно объяснить тем, что связи первого уровня добавляются в поиск раньше связей второго уровня. Для реализации этого подхода отлично подходят Очереди. Очередь относится к категории структур данных FIFO (First In, First Out). 

Заметьте, что узел B является одновременно узлом второго уровня и узлом третьего уровня. Это происходит из-за того, что до него можно добраться за различное количество ходов. 

Давайте перейдем к реализации алгоритма поиска в ширину. В первую очередь нам необходимо связать все узлы со всеми их соседями. Для этого отлично подходят хеш-таблицы. Я буду писать на языке JS, т.к. он более распространенный и интуитивно понятный: 

const graph = { 

A: ['C', 'D'], 

B: [], 

C: ['B'], 

D: ['E'],

E: ['B'], 

} 

После этого нам необходимо создать очередь. Так как в JS нет специального объекта, предназначенного для создания очередей, как это, например, реализовано в Python или Java, я буду использовать обычный массив и его методы. Ну и теперь мы можем реализовать сам алгоритм:

const node_is_endpoint = (node) => { 

    if (node === 'B') { <------ there can be any check here 

        return true 

    } 

    return false 

} 

const search = (graph, startNode) => { 

    const search_queue = [] <------ create a queue 

    search_queue.unshift(...graph[startNode]) <------ add the start node to the queue

    const searched = [] <------- checked nodes do not need to be checked again.

    while (search_queue.length) { 

        const node = search_queue.shift() <------- remove the first node from the queue

        if (!searched.includes(node)) { 

            if (node_is_endpoint(node)) { <------- check if this is endpoint 

                console.log(`We are on place!`) 

                return true 

            } else { 

                search_queue.push(...graph[node]) <------- add all neighbors of this node            

                searched.push(node) 

            } 

        } 

    } 

    return false 

} 

Вот и все, мы реализовали алгоритм поиска в ширину. 

P.S. Но что, если наш граф будет выглядеть вот так? 

Ох, а точно ли этот путь является кратчайшим? И никакой поиск в ширину нам тут уже не поможет. На помощь решения такой задачи существует алгоритм Дейкстры. Но разберем мы его в следующей части.

Pavel P.
Pavel P.
September 14, 2022#tech
другие статьи
Light and dark theme with CSS variables

Сегодня современное веб-приложение (и не только веб), кот...

читать
читать
Fear of public speaking. How to overcome?

Страх публичных выступлений — это вторая по популярности ...

читать
читать
Top-3 libraries for animation in React

Создать анимацию в React можно многими способами. Наприме...

читать
читать
Questions that you should ask the employer at the interview

Прежде всего нужно понимать, что собеседование - это не д...

читать
читать