當(dāng)前位置:首頁 > 公眾號精選 > C語言與CPP編程
[導(dǎo)讀]雷神之錘3是一款九十年代非常經(jīng)典的游戲,內(nèi)容畫面都相當(dāng)不錯(cuò),作者是大名鼎鼎的約翰卡馬克。由于當(dāng)時(shí)游戲背景原因,如果想要高效運(yùn)行游戲優(yōu)化必須做的非常好,否則普通人的配置性能根本不夠用,在這個(gè)背景下就誕生了“快速開平方取倒數(shù)的算法”。


一戰(zhàn)封神的 0x5f375a86


雷神之錘3是一款九十年代非常經(jīng)典的游戲,內(nèi)容畫面都相當(dāng)不錯(cuò),作者是大名鼎鼎的約翰卡馬克。由于當(dāng)時(shí)游戲背景原因,如果想要高效運(yùn)行游戲優(yōu)化必須做的非常好,否則普通人的配置性能根本不夠用,在這個(gè)背景下就誕生了“快速開平方取倒數(shù)的算法”。

在早前自雷神之錘3的源碼公開后,卡馬克大神的代碼“一戰(zhàn)封神”,令人“匪夷所思”的 0x5f375a86 ,引領(lǐng)了一代傳奇,源碼如下:


float?Q_rsqrt(?float?number?)?{ long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = * ( long * ) &y;  // evil floating point bit level hacking i = 0x5f3759df - ( i >> 1 ); // what the fuck? y = * ( float * ) &i; y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration // y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed #ifndef Q3_VM #ifdef __linux__ assert( !isnan(y) ); // bk010122 - FPE? #endif  #endif return y; }


相比 sqrt() 函數(shù),這套算法要快將近4倍,要知道,編譯器自帶的函數(shù),可是經(jīng)過嚴(yán)格仔細(xì)的匯編優(yōu)化的啊!

牛頓迭代法的原理是先猜測一個(gè)值,然后從這個(gè)值開始進(jìn)行疊代。因此,猜測的值越準(zhǔn),疊代的次數(shù)越少。卡馬克選了0x5f3759df這個(gè)值作為猜測的結(jié)果,再加上后面的移位算法,得到的y非常接近1/sqrt(n)。這樣,我們只需要2次牛頓迭代法就可以達(dá)到我們所需要的精度。


函數(shù)返回1/sqrt(x),這個(gè)函數(shù)在圖像處理中比sqrt(x)更有用。


注意到這個(gè)正數(shù)只用了一次疊代!(其實(shí)就是根本沒用疊代,直接運(yùn)算)。編譯、實(shí)驗(yàn),這個(gè)團(tuán)數(shù)不僅工作的很好,而且比標(biāo)準(zhǔn)的sqrt()函數(shù)快4倍!

這個(gè)簡潔的定數(shù),最核心,也是最讓人費(fèi)解的,就是標(biāo)注了what the fuck的一句
i ??=?0x5f3759df -?( i >> 1 ) ;再加上 y???=?y?*?(?threehalfs?-?(?x2?*?y?*?y?)?) 。


兩句話就完成了開方運(yùn)算!而且注意到,核心那句是移位運(yùn)算,速度極快!特別在很多沒有乘法指令的RISC結(jié)構(gòu)CPU上,這樣做是極其高效的。


算法的原理就是使用牛頓迭代法,用 x-f(x)/f'(x) 來不斷的逼近 f(x)=a 的根。


求平方根:f(x)=x^2=a ,f'(x)= 2*x, f(x)/f'(x)=x/2,把 f(x) 代入 x-f(x)/f'(x)后有(x+a/x)/2,


現(xiàn)在我們選 a=5,選一個(gè)猜測值比如 2, ?那么我們可以這么算 ?5/2 = 2.5; (2.5+2)/2 = 2.25; 5/2.25 = …… ?這樣反復(fù)迭代下去,結(jié)果必定收斂于 sqrt(5)。


但是卡馬克作者真正厲害的地方是他選擇了一個(gè)神秘的常數(shù) 0x5f375a86 來計(jì)算那個(gè)夢“值,

就是我們加注釋的那一行那行算出的值非常接近1/sqrt(n)這樣我們只需要2次牛頓迭代就可以達(dá)到我們所需要的精度。

當(dāng)然目前也已有翻譯過C++語言的版本:

float?Q_rsqrt(?float?number?){ long i; float x2, y; const float threehalfs = 1.5F;
x2 = number * 0.5F; y = number; i = * ( long * ) &y; i = 0x5f3759df - ( i >> 1 ); // what the fuck? y = * ( float * ) &i; y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration // y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
#ifndef Q3_VM #ifdef __linux__ assert( !isnan(y) ); // bk010122 - FPE? #endif #endif return y;}

當(dāng)然,更加魔幻的是,普渡大學(xué)的數(shù)學(xué)家Chris Lomont看了以后覺得有趣,但也不服氣,決定要研究一下卡馬克弄出來的這個(gè)猜測值有什么奧秘。


在精心研究之后,Lomont從理論上也推導(dǎo)出一個(gè)最佳猜測值,和卡馬克的數(shù)字非常接近, 0x5f37642f。Lomont計(jì)算出結(jié)果以后非常滿意,于是拿自己計(jì)算出的起始值和卡馬克的神秘?cái)?shù)字做比賽,看看誰的數(shù)字能夠更快更精確的求得平方根。結(jié)果是卡馬克贏了...


