樹形索引(鍵樹)
一、鍵樹
鍵樹又稱為數字查詢樹,它是一顆度大於等於2的樹,樹中的每個結點中不是包含一個或幾個關鍵字,而是隻含有組成關鍵字的符號。例如,若關鍵字為數值,則結點中只包含一個數位;若關鍵字為單詞,則結點中只包含一個字母字元。這種樹會給某種型別關鍵字的表的查詢帶來方便。
我們來舉一個例子吧,下面有一個集合:
{CAI,CAO,LI,LAN,CHA,CHANG,WEN,CHAO,YUN,YANG,LONG,WANG,ZHAO,LIU,WU,CHEN}
我們先針對首字母將其分配:
{CAI,CAO,CHA,CHANG,CHAO,CHEN}
{WEN,WANG,WU}
{ZHAO}
{LI,LAN,LONG,LIU}
{YUN,YANG}
然後對其中4個關鍵字大於1的子集再按照第二個字元不同進行分割,直到分割到每個子集包含一個關鍵字為止。如下圖所示:
從根節點到葉子結點路徑上的元素組成一個關鍵字,其中葉子結點的特殊字元$表示字串的結束。
為了查詢的方便,我們約定同一層兄弟節點關鍵字從左到右有序。並約定$字元小於任何字元
二、儲存結構
1、孩子兄弟連結串列
結點結構:
nodetp | symbol | (first/next)/recptr |
symbol:儲存關鍵碼的一個字元
此外,每個非葉結點包括兩個指標域:
first:儲存指向第一棵子樹根的指標
next:儲存指向右兄弟的指標
每個葉結點只包括recptr域,儲存指向該關鍵碼記錄的指標
在雙鏈樹的表示下結點型別的描述如下:
enum NodeType {leaf,nonleaf}; struct SBNode; struct twopr { SBNode *first,*next; }; union unionptrnode { RecLinkType recptr;//若是葉結點,即nodetp=leaf twopr tptr;//若是非葉結點,即nodetp=nonleaf }; struct SBNode { char symbol; NodeType nodetp;//列舉型別 unionptrnode uptr; };
鍵樹的孩子兄弟連結串列表示的邏輯樹稱為雙鏈樹。例:
查詢過程是,從根結點出發,順著first查詢,如果相等,繼續下一個first。否則沿著next查詢。直到到了空指標為止。此時若仍未完成key的匹配,查詢不成功。雙鏈樹中查詢演算法:
//雙鏈樹中查詢演算法
RecLinkType KeyTreeSearch(SBNode *t,char K[])
{//在由指標t指向其根的非空雙鏈樹中查詢關鍵碼等於K的記錄
//若查詢成功,則返回指向記錄指標,否則返回空指標
if(!t) return NULL;
int num=strlen(K);
SBNode *p;
int i=0;
p=t->uptr.tptr.first;
while(p)
{
switch(p->nodetp)
{
case leaf:
if(i==num) return p->uptr.recptr;//查詢成功
else return NULL;//查詢失敗
break;
case nonleaf:
i=0;
if(i<num&&p->symbol<K[i])
p=p->uptr.tptr.next;//查詢關鍵碼第i位字元
if(i==num&&p->symbol=='$')
return(p->uptr.recptr);//查詢成功
if(i<num&&p->symbol==K[i])
{
p=p->uptr.tptr.first;//準備查詢下一位
i++;
break;
}
if(i==num&&p->symbol!='$') return NULL;//查詢失敗
break;
}
}
return NULL;//查詢失敗
}
鍵樹中每個結點的最大度和關鍵碼的基數有關,若為單詞,則d=26,若為數值,則d=10.鍵樹的深度h取決於關鍵碼中字元或數位的個數。假設關鍵碼是隨機的(即關鍵碼中每一位取基數中任何值的概率相等),則在雙鏈樹中對每一位的平均查詢長度為(1+d)/2,。又假設關鍵碼中字元(或數位)的個數都是h,則在雙鏈樹中查詢的平均查詢長度為(1+d)*h/2
在雙鏈樹中插入一個關鍵碼,首先要查詢,以便關鍵碼的字首部分與樹中已儲存的與之匹配的從根開始到達某個節點的部分不必重複儲存,然後將關鍵碼後續部分從頭至尾按每個字元字母逐一生成結點連線在該節點之後,最後生成‘$’結點(其中還包括指向該關鍵碼記錄的指標)。
在雙鏈樹中刪除一個關鍵碼,則也需從根開始先向下查詢,記下最後的分支點,該分支點過後有一條路徑中字母字元構成的序列和要刪除的關鍵碼的字尾匹配,且其上不會再出現新的分支,然後,從該分支點將通向與其後綴匹配的路徑上的所有結點刪除。
2、多重連結串列
每個結點中含有d+1個指標域,此時又稱Trie樹(字母關鍵碼時d=26,數字關鍵碼時d=10)。Trie樹中含有兩種節點:
分支節點:含有d+1個指標域以及記錄節點中非空指標域節點個數的整數域。每個分支節點所表示的關鍵字與其父節點中指向該節點的指標所在的位置決定。
葉結點:含有關鍵碼域和指向記錄的指標域
Trie樹結點結構:const int maxlen=30;//設關鍵碼最大長度為30
enum NodeType {branch,leaf};
struct TNode;
struct BranchNode
{
TNode *ptr[27];//按字母字元關鍵碼設計,d=26,d+1=27
int num;
};
struct LeafNode
{
char data[maxlen];
RecordLinkType recptr;
};
union UnionMixNode
{
BranchNode branchfld;
LeafNode leaffld;
};
struct TNode
{
NodeType nodetp;
UnionMixNode mxp;
};
Trie樹查詢:從根結點出發,沿和給定值相應的指標逐層向下,直到葉結點。RecordLinkType TrieSearch(TNode *t,char K[])
{//在Trie樹上查詢關鍵碼等於給定值K的記錄
//若存在,則返回指向記錄的指標,否則返回空指標
TNode *p=t;
int i=1;
int num=strlen(K);
while(p&&i<num&&p->nodetp!=leaf)//p為分支節點
{
p=p->mxp.branchfld.ptr[ord(K[i])];//ord求字母在字母表中的序號
//假設字元'$'的序號為0.ord(K[i])可用K[i]-'A'+1代替
i++;
}
if(p&&p->nodetp==leaf&&strcmp(p->mxp.leaffld.data,K)==1)
return p->mxp.leaffld.recptr;
else return NULL;
}
查詢與B+樹查詢類似,都走了一條從根結點到葉結點的路徑。Trie樹查詢的時間依賴於樹的深度,。