- 定義指針變量,如果不賦給它地址,系統會隨機給它分配一個地址。
C++標準庫
C++ Standard Library,是類庫和函數的集合,其使用核心語言寫成,由c++標準委員會制定,并不斷維護更新。C++強大的功能來源于其豐富的類庫及庫函數資源。 C++標準庫(C++ Standard Library, 亦可稱作,C++標準程序庫)的內容總共在50個標準頭文件中定義。 在C++開發中,要盡可能地利用標準庫完成。這樣做的直接好處包括:
(1)成本:
已經作為標準提供,不必再花費時間、人力重新開發。
(2)質量:
標準庫的都是經過嚴格測試的,正確性有保證。
(3)效率:
關于人的效率已經體現在成本中了,關于代碼的執行效率要相信實現標準庫的前輩的水平。
(4)良好的編程風格:
C++模板
模板可以實現類型的參數化(把類型定義為參數),從而實現了真正的代碼可重用性。C++中的模板可分為函數模板和類模板,而把函數模板的具體化稱為模板函數,把類模板的具體化成為模板類。
哈希表
散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的[數組]叫做散列表。
給定表M,存在函數f(key),對任意給定的關鍵字值key,代入函數后若能得到包含該關鍵字的記錄在表中的地址,則稱表M為哈希(Hash)表,函數f(key)為哈希(Hash) 函數。
散列函數能使對一個數據序列的訪問過程更加迅速有效,通過散列函數,[數據元素]將被更快地定位。
遞歸
使用遞歸解決問題,思路清晰,代碼少。但是在主流高級語言中(如C語言、Pascal語言等)使用遞歸算法要耗用更多的棧空間,所以在堆棧尺寸受限制時(如嵌入式系統或者內核態編程),應避免采用。所有的遞歸算法都可以改寫成與之等價的非遞歸[算法]
雙向鏈表
雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數據結點中都有兩個[指針],分別指向直接后繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和后繼結點。一般我們都構造雙向[循環鏈表]
霍夫曼編碼
[哈夫曼]編碼(Huffman Coding),又稱霍夫曼編碼,是一種編碼方式,哈夫曼編碼是可變[字長]編碼(VLC)的一種。Huffman于1952年提出一種編碼方法,該方法完全依據[字符]出現概率來構造異字頭的平均長度最短的碼字,有時稱之為最佳編碼,一般就叫做Huffman編碼(有時也稱為霍夫曼編碼)。
在計算機數據處理中,霍夫曼編碼使用變長編碼表對源符號(如文件中的一個字母)進行編碼,其中變長編碼表是通過一種評估來源符號出現機率的方法得到的,出現機率高的字母使用較短的編碼,反之出現機率低的則使用較長的編碼,這便使編碼之后的字符串的平均長度、期望值降低,從而達到無損壓縮數據的目的。
例如,在英文中,e的出現機率最高,而z的出現概率則最低。當利用霍夫曼編碼對一篇英文進行壓縮時,e極有可能用一個比特來表示,而z則可能花去25個比特(不是26)。用普通的表示方法時,每個英文字母均占用一個字節(byte),即8個比特。二者相比,e使用了一般編碼的1/8的長度,z則使用了3倍多。倘若我們能實現對于英文中各個字母出現概率的較準確的估算,就可以大幅度提高無損壓縮的比例。
霍夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點的權值乘上其到根結點的路徑長度(若根結點為0層,葉結點到根結點的路徑長度為葉結點的層數)。樹的路徑長度是從樹根到每一結點的路徑長度之和,記為WPL=(W1L1+W2L2+W3L3+...+WnLn),N個權值Wi(i=1,2,...n)構成一棵有N個葉結點的二叉樹,相應的葉結點的路徑長度為Li(i=1,2,...n)。可以證明霍夫曼樹的WPL是最小的。
二叉樹
順序存儲結構用一組連續的存儲單元存放二叉樹中的結點。一般是按照二叉樹結點從上至下、從左到右的順序存儲。依據二叉樹的性質,完全二叉樹和滿二叉樹采用順序存儲比較合適,樹中結點的序號可以唯一地反映出結點之間的邏輯關系,這樣既能夠最大可能地節省存儲空間,又可以利用數組元素的下標值確定結點在二叉樹中的位置,以及結點之間的關系。
二叉樹的鏈式存儲結構是用鏈表來表示一棵二叉樹,即用鏈來指示著元素的邏輯關系。通常有下面兩種形式。
三叉鏈表存儲:其中,data、lchild以及rchild三個域的意義同二叉鏈表結構;parent域為指向該結點雙親結點的指針。
線索二叉樹的定義
按照某種遍歷方式對二叉樹進行遍歷,可以把二叉樹中所有結點排列為一個線性序列。在該序列中,除第一個結點外,每個結點有且僅有一個直接前驅結點;除最后一個結點外,每個結點有且僅有一個直接后繼結點。但是,二叉樹中每個結點在這個序列中的直接前驅結點和直接后繼結點是什么,二叉樹的存儲結構中并沒有反映出來,只能在對二叉樹遍歷的動態過程中得到這些信息。為了保留結點在某種遍歷序列中直接前驅和直接后繼的位置信息,可以利用二叉樹的二叉鏈表存儲結構中的那些空指針域來指示。這些指向直接前驅結點和指向直接后繼結點的指針被稱為線索(thread),加了線索的二叉樹稱為線索二叉樹。
Haffma Tree
?The optimal binary tree最優二叉樹,也稱哈夫曼(Haffman)樹,是指對于一組帶有確定權值的葉結點,構造的具有最小帶權路徑長度的二叉樹。
?那么什么是二叉樹的帶權路徑長度呢?
?在前面我們介紹過路徑和結點的路徑長度的概念,而二叉樹的路徑長度則是指由根結點到所有葉結點的路徑長度之和。如果二叉樹中的葉結點都具有一定的權值,則可將這一概念加以推廣。
具有相同葉結點和不同帶權路徑長度的二叉樹
由相同權值的一組葉結點所構成的二叉樹有不同的形態和不同的帶權路徑長度,那么如何找到帶權路徑長度最小的二叉樹呢?根據哈夫曼樹的定義,一棵二叉樹要使其WPL值最小,必須使權值越大的葉結點越靠近根結點,而權值越小的葉結點越遠離根結點。
?哈夫曼(Haffman)依據這一特點提出了一種方法。
哈夫曼樹的基本思想
⑴由給定的n個權值{W1,W2,…,Wn}構造n棵只有一個葉結點的二叉樹,從而得到一個二叉樹的集合F={T1,T2,…,Tn};
⑵在F中選取根結點的權值最小和次小的兩棵二叉樹作為左、右子樹構造一棵新的二叉樹,這棵新的二叉樹根結點的權值為其左、右子樹根結點權值之和;
⑶在集合F中刪除作為左、右子樹的兩棵二叉樹,并將新建立的二叉樹加入到集合F中;
⑷重復(2)、(3)兩步,當F中只剩下一棵二叉樹時,這棵二叉樹便是所要建立的哈夫曼樹。
哈夫曼樹可用于構造使電文的編碼總長最短的編碼方案。具體做法如下:設需要編碼的字符集合為{d1,d2,…,dn},它們在電文中出現的次數或頻率集合為{w1,w2,…,wn},以d1,d2,…,dn作為葉結點,w1,w2,…,wn作為它們的權值,構造一棵哈夫曼樹,規定哈夫曼樹中的左分支代表0,右分支代表1,則從根結點到每個葉結點所經過的路徑分支組成的0和1的序列便為該結點對應字符的編碼,我們稱之為哈夫曼編碼。
在哈夫曼編碼樹中,樹的帶權路徑長度的含義是各個字符的碼長與其出現次數的乘積之和,也就是電文的代碼總長,所以采用哈夫曼樹構造的編碼是一種能使電文代碼總長最短的不等長編碼。
實現哈夫曼編碼的算法可分為兩大部分:
(1)構造哈夫曼樹;
(2)在哈夫曼樹上求葉結點的編碼。
?求哈夫曼編碼,實質上就是在已建立的哈夫曼樹中,從葉結點開始,沿結點的雙親鏈域回退到根結點,每回退一步,就走過了哈夫曼樹的一個分支,從而得到一位哈夫曼碼值,由于一個字符的哈夫曼編碼是從根結點到相應葉結點所經過的路徑上各分支所組成的0,1序列,因此先得到的分支代碼為所求編碼的低位碼,后得到的分支代碼為所求編碼的高位碼。
哈希表
哈希法又稱散列法、雜湊法以及關鍵字地址計算法等,相應的表稱為哈希表。這種方法的基本思想是:首先在元素的關鍵字k和元素的存儲位置p之間建立一個對應關系f,使得p=f(k),f稱為哈希函數。創建哈希表時,把關鍵字為k的元素直接存入地址為f(k)的單元;以后當查找關鍵字為k的元素時,再利用哈希函數計算出該元素的存儲位置p=f(k),從而達到按關鍵字直接存取元素的目的。
當關鍵字集合很大時,關鍵字值不同的元素可能會映象到哈希表的同一地址上,即 k1≠k2 ,但 H(k1)=H(k2),這種現象稱為沖突,此時稱k1和k2為同義詞。實際中,沖突是不可避免的,只能通過改進哈希函數的性能來減少沖突。
綜上所述,哈希法主要包括以下兩方面的內容:
1)如何構造哈希函數
2)如何處理沖突。
8.4.1 哈希函數的構造方法
構造哈希函數的原則是:
①函數本身便于計算;
②計算出來的地址分布均勻,即對任一關鍵字k,f(k) 對應不同地址的概率相等,目的是盡可能減少沖突。
下面介紹構造哈希函數常用的五種方法。
1. 數字分析法
如果事先知道關鍵字集合,并且每個關鍵字的位數比哈希表的地址碼位數多時,可以從關鍵字中選出分布較均勻的若干位,構成哈希地址。例如,有80個記錄,關鍵字為8位十進制整數d1d2d3…d7d8,如哈希表長取100,則哈希表的地址空間為:00~99。假設經過分析,各關鍵字中 d4和d7的取值分布較均勻,則哈希函數為:h(key)=h(d1d2d3…d7d8)=d4d7。例如,h(81346532)=43,h(81301367)=06。相反,假設經過分析,各關鍵字中 d1和d8的取值分布極不均勻, d1 都等于5,d8 都等于2,此時,如果哈希函數為:h(key)=h(d1d2d3…d7d8)=d1d8,則所有關鍵字的地址碼都是52,顯然不可取。
2. 平方取中法 當無法確定關鍵字中哪幾位分布較均勻時,可以先求出關鍵字的平方值,然后按需要取平方值的中間幾位作為哈希地址。這是因為:平方后中間幾位和關鍵字中每一位都相關,故不同關鍵字會以較高的概率產生不同的哈希地址。
例:我們把英文字母在字母表中的位置序號作為該英文字母的內部編碼。例如K的內部編碼為11,E的內部編碼為05,Y的內部編碼為25,A的內部編碼為01, B的內部編碼為02。由此組成關鍵字“KEYA”的內部代碼為11052501,同理我們可以得到關鍵字“KYAB”、“AKEY”、“BKEY”的內部編碼。之后對關鍵字進行平方運算后,取出第7到第9位作為該關鍵字哈希地址,如圖8.23所示。
8.4.2 處理沖突的方法
通過構造性能良好的哈希函數,可以減少沖突,但一般不可能完全避免沖突,因此解決沖突是哈希法的另一個關鍵問題。創建哈希表和查找哈希表都會遇到沖突,兩種情況下解決沖突的方法應該一致。
下面以創建哈希表為例,說明解決沖突的方法。常用的解決沖突方法有以下四種:
1.開放定址法
這種方法也稱再散列法,其基本思想是:當關鍵字key的哈希地址p=H(key)出現沖突時,以p為基礎,產生另一個哈希地址p1,如果p1仍然沖突,再以p為基礎,產生另一個哈希地址p2,…,直到找出一個不沖突的哈希地址pi ,將相應元素存入其中。
這種方法有一個通用的再散列函數形式:
Hi=(H(key)+di)% m i=1,2,…,n
其中H(key)為哈希函數,m 為表長,di稱為增量序列。增量序列的取值方式不同,相應的再散列方式也不同。
主要有以下三種:
- 線性探測再散列 dii=1,2,3,…,m-1 這種方法的特點是:沖突發生時,順序查看表中下一單元,直到找出一個空單元或查遍全表。
- 二次探測再散列 di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 ) 這種方法的特點是:沖突發生時,在表的左右進行跳躍式探測,比較靈活。
- 偽隨機探測再散列 di=偽隨機數序列。 具體實現時,應建立一個偽隨機數發生器,(如i=(i+p) % m),并給定一個隨機數做起點。
例如,已知哈希表長度m=11,哈希函數為:H(key)= key % 11,則H(47)=3,H(26)=4,H(60)=5,假設下一個關鍵字為69,則H(69)=3,與47沖突。如果用線性探測再散列處理沖突,下一個哈希地址為H1=(3 + 1)% 11 = 4,仍然沖突,再找下一個哈希地址為H2=(3 + 2)% 11 = 5,還是沖突,繼續找下一個哈希地址為H3=(3 + 3)% 11 = 6,此時不再沖突,將69填入5號單元,參圖8.26 (a)。如果用二次探測再散列處理沖突,下一個哈希地址為H1=(3 + 12)% 11 = 4,仍然沖突,再找下一個哈希地址為H2=(3 - 12)% 11 = 2,此時不再沖突,將69填入2號單元,參圖8.26 (b)。如果用偽隨機探測再散列處理沖突,且偽隨機數序列為:2,5,9,……..,則下一個哈希地址為H1=(3 + 2)% 11 = 5,仍然沖突,再找下一個哈希地址為H2=(3 + 5)% 11 = 8,此時不再沖突,將69填入8號單元,參圖8.26 (c)。
2.再哈希法
這種方法是同時構造多個不同的哈希函數:
Hi=RH1(key) i=1,2,…,k 當哈希地址Hi=RH1(key)發生沖突時,再計算Hi=RH2(key)……,直到沖突不再產生。這種方法不易產生聚集,但增加了計算時間。
3.鏈地址法
這種方法的基本思想是將所有哈希地址為i的元素構成一個稱為同義詞鏈的單鏈表,并將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用于經常進行插入和刪除的情況。 例如,已知一組關鍵字(32,40,36,53,16,46,71,27,42,24,49,64),哈希表長度為13,哈希函數為:H(key)= key % 13,則用鏈地址法處理沖突的結果如圖8.27所示:
4、建立公共溢出區 這種方法的基本思想是:將哈希表分為基本表和溢出表兩部分,凡是和基本表發生沖突的元素,一律填入溢出表。
B樹
1.前言:
動態查找樹主要有:二叉查找樹(Binary Search Tree),平衡二叉查找樹(Balanced Binary Search Tree),紅黑樹。前三者是典型的二叉查找樹結構,其查找的時間復雜度O(log2N)與樹的深度相關,那么降低樹的深度自然會提高查找效率。
但是咱們有面對這樣一個實際問題:就是大規模數據存儲中,實現索引查詢這樣一個實際背景下,樹節點存儲的元素數量是有限的(如果元素數量非常多的話,查找就退化成節點內部的線性查找了),這樣導致二叉查找樹結構由于樹的深度過大而造成磁盤I/O讀寫過于頻繁,進而導致查詢效率低下(為什么會出現這種情況,待會在外部存儲器-磁盤中有所解釋),那么如何減少樹的深度(當然是不能減少查詢的數據量),一個基本的想法就是:采用多叉樹結構(由于樹節點元素數量是有限的,自然該節點的子樹數量也就是有限的)。
也就是說,因為磁盤的操作費時費資源,如果過于頻繁的多次查找勢必效率低下。那么如何提高效率,即如何避免磁盤過于頻繁的多次查找呢?根據磁盤查找存取的次數往往由樹的高度所決定,所以,只要我們通過某種較好的樹結構減少樹的結構盡量減少樹的高度,那么是不是便能有效減少磁盤查找存取的次數呢?那這種有效的樹結構是一種怎樣的樹呢?
這樣我們就提出了一個新的查找樹結構——多路查找樹。根據平衡二叉樹的啟發,自然就想到平衡多路查找樹結構,也就是這篇文章所要闡述的第一個主題B~tree,即B樹結構(后面,我們將看到,B樹的各種操作能使B樹保持較低的高度,從而達到有效避免磁盤過于頻繁的查找存取操作,從而有效提高查找效率)。
B-tree(B-tree樹即B樹,B即Balanced,平衡的意思)這棵神奇的樹是在Rudolf Bayer寫的一篇論文《Organization and Maintenance of Large Ordered Indices》中首次提出的,闡述了B-tree名字來源以及相關的開源地址)。
3.B- 樹
3.1什么是B-樹
具體講解之前,有一點,再次強調下:B-樹,即為B樹。因為B樹的原英文名稱為B-tree,而國內很多人喜歡把B-tree譯作B-樹,其實,這是個非常不好的直譯,很容易讓人產生誤解。如人們可能會以為B-樹是一種樹,而B樹又是一種一種樹。而事實上是,B-tree就是指的B樹。特此說明。
我們知道,B 樹是為了磁盤或其它存儲設備而設計的一種多叉(下面你會看到,相對于二叉,B樹每個內結點有多個分支,即多叉)平衡查找樹。與本blog之前介紹的紅黑樹很相似,但在降低磁盤I/0操作方面要更好一些。許多[數據庫]系統都一般使用B樹或者B樹的各種變形結構,如下文即將要介紹的B+樹,B樹來存儲信息。
B樹與紅黑樹最大的不同在于,B樹的結點可以有許多子女,從幾個到幾千個。那為什么又說B樹與紅黑樹很相似呢?因為與紅黑樹一樣,一棵含n個結點的B樹的高度也為O(lgn),但可能比一棵紅黑樹的高度小許多,應為它的分支因子比較大。所以,B樹可以在O(logn)時間內,實現各種如插入(insert),刪除(delete)等動態集合操作。
如下圖所示,即是一棵B樹,一棵關鍵字為英語中輔音字母的B樹,現在要從樹種查找字母R(包含n[x]個關鍵字的內結點x,x有n[x]+1]個子女(也就是說,一個內結點x若含有n[x]個關鍵字,那么x將含有n[x]+1個子女)。所有的葉結點都處于相同的深度,帶陰影的結點為查找字母R時要檢查的結點):
用階定義的B樹
B 樹又叫平衡多路查找樹。一棵m階的B 樹 (注:切勿簡單的認為一棵m階的B樹是m叉樹,雖然存在[四叉樹],[八叉樹],[KD][樹],及vp/R樹/R樹/R+樹/X樹/M樹/線段樹/希爾伯特R樹/優先R樹等空間劃分樹,但與B樹完全不等同)的特性如下*:
樹中每個結點最多含有m個孩子(m>=2);
除根結點和葉子結點外,其它每個結點至少有[ceil(m / 2)]個孩子(其中ceil(x)是一個取上限的函數);
若根結點不是葉子結點,則至少有2個孩子(特殊情況:沒有孩子的根結點,即根結點為葉子結點,整棵樹只有一個根節點);
所有葉子結點都出現在同一層,葉子結點不包含任何關鍵字信息(可以看做是外部接點或查詢失敗的接點,實際上這些結點不存在,指向這些結點的指針都為null);(讀者反饋@冷岳:這里有錯,葉子節點只是沒有孩子和指向孩子的指針,這些節點也存在,也有元素。@研究者July:其實,關鍵是把什么當做葉子結點,因為如紅黑樹中,每一個NULL指針即當做葉子結點,只是沒畫出來而已)。
每個非終端結點中包含有n個關鍵字信息: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中: a) Ki (i=1...n)為關鍵字,且關鍵字按順序升序排序K(i-1)< Ki。 b) Pi為指向子樹根的接點,且指針P(i-1)指向子樹種所有結點的關鍵字均小于Ki,但都大于K(i-1)。 c) 關鍵字的個數n必須滿足: [ceil(m / 2)-1]<= n <= m-1。如下圖所示:
平衡二叉樹
平衡二叉樹(Balanced Binary Tree)又被稱為AVL樹(有別于AVL算法),且具有以下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹。構造與調整方法 平衡二叉樹的常用[算法]有紅黑樹、AVL、Treap等。 最小二叉平衡樹的節點的公式如下 F(n)=F(n-1)+F(n-2)+1 這個類似于一個遞歸的[數列],可以參考Fibonacci數列,1是根節點,F(n-1)是左子樹的節點數量,F(n-2)是右子樹的節點數量。
我們知道,對于一般的二叉搜索樹(Binary Search Tree),其期望高度(即為一棵平衡樹時)為log2n,其各操作的時間復雜度(O(log2n))同時也由此而決定。但是,在某些極端的情況下(如在插入的序列是有序的時),二叉搜索樹將退化成近似鏈或鏈,此時,其操作的時間復雜度將退化成線性的,即O(n)。我們可以通過[隨機化]建立二叉搜索樹來盡量的避免這種情況,但是在進行了多次的操作之后,由于在刪除時,我們總是選擇將待刪除節點的后繼代替它本身,這樣就會造成總是右邊的節點數目減少,以至于樹向左偏沉。這同時也會造成樹的平衡性受到破壞,提高它的操作的[時間復雜度]
[平衡二叉搜索樹](Balanced Binary Tree)具有以下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹。常用算法有紅黑樹、AVL、Treap、伸展樹等。在平衡二叉搜索樹中,我們可以看到,其高度一般都良好地維持在O(log2n),大大降低了操作的時間復雜度。
度
子樹就是二叉樹的分支。度就是分支的數目。沒有分叉的二叉樹節點的度就是0度。如果一個節點只有一個分叉就是1度。兩個分叉就是2度的子樹。