無損數(shù)據(jù)壓縮是一件奇妙的事情,想一想,一串任意的數(shù)據(jù)能夠根據(jù)一定的規(guī)則轉(zhuǎn)換成只有原來 1/2 - 1/5 長度的數(shù)據(jù),并且能夠按照相應(yīng)的規(guī)則還原到原來的樣子,聽起來真是很酷。
半年前,苦熬過初學(xué) vc 時(shí)那段艱難的學(xué)習(xí)曲線的我,對 MFC、SDK 開始失望和不滿,這些雖然不算易學(xué),但和 DHTML 沒有實(shí)質(zhì)上的區(qū)別,都是調(diào)用微軟提供的各種各樣的函數(shù),不需要你自己去創(chuàng)建一個(gè)窗口,多線程編程時(shí),也不需要你自己去分配 CPU 時(shí)間。我也做過驅(qū)動(dòng),同樣,有DDK(微軟驅(qū)動(dòng)開發(fā)包),當(dāng)然,也有 DDK 的“參考手冊”,連一個(gè)最簡單的數(shù)據(jù)結(jié)構(gòu)都不需要你自己做,一切都是函數(shù)、函數(shù)……
微軟的高級程序員編寫了函數(shù)讓我們這些搞應(yīng)用的去調(diào)用,我不想在這里貶低搞應(yīng)用的人,正是這些應(yīng)用工程師連接起了科學(xué)和社會之間的橋梁,將來可以做銷售,做管理,用自己逐漸積累起來的智慧和經(jīng)驗(yàn)在社會上打拼。
但是,在技術(shù)上來說,誠實(shí)地說,這并不高深,不是嗎?第一流的公司如微軟、Sybase、Oracle 等總是面向社會大眾的,這樣才能有巨大的市場。但是他們往往也是站在社會的最頂層的:操作系統(tǒng)、編譯器、數(shù)據(jù)庫都值得一代代的專家去不斷研究。這些帝國般的企業(yè)之所以偉大,恐怕不是“有經(jīng)驗(yàn)”、“能吃苦”這些中國特色的概念所能涵蓋的,艱深的技術(shù)體系、現(xiàn)代的管理哲學(xué)、強(qiáng)大的市場能力都是缺一不可的吧。我們既然有志于技術(shù),并且正在起步階段,何必急不可耐地要轉(zhuǎn)去做“管理”,做“青年才俊”,那些所謂的“成功人士”的根底能有幾何,這樣子浮躁,胸中的規(guī)模和格局能有多大?
在我發(fā)現(xiàn)vc只是一個(gè)用途廣泛的編程工具,并不能代表“知識”、“技術(shù)”的時(shí)候,我有些失落,無所不能的不是我,而是 MFC、SDK、DDK,是微軟的工程師,他們做的,正是我想做的,或者說,我也想成為那種層次的人,現(xiàn)在我知道了,他們是專家,但這不會是一個(gè)夢,有一天我會做到的,為什么不能說出我的想法呢。
那時(shí)公司做的系統(tǒng)里有一個(gè)壓縮模塊,領(lǐng)導(dǎo)找了一個(gè) zlib 庫,不讓我自己做壓縮算法,站在公司的立場上,我很理解,真的很理解,自己做算法要多久啊。但那時(shí)自己心中隱藏的一份倔強(qiáng)驅(qū)使我去尋找壓縮原理的資料,我完全沒有意識到,我即將打開一扇大門,進(jìn)入一個(gè)神奇的“數(shù)據(jù)結(jié)構(gòu)”的世界。“計(jì)算機(jī)藝術(shù)”的第一線陽光,居然也照到了我這樣一個(gè)平凡的人的身上。
上面說到“計(jì)算機(jī)藝術(shù)”,或者進(jìn)一步細(xì)化說“計(jì)算機(jī)編程藝術(shù)”,聽起來很深?yuàn)W,很高雅,但是在將要進(jìn)入專業(yè)的壓縮算法的研究時(shí),我要請大家做的第一件事情是:忘掉自己的年齡、學(xué)歷,忘掉自己的社會身份,忘掉編程語言,忘掉“面向?qū)ο蟆薄ⅰ叭龑蛹軜?gòu)”等一切術(shù)語。把自己當(dāng)作一個(gè)小孩,有一雙求知的眼睛,對世界充滿不倦的、單純的好奇,唯一的前提是一個(gè)正常的具有人類理性思維能力的大腦。
下面就讓我們開始一段神奇的壓縮算法之旅吧:
1. 原理部分:
有兩種形式的重復(fù)存在于計(jì)算機(jī)數(shù)據(jù)中,zip 就是對這兩種重復(fù)進(jìn)行了壓縮。
一種是短語形式的重復(fù),即三個(gè)字節(jié)以上的重復(fù),對于這種重復(fù),zip用兩個(gè)數(shù)字:1.重復(fù)位置距當(dāng)前壓縮位置的距離;2.重復(fù)的長度,來表示這個(gè)重復(fù),假設(shè)這兩個(gè)數(shù)字各占一個(gè)字節(jié),于是數(shù)據(jù)便得到了壓縮,這很容易理解。
一個(gè)字節(jié)有 0 - 255 共 256 種可能的取值,三個(gè)字節(jié)有 256 * 256 * 256 共一千六百多萬種可能的情況,更長的短語取值的可能情況以指數(shù)方式增長,出現(xiàn)重復(fù)的概率似乎極低,實(shí)則不然,各種類型的數(shù)據(jù)都有出現(xiàn)重復(fù)的傾向,一篇論文中,為數(shù)不多的術(shù)語傾向于重復(fù)出現(xiàn);一篇小說,人名和地名會重復(fù)出現(xiàn);一張上下漸變的背景圖片,水平方向上的像素會重復(fù)出現(xiàn);程序的源文件中,語法關(guān)鍵字會重復(fù)出現(xiàn)(我們寫程序時(shí),多少次前后copy、paste?),以幾十 K 為單位的非壓縮格式的數(shù)據(jù)中,傾向于大量出現(xiàn)短語式的重復(fù)。經(jīng)過上面提到的方式進(jìn)行壓縮后,短語式重復(fù)的傾向被完全破壞,所以在壓縮的結(jié)果上進(jìn)行第二次短語式壓縮一般是沒有效果的。
第二種重復(fù)為單字節(jié)的重復(fù),一個(gè)字節(jié)只有256種可能的取值,所以這種重復(fù)是必然的。其中,某些字節(jié)出現(xiàn)次數(shù)可能較多,另一些則較少,在統(tǒng)計(jì)上有分布不均勻的傾向,這是容易理解的,比如一個(gè) ASCII 文本文件中,某些符號可能很少用到,而字母和數(shù)字則使用較多,各字母的使用頻率也是不一樣的,據(jù)說字母 e 的使用概率最高;許多圖片呈現(xiàn)深色調(diào)或淺色調(diào),深色(或淺色)的像素使用較多(這里順便提一下:png 圖片格式是一種無損壓縮,其核心算法就是 zip 算法,它和 zip 格式的文件的主要區(qū)別在于:作為一種圖片格式,它在文件頭處存放了圖片的大小、使用的顏色數(shù)等信息);上面提到的短語式壓縮的結(jié)果也有這種傾向:重復(fù)傾向于出現(xiàn)在離當(dāng)前壓縮位置較近的地方,重復(fù)長度傾向于比較短(20字節(jié)以內(nèi))。這樣,就有了壓縮的可能:給 256 種字節(jié)取值重新編碼,使出現(xiàn)較多的字節(jié)使用較短的編碼,出現(xiàn)較少的字節(jié)使用較長的編碼,這樣一來,變短的字節(jié)相對于變長的字節(jié)更多,文件的總長度就會減少,并且,字節(jié)使用比例越不均勻,壓縮比例就越大。
在進(jìn)一步討論編碼的要求以及辦法前,先提一下:編碼式壓縮必須在短語式壓縮之后進(jìn)行,因?yàn)榫幋a式壓縮后,原先八位二進(jìn)制值的字節(jié)就被破壞了,這樣文件中短語式重復(fù)的傾向也會被破壞(除非先進(jìn)行解碼)。另外,短語式壓縮后的結(jié)果:那些剩下的未被匹配的單、雙字節(jié)和得到匹配的距離、長度值仍然具有取值分布不均勻性,因此,兩種壓縮方式的順序不能變。
在編碼式壓縮后,以連續(xù)的八位作為一個(gè)字節(jié),原先未壓縮文件中所具有的字節(jié)取值不均勻的傾向被徹底破壞,成為隨機(jī)性取值,根據(jù)統(tǒng)計(jì)學(xué)知識,隨機(jī)性取值具有均勻性的傾向(比如拋硬幣試驗(yàn),拋一千次,正反面朝上的次數(shù)都接近于 500 次)。因此,編碼式壓縮后的結(jié)果無法再進(jìn)行編碼式壓縮。
短語式壓縮和編碼式壓縮是目前計(jì)算機(jī)科學(xué)界研究出的僅有的兩種無損壓縮方法,它們都無法重復(fù)進(jìn)行,所以,壓縮文件無法再次壓縮(實(shí)際上,能反復(fù)進(jìn)行的壓縮算法是不可想象的,因?yàn)樽罱K會壓縮到 0 字節(jié))。
=====================================
(補(bǔ)充)
壓縮文件無法再次壓縮是因?yàn)椋?br />1. 短語式壓縮去掉了三個(gè)字節(jié)以上的重復(fù),壓縮后的結(jié)果中包含的是未匹配的單雙字節(jié),和匹配距離、長度的組合。這個(gè)結(jié)果當(dāng)然仍然可能包含三個(gè)字節(jié)以上的重復(fù),但是概率極低。因?yàn)槿齻€(gè)字節(jié)有 256 * 256 * 256 共一千六百多萬種可能的情況,一千六百萬分之一的概率導(dǎo)致匹配的距離很長,需要二進(jìn)制數(shù)24位來表示這個(gè)匹配距離,再加上匹配長度就超過了三個(gè)字節(jié),得不償失。所以只能壓縮掉原始文件中“自然存在的,并非隨機(jī)的短語式重復(fù)傾向”。
2.編碼式壓縮利用各個(gè)單字節(jié)使用頻率不一樣的傾向,使定長編碼變?yōu)椴欢ㄩL編碼,給使用頻率高的字節(jié)更短的編碼,使用頻率低的字節(jié)更長的編碼,起到壓縮的效果。如果把編碼式壓縮的“結(jié)果”按照8位作為1字節(jié),重新統(tǒng)計(jì)各字節(jié)的使用頻率,應(yīng)該是大致相等的。因?yàn)樾碌淖止?jié)使用頻率是隨機(jī)的。相等的頻率再去變換字節(jié)長短是沒有意義的,因?yàn)樽兌痰淖止?jié)沒有比變長的字節(jié)更多。
=======================================
短語式重復(fù)的傾向和字節(jié)取值分布不均勻的傾向是可以壓縮的基礎(chǔ),兩種壓縮的順序不能互換的原因也說了,下面我們來看編碼式壓縮的要求及方法:
首先,為了使用不定長的編碼表示單個(gè)字符,編碼必須符合“前綴編碼”的要求,即較短的編碼決不能是較長編碼的前綴,反過來說就是,任何一個(gè)字符的編碼,都不是由另一個(gè)字符的編碼加上若干位 0 或 1 組成,否則解壓縮程序?qū)o法解碼。
看一下前綴編碼的一個(gè)最簡單的例子:
符號 編碼
A 0
B 10
C 110
D 1110
E 11110
有了上面的碼表,你一定可以輕松地從下面這串二進(jìn)制流中分辨出真正的信息內(nèi)容了:
1110010101110110111100010 - DABBDCEAAB
要構(gòu)造符合這一要求的二進(jìn)制編碼體系,二叉樹是最理想的選擇。考察下面這棵二叉樹:
根(root)
0 | 1
+-------+--------+
0 | 1 0 | 1
+-----+------+ +----+----+
| | | |
a | d e
0 | 1
+-----+-----+
| |
b c
要編碼的字符總是出現(xiàn)在樹葉上,假定從根向樹葉行走的過程中,左轉(zhuǎn)為0,右轉(zhuǎn)為1,則一個(gè)字符的編碼就是從根走到該字符所在樹葉的路徑。正因?yàn)樽址荒艹霈F(xiàn)在樹葉上,任何一個(gè)字符的路徑都不會是另一字符路徑的前綴路徑,符合要求的前綴編碼也就構(gòu)造成功了:
a - 00 b - 010 c - 011 d - 10 e - 11
接下來來看編碼式壓縮的過程:
為了簡化問題,假定一個(gè)文件中只出現(xiàn)了 a,b,c,d ,e五種字符,它們的出現(xiàn)次數(shù)分別是
a : 6次
b : 15次
c : 2次
d : 9次
e : 1次
如果用定長的編碼方式為這四種字符編碼: a : 000 b : 001 c : 010 d : 011 e : 100
那么整個(gè)文件的長度是 3*6 + 3*15 + 3*2 + 3*9 + 3*1 = 99
用二叉樹表示這四種編碼(其中葉子節(jié)點(diǎn)上的數(shù)字是其使用次數(shù),非葉子節(jié)點(diǎn)上的數(shù)字是其左右孩子使用次數(shù)之和):
根
|
+---------33---------+
| |
+----32---+ +----1---+
| | | |
+-21-+ +-11-+ +--1--+
| | | | | |
6 15 2 9 1
(如果某個(gè)節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn),可以去掉這個(gè)子節(jié)點(diǎn)。)
根
|
+------33------+
| |
+-----32----+ 1
| |
+--21--+ +--11--+
| | | |
6 15 2 9
現(xiàn)在的編碼是: a : 000 b : 001 c : 010 d : 011 e : 1 仍然符合“前綴編碼”的要求。
第一步:如果發(fā)現(xiàn)下層節(jié)點(diǎn)的數(shù)字大于上層節(jié)點(diǎn)的數(shù)字,就交換它們的位置,并重新計(jì)算非葉子節(jié)點(diǎn)的值。
先交換11和1,由于11個(gè)字節(jié)縮短了一位,1個(gè)字節(jié)增長了一位,總文件縮短了10位。
根
|
+----------33---------+
| |
+-----22----+ +----11----+
| | | |
+--21--+ 1 2 9
| |
6 15
再交換15和1、6和2,最終得到這樣的樹:
根
|
+----------33---------+
| |
+-----18----+ +----15----+
| | | |
+--3--+ 15 6 9
| |
2 1
這時(shí)所有上層節(jié)點(diǎn)的數(shù)值都大于下層節(jié)點(diǎn)的數(shù)值,似乎無法再進(jìn)一步壓縮了。但是我們把每一層的最小的兩個(gè)節(jié)點(diǎn)結(jié)合起來,常會發(fā)現(xiàn)仍有壓縮余地。
第二步:把每一層的最小的兩個(gè)節(jié)點(diǎn)結(jié)合起來,重新計(jì)算相關(guān)節(jié)點(diǎn)的值。
在上面的樹中,第一、二、四三層都只有一或二個(gè)節(jié)點(diǎn),無法重新組合,但第三層上有四個(gè)節(jié)點(diǎn),我們把最小的3和6結(jié)合起來,并重新計(jì)算相關(guān)節(jié)點(diǎn)的值,成為下面這棵樹。
根
|
+----------33---------+
| |
+------9-----+ +----24----+
| | | |
+--3--+ 6 15 9
| |
2 1
然后,再重復(fù)做第一步。
這時(shí)第二層的9小于第三層的15,于是可以互換,有9個(gè)字節(jié)增長了一位,15個(gè)字節(jié)縮短了一位,文件總長度又縮短了6位。然后重新計(jì)算相關(guān)節(jié)點(diǎn)的值。
根
|
+----------33---------+
| |
15 +----18----+
| |
+------9-----+ 9
| |
+--3--+ 6
| |
2 1
這時(shí)發(fā)現(xiàn)所有的上層節(jié)點(diǎn)都大于下層節(jié)點(diǎn),每一層上最小的兩個(gè)節(jié)點(diǎn)被并在了一起,也不可能再產(chǎn)生比同層其他節(jié)點(diǎn)更小的父節(jié)點(diǎn)了。
這時(shí)整個(gè)文件的長度是 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63
這時(shí)可以看出編碼式壓縮的一個(gè)基本前提:各節(jié)點(diǎn)之間的值要相差比較懸殊,以使某兩個(gè)節(jié)點(diǎn)的和小于同層或下層的另一個(gè)節(jié)點(diǎn),這樣,交換節(jié)點(diǎn)才有利益。
所以歸根結(jié)底,原始文件中的字節(jié)使用頻率必須相差較大,否則將沒有兩個(gè)節(jié)點(diǎn)的頻率之和小于同層或下層其他節(jié)點(diǎn)的頻率,也就無法壓縮。反之,相差得越懸殊,兩個(gè)節(jié)點(diǎn)的頻率之和比同層或下層節(jié)點(diǎn)的頻率小得越多,交換節(jié)點(diǎn)之后的利益也越大。
在這個(gè)例子中,經(jīng)過上面兩步不斷重復(fù),得到了最優(yōu)的二叉樹,但不能保證在所有情況下,都能通過這兩步的重復(fù)得到最優(yōu)二叉樹,下面來看另一個(gè)例子:
根
|
+---------19--------+
| |
+------12------+ 7
| |
+---5---+ +---7---+
| | | |
+-2-+ +-3-+ +-3-+ +-4-+
| | | | | | | |
1 1 1 2 1 2 2 2
這個(gè)例子中,所有上層節(jié)點(diǎn)都大于等于下層節(jié)點(diǎn),每一層最小的兩個(gè)節(jié)點(diǎn)結(jié)合在了一起,但仍然可以進(jìn)一步優(yōu)化:
根
|
+---------19--------+
| |
+------12------+ 7
| |
+---4---+ +---8---+
| | | |
+-2-+ +-2-+ +-4-+ +-4-+
| | | | | | | |
1 1 1 1 2 2 2 2
通過最低一層的第4第5個(gè)節(jié)點(diǎn)對換,第3層的8大于第2層的7。
到這里,我們得出這樣一個(gè)結(jié)論:一棵最優(yōu)二叉編碼樹(所有上層節(jié)點(diǎn)都無法和下層節(jié)點(diǎn)交換),必須符合這樣兩個(gè)條件:
1.所有上層節(jié)點(diǎn)都大于等于下層節(jié)點(diǎn)。
2.某節(jié)點(diǎn),設(shè)其較大的子節(jié)點(diǎn)為m,較小的子節(jié)點(diǎn)為n,m下的任一層的所有節(jié)點(diǎn)都應(yīng)大于等于n下的該層的所有節(jié)點(diǎn)。
當(dāng)符合這兩個(gè)條件時(shí),任一層都無法產(chǎn)生更小的節(jié)點(diǎn)去和下層節(jié)點(diǎn)交換,也無法產(chǎn)生更大的節(jié)點(diǎn)去和上層節(jié)點(diǎn)交換。
上面的兩個(gè)例子是比較簡單的,實(shí)際的文件中,一個(gè)字節(jié)有256種可能的取值,所以二叉樹的葉子節(jié)點(diǎn)多達(dá)256個(gè),需要不斷的調(diào)整樹形,最終的樹形可能非常復(fù)雜,有一種非常精巧的算法可以快速地建起一棵最優(yōu)二叉樹,這種算法由D.Huffman(戴·霍夫曼)提出,下面我們先來介紹霍夫曼算法的步驟,然后再來證明通過這么簡單的步驟得出的樹形確實(shí)是一棵最優(yōu)二叉樹。
霍夫曼算法的步驟是這樣的:
·從各個(gè)節(jié)點(diǎn)中找出最小的兩個(gè)節(jié)點(diǎn),給它們建一個(gè)父節(jié)點(diǎn),值為這兩個(gè)節(jié)點(diǎn)之和。
·然后從節(jié)點(diǎn)序列中去除這兩個(gè)節(jié)點(diǎn),加入它們的父節(jié)點(diǎn)到序列中。
重復(fù)上面兩個(gè)步驟,直到節(jié)點(diǎn)序列中只剩下唯一一個(gè)節(jié)點(diǎn)。這時(shí)一棵最優(yōu)二叉樹就已經(jīng)建成了,它的根就是剩下的這個(gè)節(jié)點(diǎn)。
仍以上面的例子來看霍夫曼樹的建立過程。
最初的節(jié)點(diǎn)序列是這樣的:
a(6) b(15) c(2) d(9) e(1)
把最小的c和e結(jié)合起來
| (3)
a(6) b(15) d(9) +------+------+
| |
c e
不斷重復(fù),最終得到的樹是這樣的:
根
|
+-----33-----+
| |
15 +----18----+
| |
9 +------9-----+
| |
6 +--3--+
| |
2 1
這時(shí)各個(gè)字符的編碼長度和前面我們說過的方法得到的編碼長度是相同的,因而文件的總長度也是相同的: 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63
考察霍夫曼樹的建立過程中的每一步的節(jié)點(diǎn)序列的變化:
6 15 2 9 1
6 15 9 3
15 9 9
15 18
33
下面我們用逆推法來證明對于各種不同的節(jié)點(diǎn)序列,用霍夫曼算法建立起來的樹總是一棵最優(yōu)二叉樹:
對霍夫曼樹的建立過程運(yùn)用逆推法:
當(dāng)這個(gè)過程中的節(jié)點(diǎn)序列只有兩個(gè)節(jié)點(diǎn)時(shí)(比如前例中的15和18),肯定是一棵最優(yōu)二叉樹,一個(gè)編碼為0,另一個(gè)編碼為1,無法再進(jìn)一步優(yōu)化。
然后往前步進(jìn),節(jié)點(diǎn)序列中不斷地減少一個(gè)節(jié)點(diǎn),增加兩個(gè)節(jié)點(diǎn),在步進(jìn)過程中將始終保持是一棵最優(yōu)二叉樹,這是因?yàn)椋?br />1.按照霍夫曼樹的建立過程,新增的兩個(gè)節(jié)點(diǎn)是當(dāng)前節(jié)點(diǎn)序列中最小的兩個(gè),其他的任何兩個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)都大于(或等于)這兩個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn),只要前一步是最優(yōu)二叉樹,其他的任何兩個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)就一定都處在它們的父節(jié)點(diǎn)的上層或同層,所以這兩個(gè)節(jié)點(diǎn)一定處在當(dāng)前二叉樹的最低一層。
2.這兩個(gè)新增的節(jié)點(diǎn)是最小的,所以無法和其他上層節(jié)點(diǎn)對換。符合我們前面說的最優(yōu)二叉樹的第一個(gè)條件。
3.只要前一步是最優(yōu)二叉樹,由于這兩個(gè)新增的節(jié)點(diǎn)是最小的,即使同層有其他節(jié)點(diǎn),也無法和同層其他節(jié)點(diǎn)重新結(jié)合,產(chǎn)生比它們的父節(jié)點(diǎn)更小的上層節(jié)點(diǎn)來和同層的其他節(jié)點(diǎn)對換。它們的父節(jié)點(diǎn)小于其他節(jié)點(diǎn)的父節(jié)點(diǎn),它們又小于其他所有節(jié)點(diǎn),只要前一步符合最優(yōu)二叉樹的第二個(gè)條件,到這一步仍將符合。
這樣一步步逆推下去,在這個(gè)過程中霍夫曼樹每一步都始終保持著是一棵最優(yōu)二叉樹。
由于每一步都從節(jié)點(diǎn)序列中刪除兩個(gè)節(jié)點(diǎn),新增一個(gè)節(jié)點(diǎn),霍夫曼樹的建立過程共需 (原始節(jié)點(diǎn)數(shù) - 1) 步,所以霍夫曼算法不失為一種精巧的編碼式壓縮算法。
附:對于 huffman 樹,《計(jì)算機(jī)程序設(shè)計(jì)藝術(shù)》中有完全不同的證明,大意是這樣的:
1.二叉編碼樹的內(nèi)部節(jié)點(diǎn)(非葉子節(jié)點(diǎn))數(shù)等于外部節(jié)點(diǎn)(葉子節(jié)點(diǎn))數(shù)減1。
2.二叉編碼樹的外部節(jié)點(diǎn)的加權(quán)路徑長度(值乘以路徑長度)之和,等于所有內(nèi)部節(jié)點(diǎn)值之和。(這兩條都可以通過對節(jié)點(diǎn)數(shù)運(yùn)用數(shù)學(xué)歸納法來證明,留給大家做練習(xí)。)
3.對 huffman 樹的建立過程運(yùn)用逆推,當(dāng)只有一個(gè)內(nèi)部節(jié)點(diǎn)時(shí),肯定是一棵最優(yōu)二叉樹。
4.往前步進(jìn),新增兩個(gè)最小的外部節(jié)點(diǎn),它們結(jié)合在一起產(chǎn)生一個(gè)新的內(nèi)部節(jié)點(diǎn),當(dāng)且僅當(dāng)原先的內(nèi)部節(jié)點(diǎn)集合是極小化的,加入這個(gè)新的內(nèi)部節(jié)點(diǎn)后仍是極小化的。(因?yàn)樽钚〉膬蓚€(gè)節(jié)點(diǎn)結(jié)合在一起,并處于最低層,相對于它們分別和其他同層或上層節(jié)點(diǎn)結(jié)合在一起,至少不會增加加權(quán)路徑長度。)
5.隨著內(nèi)部節(jié)點(diǎn)數(shù)逐個(gè)增加,內(nèi)部節(jié)點(diǎn)集合總維持極小化。
[ 本帖由 ncs 最后編輯于 2006-3-3 14:54 ]
半年前,苦熬過初學(xué) vc 時(shí)那段艱難的學(xué)習(xí)曲線的我,對 MFC、SDK 開始失望和不滿,這些雖然不算易學(xué),但和 DHTML 沒有實(shí)質(zhì)上的區(qū)別,都是調(diào)用微軟提供的各種各樣的函數(shù),不需要你自己去創(chuàng)建一個(gè)窗口,多線程編程時(shí),也不需要你自己去分配 CPU 時(shí)間。我也做過驅(qū)動(dòng),同樣,有DDK(微軟驅(qū)動(dòng)開發(fā)包),當(dāng)然,也有 DDK 的“參考手冊”,連一個(gè)最簡單的數(shù)據(jù)結(jié)構(gòu)都不需要你自己做,一切都是函數(shù)、函數(shù)……
微軟的高級程序員編寫了函數(shù)讓我們這些搞應(yīng)用的去調(diào)用,我不想在這里貶低搞應(yīng)用的人,正是這些應(yīng)用工程師連接起了科學(xué)和社會之間的橋梁,將來可以做銷售,做管理,用自己逐漸積累起來的智慧和經(jīng)驗(yàn)在社會上打拼。
但是,在技術(shù)上來說,誠實(shí)地說,這并不高深,不是嗎?第一流的公司如微軟、Sybase、Oracle 等總是面向社會大眾的,這樣才能有巨大的市場。但是他們往往也是站在社會的最頂層的:操作系統(tǒng)、編譯器、數(shù)據(jù)庫都值得一代代的專家去不斷研究。這些帝國般的企業(yè)之所以偉大,恐怕不是“有經(jīng)驗(yàn)”、“能吃苦”這些中國特色的概念所能涵蓋的,艱深的技術(shù)體系、現(xiàn)代的管理哲學(xué)、強(qiáng)大的市場能力都是缺一不可的吧。我們既然有志于技術(shù),并且正在起步階段,何必急不可耐地要轉(zhuǎn)去做“管理”,做“青年才俊”,那些所謂的“成功人士”的根底能有幾何,這樣子浮躁,胸中的規(guī)模和格局能有多大?
在我發(fā)現(xiàn)vc只是一個(gè)用途廣泛的編程工具,并不能代表“知識”、“技術(shù)”的時(shí)候,我有些失落,無所不能的不是我,而是 MFC、SDK、DDK,是微軟的工程師,他們做的,正是我想做的,或者說,我也想成為那種層次的人,現(xiàn)在我知道了,他們是專家,但這不會是一個(gè)夢,有一天我會做到的,為什么不能說出我的想法呢。
那時(shí)公司做的系統(tǒng)里有一個(gè)壓縮模塊,領(lǐng)導(dǎo)找了一個(gè) zlib 庫,不讓我自己做壓縮算法,站在公司的立場上,我很理解,真的很理解,自己做算法要多久啊。但那時(shí)自己心中隱藏的一份倔強(qiáng)驅(qū)使我去尋找壓縮原理的資料,我完全沒有意識到,我即將打開一扇大門,進(jìn)入一個(gè)神奇的“數(shù)據(jù)結(jié)構(gòu)”的世界。“計(jì)算機(jī)藝術(shù)”的第一線陽光,居然也照到了我這樣一個(gè)平凡的人的身上。
上面說到“計(jì)算機(jī)藝術(shù)”,或者進(jìn)一步細(xì)化說“計(jì)算機(jī)編程藝術(shù)”,聽起來很深?yuàn)W,很高雅,但是在將要進(jìn)入專業(yè)的壓縮算法的研究時(shí),我要請大家做的第一件事情是:忘掉自己的年齡、學(xué)歷,忘掉自己的社會身份,忘掉編程語言,忘掉“面向?qū)ο蟆薄ⅰ叭龑蛹軜?gòu)”等一切術(shù)語。把自己當(dāng)作一個(gè)小孩,有一雙求知的眼睛,對世界充滿不倦的、單純的好奇,唯一的前提是一個(gè)正常的具有人類理性思維能力的大腦。
下面就讓我們開始一段神奇的壓縮算法之旅吧:
1. 原理部分:
有兩種形式的重復(fù)存在于計(jì)算機(jī)數(shù)據(jù)中,zip 就是對這兩種重復(fù)進(jìn)行了壓縮。
一種是短語形式的重復(fù),即三個(gè)字節(jié)以上的重復(fù),對于這種重復(fù),zip用兩個(gè)數(shù)字:1.重復(fù)位置距當(dāng)前壓縮位置的距離;2.重復(fù)的長度,來表示這個(gè)重復(fù),假設(shè)這兩個(gè)數(shù)字各占一個(gè)字節(jié),于是數(shù)據(jù)便得到了壓縮,這很容易理解。
一個(gè)字節(jié)有 0 - 255 共 256 種可能的取值,三個(gè)字節(jié)有 256 * 256 * 256 共一千六百多萬種可能的情況,更長的短語取值的可能情況以指數(shù)方式增長,出現(xiàn)重復(fù)的概率似乎極低,實(shí)則不然,各種類型的數(shù)據(jù)都有出現(xiàn)重復(fù)的傾向,一篇論文中,為數(shù)不多的術(shù)語傾向于重復(fù)出現(xiàn);一篇小說,人名和地名會重復(fù)出現(xiàn);一張上下漸變的背景圖片,水平方向上的像素會重復(fù)出現(xiàn);程序的源文件中,語法關(guān)鍵字會重復(fù)出現(xiàn)(我們寫程序時(shí),多少次前后copy、paste?),以幾十 K 為單位的非壓縮格式的數(shù)據(jù)中,傾向于大量出現(xiàn)短語式的重復(fù)。經(jīng)過上面提到的方式進(jìn)行壓縮后,短語式重復(fù)的傾向被完全破壞,所以在壓縮的結(jié)果上進(jìn)行第二次短語式壓縮一般是沒有效果的。
第二種重復(fù)為單字節(jié)的重復(fù),一個(gè)字節(jié)只有256種可能的取值,所以這種重復(fù)是必然的。其中,某些字節(jié)出現(xiàn)次數(shù)可能較多,另一些則較少,在統(tǒng)計(jì)上有分布不均勻的傾向,這是容易理解的,比如一個(gè) ASCII 文本文件中,某些符號可能很少用到,而字母和數(shù)字則使用較多,各字母的使用頻率也是不一樣的,據(jù)說字母 e 的使用概率最高;許多圖片呈現(xiàn)深色調(diào)或淺色調(diào),深色(或淺色)的像素使用較多(這里順便提一下:png 圖片格式是一種無損壓縮,其核心算法就是 zip 算法,它和 zip 格式的文件的主要區(qū)別在于:作為一種圖片格式,它在文件頭處存放了圖片的大小、使用的顏色數(shù)等信息);上面提到的短語式壓縮的結(jié)果也有這種傾向:重復(fù)傾向于出現(xiàn)在離當(dāng)前壓縮位置較近的地方,重復(fù)長度傾向于比較短(20字節(jié)以內(nèi))。這樣,就有了壓縮的可能:給 256 種字節(jié)取值重新編碼,使出現(xiàn)較多的字節(jié)使用較短的編碼,出現(xiàn)較少的字節(jié)使用較長的編碼,這樣一來,變短的字節(jié)相對于變長的字節(jié)更多,文件的總長度就會減少,并且,字節(jié)使用比例越不均勻,壓縮比例就越大。
在進(jìn)一步討論編碼的要求以及辦法前,先提一下:編碼式壓縮必須在短語式壓縮之后進(jìn)行,因?yàn)榫幋a式壓縮后,原先八位二進(jìn)制值的字節(jié)就被破壞了,這樣文件中短語式重復(fù)的傾向也會被破壞(除非先進(jìn)行解碼)。另外,短語式壓縮后的結(jié)果:那些剩下的未被匹配的單、雙字節(jié)和得到匹配的距離、長度值仍然具有取值分布不均勻性,因此,兩種壓縮方式的順序不能變。
在編碼式壓縮后,以連續(xù)的八位作為一個(gè)字節(jié),原先未壓縮文件中所具有的字節(jié)取值不均勻的傾向被徹底破壞,成為隨機(jī)性取值,根據(jù)統(tǒng)計(jì)學(xué)知識,隨機(jī)性取值具有均勻性的傾向(比如拋硬幣試驗(yàn),拋一千次,正反面朝上的次數(shù)都接近于 500 次)。因此,編碼式壓縮后的結(jié)果無法再進(jìn)行編碼式壓縮。
短語式壓縮和編碼式壓縮是目前計(jì)算機(jī)科學(xué)界研究出的僅有的兩種無損壓縮方法,它們都無法重復(fù)進(jìn)行,所以,壓縮文件無法再次壓縮(實(shí)際上,能反復(fù)進(jìn)行的壓縮算法是不可想象的,因?yàn)樽罱K會壓縮到 0 字節(jié))。
=====================================
(補(bǔ)充)
壓縮文件無法再次壓縮是因?yàn)椋?br />1. 短語式壓縮去掉了三個(gè)字節(jié)以上的重復(fù),壓縮后的結(jié)果中包含的是未匹配的單雙字節(jié),和匹配距離、長度的組合。這個(gè)結(jié)果當(dāng)然仍然可能包含三個(gè)字節(jié)以上的重復(fù),但是概率極低。因?yàn)槿齻€(gè)字節(jié)有 256 * 256 * 256 共一千六百多萬種可能的情況,一千六百萬分之一的概率導(dǎo)致匹配的距離很長,需要二進(jìn)制數(shù)24位來表示這個(gè)匹配距離,再加上匹配長度就超過了三個(gè)字節(jié),得不償失。所以只能壓縮掉原始文件中“自然存在的,并非隨機(jī)的短語式重復(fù)傾向”。
2.編碼式壓縮利用各個(gè)單字節(jié)使用頻率不一樣的傾向,使定長編碼變?yōu)椴欢ㄩL編碼,給使用頻率高的字節(jié)更短的編碼,使用頻率低的字節(jié)更長的編碼,起到壓縮的效果。如果把編碼式壓縮的“結(jié)果”按照8位作為1字節(jié),重新統(tǒng)計(jì)各字節(jié)的使用頻率,應(yīng)該是大致相等的。因?yàn)樾碌淖止?jié)使用頻率是隨機(jī)的。相等的頻率再去變換字節(jié)長短是沒有意義的,因?yàn)樽兌痰淖止?jié)沒有比變長的字節(jié)更多。
=======================================
短語式重復(fù)的傾向和字節(jié)取值分布不均勻的傾向是可以壓縮的基礎(chǔ),兩種壓縮的順序不能互換的原因也說了,下面我們來看編碼式壓縮的要求及方法:
首先,為了使用不定長的編碼表示單個(gè)字符,編碼必須符合“前綴編碼”的要求,即較短的編碼決不能是較長編碼的前綴,反過來說就是,任何一個(gè)字符的編碼,都不是由另一個(gè)字符的編碼加上若干位 0 或 1 組成,否則解壓縮程序?qū)o法解碼。
看一下前綴編碼的一個(gè)最簡單的例子:
符號 編碼
A 0
B 10
C 110
D 1110
E 11110
有了上面的碼表,你一定可以輕松地從下面這串二進(jìn)制流中分辨出真正的信息內(nèi)容了:
1110010101110110111100010 - DABBDCEAAB
要構(gòu)造符合這一要求的二進(jìn)制編碼體系,二叉樹是最理想的選擇。考察下面這棵二叉樹:
根(root)
0 | 1
+-------+--------+
0 | 1 0 | 1
+-----+------+ +----+----+
| | | |
a | d e
0 | 1
+-----+-----+
| |
b c
要編碼的字符總是出現(xiàn)在樹葉上,假定從根向樹葉行走的過程中,左轉(zhuǎn)為0,右轉(zhuǎn)為1,則一個(gè)字符的編碼就是從根走到該字符所在樹葉的路徑。正因?yàn)樽址荒艹霈F(xiàn)在樹葉上,任何一個(gè)字符的路徑都不會是另一字符路徑的前綴路徑,符合要求的前綴編碼也就構(gòu)造成功了:
a - 00 b - 010 c - 011 d - 10 e - 11
接下來來看編碼式壓縮的過程:
為了簡化問題,假定一個(gè)文件中只出現(xiàn)了 a,b,c,d ,e五種字符,它們的出現(xiàn)次數(shù)分別是
a : 6次
b : 15次
c : 2次
d : 9次
e : 1次
如果用定長的編碼方式為這四種字符編碼: a : 000 b : 001 c : 010 d : 011 e : 100
那么整個(gè)文件的長度是 3*6 + 3*15 + 3*2 + 3*9 + 3*1 = 99
用二叉樹表示這四種編碼(其中葉子節(jié)點(diǎn)上的數(shù)字是其使用次數(shù),非葉子節(jié)點(diǎn)上的數(shù)字是其左右孩子使用次數(shù)之和):
根
|
+---------33---------+
| |
+----32---+ +----1---+
| | | |
+-21-+ +-11-+ +--1--+
| | | | | |
6 15 2 9 1
(如果某個(gè)節(jié)點(diǎn)只有一個(gè)子節(jié)點(diǎn),可以去掉這個(gè)子節(jié)點(diǎn)。)
根
|
+------33------+
| |
+-----32----+ 1
| |
+--21--+ +--11--+
| | | |
6 15 2 9
現(xiàn)在的編碼是: a : 000 b : 001 c : 010 d : 011 e : 1 仍然符合“前綴編碼”的要求。
第一步:如果發(fā)現(xiàn)下層節(jié)點(diǎn)的數(shù)字大于上層節(jié)點(diǎn)的數(shù)字,就交換它們的位置,并重新計(jì)算非葉子節(jié)點(diǎn)的值。
先交換11和1,由于11個(gè)字節(jié)縮短了一位,1個(gè)字節(jié)增長了一位,總文件縮短了10位。
根
|
+----------33---------+
| |
+-----22----+ +----11----+
| | | |
+--21--+ 1 2 9
| |
6 15
再交換15和1、6和2,最終得到這樣的樹:
根
|
+----------33---------+
| |
+-----18----+ +----15----+
| | | |
+--3--+ 15 6 9
| |
2 1
這時(shí)所有上層節(jié)點(diǎn)的數(shù)值都大于下層節(jié)點(diǎn)的數(shù)值,似乎無法再進(jìn)一步壓縮了。但是我們把每一層的最小的兩個(gè)節(jié)點(diǎn)結(jié)合起來,常會發(fā)現(xiàn)仍有壓縮余地。
第二步:把每一層的最小的兩個(gè)節(jié)點(diǎn)結(jié)合起來,重新計(jì)算相關(guān)節(jié)點(diǎn)的值。
在上面的樹中,第一、二、四三層都只有一或二個(gè)節(jié)點(diǎn),無法重新組合,但第三層上有四個(gè)節(jié)點(diǎn),我們把最小的3和6結(jié)合起來,并重新計(jì)算相關(guān)節(jié)點(diǎn)的值,成為下面這棵樹。
根
|
+----------33---------+
| |
+------9-----+ +----24----+
| | | |
+--3--+ 6 15 9
| |
2 1
然后,再重復(fù)做第一步。
這時(shí)第二層的9小于第三層的15,于是可以互換,有9個(gè)字節(jié)增長了一位,15個(gè)字節(jié)縮短了一位,文件總長度又縮短了6位。然后重新計(jì)算相關(guān)節(jié)點(diǎn)的值。
根
|
+----------33---------+
| |
15 +----18----+
| |
+------9-----+ 9
| |
+--3--+ 6
| |
2 1
這時(shí)發(fā)現(xiàn)所有的上層節(jié)點(diǎn)都大于下層節(jié)點(diǎn),每一層上最小的兩個(gè)節(jié)點(diǎn)被并在了一起,也不可能再產(chǎn)生比同層其他節(jié)點(diǎn)更小的父節(jié)點(diǎn)了。
這時(shí)整個(gè)文件的長度是 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63
這時(shí)可以看出編碼式壓縮的一個(gè)基本前提:各節(jié)點(diǎn)之間的值要相差比較懸殊,以使某兩個(gè)節(jié)點(diǎn)的和小于同層或下層的另一個(gè)節(jié)點(diǎn),這樣,交換節(jié)點(diǎn)才有利益。
所以歸根結(jié)底,原始文件中的字節(jié)使用頻率必須相差較大,否則將沒有兩個(gè)節(jié)點(diǎn)的頻率之和小于同層或下層其他節(jié)點(diǎn)的頻率,也就無法壓縮。反之,相差得越懸殊,兩個(gè)節(jié)點(diǎn)的頻率之和比同層或下層節(jié)點(diǎn)的頻率小得越多,交換節(jié)點(diǎn)之后的利益也越大。
在這個(gè)例子中,經(jīng)過上面兩步不斷重復(fù),得到了最優(yōu)的二叉樹,但不能保證在所有情況下,都能通過這兩步的重復(fù)得到最優(yōu)二叉樹,下面來看另一個(gè)例子:
根
|
+---------19--------+
| |
+------12------+ 7
| |
+---5---+ +---7---+
| | | |
+-2-+ +-3-+ +-3-+ +-4-+
| | | | | | | |
1 1 1 2 1 2 2 2
這個(gè)例子中,所有上層節(jié)點(diǎn)都大于等于下層節(jié)點(diǎn),每一層最小的兩個(gè)節(jié)點(diǎn)結(jié)合在了一起,但仍然可以進(jìn)一步優(yōu)化:
根
|
+---------19--------+
| |
+------12------+ 7
| |
+---4---+ +---8---+
| | | |
+-2-+ +-2-+ +-4-+ +-4-+
| | | | | | | |
1 1 1 1 2 2 2 2
通過最低一層的第4第5個(gè)節(jié)點(diǎn)對換,第3層的8大于第2層的7。
到這里,我們得出這樣一個(gè)結(jié)論:一棵最優(yōu)二叉編碼樹(所有上層節(jié)點(diǎn)都無法和下層節(jié)點(diǎn)交換),必須符合這樣兩個(gè)條件:
1.所有上層節(jié)點(diǎn)都大于等于下層節(jié)點(diǎn)。
2.某節(jié)點(diǎn),設(shè)其較大的子節(jié)點(diǎn)為m,較小的子節(jié)點(diǎn)為n,m下的任一層的所有節(jié)點(diǎn)都應(yīng)大于等于n下的該層的所有節(jié)點(diǎn)。
當(dāng)符合這兩個(gè)條件時(shí),任一層都無法產(chǎn)生更小的節(jié)點(diǎn)去和下層節(jié)點(diǎn)交換,也無法產(chǎn)生更大的節(jié)點(diǎn)去和上層節(jié)點(diǎn)交換。
上面的兩個(gè)例子是比較簡單的,實(shí)際的文件中,一個(gè)字節(jié)有256種可能的取值,所以二叉樹的葉子節(jié)點(diǎn)多達(dá)256個(gè),需要不斷的調(diào)整樹形,最終的樹形可能非常復(fù)雜,有一種非常精巧的算法可以快速地建起一棵最優(yōu)二叉樹,這種算法由D.Huffman(戴·霍夫曼)提出,下面我們先來介紹霍夫曼算法的步驟,然后再來證明通過這么簡單的步驟得出的樹形確實(shí)是一棵最優(yōu)二叉樹。
霍夫曼算法的步驟是這樣的:
·從各個(gè)節(jié)點(diǎn)中找出最小的兩個(gè)節(jié)點(diǎn),給它們建一個(gè)父節(jié)點(diǎn),值為這兩個(gè)節(jié)點(diǎn)之和。
·然后從節(jié)點(diǎn)序列中去除這兩個(gè)節(jié)點(diǎn),加入它們的父節(jié)點(diǎn)到序列中。
重復(fù)上面兩個(gè)步驟,直到節(jié)點(diǎn)序列中只剩下唯一一個(gè)節(jié)點(diǎn)。這時(shí)一棵最優(yōu)二叉樹就已經(jīng)建成了,它的根就是剩下的這個(gè)節(jié)點(diǎn)。
仍以上面的例子來看霍夫曼樹的建立過程。
最初的節(jié)點(diǎn)序列是這樣的:
a(6) b(15) c(2) d(9) e(1)
把最小的c和e結(jié)合起來
| (3)
a(6) b(15) d(9) +------+------+
| |
c e
不斷重復(fù),最終得到的樹是這樣的:
根
|
+-----33-----+
| |
15 +----18----+
| |
9 +------9-----+
| |
6 +--3--+
| |
2 1
這時(shí)各個(gè)字符的編碼長度和前面我們說過的方法得到的編碼長度是相同的,因而文件的總長度也是相同的: 3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63
考察霍夫曼樹的建立過程中的每一步的節(jié)點(diǎn)序列的變化:
6 15 2 9 1
6 15 9 3
15 9 9
15 18
33
下面我們用逆推法來證明對于各種不同的節(jié)點(diǎn)序列,用霍夫曼算法建立起來的樹總是一棵最優(yōu)二叉樹:
對霍夫曼樹的建立過程運(yùn)用逆推法:
當(dāng)這個(gè)過程中的節(jié)點(diǎn)序列只有兩個(gè)節(jié)點(diǎn)時(shí)(比如前例中的15和18),肯定是一棵最優(yōu)二叉樹,一個(gè)編碼為0,另一個(gè)編碼為1,無法再進(jìn)一步優(yōu)化。
然后往前步進(jìn),節(jié)點(diǎn)序列中不斷地減少一個(gè)節(jié)點(diǎn),增加兩個(gè)節(jié)點(diǎn),在步進(jìn)過程中將始終保持是一棵最優(yōu)二叉樹,這是因?yàn)椋?br />1.按照霍夫曼樹的建立過程,新增的兩個(gè)節(jié)點(diǎn)是當(dāng)前節(jié)點(diǎn)序列中最小的兩個(gè),其他的任何兩個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)都大于(或等于)這兩個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn),只要前一步是最優(yōu)二叉樹,其他的任何兩個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)就一定都處在它們的父節(jié)點(diǎn)的上層或同層,所以這兩個(gè)節(jié)點(diǎn)一定處在當(dāng)前二叉樹的最低一層。
2.這兩個(gè)新增的節(jié)點(diǎn)是最小的,所以無法和其他上層節(jié)點(diǎn)對換。符合我們前面說的最優(yōu)二叉樹的第一個(gè)條件。
3.只要前一步是最優(yōu)二叉樹,由于這兩個(gè)新增的節(jié)點(diǎn)是最小的,即使同層有其他節(jié)點(diǎn),也無法和同層其他節(jié)點(diǎn)重新結(jié)合,產(chǎn)生比它們的父節(jié)點(diǎn)更小的上層節(jié)點(diǎn)來和同層的其他節(jié)點(diǎn)對換。它們的父節(jié)點(diǎn)小于其他節(jié)點(diǎn)的父節(jié)點(diǎn),它們又小于其他所有節(jié)點(diǎn),只要前一步符合最優(yōu)二叉樹的第二個(gè)條件,到這一步仍將符合。
這樣一步步逆推下去,在這個(gè)過程中霍夫曼樹每一步都始終保持著是一棵最優(yōu)二叉樹。
由于每一步都從節(jié)點(diǎn)序列中刪除兩個(gè)節(jié)點(diǎn),新增一個(gè)節(jié)點(diǎn),霍夫曼樹的建立過程共需 (原始節(jié)點(diǎn)數(shù) - 1) 步,所以霍夫曼算法不失為一種精巧的編碼式壓縮算法。
附:對于 huffman 樹,《計(jì)算機(jī)程序設(shè)計(jì)藝術(shù)》中有完全不同的證明,大意是這樣的:
1.二叉編碼樹的內(nèi)部節(jié)點(diǎn)(非葉子節(jié)點(diǎn))數(shù)等于外部節(jié)點(diǎn)(葉子節(jié)點(diǎn))數(shù)減1。
2.二叉編碼樹的外部節(jié)點(diǎn)的加權(quán)路徑長度(值乘以路徑長度)之和,等于所有內(nèi)部節(jié)點(diǎn)值之和。(這兩條都可以通過對節(jié)點(diǎn)數(shù)運(yùn)用數(shù)學(xué)歸納法來證明,留給大家做練習(xí)。)
3.對 huffman 樹的建立過程運(yùn)用逆推,當(dāng)只有一個(gè)內(nèi)部節(jié)點(diǎn)時(shí),肯定是一棵最優(yōu)二叉樹。
4.往前步進(jìn),新增兩個(gè)最小的外部節(jié)點(diǎn),它們結(jié)合在一起產(chǎn)生一個(gè)新的內(nèi)部節(jié)點(diǎn),當(dāng)且僅當(dāng)原先的內(nèi)部節(jié)點(diǎn)集合是極小化的,加入這個(gè)新的內(nèi)部節(jié)點(diǎn)后仍是極小化的。(因?yàn)樽钚〉膬蓚€(gè)節(jié)點(diǎn)結(jié)合在一起,并處于最低層,相對于它們分別和其他同層或上層節(jié)點(diǎn)結(jié)合在一起,至少不會增加加權(quán)路徑長度。)
5.隨著內(nèi)部節(jié)點(diǎn)數(shù)逐個(gè)增加,內(nèi)部節(jié)點(diǎn)集合總維持極小化。
[ 本帖由 ncs 最后編輯于 2006-3-3 14:54 ]
搜索更多相關(guān)主題的帖子: zip 原理 ?
|
? |
|
? |
|


