1. 程式人生 > >資料結構知識整理 - 樹表查詢演算法

資料結構知識整理 - 樹表查詢演算法

主要內容

資料結構

查詢演算法

插入演算法

建立演算法

刪除演算法


 

前提

(可回顧“基本概念”)

若在查詢的同時對錶做插入、刪除等修改操作,則被查詢的表稱為動態查詢表,反之則稱為靜態查詢表

換句話說,動態查詢表的表結構本身是在查詢過程中動態生成的,即在建立表時,對於給定值,若表中存在其關鍵字等於給定值的記錄,則返回“已存在此關鍵字”等資訊;否則插入關鍵字等於給定值的記錄。

 

線性表查詢演算法明顯更適合靜態查詢表。

若要對動態查詢表進行高效率的查詢,可採用二叉樹作為查詢表的組織形式,這類查詢表統稱為樹表

(本篇只對二叉排序樹和平衡二叉樹作較為詳細的介紹,對於B-樹和B+樹只作初步描述(先挖坑,後補))


 

二叉排序樹(Binary Sort Tree)

二叉排序樹又稱二叉查詢樹,它是一種對排序查詢都很有用的特殊二叉樹。

特徵:

1)若左子樹不為空,則左子樹上所有結點的值一定小於根結點的值。在資料結構中,對應左子樹上所有結點的關鍵字的值一定小於根結點的關鍵字的值,data.key > lchild->data.key

2)若右子樹不為空,則右子樹上所有結點的值一定大於根結點的值。

3)左子樹和右子樹都為二叉排序樹。(遞迴)

4)中序遍歷二叉排序樹可以得到一個結點值遞增的有序線性表。

*判定樹是特殊的二叉排序樹。

 

資料結構

typedef struct          /*定義資料元素結構體*/
{
    KeyType key;        /*關鍵字*/
    OtherType other;    /*其他資料項*/
} Element;


typedef struct          /*以二叉樹結構定義查詢表*/
{
    Element data;      
    struct Element *lchild, *rchild;
} BSTNode, *BSTree;              /*BSTNode表示二叉樹上的結點,BSTree表示指向二叉樹的指標*/

 

查詢演算法

查詢演算法是建立插入刪除操作的基礎,也比較簡單。

<思路>

1)若二叉樹為空,查詢失敗,返回空指標。

2)若二叉樹不為空,比較根結點的關鍵字的值data.key和給定值key

1. 若 data.key key,則查詢成功,返回根結點的地址;

2. 若 data.key < key,則遞迴查詢右子樹;

3. 若 data.key > key,則遞迴查詢左子樹。

/*------------非遞迴寫法-------------*/

BSTree Bin_Search(BSTree t, KeyType key)
{
    while(t)
    {
        if(data.key == key) return t;

        else if(data.key < key) t = t.rchild;

        else t = t.lchild;
    }

    if(!t) return NULL;
}

/*-------------遞迴寫法--------------*/

BSTree Bin_Search(BSTree t,KeyType key)
{
    if(t)
    {
        if(data.key == key) return t;

        else if(data.key < key) Bin_Search(t.rchild, key);

        else Bin_Search(t.lchild, key);
    }

    if(!t) return NULL;
}

 

需要注意的是,含有n個結點的二叉排序樹的平均查詢長度與樹的形態有關,即與二叉樹的建立演算法有關。

同一組結點,以不同的次序輸入會得到不同的二叉排序樹,其中最好的情況是二叉樹的形態與折半查詢的判定樹相似,深度達到最小。(回顧“折半查詢”)

二叉排序樹的查詢與折半查詢類似。

但就維護查詢表而言,二叉排序樹更加有效,因為無需移動資料,只需要修改指標就可以完成對查詢表的插入和刪除。因此動態查詢表更適合採用二叉樹結構。

 

插入演算法

<思路>

1)若二叉樹為空,插入新結點NewNode(這裡函式引數為KeyType nkey,OtherType nother);

2)若二叉樹不為空,比較關鍵字的值,若新結點的關鍵字ndata.key等於根結點的關鍵字data.key,由於關鍵字唯一的原則,列印ERROR等資訊;否則:

1. ndata.key < data.key,則遞迴插入左子樹;

2. ndata.key > data.key,則遞迴插入右子樹。

