查找
查找的基本概念
查找——在數據集合中尋找滿足某種條件的數據元素的過程稱為查找
查找表(查找結構)——用於查找的數據集合稱為查找表,它由同一類型的數據元素(或記錄)組成
關鍵字——數據元素中唯一標識該元素的某個數據項的值,使用基於關鍵字的查找,查找結果應該時唯一的。
查找長度——在查找運算中,需要對比關鍵字的次數稱為查找長度
平均查找長度(ASL)——所有查找過程中進行關鍵字的比較次數的平均值
順序查找
算法思想
順序查找,又叫“線性查找”,通常用於線性表
算法思想:從頭到jio挨個找
順序查找的實現
typedef struct{
ElemType* elem;
int TableLen;
}SSTable;
//順序查找
int Search_Seq(SSTable ST,ELemtype key)
{
int i;
for(i=0;i<ST.TableLen&&ST.elem[i]!=key;++i);
//查找成功,則返回元素下標,查找失敗,則返回-1
return i==ST.TableLen?-1:i;
}
//順序查找的實現(哨兵)
//優點:無需判斷是否越界
int Search_Seq(SSTable ST,ELemtype key)
{
ST.elem[0]=key; //“哨兵”
int i;
for(i=ST.TableLen;ST.elem[i]!=key;--i);//從后往前
//查找成功,則返回元素下標,查找失敗,則返回0
return i;
}
查找效率分析
順序查找的優化(對有序表)
順序查找的優化(被查概論不相等)
折半查找
算法思想
折半查找,又稱“二分查找”,僅適用於有序的順序表
typedef struct{
ElemType* elem;
int TableLen;
}SSTable;
//折半查找
int Binary_Search(SSTable L,ElemType key)
{
int low=0,high=L.TableLen-1,mid;
while(low<=high)
{
mid=(low+high)/2;
if(L.elem[mid]==key)
return mid; //查找成功則返回所在位置
else if(L.elem[mid]>key)
high=mid-1; //前半部分
else
low=mid+1; //后半部分
}
return -1; //查找失敗,返回-1
}
查找效率分析
折半查找判定樹的構造
折半查找判斷樹種,若\(mid=\lfloor(low+high)/2\rfloor\),則對於任何一個結點,必有:\(右子樹結點數-左子樹結點數=0或1\)
折半查找的判定樹一定是平衡二叉排序樹
折半查找的判定樹中,只有最下面一層時不滿的,因此,元素個數為n時樹高\(h=\lceil log_2(n+1) \rceil\)
查找效率
分塊查找
算法思想
//索引表
typedef struct{
ElemType maxValue;
int low,high;
}Index;
//順序表存儲實際元素
ElemType List[100];
分塊查找,又稱索引順序查找,算法過程如下:
- 在索引表中確定待查記錄所屬的分塊(可順序,可折半)
- 在塊內順序查找
查找效率分析(ASL)
設索引查找和塊內擦杭州的平均查找長度分別為\(L_I,L_s,\)則分塊查找的平均查找長度為\(ASL=L_I+L_S\)
用順序查找索引表,則\(L_I=\frac{(1+2+3+...+n)}{b}=\frac{b+1}{2}\),\(L_S=\frac{(1+2+3+...+n)}{s}=\frac{s+1}{2}\)
則\(ASL=\frac{b+1}{2}=\frac{S+1}{2}=\frac{s^2+2s+n}{2s}\),當\(S=\sqrt n\)時,\(ASL_{最小}=\sqrt n+1\)
用折半查找索引表,則\(L_I=\lceil log_2(b+1) \rceil,L_S=\frac{(1+2+3+...+n)}{s}=\frac{s+1}{2}\)
則\(ASL=\lceil log_2(b+1) \rceil+\frac{s+1}{2}\)
拓展
B樹
B樹,又稱多路平衡查找樹,B樹種所有結點的孩子個數的最大值稱為B樹的3,通常用m表示。一顆m階B樹或為空樹,或為滿足如下特性的m叉樹:
-
樹中每個結點至多有m課子樹,即至多含有m-1個關鍵字
-
若根節點不是終點結點,則至少有兩顆子樹
-
除根結點外的所有非葉結點至少有\(\lceil m/2 \rceil\)子樹,即至少含有\(\lceil m/2 \rceil-1\)個關鍵字
-
所有葉結點都出現在同一層次上,並且不帶信息(可以視為外部結點或類似於折半查找判定樹的查找失敗結點,實際上這些結點不存在,指向這些結點的指針為空)。
-
所有非葉結點的結構如下:
其中,\(k_i(i=1,2,...,n)\)為結點的關鍵字i,且滿足\(K_,<k_2<K_n\);\(P_i(i=0,1,...,n)\)為指向子樹根結點的指針,且指針\(P_{i-1}\)所指子樹中所有結點的關鍵字均小於\(k_i\),\(P_{i-}\)所指樹中所有結點的關鍵字均大於\(k_i\),\(n(\lceil m/2 \rceil-1\le n\le m-1)\)為結點中關鍵字的個數
B樹的高度
最小高度——讓每個結點盡可能的滿,有\(m-1\)個關鍵字,\(m\)個分叉,則有\(n\le(m-1)(1+m+m^2+m^3+...+m^{h-1})=m^h-1\),因此\(h\ge log_m(n+1)\)
最大高度——讓各層的分叉盡可能的少,即根節點只有2個分叉,其他節點只有\(\lceil m/2 \rceil\)個分叉
各層結點至少有:第一層 1,第二層 2,第三層 2\(\lceil m/2 \rceil\)...第h層\(2(\lceil m/2 \rceil)^{h-2}\)
第\(h+1\)層共有葉子結點(失敗點):\(2(\lceil m/2 \rceil)^{h-1}\)個
n個關鍵字的B樹必有n+1個葉子結點,則\(n+1>2(\lceil m/2 \rceil)^{h-1}\),即\(h\le log_{\lceil m/2 \rceil}\frac{n+1}{2}+1\)
B樹的核心特性
B的插入
核心要求:
- 對m階B樹——除根節點外,結點關鍵字個數\(\lceil m/2 \rceil \le n\le m-1\)
- 子樹0<關鍵字<子樹1<關鍵字2<子樹2<...
新元素一定時插入到最底層“終端節點”,用“查找”來確定插入位置
在插入key后,若導致原結點關鍵字超過上次,則從中間位置(\(\lceil m/2 \rceil\))將其中的關鍵字分為兩部分,左部分包含的關鍵字放在原結點中,右部分包含的關機按放到新結點中,中間位置(\(\lceil m/2 \rceil\))的結點插入原結點的父結點。若此時導致其父結點的關鍵字個數葉超過了上限,則繼續進行這種分裂操作,直到這個過程傳到根結點位置,進而導致B樹高度增1。
B樹刪除
終端節點:則直接刪除該關鍵字(要注意節點關鍵字個數是否低於下限\(\lceil m/2 \rceil-1\))
非終端節點:
- 兄弟夠借:若被刪除關鍵字所在節點刪除前的關鍵字個數低於下限,且此結點右(左)兄弟結點的關鍵字個數還很寬裕,則需要調整該結點,右(左)兄弟結點及其雙親結點(父子換位法)
- 右兄弟寬裕:用當前結點的后繼,后繼的后繼來填補空缺
- 左兄弟寬裕:用當前結點的前驅,后繼的前驅來填補空缺
- 兄弟不夠借:若被刪除關鍵字所在節點刪除前的關鍵字個數低於下限,且此結點右(左)兄弟結點的關鍵字個數均為\(\lceil m/2 \rceil-1\),則將關鍵字刪除后與左(右)兄弟結點及雙親結點中的關鍵字進行合並。
- 在合並過程中,雙親結點中的關鍵字個數會減1。若其雙親結點是根節點且關鍵字個數減少至0,則直接將根接待你刪除,合並后的新結點成為根;若雙親結點不是根結點,且關鍵字個數減少到\(\lceil m/2 \rceil-2\),則又要和它自己的兄弟結點進行調整或合並操作,並重復上述步驟,直至符號B樹的要求為止。
B+樹
一顆m階的B+樹需滿足下列條件:
- 每個分支結點最多有m課子樹(孩子結點)
- 非葉根結點至少有兩顆子樹,其他每個分支結點至少有\(\lceil m/2 \rceil\)棵子樹
- 結點的子樹個數與關鍵字個數相等
- 所有葉結點包含全部關鍵字及指向相應記錄的指針,葉結點中將關鍵字按大小順序排列,並且相鄰葉結點按大小順序相互鏈接起來。(支持順序查找)
- 所有分支結點中僅包含它的各個子結點中關鍵字的最大值及指向其子結點的指針。
B+樹的查找
B樹 VS B+樹
散列查找
散列表
散列表(Hash Table),又稱哈希表。是一種數據結構,特點:數據元素的關鍵字與其存儲地址直接相關。
處理沖突
拉鏈法
用拉鏈法(又稱鏈接法,鏈地址法)處理“沖突”:把所有“同義詞”存儲在一個鏈表中
裝填因子\(\alpha=表中記錄數/散列表長度\)
常見散列函數:
除留余數法——\(H(key)=key%p\),散列表表廠為m,取一個不大於m但最接近或等於m的質數p
直接定址法——\(H(key)=key或H(Key)=a*key+b\)
其中,a和b是常熟。這種方法計算最簡單,且不會產生沖突。它適合關鍵字的分布基本連續的情況,若關鍵字分布不連續,空位較多,則會造成存儲空間的浪費。
數字分析法——選取數碼分布較為均勻的若干位作為散列地址
若關鍵字是r進制數(如十進制數),而r個數碼在各位上出現的頻率不一定相同,可能在某些位上分布均勻一些,每種數碼出現的機會均等;而在某些位上分布不均勻,只有某幾種數碼經常出現,此時課選取數碼分布較為均勻的若干位作為散列地址。這種方法適合於已知的關鍵字集合,若更換了關鍵字,則需要重新構造新的散列函數。
平方取中法——取關鍵字的平方值的中間幾位作為散列地址。
具體取多少位要視實際情況而定。這種方法得到的散列地址與關鍵字的每位都有關系,因此使得散列地址分布比較均勻,適合於關鍵字的每位取值都不夠均勻或均小於散列地址所需的位數。
開放地址法
指可存放新表現得空閑地址既向他的同義詞表項開放,又向它得非同義詞表項開放。
其數學遞推公式為:\(H_i=(H(key)+d_i)\%m-1,(i=0,1,2,...,k(k\le m-1))\),m表示散列表表長,\(d_i\)為增量序列;\(i\)可理解為"第i次發生沖突"
注意:采用“開放定址法”時,刪除結點不能簡單地將被刪結點得空間置為空,否則將截斷在它之后填入散列表的同義詞結點的查找路徑,可以做一個“刪除標記”,進行邏輯刪除。
- 線性探測法:——\(d_i=0,1,2,3,...,m-1\);即發生沖突時,每次往后探測相鄰得下一個單元是否為空。
線性探測法很容易造成同義詞,非同義詞的“聚集(堆積)”現象,嚴重影響查找效率。
- 平方探測法——當\(d_i=0^2,1^2,-1^2,2^2,...,k^2-k^2\)成為平方探測法,又稱二次探測法其中\(k\le m/2\)
平方探測法:比起線性探測法更不易產生“聚集(堆積)”問題
非重點小坑:散列表長度m必須是一個可以表示成\(4j+3\)的素數,才能探測到所有的位置
- 偽隨機序列法——\(d_i\)是一個偽隨機序列,如\(d_i=0,5,24,11\)
再散列法
再散列法(再哈希法):除了原始的散列函數\(H(key)\)之外,多准備幾個散列函數,當散列函數沖突時,用下一個散列函數計算一個新地址,直到不沖突為止: