自己總結的Dijkstra演算法(未完成)
原始演算法
思想
利用貪心法則,每次從已發現節點中選取一個距離起點距離最小的節點,然後由此節點更新其周圍節點資訊,其正確性很容易證明,我們每次選到一個距離起點最近的點,那麼下一個距離起點最近的點要麼是之前更新的,要麼是和此節點相連的。並且之後的節點不可能再更改此節點資訊,因為我們以貪心法則選取結點,之後節點的距離一定大於此節點,故不可能更新。
實現
我們設V為圖的頂點集,dis[|V|]表示起點到各點的距離,w(u,v)為結點u和節點v之間的權值。初始化階段中,令起點為start,則dis[start]=0,其餘節點的dis值為INF,之後令point指示當前節點,初始為start,當dis[u]>dis[pos]+w(point,u)(edge(point,u)為圖的一條邊),則更新dis[u]。具體過程如下:
//此處我們求解的是起點到所有頂點的最短距離
dis[start]=0;
while(|V|){
for i=V中每個節點
pos=min(dis[i]);
V=V-{pos};
for u|u屬於edge(pos,u)&&u屬於V
if(dis[u]>dis[pos]+w(u,v)) dis[u]=dis[pos]+w(u,v);
}
此時我們就已經得到了所有節點到start的最短路徑長度。
使用範圍
dijkstra演算法只適用於權值為非負的圖中。
dijkstra演算法優化
優化1 “盒子”優化
我們分析上邊的演算法可知,原始dijkstra演算法的時間複雜度為O(|V|^2),空間複雜度我們不去關心。很明顯的一點是,我們在點集中搜索權值最小的節點時,連帶著許多未發現(即dis[i]=INF)的節點也進行了掃描,一種解決方案是我們建立一個“盒子”,把所有發現的節點儲存進來,這樣我們只需要每次掃描盒子,更新盒子就可以了,減少了未發現節點的多餘掃描。
優化2 二叉堆優化
優化1的方案確實可以優化,但這種優化是非常不穩定的,在特殊資料(例如start節點直接把其他所有節點都更新進“盒子”)下,時間複雜度依然可能達到O(|V|^2)。那麼,我們可以使用優先佇列來儲存已發現節點或所有為確定節點(時間差異幾乎沒有)。我們以將所有節點初始化進堆為例,堆中每個元素儲存兩項,key表示此節點代表圖中的節點編號,dis表示此節點到起點的距離,我們以dis為關鍵字建立一個size=|V|的最小堆,r[|V|]表示各節點到起點的距離,初始化為INF。
我們每次選出堆頂元素,將圖中與其key值連線的節點嘗試進行鬆弛,並且在每次鬆弛後對堆中對應元素進行調整即可。
首先我們先定義堆中元素的型別
struct heapelem{
int key,dis;
};
heapelem heap[810];
按照上述過程,我們先定義初始化堆的操作
void init(int Size)
{
size=Size;
heap[0].dis=-INF;
for(int i=1;i<=Size;i++){
heap[i].dis=r[i]=INF;
heap[i].key=i;
pos[i]=i;//pos[i]指示i號節點在堆中的位置
}
}
下面是堆的decrease操作
void decrease(int x,int ith)//修改第ith號節點權值為x
{
int i,f;
for(i=pos[ith];heap[f=i>>1].dis>x;i=f){
heap[i]=heap[f];
pos[heap[i].key]=i;
}
heap[i].dis=x;
heap[i].key=ith;
pos[ith]=i;
}
然後就是堆的delmin操作
void delmin()
{
heapelem last=heap[size--];
int i,c;
for(i=1;(c=i<<1)<=size;i=c){
if(c!=size&&heap[c+1].dis<heap[c].dis)
c++;
if(heap[c].dis<last.dis){
heap[i]=heap[c];
pos[heap[i].key]=i;
}
else break;
}
heap[i]=last;
pos[last.key]=i;
}
而dijkstra演算法的實現是基於上述操作和對圖的儲存的,所以在這裡我們再次宣告一下圖的儲存方式
struct EDGE{
int to,dis;
struct EDGE *next;
}edge[3000];
int totale=0;
EDGE *point[810];
其中point[i]儲存的是一個指向i號節點連線的所有節點和其權值的連結串列,而加入邊的操作為(類似於連結串列的頭插法)
void addedge(int a,int b,int c)
{
edge[totale].dis=c;
edge[totale].to=b;
edge[totale].next=point[a];
point[a]=&edge[totale];
totale++;
}//注:若為無向圖,則應執行兩次操作
那麼接下來的dijkstra演算法也就很容易編寫了
void dijkstra(int start,int Size)
{
init(Size);
r[start]=0;
decrease(0,start);
int pos=start;
while(size){
delmin();
EDGE *e;
e=point[pos];
while(e){
if((r[pos]+e->dis)<r[e->to]){
r[e->to]=r[pos]+e->dis;
decrease(r[e->to],e->to);
}
e=e->next;
}
pos=heap[1].key;
}
}
這種優化的好處在於其使用範圍廣,只要能使用dijkstra演算法,就一定可以這樣優化,並且其時間複雜度非常穩定,為|V|log|V|。
優化三 扇形優化
當一個圖可以直接抽象為一個平面時,我們可以將dijkstra演算法理解為以起點為圓心畫一個圓,其半徑逐漸增大,每當圓周遇到一個點,就標記其為確定節點,並畫出此節點所連線的未發現節點與更新與其連線的已發現節點,那麼可以清楚的看到,此時的搜尋有很大一部分“南轅北轍”了,
例如我們的目標節點在起點的正北方向,而我們的搜尋卻把2pi的角度全進行了,那麼此時,可以採取有損演算法來提高效率,雖然並不能保證一定可以得到最優解,但多數情況下不會發生誤差,即使發生也不會很大。
有損演算法核心在於限定一個扇形,起點與目標點的連線為扇形圓心角的角平分線,圓心角的大小可以根據圖的具體情況而定,而判斷一個頂點是否在扇形區域內可以採用餘弦定理來實現。
對於整個演算法流程,其餘大部分和上述程式碼一致(可能變數名字或者型別需要稍微修改),我們假設對於每個頂點的定義如下:
struct Point{
int x,y;
}point[800];
那麼接下來的工作就是求出需要處理的點,即在限定角度內的頂點,之後執行dijkstra演算法即可,讓我們定義sift函式,並假設start是起點,target是目標頂點,range是設定的扇形的角度的一般:
void sift()
{
for i=V中每一個頂點
double dis_si,dis_st,dis_it;//表平面上距離,不一定等於路徑長度
dis_si=sqrt((point[start].x-point[i].x)^2+(point[start].y-point[i].y)^2);
dis_st=sqrt((point[start].x-point[t].x)^2+(point[start].y-point[t].y)^2);
dis_it=sqrt((point[i].x-point[target].x)^2+(point[i].y-point[target].y)^2);
double angle=arccos((dis_si^2+dis_st^2-dis_it^2)/(2*dis_si*dis_st));
if(angle>range) V=V-{i};
end for;
}
注意,此演算法的前提是圖可抽象成平面的,即每個點都是有二維座標的,是固定的,而且當利用dijkstra求全點對最短路徑是,此優化是沒有必要的。
實際應用中還可以通過A*演算法或十字連結串列等方法進行優化,而以上三點(優化1可忽略)基本已滿足競賽中的時間要求,因此個人認為重心應該在dijkstra的功能上,因為dijkstra演算法的強大不僅僅在其能求單源最短路徑,以下是dijkstra演算法的一些拓展功能。
拓展一
記錄最短路徑,生成最短路徑樹,即儲存每個最短路徑節點的父節點,這一點實現起來比較簡單,我們只需在dijkstra函式中第二層while迴圈的if語句中加入 path[e->to]=pos; 即可完成。
拓展二
路徑統計。兩頂點間不一定只有一條最短路徑,例如有a,b,c三個頂點和三條邊(a,b)=3,(b,c)=4,(a,c)=7,此時a到c的最短路徑長為7,有兩條。對於這類問題,我們選擇好起點和終點,先利用普通dijkstra演算法求出起點到達各點的最短路徑,之後我們需要承認這樣一個事實,起點和終點的任意一條最短路徑上,到達每一個內點的距離一定是最短的,這個很容易證明,若到達某個內點時所走距離大於該點最短距離,則一定不是最短路徑。所以我們假設當前處於點i,則我們只能選擇dis[j]=dis[i]+w(i,j),此處的w(i,j)表點i和點j間存在一條邊,且其權值為w(i,j)。那麼點i到終點的最短路徑數為dp[i]=sum{dp[j]|dis[j]=dis[i]+w(i,j)},使用記憶化搜尋即可在O(|V|)時間完成統計。
在實現的時候我們有多種方法。我們可以根據dp方程的條件構造新圖,也可以在原圖的基礎上判斷條件再dp。後者的工作量明顯小於前者。
//在呼叫記憶化搜尋函式前需初始化dp陣列為0
//呼叫此函式時格式為 int ans=dfs(start);
//
int dfs(int u)
{
if(u==target) return 1;
if(dp[u]) return dp[u];
EDGE *e;
for(e=point[u];e;e=e->next) if(dis[e->to]==dis[u]+e->dis)
dp[u]+=dfs(e->to);
return dp[u];
}
拓展三
求出兩點間所有最短路徑。由拓展一可知,當圖中存在多條最短路徑是,path陣列中儲存的為第一次更新的父節點,而之後的父節點沒有儲存,我們有兩種方案求出所有路徑,一種是在拓展一的基礎上構造向量儲存父節點,當然需要增加一個相等情況的判斷,此時時間複雜度不變,但由於最壞情況下會有|V|-2條路徑,所以最壞空間複雜度為|V|^2,對於大多數的計算機記憶體來說,這是巨大的。值得慶幸的是,我們還有另一種方案,這種方案是基於拓展二的,我們先用dijkstra演算法對圖進行預處理,之後從起點開始進行深度優先搜尋,搜尋時只允許拓展dis[j]|dis[j]=dis[i]+w(i,j)的節點,每次遇到目標節點,就輸出路徑。
拓展四
求第K短路長度,這個還沒有找到什麼特別可靠的資料,傳說有個曹氏短邊演算法,具體不太清楚,等自己弄明白了在補充。
相關推薦
自己總結的Dijkstra演算法(未完成)
原始演算法 思想 利用貪心法則,每次從已發現節點中選取一個距離起點距離最小的節點,然後由此節點更新其周圍節點資訊,其正確性很容易證明,我們每次選到一個距離起點最近的點,那麼下一個距離起點最近的點要麼是之前更新的,要麼是和此節點相連的。並且之後的節點不可能再更改此節點資
tarjan演算法入門(二)——無向圖的雙連通分量(未完成)
一.雙連通分量. 無向圖的雙連通分量分為兩種,一種是點雙連通分量,簡稱v-DCC;一種是邊雙連通分量,簡稱e-DCC. 一個v-DCC的概念即為一張沒有割點的圖,一個e-DCC的概念即為一張沒有割邊的圖. 一張極大雙連通分量子圖的是指沒有一張雙連通分量子圖完全包含這張子圖的所有點且點
sed 學習筆記(未完成)
sed#sedsed是一種流編輯器,它是文本處理中非常中的工具,能夠完美的配合正則表達式使用,功能不同凡響。處理時,把當前處理的行存儲在臨時緩沖區中,稱為“模式空間”(pattern space),接著用sed命令處理緩沖區中的內容,處理完成後,把緩沖區的內容送往屏幕。接著處理下一行,這樣不斷重復,直到文件末
《現代前端技術解析》第一章讀書筆記(未完成)
服務 異步 網絡請求 會話 開始 註冊 復雜 技術 顯示 今天是2017年6月26日,星期一,開始從第一章看起。第一章主要講的是前端技術的發展概況以及一些必須掌握的瀏覽器基礎知識與常用開發技術。 頁面內容多而復雜,為了保證開發效率,我們可以借助符合特定場景的前端框架
leetcode 算法 之 馬拉松算法(Manacher's algorithm)(未完成)
馬拉松 字符串 algo abc 出現 回文字 acc c# 現在 馬拉松算法:馬拉松算法是用來計算一個字符串中最長的回文字符串(對稱字符串,如aba abba)。 首先,我們拿到一個字符串S,然後在S中的每個字符之間加#。例如:S="abcb" T="a#b#c#b"
洛谷——P2706 巧克力(未完成)
表示 mda isp 分享 code 輸出 有一個 pen names P2706 巧克力 題目背景 王7的生日到了,他的弟弟準備送他巧克力。 題目描述 有一個被分成n*m格的巧克力盒,在(i,j)的位置上有a[i,j]塊巧克力。就在送出它的前一天晚上,有老鼠夜襲巧
有趣的tcp交互小數據包-telnet(未完成)
type ext 處理 單獨 打包 shadow 方式 process ffffff TCP有趣的處理方式: 133 telnet 到 192.168.100.157 後,輸入命令ls . 133和157對每個字母單獨打包 有趣的tcp交互小數據包-telnet(未完成)
13)添加一個類來創建別的類(未完成)
函數指針 分享 nbsp 9.png gpo bubuko div 對象 square 1)首先在我們的全局區有一個這樣的全局變量 , 2)但是 每次我都要改我的這個new的子類 比如 我要是創建 俄羅斯方塊 就是 new Squar
多校寒訓TaoTao要吃雞dp(未完成)
gpo 解釋 scanf div item 背包 -cp 並且 輸入 題目描述 Taotao的電腦帶不動絕地求生,所以taotao只能去玩pc版的荒野行動了, 和絕地求生一樣,遊戲人物本身可以攜帶一定重量m的物品,裝備背包 之後可以多攜帶h(h為0代表沒有裝備背包)重
MYSQL SQL模式 (未完成)
引用 文件 禁用 創建用戶 是個 同義詞 ted fma sele SQL模式影響MySQL支持的SQL語法和執行的數據驗證檢查。本篇內容根據官方手冊https://dev.mysql.com/doc/refman/5.7/en/sql-mode.htmlvgli 進行整理
python作業03-文件操作&函數(未完成)
turn remove col spa 地址 輸出 n的階乘 test dict 一、文件處理相關 1、編碼問題 (1)請說明python2 與python3中的默認編碼是什麽?答:Python2是ascii python3是utf-8 (2
PAT(未完成)
post main blog AI OS std n) 第一個 pri #include<stdio.h>int FiveZ(int a[]){ int sum=0; int b = sizeof(a)/sizeof(a[0]); for(int i=0;i&l
Javascript知識匯總------面向對象中繼承(未完成)
clas 繼承性 封裝 class 原型繼承 行為 特征 body 多態性 面向對象的特征就是封裝性,繼承性和多態性 | 封裝:就是將復雜包裹,隱藏起來,讓簡單的東西預留在外面 | 繼承:拿來主義,自己沒用,把別人的拿過來,讓其成為自己的東西
大世界Oracle體系(未完成)
Oracle 體系結構 PGA SGA 寫本篇文章主要受兩本書的啟發寫一些個人心得筆記,分享知識,並把書籍推薦給大家閱讀1、《收獲,不止Oracle》 2、《Oracle內核技術》 不涉及編程,設計體系架構思路,如果有編程書更是好!還有官方文檔一定要讀,英語水平差看翻譯也要讀一遍,真正
Bonita portal 源碼編譯(未完成)
using res bug sin adding pan .json before grunt 首先下載源代碼 https://github.com/bonitasoft/bonita-portal-js 以下內容為Github 的安裝教程包含我安裝過程中遇到的問題。
主成分分析和因子分析(未完成)
svd分解 主成分分析 http src inf 因子分析 分解 spa span 並且SVD分解也適用於一般的矩陣。 主成分分析和因子分析(未完成)
博弈學習筆記(未完成)
lin 應用 有一種 部落 自然數 .cn clas sg函數 二進制 博弈 Tags:數學 作業部落 評論地址 前言 本博文分三部分,第一部分簡單介紹SG函數,第二部分簡單介紹博主所理解的一些博弈模型,第三部分推薦題目以及分享做題心得,本文基本不適合初學者食用,初學者請
【內存分析】內存分析(未完成)
lan 初始 println 9.png 內存空間 需要 地址 idt student 棧的特點如下: 1. 棧描述的是方法執行的內存模型。每個方法被調用都會創建一個棧幀(存儲局部變量、操作數、方法出口等) 2. JVM為每個線程創建一個棧,用於存放該線程執行方法的
JavaSE習題 第八章 執行緒(未完成)
問答題 1.執行緒和程序是什麼關係? 程序是程式的一次動態執行,對應了從程式碼載入,執行至執行完畢的一個完整的過程 執行緒是比程序更小的執行單位,一個程序在其執行過程中可以產生多個執行緒,形成多條執行線索 2.執行緒有幾種狀態? 4種,新建,執行,中斷,死亡 3.引起執行緒中斷的常見原
福大軟工 · 第七次作業 - 需求分析報告(未完成)
福大軟工 · 第七次作業 - 需求分析報告 "Jarvis For Chat"需求分析報告 組長部落格連結 本次作業連結 團隊專案的整體計劃安排 專案logo及思維導圖 專案logo 思維導圖 點此檢視大圖 個人貢獻分分配 本著多幹多得的原則,我們儘可能地降低基礎績效的佔比,