void Bin_Insert(BSTree &t, KeyType nkey, OtherType nother)
{

/*------------新結點初始化-----------*/

    BSTNode *NewNode = new BSTNode;
    NewNode->data.key = nkey;
    NewNode->data.other = nother;
    NewNode->lchild = NewNode->rchild = NULL;


    while((t) && (nkey != t.data.key))    /*當結點不為空,且關鍵字不重複*/
    {

    /*----------------插入--------------*/

        if(!t.lchild) t.lchild = NewNode;break;
        if(!t.rchild) t.rchild = NewNode;break;

    /*----------------遍歷--------------*/

        if(nkey < t.data.key) t = t.lchild;
        if(nkey > t.data.key) t = t.rchild;
    }

    if(!t) t = NewNode;                   /*如果根結點為空,直接插入*/

    if(nkey == t.data.key) cout<<"ERROR"; /*當關鍵字重複,列印“ERROR”*/

 

建立演算法

建立演算法的思路就如前提中說到的那樣。

一個無序序列可以通過構造一棵二叉排序樹而變成一個有序序列,構造過程相當於排序過程。

#define NoMore 2333              /*定義NoMore來控制結點數或表示輸入結束的標識*/

void Bin_Create(BSTree &t)
{
    t = NULL;                    /*初始化為空樹*/

    KeyType nkey;OtherType nother;

    cin>>nkey>>nother;

    while(nkey != NoMore)        /*當未達到最大結點數或輸入未結束時,迴圈輸入資料*/
    {
        Insert(t, nkey, nother);
        cin>>nkey>>nother;
    }
}

 

刪除演算法

通過關鍵字查詢待刪除結點。若不存在待刪除結點,返回“ERROR”等資訊;若存在,刪除結點並修改相連指標。

刪除某個結點後,將有可能導致其他結點的相對位置發生變化,因此我們需給予相應處理。

刪除結點有3種情況:

1)被刪除結點既有左子樹,又有右子樹;

2)被刪除結點無左子樹;

3)被刪除節點無右子樹。

 

只含單子樹的情況比較簡單,將結點刪除後用下一個結點填補就可以了。

而含有雙子樹的情況則需要找到左子樹中關鍵字最大的結點(即小於被刪除結點的關鍵字中最大的值),然後用這個結點填補被刪除結點的位置。

為什麼?

因為左子樹上所有的關鍵字必須小於根結點的關鍵字,為了在刪除結點後仍保持這一特性,必須用大於左子樹上其餘結點關鍵字的結點來填補被刪除結點的位置。

雖然右子樹上所有結點的關鍵字都大於被刪除結點,但是我們並不能直接用右子樹中的根結點(即被刪除節點的右孩子)來填補它的位置,因為右孩子本身還可能含有左子樹,這將給操作帶來十分多不必要的麻煩。

而左子樹中關鍵字最大的結點一定不含有右子樹(否則它就不是最大的),所以不會給操作帶來麻煩。

void Bin_Delete(BSTree &t, KeyType key)
{
    BSTNode *p = new BSTNode;    /*待刪除結點*p*/
    
    BSTNode *f = new BSTNode;    /*待刪除結點的父節點*f*/

    p = Bin_Search(t, key);
    f = Bin_SearchP(t, key);     /*稍微修改Bin_Search函式為Bin_SearchP函式,用作查詢父結點*/         

    BSTNode *q = p;              /*臨時結點*q,用以儲存*p的位置*/


    /*---------既有左子樹又有右子樹---------*/

    if((p->lchild) && (p->rchild))
    {
        BSTNode *s = p->lchild;  /*在左子樹中...*/
    
        while(s->rchild)         /*...找關鍵字最大的結點以及該結點的直接前驅,直接前驅輔助後續判斷*/
        {
            q = s;
            s = s->rchild;
        }
    
        p->data = s->data;       /*用上面找到的結點的資訊覆蓋待刪除結點的資訊*/

        if(q != p) q->rchild = s->lchild;

        else q->lchild = s->lchild;    /*這種情況下待刪除結點沒有左孩子*/

        delete s;                      /*s的任務完成,撤銷儲存空間*/
    }

    /*-------------不含右子樹------------*/

    else if(!p->rchild)
        p = p->lchild;

    /*-------------不含左子樹------------*/

    else if(!p->lchild)
        p = p->rchild;

/*--------以上步驟僅完成了*p子樹的連線,還需將其與父結點*f連線-------*/

    if(!f) t = p;            /*當沒有父結點,即待刪除結點為根結點時*/

    else if(q == f->lchild) f->lchild = p;    /*原本為左子樹則掛回去左子樹位置*/

    else f->rchild = p;                       /*反之掛回右子樹位置*/

    delete q;                                 /*q的任務完成,撤銷儲存空間*/
}

 

