<НазадВзвешенные графы
12/26/2022В прошлой публикации мы познакомились с общим определением графа, его реализацией в виде матрицы смежности и алгоритмом поиска в ширину. Этот алгоритм хороший, но он не будет работать для взвешенных графов.
Важно: В этой статье я буду расширять функционал матрицы смежности из прошлой статьи. Если вы еще не знакомы с кодом, то можно это сделать здесь.
В этой статье мы познакомимся со взвешенными графами, алгоритмом Дейкстры и его реализацией на языке программирования Rust.
Что такое взвешенный граф?
Взвешенный граф - это такой граф, у которого с каждым ребром связывается число, называемое весом.
Вот как он выглядит:
Для поиска оптимального пути в таких графах используется алгоритм Дейкстры.
Алгоритм Дейкстры
Марк проспал, и он опаздывает на работу. Ему нужна помощь в выборе быстрого маршрута до работы. Вот так выглядит граф этой задачи:
Самый оптимальный маршрут выглядит так:
Основное назначение алгоритма Дейкстры - поиск пути с наименьшей стоимостью.
Алгоритм состоит из нескольких шагов:
- Найти узел с наименьшей стоимость.
- Обновить стоимость соседей этого узла.
- Повторять, пока не будут пройдены все узлы.
- Вычислить итоговый путь.
Применим алгоритм к нашей задаче.
Шаг 1:
- Первый наименьший сосед B = 1 мин;
- Стоимость прохождения пути Дом -> B -> С = 3 мин, а Дом -> B -> D = 8 мин.
Шаг 2:
- Следующий узел - A = 2 мин;
- Стоимость прохождения пути Дом -> A -> D = 4 мин, а Дом -> А -> C = 8 мин.
После этих шагов мы получаем следующие данные:
Дом -> B -> C = 3 мин - наиболее оптимальный путь до точки C, запоминаем
Дом -> B -> D = 8 мин
Дом -> A -> D = 4 мин - наиболее оптимальный путь до точки D, запоминаем
Дом -> A -> C = 7 мин
После этих шагов осталось два узла C и D.
Шаг 4:
- Узел с наименьшей стоимостью C = 3 мин;
- Стоимость прохождения пути Дом -> B -> C -> Работа = 13 мин.
Шаг 5:
- Следующий узел - D = 4 мин;
- Стоимость прохождения пути Дом -> A -> D -> Работа = 6 мин.
Получает, что самый быстрый путь до работы - Дом -> A -> D.
ВАЖНО: Ребра взвешенного графа могут быть с отрицательными весами. Для таких графов алгоритм Дейкстры не работает, но существует алгоритм Беллмана-Форда, который поможет решить задачу.
А теперь перейдем к реализации нашего алгоритма.
Реализация
Добавим для структуры Matrix добавим функцию, которая будет вычислять индекс узла с минимальным весом.
use std::i32::MAX; impl Matrix { pub fn min_distance(&self, dists: &mut Vec<i32>, done: &mut Vec<bool>) -> usize { let mut min = MAX; let mut min_node = 0; for v in 0..self.n { if !done[v] && dists[v] <= min { min = dists[v]; min_node = v; } } return min_node; } }
Теперь опишем базовую версию алгоритма, которая вычисляет оптимальная стоимость для каждого узла графа. В dist
будем хранить оптимальные стоимость, где индекс - это номер узла. В векторе done
храним информацию о пройденных узлах.
impl Matrix { pub fn dijsktra(&self, start_node: usize) { let mut dists: Vec<i32> = vec![MAX; self.n]; let mut done: Vec<bool> = vec![false; self.n]; dists[start_node] = 0; for _ in 0..(self.n - 1) { let min_node = self.min_distance(&mut dists, &mut done); done[min_node] = true; for adj_node in 0..self.n { let not_zero = self[min_node][adj_node] != 0; let new_dist = dists[min_node] + self[min_node][adj_node]; if !done[adj_node] && not_zero && dists[min_node] != MAX && new_dist < dists[adj_node] { path[adj_node] = min_node; dists[adj_node] = new_dist; } } } println!("Distances: {:?}", dists); } }
Хочется, чтобы алгоритм выводил оптимальный путь и стоимость для конкретного узла. Давайте модернизируем алгоритм. Создадим вектор path
, индекс которого соответствует узлу, а значение по индексу предшествующему оптимальному узлу. Еще на входе алгоритма будем принимать индекс интересующего нас узла.
pub fn dijsktra(&self, start_node: usize, target_node: usize) -> Option<(i32, Vec<usize>)> { let mut dists: Vec<i32> = vec![MAX; self.n]; let mut done: Vec<bool> = vec![false; self.n]; dists[start_node] = 0; let mut path: Vec<usize> = vec![0; self.n]; for _ in 0..(self.n - 1) { let min_node = self.min_distance(&mut dists, &mut done); done[min_node] = true; for adj_node in 0..self.n { let not_zero = self[min_node][adj_node] != 0; let new_dist = dists[min_node] + self[min_node][adj_node]; if !done[adj_node] && not_zero && dists[min_node] != MAX && new_dist < dists[adj_node] { path[adj_node] = min_node; dists[adj_node] = new_dist; } } } if dists[target_node] == MAX { return None; } let mut target_path = path[target_node]; let mut final_path = vec![target_node]; while !final_path.contains(&0) { final_path.push(target_path); target_path = path[target_path]; } final_path.reverse(); return Some((dists[target_node], final_path)); } }
Теперь самое время протестировать алгоритм на нашей задаче.
fn main() { // Node 0 - Home // Node 1 - A // Node 2 - B // Node 3 - C // Node 4 - D // Node 5 - Work let mut graph: Matrix = Matrix::new(6, 0); graph.add_w_directed_edge(0, 1, 2); graph.add_w_directed_edge(0, 2, 1); graph.add_w_directed_edge(1, 3, 6); graph.add_w_directed_edge(1, 4, 2); graph.add_w_directed_edge(2, 3, 2); graph.add_w_directed_edge(2, 4, 7); graph.add_w_directed_edge(3, 5, 10); graph.add_w_directed_edge(4, 5, 2); let res = graph.dijsktra(0, 5); if res.is_some() { let res = res.unwrap(); println!("Weights sum: {:?}", res.0); println!("Path: {:?}", res.1); } } На выходе: Weights sum: 6 Path: [0, 1, 4, 5]
Самое свежее
Состав команды разработки
7/6/2023В этой статье мы рассмотрим состав команды разработки ИТ решения
Простыми словами о графах
12/18/2022В этой статье мы начнем знакомство с графами, познакомимся с одним из алгоритмов для работы с графами и реализуем граф на языке программирования Rust.
В чем отличие аутсорсинга разработки от аутстаффинга ИТ-сотрудника для разработки?
10/17/2022В этой статье разберемся, что такое аутсорс- и аутстафф-разработка.
UI/UX дизайн: Процесс создания
4/9/2023В этой статье поговорим об основных шагах в процессе создания UI/UX дизайна.
UI/UX дизайн: Введение
3/29/2023В этой статье мы начинаем знакомиться с UI/UX дизайном. Это важнейший этап в разработке любого визуального интерфейса приложений.
Agile, Шесть сигм и Отсутствие принципа
9/14/2023В прошлой статье мы начали погружение в процесс разработки. Первый этап этого процесса — планирование. На этом этапе проектный менеджер вместе с другими участниками команды формирует пул задач в соответсвии с какой-то методологией ведения проектов.
Знакомьтесь, Пентест
8/22/2022Начинаем рассматривать один из основных методов оценки безопасности компьютерных систем и сетей на предмет потенциальных уязвимостей - тестирование на проникновение
Сокращаем срок реализации MVP
12/8/2022Разберемся со сроками реализации MVP.
Выбираем язык программирования
3/17/2023В этой статье мы поговорим о выборе языка программирования для изучения
Тестирование концепции MVP
1/9/2023Разбираемся с тем, как не потратить бюджеты на разработку MVP впустую
Проектирование архитектуры приложений: Введение
3/6/2023В этой статье поговорим о процессе создания архитектуры ИТ-решения
Техническое задание: Структура
2/17/2023В этой публикации мы рассмотрим универсальную структуру ТЗ
Неверная оценка стоимости услуг ИТ подрядчика
9/10/2022Сегодня мы поговорим о неверной оценке стоимости разработки ИТ решений. Эта боль - одна из основных для предприятий и стартапов, включая самих ИТ подрядчиков.
Введение в паттерны проектирования в разработке программного обеспечения
10/3/2022В этой статье мы начнем погружаться в мир оптимизации архитектуры приложений с помощью шаблонов проектирования
Выбираем направление разработки для обучения программированию
2/5/2023В этой статье вы узнаете какие бывают направления разработки, чем они отличаются и в каком больше платят.
Уровни модели OSI
9/6/2022В этой статье мы более подробно рассмотрим каждый из уровней модели OSI
Основные типы архитектуры приложений
3/7/2023В этой публикации разберемся с тем, какие бывают архитектуры приложений
10 способов использования Rust Cargo
2/11/2023В этой небольшой статье я собрал 10 способов использования системы сборки и менеджера пакетов языка программирования Rust
Документирование кода в языке программирования Rust
8/24/2022В этой статье рассмотрим то, как происходит документирование в Rust и рассмотрим очень полезную возможность - написание тестов через документирование.
Знакомство с моделью OSI
8/19/2022В этой статье начинаем рассматривать фундаментальную модель сетевого взаимодействия - OSI
CSS анимация пульсации
8/31/2022Простой пример того, как реализовать анимацию пульсации, используя HTML и CSS
Для чего нужна ER-диаграмма в процессе разработки?
4/28/2023Обсудим в общих чертах, что такое ER-диаграмма и для чего она нужна.
От концепции к MVP
11/18/2022В этой статье вы узнаете, на примере, о том, как перейти от концепции к MVP без лишних усложнений в функционале продукта
Для чего нужны UML диаграммы?
5/23/2023В этой статье мы поговорим о том, что такое UML диаграммы, какие они бывают и где используются
Введение в написание технического задания
1/31/2023Техническое задание - это важная часть процесса разработки. В этой статье начнем погружение в данный вопрос.
Введение в разработку
10/10/2022Сегодня большинство компаний сталкивается с ИТ-разработкой и часто не получают то, чего хотят. В этой статье мы начинаем погружение в процесс создания ИТ-решений.
От идеи к концепции
10/27/2022В этой публикации мы поговорим о том, чем идея отличается от концепции. Сделаем это на примере конкретной цели
Методологии управления ИТ-проектами: Waterfall, Scrum, Prince2
11/27/2023В этой статье рассмотрим основные методологии управления ИТ-проектами.
Процесс разработки: Планирование
8/16/2023В этой публикации мы начнем рассматривать процесс разработки. Начнем рассмотрение с процесса планирования.