哈夫曼編碼與哈夫曼算法
哈弗曼編碼的目的是,如何用更短的bit來編碼數據。
通過變長編碼壓縮編碼長度。我們知道普通的編碼都是定長的,比如常用的ASCII編碼,每個字符都是8個bit。但在很多情況下,數據文件中的字符出現的概率是不均勻的,比如在一篇英語文章中,字母“E”出現的頻率最高,“Z”最低,這時我們可以使用不定長的bit編碼,頻率高的字母用比較短的編碼表示,頻率低的字母用長的編碼表示。
但這就要求編碼要符合“前綴編碼”的要求,即較短的編碼不能是任何較長的編碼的前綴,這樣解析的時候才不會混淆。要生成這種編碼,最方便的就是用二叉樹,把要編碼的字符放在二叉樹的葉子上,所有的左節點是0,右節點是1,從根瀏覽到葉子上,因為字符只能出現在樹葉上,任何一個字符的路徑都不會是另一字符路徑的前綴路徑,符合前綴原則編碼就可以得到。
現在我們可以開始考慮壓縮的問題,如果有一篇只包含這五個字符的文章,而這幾個字符的出現的次數如下:
A: 6次
B : 15次
C: 2次
D : 9次
E: 1次
用過用定長的編碼,每個字符3bit,這篇文章總長度為:3*6 + 3*15 + 3*2 + 3*9 + 3*1 = 99
而用上面用二叉樹生成的編碼,總長度為: 2*6 + 3*15 + 2*2 + 2*9 + 2*1 = 80
字符 |
編碼 |
A |
00 |
B |
010 |
C |
011 |
D |
10 |
E |
11 |
顯然,這顆樹還可以進一步優化,使得編碼更短,比如下面的編碼
生成的數據長度為:3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63
下面我們先來介紹哈弗曼算法是如何構造最優二叉樹的。
哈夫曼算法的步驟是這樣的:
- 從各個節點中找出最小的兩個節點,給它們建一個父節點,值為這兩個節點之和。
- 然后從節點序列中去除這兩個節點,加入它們的父節點到序列中。
- 重復上面兩個步驟,直到節點序列中只剩下唯一一個節點。這時一棵最優二叉樹就已經建成了,它的根就是剩下的這個節點。
比如上面的例子,哈弗曼樹建立的過程如下:
1) 列出原始的節點數據:
2) 將最小的兩個節點C和E結合起來:
3) 再將新的節點和A組合起來
4) 再將D節點加入
5) 如此循環,最終得到一個最優二叉樹
生成的數據文件長度為: 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63
下面我們用逆推法來證明對於各種不同的節點序列,用哈弗曼算法建立起來的樹總是一棵最優二叉樹:
- 當這個過程中的節點序列只有兩個節點時(比如前例中的15和18),肯定是一棵最優二叉樹,一個編碼為0,另一個編碼為1,無法再進一步優化。
- 然后往前步進,節點序列中不斷地減少一個節點,增加兩個節點,在步進過程中將始終保持是一棵最優二叉樹,這是因為:
- 按照哈弗曼樹的建立過程,新增的兩個節點是當前節點序列中最小的兩個,其他的任何兩個節點的父節點都大於(或等於)這兩個節點的父節點,只要前一步是最優 二叉樹,其他的任何兩個節點的父節點就一定都處在它們的父節點的上層或同層,所以這兩個節點一定處在當前二叉樹的最低一層。
- 這兩個新增的節點是最小的,所以無法和其他上層節點對換。符合我們前面說的最優二叉樹的第一個條件。
- 只要前一步是最優二叉樹,由於這兩個新增的節點是最小的,即使同層有其他節點,也無法和同層其他節點重新結合,產生比它們的父節點更小的上層節點來和同層 的其他節點對換。它們的父節點小於其他節點的父節點,它們又小於其他所有節點,只要前一步符合最優二叉樹的第二個條件,到這一步仍將符合。
這樣一步步逆推下去,在這個過程中哈弗曼樹每一步都始終保持着是一棵最優二叉樹。
Dijstra單源最短路徑算法
單元最短路徑:給定頂點到其它任一頂點的最短路徑
Dijstra單源最短路徑算法的基本思想是,設置頂點集合S並不斷地作貪心選擇來擴充這個集合。一個頂點屬於集合S當且僅當從源到該頂點的最短路徑長度已知。
初始時,S中僅含有源。設u是G的某一個頂點,把從源到u且中間只經過S中頂點的路稱為從源到u的特殊路徑,並用數組dist記錄當前每個頂點所對應的最短特殊路徑長度。Dijkstra算法每次從V-S中取出具有最短特殊路長度的頂點u,將u添加到S中,同時對數組dist作必要的修改。一旦S包含了所有V中頂點,dist就記錄了從源到所有其它頂點之間的最短路徑長度。
例如,對下圖中的有向圖,應用Dijkstra算法計算從源頂點1到其它頂點間最短路徑的過程列在下表中。
Dijkstra算法的迭代過程:
迭代 |
S |
u |
dist[2] |
dist[3] |
dist[4] |
dist[5] |
初始 |
{1} |
- |
10 |
maxint |
30 |
100 |
1 |
{1,2} |
2 |
10 |
60 |
30 |
100 |
2 |
{1,2,4} |
4 |
10 |
50 |
30 |
90 |
3 |
{1,2,4,3} |
3 |
10 |
50 |
30 |
60 |
4 |
{1,2,4,3,5} |
5 |
10 |
50 |
30 |
60 |
***Floyd算法求兩個頂點間的最短距離***
Floyd算法的基本思想如下:從任意節點A到任意節點B的最短路徑不外乎2種可能,1是直接從A到B,2是從A經過若干個節點X到B。所以,我們假設Dis(AB)為節點A到節點B的最短路徑的距離,對於每一個節點X,我們檢查Dis(AX) + Dis(XB) < Dis(AB)是否成立,如果成立,證明從A到X再到B的路徑比A直接到B的路徑短,我們便設置Dis(AB) = Dis(AX) + Dis(XB),這樣一來,當我們遍歷完所有節點X,Dis(AB)中記錄的便是A到B的最短路徑的距離。
Floyd算法由三個for循環構成,所以時間復雜度為O(n3)
最小費用最大流
對於流f,每次選擇最小費用增廣鏈進行改進,直到不存在增廣鏈為止。這樣的得到的最大流必然是費用最小的。
增廣鏈,指某可行流上,沿着從始點到終點的某條鏈上每條弧的前向弧都大於零。也稱為“可改進流”
假如有這么一條路,這條路從源點(Source點)開始一直一段一段的連到了匯點(Sink點),並且,這條路上的每一段都滿足流量 < 容量。那么,我們一定能找到這條路上的每一段的(容量-流量)的值當中的最小值delta。我們把這條路上每一段的流量都加上這個delta,一定可以保證這個流依然是可行流。這樣我們就得到了一個更大的流,他的流量是之前的流量+delta,而這條路就叫做增廣路徑。
最大流最小割定理(max flow/min cut theory): 任意一個流網絡的最大流量等於該網絡的最小割。
什么是割?對於一個圖中的兩個節點來說,如果把圖中的一些邊去掉(流量減為0),剛好讓他們之間無法連通的話,這些被去掉的邊組成的集合就叫做割了,最小割就是指所有割中權重之和最小(流量減少最小)的一個割。
最小費用最大流
若流f滿足:
(a) 流量V(f)最大。
(b) 滿足a的前提下,流的費用Cost(f) = 最小。
就稱f是網絡流圖G的最小費用最大流。