平衡二叉樹(Balanced Binary Tree)

平衡二叉樹是由一般的二叉排序樹經過平衡調整得到的,每個結點的左右子樹深度差小於等於1的特殊的二叉排序樹。

上面已經提到,二叉排序樹的平均查詢長度與它的形態有關,其中平衡二叉樹就是一種最好的形態。

特徵:

1)左右子樹深度差的絕對值小於等於1;

2)左右子樹也是平衡二叉樹。(遞迴)

 

為了表示左右子樹的深度差,我們給每個結點增加一個屬性——平衡因子(Balance Factor),記錄每個結點左右子樹的深度差,結點值 = 左子樹深度 - 右子樹深度。只要有一個結點的平衡因子的絕對值大於1,這棵二叉排序樹就是不平衡的。

當平衡的二叉排序樹因插入結點而失去平衡時,僅需對最小不平衡子樹進行平衡調整即可,即 以 離插入結點最近的,結點平衡因子等於2或-2的結點 為根結點的 子樹。

 

插入結點的情況分為四種,相應地,平衡調整的方法也分為四種:

根結點只表示一個位置,其餘顏色代表具體的結點)

1)在根結點左子樹根結點的左子樹上插入結點(LL型):

左子樹根結點的左子樹相當於樹的“外層”,對此情況只需要將根結點左移(不違反二叉排序樹的特徵),即左子樹根結點作為新的根結點。但是左子樹根結點上可能存在右子樹,如果只是簡單地將根結點左移,可能會出現三叉樹的情況。所以我們將左子樹根結點上的右子樹掛接到原來的根結點上。

 

2)在根結點右子樹根結點的右子樹上插入結點(RR型):

這種情況與LL型的平衡調整對稱。

 

3)在根結點左子樹根結點的右子樹上插入結點(LR型):

左子樹根結點的右子樹相當於樹的“內層”,對此情況需要新增一個操作結點,即根結點的左子樹根結點的右子樹根結點

由於根結點左移並不能改變左右子樹的深度差,所以我們應該設法將插入結點“擡上去”。

具體操作是將右子樹根結點變成新的根結點

右子樹根結點存在左子樹(LRL型),則將其左子樹掛接到左子樹根結點上;若右子樹根結點存在右子樹(LRR型),則將其右子樹掛接到根結點上。

 

4)在根結點右子樹根結點的左子樹上插入結點(RL型):

這種情況與LL型的平衡調整對稱。

新增一個操作結點——根結點的右子樹根結點的左子樹根結點,並將左子樹根結點變成新的根結點

左子樹根結點存在左子樹(RLL型),則將其左子樹掛接到根結點上;若左子樹根結點存在右子樹(RLR型),則將其右子樹掛接到右子樹根結點上。

 

<平衡調整演算法的思路>

1)插入演算法:在二叉排序樹上插入新結點;

2)查詢演算法:判斷插入結點的情況;

3)平衡調整演算法:修改相關結點的指標。


 

B-樹和B+樹

前面介紹的查詢方法均適用於儲存在計算機記憶體較小的檔案,統稱為內查詢法。若檔案很大且存放於外存進行查詢時,這些查詢方法就不適用了。

內查詢法均已結點為單位進行查詢,需要反覆地進行內、外存的交換。

1970年,適用於外查詢的平衡多叉樹——B-樹被提出來,磁碟管理系統中的目錄管理,以及資料庫系統中的索引組織多數採用B-樹這種資料結構。

B+樹是B-樹的一種變形,更適用於檔案索引系統


 

路過的圈毛君:“

資料結構、作業系統和資料庫,這三個科目又聯絡起來了_(:з」∠)_

B-樹和B+樹先挖個坑,以後我需要用到再仔細去研究它們_(:з」∠)_x2