Lomont怒了,采用暴力方法一個(gè)數(shù)字一個(gè)數(shù)字試過來,終于找到一個(gè)比卡馬克數(shù)字要好上那么一丁點(diǎn)的數(shù)字,雖然實(shí)際上這兩個(gè)數(shù)字所產(chǎn)生的結(jié)果非常近似,這個(gè)暴力得出的數(shù)字是0x5f375a86。


囊括世界萬物的一段代碼


這是一段使用Processing語言的代碼,這短短的幾行代碼永無休止的就在做一件事——“窮舉”。那么它又有什么特殊之處嗎?


忽略機(jī)器本身的性能限制,假設(shè) frameCount 可以無限大(frameCount代表當(dāng)前幀數(shù))。只需安安靜靜地盯著屏幕,就可以看到所有像素的所有組合可能。


這意味著你可以在上面看到所有藝術(shù)大師的作品,蒙娜麗莎、向日葵甚至是初音……人類歷史上所有光輝的瞬間都將閃現(xiàn)在眼前。


但是這又需要多久時(shí)間呢?計(jì)算機(jī)里的每個(gè)像素都是由 256 級的 RGB 組成的。因此可以表示 256 3 (1600萬)種顏色。假如圖形的分辨率為 1000 × 1000,所有的像素可能的色值相互組合,將會產(chǎn)生 256 的 3000000 次方張不同圖片。


如果將圖片排在一個(gè)長廊上,人以一秒一張的速度去瀏覽。由于一年有 31536000 秒,因此要走完這條長廊,就需要 10?22??12 年。


這個(gè)數(shù)字已經(jīng)大得很難用人類的常用單位去表示了。硬是要用,那就是 10億億億億億億......年(90萬個(gè)億)。要清楚,宇宙的年齡也僅僅是 140 億年(1.4 × 101?年)。。


這也意味著,即使你從宇宙大爆炸看到現(xiàn)在,也無法將這個(gè)程序看完。
但如果把圖片像素的精度降低呢?用 100 × 100 的分辨率并且只用黑白二值去表示圖形?此時(shí)總數(shù)就會縮減到 2????? ,也就約等于 103?1?。


看似縮小很多了。如果同時(shí)動用全人類的力量,將這個(gè)任務(wù)分配給70億人。每人還是要不眠不休地看上 3.17 × 103??2 年,才能看完。


