路徑搜索 – Dijkstra 算法 (MATLAB實現)


     因為在上一門算法課(https://www.coursera.org/learn/robotics-motion-planning/home/welcome),看了老師的視頻也不明白,又接着百度了一些關於這個算法的說明,各種大神寫得很高深,很多專業術語,超出了我的認知水平,我看了很久也看不懂。

     最后總算明白了一些些,於是,用大白話把它記錄一下,免得下次又忘記了。要真要用這些搜索算法的話,還不如直接調用命令算了:http://www.mathworks.com/help/releases/R2015b/matlab/graph-and-network-algorithms.html

 

路徑搜索問題:

路徑搜索問題,就類似於下圖中,機器人要從綠色起點走到黃色終點,沿什么樣路線過去?

路徑搜索算法 <wbr>- <wbr>Dijkstra算法 <wbr>MATLAB實現

    這是我們從視覺上來理解這個問題,可以沿着紅線走過去。先不說怎么讓機器人自己能能找到這條線,先來說怎么把諸如“地圖”、"線路"、"位置"、“障礙物”等等概念表示成代碼呢?

 

地圖

    說算法之前,看看這個地圖怎么表示。

    首先,把地圖划分為網格,這樣就可以跟矩陣對應起來了。矩陣的行、列就直觀的表示地圖上的每一個位置坐標。

    下面的 ones 命令就生成了一個 10*10 的全 1 陣 map,表示一張 10*10 的地圖。那數字 1 表示什么呢?目前為止,什么也不表示,沒有任何意義。

rows = 10;
ncols = 10;
map = ones(rows, ncols);

     地圖的其它信息,比如起點,終點,障礙物等等,怎么在這個地圖上表示出來呢?那就給 map 矩陣賦不同的值唄,比如我們約定好:空地用 1 表示,牆用 2 表示。

     我們約定好以后,矩陣的值就變得有意義了。將來判斷 map(2,2)~=2,我們就知道 (2,2)這個位置是不是有牆。( 考慮到浮點數判斷的精度問題,將來地圖的數據類型可以設置為整型,之類。)

map (1:5, 7) = 2; % 設置一堵牆 
map(6, 2) = 5; % 設置起點 
map(4, 8) = 6; % 設置終點

    但 map 其實還只是一堆數字,不直觀。所以我們希望能把這個 map 給“畫”出來。比如,我希望空地的1對應畫白色;而牆的2對應畫黑色:

%       R G B
cmap = [1 1 1; ...% 1 - white - clear cell 
        0 0 0; ...% 2 - black - obstacle 
        1 0 0; ...% 3 - red = visited 
        0 0 1; ...% 4 - blue - on list 
        0 1 0; ...% 5 - green - start 
        1 1 0];% 6 - yellow – destination 
colormap(cmap);

    這樣設置之后,map 矩陣由於是 double 類型的,所以它的值為1-6時,畫什么顏色就按照這里配置的顏色映射來定。畫地圖:

image(1.5,1.5,map);   % image 命令畫圖時,對於超出上下限的值,依舊按照上下限對應的顏色來畫。
grid on; 
axis image;

這樣,這張地圖就畫出來了:

image

    最后我們的目的是,要找到這么條黃色的路線:

image

    可以看到圖中還有其他顏色,這是為了表示算法的搜索過程,特意修改了地圖的值來做顯示用。

比如紅色,就是我搜索過的區域,藍色,就是下一步的搜索候選區。

 

算法的簡單版本

這個算法的大概意思就是(發現好難說清楚,對照上圖),

1. 從第一個搜索中心:綠點開始,找到上下左右鄰居,其實就是起點行列坐標i-1,j之類

2. 把找到的鄰居變成藍色(加入下次的搜索備選中心),計算它們到起點的路程(其實就是+1),保存到路程矩陣里;

1’選新的搜索中心(從藍色鄰居里挑 “距離起點路程最近的那個”),找到上下左右鄰居

2’把它的新鄰居也變成藍色,計算新鄰居到起點的路程(在當前搜索中心路程1的基礎上,再+1),保存到路程矩陣里;

1”再從所有藍色鄰居里挑選搜索中心,找到新鄰居

2”把新鄰居變成藍色,計算鄰居到起點的路程,保存到路程矩陣

一直這么循環,總有一天,會一直搜索到終點。

 

PS:用不同的方法從藍色鄰居里來挑選搜索中心,就構成了 Dijkstra 算法和 A* 算法的主要區別。
Dijkstra 里只有一個原則,就是挑選離起點路程最近的,其實就是無方向向外擴散的。而 A*算法還會加上一個跟終點的距離考量,所以就是帶有方向性的擴散。一般情況下,A* 會搜索的快一點。

 

可以看到,這個算法要保存一些信息,所以先定義一些變量。

start_node = sub2ind(size(map), 6, 2);
% 為了方便,把起點和終點的行列下標換成索引,所以 map(6,2)和 map(start_node)就是同一個意思。 
dest_node = sub2ind(size(map), 4, 8);

a. 定義一個變量來保存藍色鄰居以及它們到起始格的路程

所以這里定義了 distanceFromStart  來保存這些信息,初始化為 Inf,表示從沒有訪問過。一旦有值,就說明是藍色鄰居,賦值的大小就表示改點跟起始點的路程。一旦變成紅色,就把它的值再改回 Inf。
至於具體要顯示動態圖,要修改的是 map 的值。

distanceFromStart = Inf(nrows,ncols); 
distanceFromStart(start_node) = 0;

最后,這個矩陣會更新成這個樣子。路程更新到了第4行第8列的終點位置,而且它距離起點有8步。那么這八步是怎么走的呢,就需要有矩陣來保存路線信息。

image

 

b. 定義一個變量來保存路徑。

parent = zeros(nrows,ncols);
 
這個矩陣最后會更新成什么樣,它怎么表示路線呢?
比如下圖,第4行第8列的值是75,就表示它的上家的格子編號是75,其實也就是矩陣的索引位置,按列數過來就是第8列第5行。這里的值是76,就表示它的上家的格子第8列第6行。一次類推,這樣就可以反推出一條線路來了。
image
====================================================
變量定義完了,那么開始循環搜索路徑
while true
先畫一下當前的地圖
map(start_node) = 5; 
map(dest_node) = 6; 
image(1.5, 1.5, map); 
 grid on; axis image; drawnow;

這時就出來了這張圖

image

那么,就開始搜索了,可是以哪個點為搜索中心呢。

當然是從藍色鄰居列表選離初始點最近的那一個點。

因為 distanceFromStart 的初始值是 Inf,已知的只有起始點的路程,為0。所以如果是第一次循環,那min函數得到的current自然就是16了,這也是初始點的索引坐標;這時得到的 min_dist =0。
[min_dist, current] = min(distanceFromStart(:));
%搜索中心的索引坐標:current,
%搜索中心與起始點的路程:min_dist
% 這兩個值后面會用。
 
%這里做一些簡單判斷,如果已經擴張到終點了,或者沒有路徑,則退出循環。
if ((current == dest_node) || isinf(min_dist)) 
    break;
end;

為了畫圖效果,把 map 的前點坐標賦值為 3 ,表示本次循環已經以此為中心搜索一次了。

map(current) = 3;
可以看到這是現在 map 的值,如果畫出來就是這樣:

image

同時把distanceFromStart的這個位置賦值為 Inf,表示它已經當過搜索中心了。
上面的 min 函數將來就不可能再找到這個坐標。
distanceFromStart(current) = Inf;

把搜索中心的鄰居的坐標點找出來,這里只找上下左右的鄰居沒有計算斜角:

% 把索引坐標變成行列坐標,方便計算鄰居的坐標。
[i, j] = ind2sub(size(distanceFromStart), current);
neighbor = [i-1, j ;... 
                      i+1, j ;... 
                      i, j+1 ;... 
                       i, j-1]

所以(6,2)的四個鄰居的坐標計算出來,得到一個四行兩列的結果:

neighbor =

5  2
7  2
6  3
6  1

為了方便,現在把這種行列形式變為索引形式:

neighborIndex = sub2ind(size(map),neighbor(:,1),neighbor(:,2))

neighborIndex =

15
17
26
6

下面就要計算每個鄰居的路程,保存每個鄰居的路線。

for i=1:length(neighborIndex) 
   if (map(neighborIndex(i))~=2) && (map(neighborIndex(i))~=3 && map(neighborIndex(i))~= 5)
          map(neighborIndex(i)) = 4; %  在地圖上把鄰居變成藍色。這里純為了顯示用。

% distanceFromStart這個矩陣初始值是 Inf ,所以第一次找到它的時候肯定會更新路徑的,
% 循環多次以后,可能會有多次機會走到這個鄰居,所以要看哪條路近。
% 只有鄰居已有的路線比“從當前搜索中心走過去”要長的話,才會更新這個鄰居的信息。
           if distanceFromStart(neighborIndex(i))> min_dist + 1
                  distanceFromStart(neighborIndex(i)) = min_dist+1; %更新鄰居的路程信息 
                  parent(neighborIndex(i)) = current; % 更新鄰居的路徑信息
          end 
     end 
end

第一次搜索后,可以看到這四個鄰居的距離值 distanceFromStart,更新為 1 了:

image

然后,這四個鄰居的線路,16 就是表示是從起始點(坐標索引是16)走過來的。

image

map 就變成這樣:
image
end   %這是 while true 對應的end

 

這是第一輪循環,然后再把上面的 while 循環再走一遍。

從之前的代碼可以看到,會先把起始點的顏色重置一下。然后上一輪找到的四個鄰居已經標記為藍色了。

image

同樣,第二次也會先定搜索中心,根據上一次循環得到的 distanceFromStart 矩陣的結果,其實最小值有四個,都是1,Dijkstra算法沒有別的考量權重,所以這里只是按順序,取了第一個 1 作為搜索中心:

[min_dist, current] = min(distanceFromStart(:));
if ((current == dest_node) || isinf(min_dist)) 
   break; 
end; 
map(current) = 3; %把新的搜索中心標記為紅色
distanceFromStart(current) = Inf; %把搜索中心從鄰居去除。
[i, j] = ind2sub(size(distanceFromStart), current);

得到的current就是左邊這個紅點,min_dist就是剛才計算出來的1。

image

所以,你看這時候,搜索中心在邊界,左邊那個(6,0)超出了邊界,就是假鄰居了:

neighbor = [i-1,j;... 
                      i+1,j;... 
                    i,j+1;... 
                   i,j-1]

neighbor =

5  1
7  1
6  2
6  0

需要把它給去掉:

outRangetest = (neighbor(:,1)<1) + (neighbor(:,1)>nrows) +...
                   (neighbor(:,2)<1) + (neighbor(:,2)>ncols ) 
locate = find(outRangetest>0); 
neighbor(locate,:)=[]    % =[]就是刪除的意思。
 

neighbor =

5  1
7  1
6  2

同樣,開始更新每一個鄰居的信息:

neighborIndex = sub2ind(size(map),neighbor(:,1),neighbor(:,2)) 
for i=1:length(neighborIndex) 
  if (map(neighborIndex(i))~=2) && (map(neighborIndex(i))~=3 && map(neighborIndex(i))~= 5)      map(neighborIndex(i)) = 4;
       if distanceFromStart(neighborIndex(i))> min_dist + 1                                    distanceFromStart(neighborIndex(i)) = min_dist+1; 
         parent(neighborIndex(i)) = current; 
    end 
 end 
end

最后,map 更新,紅點就是已經當過搜索中心了,藍點就是全部的待搜索鄰居。

image

下面的 distanceFromStart 就是所有藍點的位置(有數值的地方),以及這些藍點到起始點的路程(數值)。

下一個循環,便接着再找 distanceFromStart 的最小值所在的位置,作為搜索中心。

image

 

可以看到,有1,1就是從起始點1步就走到了這里;2表示從起始點兩步就走到了這里。

那,是沿着哪條線路走過來的呢?

就得對照着看 parent 矩陣的數據了,上面那張圖里的 2 步,就是下圖的箭頭這么走出來的:

6,16表示的是矩陣的索引編號。

image

那么,一直這么循環下去,最后會得到什么呢,也就是這樣圖:

image

最后得到的這個 distancdFromStart 矩陣,可以看出,從起始點到目標點[4,8],需要走8步。

image

那怎么走呢?parent里表示出了路線,按照格子里所顯示的索引坐標,倒回來追溯:

image

image

 

腳本主框架是從課程里拿出來的,算法實現部分我沒考慮代碼效率,只是為了大概理解算法:

%% % set up color map for display 
cmap = [1 1 1; ...% 1 - white - clear cell 
        0 0 0; ...% 2 - black - obstacle 
               1 0 0; ...% 3 - red = visited 
               0 0 1; ...% 4 - blue - on list 
               0 1 0; ...% 5 - green - start 
               1 1 0];% 6 - yellow - destination 
colormap(cmap); 
map = zeros(10); 
% Add an obstacle 
map (1:5, 7) = 2; 
map(6, 2) = 5; % start_coords 
map(4, 8) = 6; % dest_coords 
image(1.5,1.5,map); 
grid on; 
axis image; 
%% 
nrows = 10; 
ncols = 10; 
start_node = sub2ind(size(map), 6, 2); 
dest_node = sub2ind(size(map), 4, 8); 
% Initialize distance array 
distanceFromStart = Inf(nrows,ncols); 
distanceFromStart(start_node) = 0; 
% For each grid cell this array holds the index of its parent 
parent = zeros(nrows,ncols); 
% Main Loop 
while true 
 % Draw current map 
 map(start_node) = 5; 
 map(dest_node) = 6; 
 image(1.5, 1.5, map); 
 grid on; 
 axis image; 
 drawnow; 
  % Find the node with the minimum distance 
 [min_dist, current] = min(distanceFromStart(:)); 
  if ((current == dest_node) || isinf(min_dist)) 
       break; 
  end; 

 map(current) = 3; 
 distanceFromStart(current) = Inf; 
 [i, j] = ind2sub(size(distanceFromStart), current); 
neighbor = [i-1,j;... 
            i+1,j;... 
            i,j+1;... 
            i,j-1] 
outRangetest = (neighbor(:,1)<1) + (neighbor(:,1)>nrows) +...
                   (neighbor(:,2)<1) + (neighbor(:,2)>ncols ) 
locate = find(outRangetest>0); 
neighbor(locate,:)=[] 
neighborIndex = sub2ind(size(map),neighbor(:,1),neighbor(:,2)) 
for i=1:length(neighborIndex) 
if (map(neighborIndex(i))~=2) && (map(neighborIndex(i))~=3 && map(neighborIndex(i))~= 5) 
    map(neighborIndex(i)) = 4; 
  if distanceFromStart(neighborIndex(i))> min_dist + 1      distanceFromStart(neighborIndex(i)) = min_dist+1; 
        parent(neighborIndex(i)) = current; 
  end 
 end 
end 
end
%%
if (isinf(distanceFromStart(dest_node))) 
    route = []; 
else 
    %提取路線坐標
   route = [dest_node]; 
      while (parent(route(1)) ~= 0) 
              route = [parent(route(1)), route]; 
       end 
  % 動態顯示出路線     
        for k = 2:length(route) - 1 
          map(route(k)) = 7; 
                pause(0.1); 
                image(1.5, 1.5, map); 
              grid on; 
              axis image; 
              end 
end


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM