You intrigued me too much with the task, I had to solve it :) However, if I had information in the database, I was lazy to do it in php and I decided to do it entirely in MySQL. At the same time I learned how to create procedures. I did it quickly, for real work, you need to slightly revise the policy of creating new records in the query table and deleting them when changes are made in the distance table. But first things first ...
Base structure:
create table distances( /* Ваша таблица дистанций */ city_1 varchar(4) not null, city_2 varchar(4) not null, dist_km int not null, time_mins int not null, primary key(city_1,city_2) ); insert into distances(city_1,city_2,dist_km,time_mins) values ('A','B',80,40),('A','C',110,55),('A','E',330,240),('B','F',340,230), ('C','D',60,20),('C','E',205,80),('D','F',192,110),('E','F',80,40),('X','A',400,320); create table dquery( /* Таблица запросов. При каждом поиске маршрута сюда добавляется запись */ /* При реальной эксплуатации стоит добавлять запись, только если еще не делали поиск для данной точки отправления и метрики*/ /* И выбирать существующую - если делали */ /* при изменениях в таблице дистанций - удалять все поиски */ qid int not null auto_increment, city varchar(4) not null, /* Маршруты из этого города */ mtype char(1) default 'D' not null, /* Метрика: D - Дистанции, T - время в пути*/ primary key(qid) ); create table vertex( /* Все узлы, до которых можно добраться из пункта запроса*/ qid int not null, /* ID запроса */ vcity varchar(4) not null, /* Город назначения */ metric int, /* Лучшая метрика */ closed char(1) default 'N' not null, /* Узел обработан */ route varchar(1000), /* Лучший маршрут */ primary key (qid,vcity) );
The procedure for finding routes on the Dijkstra algorithm :
drop procedure if exists deykstra; delimiter $$ create procedure deykstra(p_city varchar(4),p_type char(1)) /* Параметры: город для которого ищем маршруты, тип метрики (расстояние/время) */ begin declare m_qid int; /* Текущий запрос */ declare m_city varchar(4); /* Город проверяемого узла */ declare m_metric int; /* Текущая метрика от пункта A до текущего узла */ declare m_route varchar(1000); /* Маршрут от пункта А до текущего узла */ declare numrows int; /* кол-во записей (флаг завершения работы */ insert into dquery(city,mtype) values(p_city,p_type); /* Вставляем запись запроса */ SELECT LAST_INSERT_ID() into m_qid; /* Сохраняем ее ID */ -- Вставляем запись для начального узла insert into vertex values(m_qid,p_city,0,'N',p_city); main: LOOP /* Главный и единственный цикл */ select vcity, metric, route, count(1) /* Получаем очередной рабочий узел */ into m_city,m_metric,m_route,numrows from vertex where qid=m_qid and closed='N' limit 1; IF numrows=0 THEN /* Если узлов больше нет - завершаем работу */ LEAVE main; END IF; /* Вставляем вновь обнаруженные узлы и меняем метрику и маршрут у существующих узлов */ /* с большей метрикой, чем найденная */ insert into vertex(qid,vcity,metric,route) select m_qid,A.city,A.metric,concat(m_route,',',A.city) /* К маршруту до текущего узла, добавляем следующий город */ from (select if(city_1=m_city,city_2,city_1) city, if(p_type='D',dist_km,time_mins)+m_metric metric /* Метрика до текущего узла + метрика из него до следующего */ from distances where (city_1=m_city or city_2=m_city) ) A where not exists( select 1 from vertex V /* только записи для которых нет уже известной, лучшей метрики */ where V.qid=m_qid and V.vcity=A.city and V.metric<=A.metric ) on duplicate key update metric=A.metric,route=concat(m_route,',',A.city); /* Помечаем текущий узел пройденным */ update vertex set closed='Y' where qid=m_qid and vcity=m_city; END LOOP main; select * from vertex where qid=m_qid; /* Возвращаем набор всех найденных узлов */ end$$
Since to search for the optimal route, you need to go through all the nodes of the network and save metrics for all nodes passed, there is no point in looking for a route from point A to point B. It is much more convenient to save all the optimal routes from point A to all reachable points. And then from this plate to choose routes as needed.
The algorithm turned out even easier than I expected initially. No recursions or complex structures. One cycle that selects a node that has not yet been processed from the node table and adds to it all the newly found ones. Passed the node, put a mark that would not return more to him. If you implement in php, then instead of my vertex
table you will need an array of records with a similar structure, except that qid can be removed.
Results of work:
Из пункта A с метрикой "дистанция": mysql> call deykstra('A','D'); +-----+-------+--------+--------+---------+ | qid | vcity | metric | closed | route | +-----+-------+--------+--------+---------+ | 8 | A | 0 | Y | A | | 8 | B | 80 | Y | A,B | | 8 | C | 110 | Y | A,C | | 8 | D | 170 | Y | A,C,D | | 8 | E | 315 | Y | A,C,E | | 8 | F | 362 | Y | A,C,D,F | | 8 | X | 400 | Y | A,X | +-----+-------+--------+--------+---------+ Из пункта A с метрикой "время": mysql> call deykstra('A','T'); +-----+-------+--------+--------+---------+ | qid | vcity | metric | closed | route | +-----+-------+--------+--------+---------+ | 10 | A | 0 | Y | A | | 10 | B | 40 | Y | A,B | | 10 | C | 55 | Y | A,C | | 10 | D | 75 | Y | A,C,D | | 10 | E | 135 | Y | A,C,E | | 10 | F | 175 | Y | A,C,E,F | | 10 | X | 320 | Y | A,X | +-----+-------+--------+--------+---------+
And to solve the problem of finding all paths in php, I think it’s worthwhile to do a not heavily nested array, for example, an array with keys in the form of paths, as I did in the tables, separated by a comma or another separator. Then, going recursively, you will always see the route passed and not return to the points where you already were, simply by checking the presence of a point in the current path.
PHP code:
<?php $input=array(array("city_1"=>'A',"city_2"=>'B',"distance_km"=>80,"time_mins"=>40),array("city_1"=>'A',"city_2"=>'C',"distance_km"=>110,"time_mins"=>55),array("city_1"=>'A',"city_2"=>'E',"distance_km"=>330,"time_mins"=>240),array("city_1"=>'B',"city_2"=>'F',"distance_km"=>340,"time_mins"=>230),array("city_1"=>'C',"city_2"=>'D',"distance_km"=>60,"time_mins"=>20),array("city_1"=>'C',"city_2"=>'E',"distance_km"=>205,"time_mins"=>80), array("city_1"=>'D',"city_2"=>'F',"distance_km"=>192,"time_mins"=>110), array("city_1"=>'E',"city_2"=>'F',"distance_km"=>80,"time_mins"=>40)); $dist=array(); foreach($input as $node) { // Строим массив дистанций для быстрого поиска [A][B]=80; [B][A]=80; $dist[$node['city_1']][$node['city_2']]=$node['distance_km']; $dist[$node['city_2']][$node['city_1']]=$node['distance_km']; } $result=Deykstra($dist,'A'); print "Route from A to F ".$result['F']['route']." distance ".$result['F']['metric']."\n"; function Deykstra($dist,$from) { $M=array(array("vert"=>$from, "metric"=>0, "route"=>$from)); // Массив вершин с стартовой вершиной $S=array($from=>0); // Массив номеров элементов в массиве вершин по кодам городов for($i=0;$i<count($M);$i++) // Перебираем все вершины { // Данные проверяемой вершины Код, Маршрут, Метрика $v1=$M[$i]['vert']; $route=$M[$i]['route']; $metric=$M[$i]['metric']; foreach($dist[$M[$i]['vert']] as $v2=>$m2) { // Перебираем все вершины до которых можно добраться напрямую if(!array_key_exists($v2,$S)) // Вершина назначения еще не встречалась { $S[$v2]=count($M); // Добавляем индекс по коду $M[]=array("vert"=>$v2, "metric"=>($metric+$m2), "route"=>"$route/$v2"); // И саму вершину } else { // Вершина уже встречалась, пересчитываем метрику, если необходимо $ind=$S[$v2]; if($M[$ind]['metric']>($metric+$m2)) // Метрика вершины больше текущей { // обновляем метрику и маршрут до вершины $M[$ind]['metric']=$metric+$m2; $M[$ind]['route']="$route/$v2"; } } } } // Из массива по номерам, делаем по именам городов foreach($S as $key=>$ind) $S[$key]=$M[$ind]; return $S; }