即使化到最簡,結(jié)果仍是大得恐怖。但如果能看完,我手上說不準(zhǔn)會有一張 100 × 100 的HelloKitty頭像,他手上或許能有一張愛因斯坦吐舌頭的照片。


可以用這么簡潔的形式去展現(xiàn)萬物,用近乎無限的時(shí)間去換取無限的可能,我覺得這就是這段代碼的魅力所在。


void setup(){ size(500,500);}void draw(){ for(int i=0;i for(int j=0;jstroke(frameCount/pow(255,i+j*width)%255,frameCount/pow(255,i+j*width+1)%255,frameCount/pow(255,i+j*width+2)%255); point(i,j); } }}


這段代碼也有5x5的精簡加速版本,當(dāng)然其中的參數(shù)也是可以任意修改的,代碼如下:


int num,w,frame,level;
void setup(){ size(400, 400); num = 5; w = width/num; level = 2; //色值精度}
void draw(){ for(int i = 0; i < num; i++){ for(int j = 0; j < num; j++){ fill((int(frame/pow(level,i + j * num)) % level)* (255 / (level - 1))); rect(w * i, w * j, w, w); } } // frame++; 勻速播放 frame = int(pow(frameCount,2)); //加速播放}


只有13個(gè)字符的Linux Fork炸彈


早在2002年,Jaromil設(shè)計(jì)了最為精簡的一個(gè)Linux Fork炸彈,整個(gè)代碼只有13個(gè)字符,在 shell 中運(yùn)行后幾秒后系統(tǒng)就會宕機(jī):

:(){:|:&};:


這樣看起來不是很好理解,我們可以更改下格式:


:(){:|:&};:


更好理解一點(diǎn)的話就是這樣:


bomb(){bomb|bomb&};bomb

因?yàn)閟hell中函數(shù)可以省略function關(guān)鍵字,所以上面的十三個(gè)字符是功能是定義一個(gè)函數(shù)與調(diào)用這個(gè)函數(shù),函數(shù)的名稱為:,主要的核心代碼是:|:&,可以看出這是一個(gè)函數(shù)本身的遞歸調(diào)用,通過&實(shí)現(xiàn)在后臺開啟新進(jìn)程運(yùn)行,通過管道實(shí)現(xiàn)進(jìn)程呈幾何形式增長,最后再通過:來調(diào)用函數(shù)引爆炸彈。因此,幾秒鐘系統(tǒng)就會因?yàn)樘幚聿贿^來太多的進(jìn)程而死機(jī),解決的唯一辦法就是重啟。

當(dāng)然有“不怕死”的小伙伴用了云主機(jī)試了一試:


結(jié)果,運(yùn)行一段時(shí)間后直接報(bào)出了-bash: fork: Cannot allocate memory,說明內(nèi)存不足了。

并且在二號終端上嘗試連接也沒有任何反應(yīng)。因?yàn)槭翘摂M的云主機(jī),所以我只能通過主機(jī)服務(wù)商的后臺來給主機(jī)斷電重啟。然后才能重新登錄:



當(dāng)然,F(xiàn)ork炸彈用其它語言也可以分分鐘寫出來一個(gè),例如,python版:


import os
while?True:???os.fork()

Fork炸彈的本質(zhì)無非就是靠創(chuàng)建進(jìn)程來搶占系統(tǒng)資源,在Linux中,我們可以通過ulimit命令來限制用戶的某些行為,運(yùn)行ulimit -a可以查看我們能做哪些限制:


ubuntu@10-10-57-151:~$ ulimit -acore file size (blocks, -c) 0data seg size (kbytes, -d) unlimitedscheduling priority (-e) 0file size (blocks, -f) unlimitedpending signals (-i) 7782max locked memory (kbytes, -l) 64max memory size (kbytes, -m) unlimitedopen files (-n) 1024pipe size (512 bytes, -p) 8POSIX message queues (bytes, -q) 819200real-time priority (-r) 0stack size (kbytes, -s) 8192cpu time (seconds, -t) unlimitedmax user processes (-u) 7782virtual memory (kbytes, -v) unlimitedfile locks (-x) unlimited


可以看到,-u參數(shù)可以限制用戶創(chuàng)建進(jìn)程數(shù),因此,我們可以使用ulimit -u 20來允許用戶最多創(chuàng)建20個(gè)進(jìn)程。這樣就可以預(yù)防bomb炸彈。但這樣是不徹底的,關(guān)閉終端后這個(gè)命令就失效了。我們可以通過修改/etc/security/limits.conf文件來進(jìn)行更深層次的預(yù)防,在文件里添加如下一行(ubuntu需更換為你的用戶名):


ubuntu - nproc 20

這樣,退出后重新登錄,就會發(fā)現(xiàn)最大進(jìn)程數(shù)已經(jīng)更改為20了,這個(gè)時(shí)候我們再次運(yùn)行炸彈就不會報(bào)內(nèi)存不足了,而是提示-bash: fork: retry: No child processes,說明Linux限制了炸彈創(chuàng)建進(jìn)程。


東尼·霍爾的快速排序算法


這個(gè)算法是由圖靈獎得主東尼·霍爾(C. A. R. Hoare)在1960年提出的,從名字中就可以看出,快速就是他的特點(diǎn)。

快速排序采用了“分治法”策略,把一個(gè)序列劃分為兩個(gè)子序列。在待排序列中,選擇一個(gè)元素作為“基準(zhǔn)”(Pivot)。


所有小于“基準(zhǔn)”的元素,都移動到“基準(zhǔn)”前面,所有大于“基準(zhǔn)”的元素,都移動到“基準(zhǔn)”后面(相同的數(shù)可以到任一邊)。此為“分區(qū)”(partition)操作。

分別對“基準(zhǔn)”兩邊的序列,不斷重復(fù)一、二步,直至所有子集只剩下一個(gè)元素。


假設(shè)現(xiàn)有一數(shù)列:



對此數(shù)列進(jìn)行快速排序。選擇第一個(gè)元素 45 作為第一趟排序的“基準(zhǔn)”(基準(zhǔn)值可以任意選擇)。


第一趟:將元素 45 拿出來,分別從數(shù)列的兩端開始探測

首先從右向左開始,找到第一個(gè)小于 45 的元素為 25,然后將 25 放置到第一個(gè)元素 45 的位置上。此時(shí)數(shù)列變?yōu)椋?br>


然后從左向右開始,找到第一個(gè)大于 45 的元素為 66 ,然后將 66 放置到原先元素 25的位置上。此時(shí)數(shù)列變?yōu)椋?br>


繼續(xù)從右向左開始,找到第二個(gè)小于 45 的元素為 10 ,然后將 10 放置到原先元素 66的位置上,此時(shí)數(shù)列變?yōu)椋?br>


繼續(xù)從左向右開始,找到第二個(gè)大于 45 的元素為 90 ,然后將 90 放置到原先元素 10的位置上,此時(shí)數(shù)列變?yōu)椋?br>


繼續(xù)從右向左開始,此時(shí)發(fā)現(xiàn)左右碰面,因此將“基準(zhǔn)”45 放置到原先元素 90 的位置上,此時(shí)數(shù)列變?yōu)椋?br>


至此,第一輪結(jié)束,“基準(zhǔn)”45 左側(cè)為小數(shù)區(qū),右側(cè)為大數(shù)區(qū)。同樣的分別對小數(shù)區(qū)和大數(shù)區(qū)應(yīng)用此方法直至完成排序。


分析完成后通過C++的源代碼如下:


快速排序核心算法:


//每一輪的快速排序int QuickPartition (int a[],int low,int high){ int temp = a[low];//選擇數(shù)組a的第一個(gè)元素作為“基準(zhǔn)” while(low < high) { while(low < high && a[high] >= temp )//從右向左查找第一個(gè)小于“基準(zhǔn)”的數(shù) { high--; } if (low < high) { a[low] = a[high];//將第一個(gè)找到的大于“基準(zhǔn)”的數(shù)移動到low處 low++; }
while(low < high && a[low] <= temp)//從左向右查找第一個(gè)大于“基準(zhǔn)”的數(shù) { low++; } if(low < high) { a[high] = a[low];//將第一個(gè)找到的小于“基準(zhǔn)”的數(shù)移動到high處 high--; }
a[low] = temp;//將“基準(zhǔn)”填到最終位置 } return low;//返回“基準(zhǔn)”的位置,用于下一輪排序。}


遞歸調(diào)用QuickSort(分治法):

//快速排序-遞歸算法void QuickSort (int a[],int low,int high){ if(low < high) { int temp = QuickPartition(a,low,high);//找出每一趟排序選擇的“基準(zhǔn)”位置 QuickSort(a,low,temp-1);//遞歸調(diào)用QuickSort,對“基準(zhǔn)”左側(cè)數(shù)列排序 QuickSort(a,temp+1,high);//對“基準(zhǔn)”右側(cè)數(shù)列排序 }}

主函數(shù)調(diào)用:

void main(){ int a[8]={45,38,66,90,88,10,25,45};//初始化數(shù)組a QuickSort(a,0,7);
cout<<endl<<"排序后:"; for(int i = 0;i <= 7;i++) { cout<" "; } getchar();}

排序后結(jié)果:


毫無炫技又驚為天人的

二分圖的最大匹配、完美匹配和匈牙利算法


二分圖:簡單來說,如果圖中點(diǎn)可以被分為兩組,并且使得所有邊都跨越組的邊界,則這就是一個(gè)二分圖。準(zhǔn)確地說:把一個(gè)圖的頂點(diǎn)劃分為兩個(gè)不相交集U和V,使得每一條邊都分別連接U、V中的頂點(diǎn)。如果存在這樣的劃分,則此圖為一個(gè)二分圖。二分圖的一個(gè)等價(jià)定義是:不含有「含奇數(shù)條邊的環(huán)」的圖。圖 1 是一個(gè)二分圖。為了清晰,我們以后都把它畫成圖 2 的形式。

匹配
:在圖論中,一個(gè)「匹配」(matching)是一個(gè)邊的集合,其中任意兩條邊都沒有公共頂點(diǎn)。例如,圖 3、圖 4 中紅色的邊就是圖 2 的匹配。
?


我們定義匹配點(diǎn)、匹配邊未匹配點(diǎn)、非匹配邊,它們的含義非常顯然。例如圖 3 中 1、4、5、7 為匹配點(diǎn),其他頂點(diǎn)為未匹配點(diǎn);1-5、4-7為匹配邊,其他邊為非匹配邊。

最大匹配
:一個(gè)圖所有匹配中,所含匹配邊數(shù)最多的匹配,稱為這個(gè)圖的最大匹配。圖 4 是一個(gè)最大匹配,它包含 4 條匹配邊。

完美匹配
:如果一個(gè)圖的某個(gè)匹配中,所有的頂點(diǎn)都是匹配點(diǎn),那么它就是一個(gè)完美匹配。圖 4 是一個(gè)完美匹配。顯然,完美匹配一定是最大匹配(完美匹配的任何一個(gè)點(diǎn)都已經(jīng)匹配,添加一條新的匹配邊一定會與已有的匹配邊沖突)。但并非每個(gè)圖都存在完美匹配。

舉例來說:如下圖所示,如果在某一對男孩和女孩之間存在相連的邊,就意味著他們彼此喜歡。是否可能讓所有男孩和女孩兩兩配對,使得每對兒都互相喜歡呢?圖論中,這就是完美匹配問題。如果換一個(gè)說法:最多有多少互相喜歡的男孩/女孩可以配對兒?這就是最大匹配問題。


基本概念講完了。求解最大匹配問題的一個(gè)算法是匈牙利算法,下面講的概念都為這個(gè)算法服務(wù)。


交替路
:從一個(gè)未匹配點(diǎn)出發(fā),依次經(jīng)過非匹配邊、匹配邊、非匹配邊…形成的路徑叫交替路。

增廣路
:從一個(gè)未匹配點(diǎn)出發(fā),走交替路,如果途徑另一個(gè)未匹配點(diǎn)(出發(fā)的點(diǎn)不算),則這條交替路稱為增廣路(agumenting path)。例如,圖 5 中的一條增廣路如圖 6 所示(圖中的匹配點(diǎn)均用紅色標(biāo)出):


增廣路有一個(gè)重要特點(diǎn):非匹配邊比匹配邊多一條。因此,研究增廣路的意義是改進(jìn)匹配。只要把增廣路中的匹配邊和非匹配邊的身份交換即可。由于中間的匹配節(jié)點(diǎn)不存在其他相連的匹配邊,所以這樣做不會破壞匹配的性質(zhì)。交換后,圖中的匹配邊數(shù)目比原來多了 1 條。

我們可以通過不停地找增廣路來增加匹配中的匹配邊和匹配點(diǎn)。找不到增廣路時(shí),達(dá)到最大匹配(這是增廣路定理)。匈牙利算法正是這么做的。在給出匈牙利算法 DFS 和 BFS 版本的代碼之前,先講一下匈牙利樹。

匈牙利樹
一般由 BFS 構(gòu)造(類似于 BFS 樹)。從一個(gè)未匹配點(diǎn)出發(fā)運(yùn)行 BFS(唯一的限制是,必須走交替路),直到不能再擴(kuò)展為止。例如,由圖 7,可以得到如圖 8 的一棵 BFS 樹:

? ??
這棵樹存在一個(gè)葉子節(jié)點(diǎn)為非匹配點(diǎn)(7 號),但是匈牙利樹要求所有葉子節(jié)點(diǎn)均為匹配點(diǎn),因此這不是一棵匈牙利樹。如果原圖中根本不含 7 號節(jié)點(diǎn),那么從 2 號節(jié)點(diǎn)出發(fā)就會得到一棵匈牙利樹。這種情況如圖 9 所示(順便說一句,圖 8 中根節(jié)點(diǎn) 2 到非匹配葉子節(jié)點(diǎn) 7 顯然是一條增廣路,沿這條增廣路擴(kuò)充后將得到一個(gè)完美匹配)。

下面給出匈牙利算法的 DFS 和 BFS 版本的代碼:
   

