dj演算法
1. dj怎麼數拍
dj基礎:BPM(beat per minute):BPM是每分鍾幾拍的單位,演算法很簡單,只要將正在聽的歌曲以分鍾為單位,紀錄拍子數即可,可聽個三五次再加以平均。BPM對DJ來說非常重要。算BPM是DJ的基本要求,但是在現在已經有新型機器替DJ代勞這項工作了。CUE POINT :記憶點、暫存點、暗示點都可以。對於將兩首歌銜接在一起,找CUE POINT是學會MIXING的第二門課。
★BPM(beat per minute):BPM是每分鍾幾拍的單位,演算法很簡單,只要將正在聽的歌曲以分鍾為單位,紀錄拍子數即可,可聽個三五次再加以平均。BPM對DJ來說非常重要。算BPM是DJ的基本要求,但是在現在已經有新型機器替DJ代勞這項工作了。
★CUE POINT :記憶點、暫存點、暗示點都可以。對於將兩首歌銜接在一起,找CUE POINT是學會MIXING的第二門課。
★PHRASE or LOOP or SECTION:不斷重復循環的樂句。由16拍或是32拍構成。亦即4個BAR或8個BAR。
★BEAT:BEAT是最基本的單位,它構成BAR,BAR再構成LOOP。耳朵里聽到"咚"就是一拍,"啪"也是一拍。
★BAR:小節,四拍為一小節。8個BAR構成1個LOOP,以此類推。
★THEME:主旋律,由數個LOOP構成。以上所講的幾個名詞是針對流行樂曲的基本單位而言。只要是流行樂節拍數的演算法大抵不脫離以上的范疇,而以HOUSE大宗派下的各分支論,也是同理可證。所以閑來無事聽一首歌時就可以開始在心中默記這首歌的BPM,然後可以想想有那些LOOPS聽起來很特別。而DJ的混音最要緊的就是利用節拍數的一致來銜接兩首歌。這也是基本中的基本。
對於一首流行歌結構有以下幾個名詞需要認識:
★INTRO:歌曲的INTRO就像是電影的開場白,簡簡單單的開始讓你進入狀況,通常不會很繁復,只有大鼓聲加上踏板或者很簡單的一段旋律,要不就是一些SAMPLE來的聲音之類的。聽起來就是很純粹很簡單。也不會太長。
★BREAK(BREAKDOWN):本人不知該怎麼翻譯,姑且稱之為『醞釀』好了,帶入一首歌高潮前的情緒;或者也可以說是暴風雨前的寧靜。醞釀過後一鼓作氣的爆發力把歌帶上高潮。這時候會抽走主歌剩下基本的大鼓還有旋律以及一些BASSLINE,或是一些效果處理過的聲音,一首好歌里BREAK的處理占很重要角色;BREAK帶入BUILD UP接著達到一首歌的CLIMAX。而不接在CLIMAX之前的BREAK(BRIDGE)就稱作是過門或是間奏。並且長度也比較短。
★BODY:一首歌佔大部分時間的主旋律都可以看作BODY。
★CLIMAX:高潮,在BREAKDOWN之後,一首歌的精華,接歌就是要讓聽眾聽到這部分。
★OUTRO or EXIT:歌曲行將完結;該進下首歌的時機。
2. 澶у︾敓鎼炲畾榪欏嚑澶х畻娉曚綘灝辨槸緙栫▼澶т漿
鎼炲畾榪32澶х畻娉曚綘灝辨槸緙栫▼澶т漿
1.A鎼滅儲綆楁硶
2.闆嗘潫鎼滅儲
3.浜屽垎鏌ユ壘
4.鍒嗘敮鐣屽畾綆楀寸洴娉
5.Buchberger綆楁硶
6.鏁版嵁鍘嬬緝
7.瀵嗛掗浜ゆ崲綆楁硶
8.Djk stra綆楁硶
9.紱繪暎寰鍒嗙畻
10.鍔ㄦ佽勫垝綆楁硶
11.嬈у嚑閲屽緱鏅屽洟鍜岀畻娉
12.鏈熸湜鏈澶х畻娉
13.蹇閫熷倕閲屽彾鍙樻崲
14.姊搴︿笅闄
15.鍝堝笇綆楁硶
16.鍫嗘帓搴
17.鐗涢】娉
18.LLL綆楁硶
19.鍚堝苟鎺掑簭
20.涓ゆ$瓫娉曟垨宀
21.BANS AC
22.Karats uoa涔樻硶
23.鏈澶ф祦閲忕畻娉
24.learning瀛︿範綆楁硶
25.RSA
26.Strassen綆楁硶
27.鍗曠函鍨嬬畻娉
28.濂囧紓鐩村垎瑙
29.奼傝В綰挎ф柟紼嬬粍
30.鍚堝苟鏌ユ壘綆楁硶
31.緇寸壒姣旂畻娉
32.Struktur tensor綆楁硶
3. 鏍煎姏絀鴻皟dj鎬庝箞鍥炰簨
鏍煎姏絀鴻皟DJ鏄涓縐嶉泦闊充箰鎾鏀懼櫒鍜岀┖娓楄儭璋冩帶鍒跺櫒浜庝竴涓涜繀鎷︿綋鐨勫壋鏂頒駭鍝併傚畠閲囩敤浜嗗厛榪涚殑鏅鴻兘鎶鏈鍜屼漢宸ユ櫤鑳界畻娉曪紝鑳藉熸牴鎹鐢ㄦ埛鐨勯煶涔愬枩濂藉拰絀鴻皟闇奼傦紝鑷鍔ㄨ皟鑺傜┖璋冪殑娓╁害銆佹箍搴﹀拰椋庨燂紝鍚屾椂鎾鏀劇敤鎴峰枩嬈㈢殑闊充箰銆傝繖涓嶄粎澶уぇ鎻愬崌浜嗙敤鎴鋒槍鍓嶇殑絀鴻皟浣跨敤浣撻獙錛岃繕鑳戒負鐢ㄦ埛鎵撻犱竴涓鑸掗傘佹剦鎮︾殑闊充箰鐜澧冦
鏍煎姏絀鴻皟DJ鐨勮繍浣滃師鐞嗗嶮鍒嗙畝鍗曡屾湁鏁堛傜敤鎴峰彧闇灝嗘墜鏈烘垨鍏朵粬闊充箰鎾鏀懼櫒鍜屾牸鍔涚┖璋僁J榪涜岃摑鐗欒繛鎺ワ紝鐒跺悗閫氳繃鎵嬫満涓婄殑闊充箰APP閫夋嫨鍠滄㈢殑闊充箰銆備竴鏃﹂煶涔愬紑濮嬫挱鏀撅紝鏍煎姏絀鴻皟DJ浼氶氳繃浼犳劅鍣ㄥ拰鏅鴻兘綆楁硶瀹炴椂媯嫻嬬┖闂寸殑娓╁害銆佹箍搴﹀拰鐢ㄦ埛鐨勯煶涔愬枩濂斤紝鑷鍔ㄨ皟鏁寸┖璋冨弬鏁幫紝浠ョ『淇濋煶涔愬拰絀鴻皟鑳藉熷畬緹庤瀺鍚堛傜敤鎴峰彲浠ラ氳繃鎵嬫満APP鎴栬呮牸鍔涚┖璋冭嚜甯︾殑鎺у埗闈㈡澘瀵圭┖璋冭繘琛屾墜鍔ㄨ皟鏁達紝浠ユ弧瓚充釜浜虹殑闇奼傘
鏍煎姏絀鴻皟DJ涓嶄粎鍦ㄨ垝閫傛у拰渚垮埄鎬т笂鍏鋒湁浼樺娍錛岃繕鑳藉府鍔╃敤鎴鋒彁鍗囩敓媧誨搧璐ㄥ拰浜鍙楁洿澶氫箰瓚c備緥濡傦紝鍦ㄥ忔棩鐨勭値鐑澶╂皵閲岋紝鏍煎姏絀鴻皟DJ浼氭牴鎹鐢ㄦ埛鐨勯煶涔愰夋嫨鍜屽枩濂芥潵璋冭妭絀鴻皟椋庨熷拰娓╁害錛岃惀閫犲嚭鍑夌埥瀹滀漢鐨勭幆澧冦傚悓鏃訛紝鏍煎姏絀鴻皟DJ榪樺彲浠ユ牴鎹鐢ㄦ埛鐨勫枩濂藉拰蹇冩儏鏉ラ夋嫨涓嶅悓綾誨瀷鐨勯煶涔愶紝涓虹敤鎴峰甫鏉ユ洿澶氶煶涔愪韓鍙楀拰鏀炬澗鐨勬椂鍒匯傛牸鍔涚┖璋僁J鐨勫嚭鐜頒笉浠呮槸絀鴻皟棰嗗煙鐨勪竴嬈¢潻鏂幫紝涔熸槸鐢ㄦ埛鎰夋偊鐢熸椿鐨勯噸瑕佸姪鍔涖
4. djstl演算法
定義Dijkstra(迪傑斯特拉)演算法是典型的單源最短路徑演算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴展,直到擴展到終點為止。Dijkstra演算法是很有代表性的最短路徑演算法,在很多專業課程中都作為基本內容有詳細的介紹,如數據結構,圖論,運籌學等等。Dijkstra一般的表述通常有兩種方式,一種用永久和臨時標號方式,一種是用OPEN,
CLOSE表的方式,這里均採用永久和臨時標號的方式。注意該演算法要求圖中不存在負權邊。
問題描述在無向圖
G=(V,E) 中,假設每條邊 E[i] 的長度為 w[i],找到由頂點 V0 到其餘各點的最短路徑。(單源最短路徑)
編輯本段迪傑斯特拉演算法迪傑斯特拉(Dijkstra)演算法思想
按路徑長度遞增次序產生最短路徑演算法:
把V分成兩組:
(1)S:已求出最短路徑的頂點的集合
(2)V-S=T:尚未確定最短路徑的頂點集合
將T中頂點按最短路徑遞增的次序加入到S中,
保證:(1)從源點V0到S中各頂點的最短路徑長度都不大於
從V0到T中任何頂點的最短路徑長度
(2)每個頂點對應一個距離值
S中頂點:從V0到此頂點的最短路徑長度
T中頂點:從V0到此頂點的只包括S中頂點作中間
頂點的最短路徑長度
依據:可以證明V0到T中頂點Vk的最短路徑,或是從V0到Vk的
直接路徑的權值;或是從V0經S中頂點到Vk的路徑權值之和
(反證法可證)
求最短路徑步驟
演算法步驟如下:
1. 初使時令 S={V0},T={其餘頂點},T中頂點對應的距離值
若存在<V0,Vi>,d(V0,Vi)為<V0,Vi>弧上的權值
若不存在<V0,Vi>,d(V0,Vi)為∝
2. 從T中選取一個其距離值為最小的頂點W且不在S中,加入S
3. 對S中頂點的距離值進行修改:若加進W作中間頂點,從V0到Vi的
距離值縮短,則修改此距離值
重復上述步驟2、3,直到S中包含所有頂點,即W=Vi為止
編輯本段迪傑斯特拉演算法的原理首先,引進一個輔助向量D,它的每個分量D表示當前所找到的從始點v到每個終點vi的最短路徑的長度。如D[3]=2表示從始點v到終點3的路徑相對最小長度為2。這里強調相對就是說在演算法過程中D的值是在不斷逼近最終結果但在過程中不一定就等於最短路徑長度。它的初始狀態為:若從v到vi有弧,則D為弧上的權值;否則置D為∞。顯然,長度為
D[j]=Min{D | vi∈V} 的路徑就是從v出發的長度最短的一條最短路徑。此路徑為(v,vj)。
那麼,下一條長度次短的最短路徑是哪一條呢?假設該次短路徑的終點是vk,則可想而知,這條路徑或者是(v,vk),或者是(v,vj,vk)。它的長度或者是從v到vk的弧上的權值,或者是D[j]和從vj到vk的弧上的權值之和。
一般情況下,假設S為已求得最短路徑的終點的集合,則可證明:下一條最短路徑(設其終點為X)或者是弧(v,x),或者是中間只經過S中的頂點而最後到達頂點X的路徑。因此,下一條長度次短的最短路徑的長度必是D[j]=Min{D
| vi∈V-S} 其中,D或者是弧(v,vi)上的權值,或者是D[k](vk∈S)和弧(vk,vi)上的權值之和。 迪傑斯特拉演算法描述如下:
1)arcs表示弧上的權值。若不存在,則置arcs為∞(在本程序中為MAXCOST)。S為已找到從v出發的最短路徑的終點的集合,初始狀態為空集。那麼,從v出發到圖上其餘各頂點vi可能達到的最短路徑長度的初值為D=arcs[Locate
Vex(G,v),i] vi∈V 2)選擇vj,使得D[j]=Min{D | vi∈V-S} 3)修改從v出發到集合V-S上任一頂點vk可達的最短路徑長度。
編輯本段迪傑斯特拉演算法C#程序public class Edge
{
public string StartNodeID ;
public string EndNodeID ;
public double Weight ; //權值,代價
} 節點則抽象成Node類,一個節點上掛著以此節點作為起點的「出邊」表。
public class Node
{
private string iD ;
private ArrayList edgeList ;//Edge的集合--出邊表
public Node(string id )
{
this.iD = id ;
this.edgeList = new ArrayList() ;
}
property#region property
public string ID
{
get
{
return this.iD ;
}
}
public ArrayList EdgeList
{
get
{
return this.edgeList ;
}
}
#endregion
}
在計算的過程中,我們需要記錄到達每一個節點權值最小的路徑,這個抽象可以用PassedPath類來表示:
/// <summary>
/// PassedPath 用於緩存計算過程中的到達某個節點的權值最小的路徑
/// </summary>
public class PassedPath
{
private string curNodeID ;
private bool beProcessed ; //是否已被處理
private double weight ; //累積的權值
private ArrayList passedIDList ; //路徑
public PassedPath(string ID)
{
this.curNodeID = ID ;
this.weight = double.MaxValue ;
this.passedIDList = new ArrayList() ;
this.beProcessed = false ;
}
#region property
public bool BeProcessed
{
get
{
return this.beProcessed ;
}
set
{
this.beProcessed = value ;
}
}
public string CurNodeID
{
get
{
return this.curNodeID ;
}
}
public double Weight
{
get
{
return this.weight ;
}
set
{
this.weight = value ;
}
}
public ArrayList PassedIDList
{
get
{
return this.passedIDList ;
}
}
#endregion
}
另外,還需要一個表PlanCourse來記錄規劃的中間結果,即它管理了每一個節點的PassedPath。
/// <summary>
/// PlanCourse 緩存從源節點到其它任一節點的最小權值路徑=》路徑表
/// </summary>
public class PlanCourse
{
private Hashtable htPassedPath ;
#region ctor
public PlanCourse(ArrayList nodeList ,string originID)
{
this.htPassedPath = new Hashtable() ;
Node originNode = null ;
foreach(Node node in nodeList)
{
if(node.ID == originID)
{
originNode = node ;
}
else
{
PassedPath pPath = new PassedPath(node.ID) ;
this.htPassedPath.Add(node.ID ,pPath) ;
}
}
if(originNode == null)
{
throw new Exception("The origin node is not exist !")
;
}
this.InitializeWeight(originNode) ;
}
private void InitializeWeight(Node originNode)
{
if((originNode.EdgeList == null)
||(originNode.EdgeList.Count == 0))
{
return ;
}
foreach(Edge edge in originNode.EdgeList)
{
PassedPath pPath = this[edge.EndNodeID] ;
if(pPath == null)
{
continue ;
}
pPath.PassedIDList.Add(originNode.ID) ;
pPath.Weight = edge.Weight ;
}
}
#endregion
public PassedPath this[string nodeID]
{
get
{
return (PassedPath)this.htPassedPath[nodeID] ;
}
}
}
在所有的基礎構建好後,路徑規劃演算法就很容易實施了,該演算法主要步驟如下:
(1)用一張表(PlanCourse)記錄源點到任何其它一節點的最小權值,初始化這張表時,如果源點能直通某節點,則權值設為對應的邊的權,否則設為double.MaxValue。
(2)選取沒有被處理並且當前累積權值最小的節點TargetNode,用其邊的可達性來更新到達其它節點的路徑和權值(如果其它節點
經此節點後權值變小則更新,否則不更新),然後標記TargetNode為已處理。
(3)重復(2),直至所有的可達節點都被處理一遍。
(4)從PlanCourse表中獲取目的點的PassedPath,即為結果。
下面就來看上述步驟的實現,該實現被封裝在RoutePlanner類中:
/// <summary>
/// RoutePlanner 提供圖演算法中常用的路徑規劃功能。
/// 2005.09.06
/// </summary>
public class RoutePlanner
{
public RoutePlanner()
{
}
#region Paln
//獲取權值最小的路徑
public RoutePlanResult Paln(ArrayList nodeList ,string
originID ,string destID)
{
PlanCourse planCourse = new PlanCourse(nodeList
,originID) ;
Node curNode = this.GetMinWeightRudeNode(planCourse
,nodeList ,originID) ;
#region 計算過程
while(curNode != null)
{
PassedPath curPath = planCourse[curNode.ID] ;
foreach(Edge edge in curNode.EdgeList)
{
PassedPath targetPath = planCourse[edge.EndNodeID] ;
double tempWeight = curPath.Weight + edge.Weight ;
if(tempWeight < targetPath.Weight)
{
targetPath.Weight = tempWeight ;
targetPath.PassedIDList.Clear() ;
for(int i=0 ;i<curPath.PassedIDList.Count ;i++)
{
targetPath.PassedIDList.Add(curPath.PassedIDList.ToString())
;
}
targetPath.PassedIDList.Add(curNode.ID) ;
}
}
//標志為已處理
planCourse[curNode.ID].BeProcessed = true ;
//獲取下一個未處理節點
curNode = this.GetMinWeightRudeNode(planCourse
,nodeList ,originID) ;
}
#endregion
//表示規劃結束
return this.GetResult(planCourse ,destID) ;
}
#endregion
#region private method
#region GetResult
//從PlanCourse表中取出目標節點的PassedPath,這個PassedPath即是規劃結果
private RoutePlanResult GetResult(PlanCourse
planCourse ,string destID)
{
PassedPath pPath = planCourse[destID] ;
if(pPath.Weight == int.MaxValue)
{
RoutePlanResult result1 = new RoutePlanResult(null
,int.MaxValue) ;
return result1 ;
}
string[] passedNodeIDs = new
string[pPath.PassedIDList.Count] ;
for(int i=0 ;i<passedNodeIDs.Length ;i++)
{
passedNodeIDs = pPath.PassedIDList.ToString() ;
}
RoutePlanResult result = new
RoutePlanResult(passedNodeIDs ,pPath.Weight) ;
return result ;
}
#endregion
#region GetMinWeightRudeNode
//從PlanCourse取出一個當前累積權值最小,並且沒有被處理過的節點
private Node GetMinWeightRudeNode(PlanCourse
planCourse ,ArrayList nodeList ,string originID)
{
double weight = double.MaxValue ;
Node destNode = null ;
foreach(Node node in nodeList)
{
if(node.ID == originID)
{
continue ;
}
PassedPath pPath = planCourse[node.ID] ;
if(pPath.BeProcessed)
{
continue ;
}
if(pPath.Weight < weight)
{
weight = pPath.Weight ;
destNode = node ;
}
}
return destNode ;
}
#endregion
#endregion
}
編輯本段迪傑斯特拉演算法pascal程序type bool=array[1..10]of
boolean;
arr=array[0..10]of integer;
var a:array[1..10,1..10]of integer;
//存儲圖的鄰接數組,無邊為10000
c,d,e:arr; //c為最短路徑數值,d為各點前趨,
t:bool; //e:路徑,t為輔助數組
i,j,n,m:integer;
inf,outf:text;
////////////////////////////////////////////////////////////////////////////////
procere init; //不同題目鄰接數組建立方式不一樣
begin
assign(inf,'dijkstra.in');
assign(outf,'dijkstra.out');
reset(inf); rewrite(outf);
read(inf,n);
for i:=1 to n do
for j:=1 to n do
begin
read(inf,a[i,j]);
if a[i,j]=0 then a[i,j]:=10000;
end;
end;
////////////////////////////////////////////////////////////////////////////////
procere dijkstra(qi:integer; t:bool; var c{,d}:arr);
//qi起點,{}中為求路徑部
var i,j,k,min:integer; //分,不需求路徑時可以不要
begin //t數組一般在調用前初始
t[qi]:=true; //化成false,也可將部分點
{for i:=1 to n do d[i]:=qi; d[qi]:=0; }
//初始化成true以迴避這些點
for i:=1 to n do c[i]:=a[qi,i];
for i:=1 to n-1 do
begin
min:=10001;
for j:=1 to n do
if (c[j]<min)and(not(t[j])) then begin k:=j;
min:=c[j];end;
t[k]:=true;
for j:=1 to n do
if (c[k]+a[k,j]<c[j])and(not(t[j])) then
begin
c[j]:=c[k]+a[k,j]; {d[j]:=k;}
end;
end;
end;
////////////////////////////////////////////////////////////////////////////////
procere make(zh:integer; d:arr; var e:arr);
//生成路徑,e[0]保存路徑
var i,j,k:integer; //上的節點個數
begin
i:=0;
while d[zh]<>0 do
begin
inc(i);e[i]:=zh;zh:=d[zh];
end;
inc(i);e[i]:=qi; e[0]:=I;
end;
主程序調用:求最短路徑長度:初始化t,然後dijkstra(qi,t,c,d)
求路徑:make(m,d,e) ,m是終點
編輯本段Dijkstra演算法的堆優化(PASCAL實現)一、思考
我們可以發現,在實現步驟時,效率較低(需要O(n),使總復雜度達到O(n^2)。對此可以考慮用堆這種數據結構進行優化,使此步驟復雜度降為O(log(n))(總復雜度降為O(n
log(n))。
二、實現
1. 將與源點相連的點加入堆,並調整堆。
2. 選出堆頂元素u(即代價最小的元素),從堆中刪除,並對堆進行調整。
3. 處理與u相鄰的,未被訪問過的,滿足三角不等式的頂點
1):若該點在堆里,更新距離,並調整該元素在堆中的位置。
2):若該點不在堆里,加入堆,更新堆。
4. 若取到的u為終點,結束演算法;否則重復步驟2、3。
三、代碼
procere Dijkstra;
var
u,v,e,i:longint;
begin
fillchar(dis,sizeof(dis),$7e); //距離
fillchar(Inh,sizeof(Inh),false); //是否在堆中
fillchar(visit,sizeof(visit),false); //是否訪問過
size:=0;
e:=last[s];
while e<>0 do //步驟1
begin
u:=other[e];
if not(Inh[u]) then //不在堆里
begin
inc(size);
heap[size]:=u;
dis[u]:=cost[e];
Loc[u]:=size; //Loc數組記錄元素在堆中的位置
Inh[u]:=true;
Shift_up(Loc[u]); //上浮
end
else
if cost[e]<dis[u] then //在堆里
begin
dis[u]:=cost[e];
Shift_up(Loc[u]);
Shift_down(Loc[u]);
end;
e:=pre[e];
end;
visit[s]:=true;
while true do
begin
u:=heap[1]; //步驟2
if u=t then break; //步驟4
visit[u]:=true;
heap[1]:=heap[size];
dec(size);
Shift_down(1);
e:=last[u];
while e<>0 do //步驟3
begin
v:=other[e];
if Not(visit[v]) and (dis[u]+cost[e]<dis[v]) then
//與u相鄰的,未被訪問過的,滿足三角不等式的頂點
if Inh[v] then //在堆中
begin
dis[v]:=dis[u]+cost[e];
Shift_up(Loc[v]);
Shift_Down(Loc[v]);
end
else //不再堆中
begin
inc(size);
heap[size]:=v;
dis[v]:=dis[u]+cost[e];
Loc[v]:=size;
Inh[v]:=true;
Shift_up(Loc[v]);
end;
e:=pre[e];
end;
end;
writeln(dis[t]);
end;
http://ke..com/view/7839.htm
http://ke..com/view/1939816.htm
5. 關於時間依賴的最短路徑演算法
Dijkstra 最短路徑演算法的一種高效率實現*
隨著計算機的普及以及地理信息科學的發展,GIS因其強大的功能得到日益廣泛和深入的應用。網路分析作為GIS最主要的功能之一,在電子導航、交通旅遊、城市規劃以及電力、通訊等各種管網、管線的布局設計中發揮了重要的作用,而網路分析中最基本最關鍵的問題是最短路徑問題。最短路徑不僅僅指一般地理意義上的距離最短,還可以引申到其他的度量,如時間、費用、線路容量等。相應地,最短路徑問題就成為最快路徑問題、最低費用問題等。由於最短路徑問題在實際中常用於汽車導航系統以及各種應急系統等(如110報警、119火警以及醫療救護系統),這些系統一般要求計算出到出事地點的最佳路線的時間應該在1 s~3 s內,在行車過程中還需要實時計算出車輛前方的行駛路線,這就決定了最短路徑問題的實現應該是高效率的。其實,無論是距離最短、時間最快還是費用最低,它們的核心演算法都是最短路徑演算法。經典的最短路徑演算法——Dijkstra演算法是目前多數系統解決最短路徑問題採用的理論基礎,只是不同系統對Dijkstra演算法採用了不同的實現方法。
據統計,目前提出的此類最短路徑的演算法大約有17種。F.Benjamin Zhan等人對其中的15種進行了測試,結果顯示有3種效果比較好,它們分別是:TQQ(graph growth with two queues)、DKA (the Dijkstra's algorithm implemented with approximate buckets) 以及 DKD (the Dijkstra�s algorithm implemented with double buckets ),這些演算法的具體內容可以參見文獻〔1〕。其中TQQ演算法的基礎是圖增長理論,較適合於計算單源點到其他所有點間的最短距離;後兩種演算法則是基於Dijkstra的演算法,更適合於計算兩點間的最短路徑問題〔1〕。總體來說,這些演算法採用的數據結構及其實現方法由於受到當時計算機硬體發展水平的限制,將空間存儲問題放到了一個很重要的位置,以犧牲適當的時間效率來換取空間節省。目前,空間存儲問題已不是要考慮的主要問題,因此有必要對已有的演算法重新進行考慮並進行改進,可以用空間換時間來提高最短路徑演算法的效率。
1 經典Dijkstra演算法的主要思想
Dijkstra演算法的基本思路是:假設每個點都有一對標號 (dj, pj),其中dj是從起源點s到點j的最短路徑的長度 (從頂點到其本身的最短路徑是零路(沒有弧的路),其長度等於零);pj則是從s到j的最短路徑中j點的前一點。求解從起源點s到點j的最短路徑演算法的基本過程如下:
1) 初始化。起源點設置為:① ds=0, ps為空;② 所有其他點: di=∞, pi= ;③ 標記起源點s,記k=s,其他所有點設為未標記的。
2) 檢驗從所有已標記的點k到其直接連接的未標記的點j的距離,並設置:
dj=min〔dj, dk+lkj〕
式中,lkj是從點k到j的直接連接距離。
3) 選取下一個點。從所有未標記的結點中,選取dj 中最小的一個i:
di=min〔dj, 所有未標記的點j〕
點i就被選為最短路徑中的一點,並設為已標記的。
4) 找到點i的前一點。從已標記的點中找到直接連接到點i的點j*,作為前一點,設置:
i=j*
5) 標記點i。如果所有點已標記,則演算法完全推出,否則,記k=i,轉到2) 再繼續。
2 已有的Dijkstra演算法的實現
從上面可以看出,在按標記法實現Dijkstra演算法的過程中,核心步驟就是從未標記的點中選擇一個權值最小的弧段,即上面所述演算法的2)~5)步。這是一個循環比較的過程,如果不採用任何技巧,未標記點將以無序的形式存放在一個鏈表或數組中。那麼要選擇一個權值最小的弧段就必須把所有的點都掃描一遍,在大數據量的情況下,這無疑是一個制約計算速度的瓶頸。要解決這個問題,最有效的做法就是將這些要掃描的點按其所在邊的權值進行順序排列,這樣每循環一次即可取到符合條件的點,可大大提高演算法的執行效率。另外,GIS中的數據 (如道路、管網、線路等)要進行最短路徑的計算,就必須首先將其按結點和邊的關系抽象為圖的結構,這在GIS中稱為構建網路的拓撲關系 (由於這里的計算與面無關,所以拓撲關系中只記錄了線與結點的關系而無線與面的關系,是不完備的拓撲關系)。如果用一個矩陣來表示這個網路,不但所需空間巨大,而且效率會很低。下面主要就如何用一個簡潔高效的結構表示網的拓撲關系以及快速搜索技術的實現進行討論。
網路在數學和計算機領域中被抽象為圖,所以其基礎是圖的存儲表示。一般而言,無向圖可以用鄰接矩陣和鄰接多重表來表示,而有向圖則可以用鄰接表和十字鏈表〔4〕 表示,其優缺點的比較見表 1。
表 1 幾種圖的存儲結構的比較
Tab. 1 The Comparsion of Several Graph for Storing Structures
名 稱 實現方法 優 點 缺 點 時間復雜度
鄰接矩陣 二維數組 1. 易判斷兩點間的關系 佔用空間大 O(n2+m*n)
2. 容易求得頂點的度
鄰接表 鏈表 1. 節省空間 1. 不易判斷兩點間的關系 O(n+m)或O(n*m)
2. 易得到頂點的出度 2. 不易得到頂點的入度
十字鏈表 鏈表 1. 空間要求較小 結構較復雜 同鄰接表
2.易求得頂點的出度和入度
鄰接多重表 鏈表 1. 節省空間 結構較復雜 同鄰接表
2. 易判斷兩點間的關系
目前,對於演算法中快速搜索技術的實現,主要有桶結構法、隊列法以及堆棧實現法。TQQ、DKA 以及 DKD 在這方面是比較典型的代表。TQQ雖然是基於圖增長理論的,但是快速搜索技術同樣是其演算法實現的關鍵,它用兩個FIFO的隊列實現了一個雙端隊列結構來支持搜索過程〔1〕。
DKA和DKD是採用如圖 1 所示的桶結構來支持這個運算,其演算法的命名也來源於此。在DKA演算法中,第i個桶內裝有權值落在 〔b*i, (i+1)*b) 范圍內的可供掃描的點,其中b是視網路中邊的權值分布情況而定的一個常數。每一個桶用隊列來維護,這樣每個點有可能被多次掃描,但最多次數不會超過b次。最壞情況下,DKA的時間復雜度將會是O(m*b+n(b+C/b)),其中,C為圖中邊的最大權值。DKD將點按權值的范圍大小分裝在兩個級別的桶內,高級別的桶保存權值較大的點,相應的權值較小的點都放在低級別的桶內,每次掃描都只針對低級別桶中的點。當然隨著點的插入和刪除,兩個桶內的點是需要動態調整的。在DKA演算法中,給每個桶一定的范圍以及DKD中使用雙桶,在一定程度上都是以空間換時間的做法,需要改進。
圖 1 一個桶結構的示例
Fig. 1 An Example of the Bucket Data Structure
3 本文提出的Dijkstra演算法實現
3.1 網路拓撲關系的建立
上面介紹的各種圖的存儲結構考慮了圖在理論上的各種特徵,如有向、無向、帶權、出度、入度等。而GIS中的網路一般為各種道路、管網、管線等,這些網路在具有圖理論中的基本特徵的同時,更具有自己在實際中的一些特點。首先,在GIS中大多數網路都是有向帶權圖,如道路有單雙向問題,電流、水流都有方向(如果是無向圖也可歸為有向圖的特例),且不同的方向可能有不同的權值。更重要的一點是,根據最短路徑演算法的特性可以知道,頂點的出度是個重要指標,但是其入度在演算法里則不必考慮。綜合以上4種存儲結構的優缺點, 筆者採用了兩個數組來存儲網路圖,一個用來存儲和弧段相關的數據(Net-Arc List),另一個則存儲和頂點相關的數據(Net-Node Index)。Net-Arc List用一個數組維護並且以以弧段起點的點號來順序排列,同一起點的弧段可以任意排序。這個數組類似於鄰接矩陣的壓縮存儲方式,其內容則具有鄰接多重表的特點,即一條邊以兩頂點表示。Net-Node Index則相當於一個記錄了頂點出度的索引表,通過它可以很容易地得到此頂點的出度以及與它相連的第一條弧段在弧段數組中的位置。此外,屬性數據作為GIS不可少的一部分也是必須記錄的。這樣,計算最佳路徑所需的網路信息已經完備了。在頂點已編號的情況下,建立Net-Arc List和Net-Node Index兩個表以及對Net-Arc List的排序,其時間復雜度共為O(2n+lgn),否則為O(m+2n+lgn)。這個結構所需的空間也是必要條件下最小的,記錄了m個頂點以及n條邊的相關信息,與鄰接多重表是相同的。圖 2 是採用這個結構的示意圖。
3.2 快速搜索技術的實現
無論何種演算法,一個基本思想都是將點按權值的大小順序排列,以節省操作時間。前面已經提到過,這兩個演算法都是以時間換空間的演算法,所以在這里有必要討論存儲空間問題 (這部分空間的大小依賴於點的個數及其出度)。根據圖中頂點和邊的個數可以求出頂點的平均出度e=m/n(m為邊數,n為頂點數),這個數值代表了圖的連通程度,一般在GIS的網路圖中,e∈〔2,5〕。這樣,如果當前永久標記的點為t個,那麼,下一步需掃描點的個數就約為t~4t個。如果採用鏈表結構,按實際應用中的網路規模大小,所需的總存儲空間一般不會超過100 K。所以完全沒有必要採用以時間換空間的做法,相反以空間換時間的做法是完全可行的。在實現這部分時,筆者採用了一個FIFO隊列,相應的操作主要是插入、排序和刪除,插入和刪除的時間復雜度都是O(1),所以關鍵問題在於選擇一個合適的排序演算法。一般可供選擇的排序演算法有快速排序、堆排序以及歸並排序等,其實現的平均時間都為O(nlgn)。經過比較實驗,筆者選擇了快速排序法。另外,Visual C++提供的run-time庫也提供了現成的快速排序的函數qsort( )可供使用。
圖 2 基於最佳路徑計算的網路拓撲表示
Fig. 2 The Presentation of the Network Topology
Used for Computing the Shortest Path
按照以上思路,筆者用Visual C++實現了吉奧之星(GeoStar)中的最佳路徑模塊。以北京的街道為數據(共6 313個結點,9 214條弧段(雙向)),在主頻為133、硬碟為1 G、內存為32 M的機器上,計算一條貫穿全城、長為155.06 km的線路,約需1 s~2 s。如圖 3所示。
圖 3 GeoStar中最佳路徑實現示意圖
ps:圖片沒有辦法貼上去.
你可以參考《演算法導論》第二版