// 頂點(diǎn)、邊的編號均從 0 開始// 鄰接表儲存
struct Edge{ int from; int to; int weight;
Edge(int f, int t, int w):from(f), to(t), weight(w) {}};
vector<int> G[__maxNodes]; /* G[i] 存儲頂點(diǎn) i 出發(fā)的邊的編號 */vector edges;typedef vector<int>::iterator iterator_t;int num_nodes;int num_left;int num_right;int num_edges;

   
int matching[__maxNodes]; /* 存儲求解結(jié)果 */int check[__maxNodes];bool dfs(int u){ for (iterator_t i = G[u].begin(); i != G[u].end(); ++i) { // 對 u 的每個(gè)鄰接點(diǎn) int v = edges[*i].to; if (!check[v]) { // 要求不在交替路中 check[v] = true; // 放入交替路 if (matching[v] == -1 || dfs(matching[v])) { // 如果是未蓋點(diǎn),說明交替路為增廣路,則交換路徑,并返回成功 matching[v] = u; matching[u] = v; return true; } } } return false; // 不存在增廣路,返回失敗}int hungarian(){ int ans = 0; memset(matching, -1, sizeof(matching)); for (int u=0; u < num_left; ++u) { if (matching[u] == -1) { memset(check, 0, sizeof(check)); if (dfs(u)) ++ans; } } return ans;}

queue<int>?Q;int prev[__maxNodes];int Hungarian(){ int ans = 0; memset(matching, -1, sizeof(matching)); memset(check, -1, sizeof(check)); for (int i=0; i if (matching[i] == -1) { while (!Q.empty()) Q.pop(); Q.push(i); prev[i] = -1; // 設(shè) i 為路徑起點(diǎn) bool flag = false; // 尚未找到增廣路 while (!Q.empty() && !flag) { int u = Q.front(); for (iterator_t ix = G[u].begin(); ix != G[u].end() && !flag; ++ix) { int v = edges[*ix].to; if (check[v] != i) { check[v] = i; Q.push(matching[v]); if (matching[v] >= 0) { // 此點(diǎn)為匹配點(diǎn) prev[matching[v]] = u; } else { // 找到未匹配點(diǎn),交替路變?yōu)樵鰪V路 flag = true; int d=u, e=v; while (d != -1) { int t = matching[d]; matching[d] = e; matching[e] = d; d = prev[d]; e = t; } } } } Q.pop(); } if (matching[i] != -1) ++ans; } } return ans;}

匈牙利算法的要點(diǎn)如下

  1. 從左邊第 1 個(gè)頂點(diǎn)開始,挑選未匹配點(diǎn)進(jìn)行搜索,尋找增廣路。
    1. 如果經(jīng)過一個(gè)未匹配點(diǎn),說明尋找成功。更新路徑信息,匹配邊數(shù) +1,停止搜索。
    2. 如果一直沒有找到增廣路,則不再從這個(gè)點(diǎn)開始搜索。事實(shí)上,此時(shí)搜索后會形成一棵匈牙利樹。我們可以永久性地把它從圖中刪去,而不影響結(jié)果。
  2. 由于找到增廣路之后需要沿著路徑更新匹配,所以我們需要一個(gè)結(jié)構(gòu)來記錄路徑上的點(diǎn)。DFS 版本通過函數(shù)調(diào)用隱式地使用一個(gè)棧,而 BFS 版本使用 prev 數(shù)組。

性能比較

兩個(gè)版本的時(shí)間復(fù)雜度均為O(V·E)。DFS 的優(yōu)點(diǎn)是思路清晰、代碼量少,但是性能不如 BFS。我測試了兩種算法的性能。對于稀疏圖,BFS 版本明顯快于 DFS 版本;而對于稠密圖兩者則不相上下。在完全隨機(jī)數(shù)據(jù) 9000 個(gè)頂點(diǎn) 4,0000 條邊時(shí)前者領(lǐng)先后者大約 97.6%,9000 個(gè)頂點(diǎn) 100,0000 條邊時(shí)前者領(lǐng)先后者 8.6%, 而達(dá)到 500,0000 條邊時(shí) BFS 僅領(lǐng)先 0.85%。
補(bǔ)充定義和定理:

最大匹配數(shù)
:最大匹配的匹配邊的數(shù)目

最小點(diǎn)覆蓋數(shù)
:選取最少的點(diǎn),使任意一條邊至少有一個(gè)端點(diǎn)被選擇

最大獨(dú)立數(shù)
:選取最多的點(diǎn),使任意所選兩點(diǎn)均不相連

最小路徑覆蓋數(shù)
:對于一個(gè) DAG(有向無環(huán)圖),選取最少條路徑,使得每個(gè)頂點(diǎn)屬于且僅屬于一條路徑。路徑長可以為 0(即單個(gè)點(diǎn))。

定理1:最大匹配數(shù) = 最小點(diǎn)覆蓋數(shù)(這是 Konig 定理)
定理2:最大匹配數(shù) = 最大獨(dú)立數(shù)

定理3:最小路徑覆蓋數(shù) = 頂點(diǎn)數(shù) - 最大匹配數(shù)

-END-

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