【筆記】靜態查詢演算法

Time-space發表於2017-11-17

  關鍵字主關鍵字:資料元素中某個資料項的值。如果該關鍵字可以將所有的資料元素區別開來,也即可以唯一標識一個資料元素,則該關鍵字稱為主關鍵字,否則稱為次關鍵字。特別地,如果資料元素只有一個資料項,則資料元素的值即是關鍵字。
  查詢表:是由同種型別的資料元素構成的集合。查詢表中的資料元素是完全鬆散的,資料元素之間沒有直接的聯絡。
  查詢:根據關鍵字在特定的查詢表中找到一個與給定關鍵字相同的資料元素的操作。如果在表中找到相應的資料元素,則稱查詢是成功的,否則稱查詢是失敗的。
  對查詢表經常進行的操作有查詢某個“特定的”資料元素是否在查詢表中、檢索某個“特定的”資料元素的各種屬性、在查詢表中插入一個資料元素、從查詢表中刪除某個資料元素。若對查詢表只進行前兩種操作,則稱此類查詢表為靜態查詢表,相應的查詢方法稱為靜態查詢。若在查詢過程中同時插入查詢表中不存在的資料元素,或者從查詢表中刪除已存在的某個資料元素,則稱此類查詢為動態查詢表,相應的查詢方法為動態查詢
  平均查詢長度:是指在查詢過程中,需要比較關鍵字的平均次數,它是衡量查詢演算法的效率標準。平均查詢長度的數學定義式為ASL=ni=1PiCi

ASL=\sum _{i=1}^n P_iC_i
。其中Pi
P_i
表示查詢表中第i個資料元素的概率,Ci
C_i
表示 找到第i個資料元素時與關鍵字比較的次數。

1.順序表的查詢

  順序表的查詢過程為從表的一端開始,逐個與關鍵字進行比較,若某個資料元素的關鍵字與給定的關鍵字相等,則查詢成功,函式返回該資料元素所在的順序表的位置;否則查詢失敗,返回0。

  • 型別定義檔案
#define MaxSize 100
typedef int KeyType;
typedef struct  /*元素的定義*/
{
    KeyType key;
}DataType;
typedef struct  /*順序表的型別定義*/
{
    DataType list[MaxSize];
    int length;
}SSTable;
  • 順序查詢函式
int SeqSearch(SSTable S,DataType x)
/*在順序表中查詢關鍵字為x的元素,如果找到返回該元素在表中的位置,否則返回0*/
{
    int i=0;
    while(i<S.length&&S.list[i].key!=x.key) /*從順序表的第一個元素開始比較*/
        i++;
    if(S.list[i].key==x.key)
        return i+1;
    else
        return 0;
}

  以上演算法也可以通過設定監視哨的方法實現:

int SeqSearch2(SSTable S,DataType x)
{
    /*設定監視哨S.list[0],在順序表中超找關鍵字為x的元素,如果找到返回該元素在表中的位置,否則返回0*/
    int i=S.length;
    S.list[0].key=x.key; /*將關鍵字存放在第0號位置,防止越界*/
    while(S.list[i].key!=x.key)/*從順序表的最後一個元素開始向前比較*/
        i--;
    return i;
}

  其中S.list[0]被稱為監視哨,可以防止出現陣列越界。
  假設表中有n個資料元素,且資料元素在表中出現的概率都相等,即1n

\frac{1}{n}
,則順序表在查詢成功時的平均查詢長度為ASL=ni=1PiCi=ni=11n(ni+1)=n+12
ASL_{成功}=\sum ^n_{i=1}P_iC_i=\sum ^n_{i=1}\frac{1}{n}*(n-i+1)=\frac{n+1}{2}
,即查詢成功時平均比較次數約為表長的一半。在查詢失敗時,即要查詢的元素沒有在表中,則每次比較都需要進行n+1次。


2.有序順序表的查詢

  有序順序表就是順序表中的元素是以關鍵字進行有序排列的。對於有序順序表的查詢有兩種方法,即順序查詢折半查詢

順序查詢

  有序順序表的查詢演算法與順序表的查詢演算法類似。在通常情況下,無須比較表中的所有元素。如果要查詢的元素在表中,則返回該元素的標號,否則返回0。

int SeqSearch3(SSTable S,DataType x)
{
    /*設定監視哨S.list[0],在有序順序表中查詢關鍵字為x的元素,如果找到返回該元素在表中的位置,否則返回0*/
    int i=S.length;
    S.list[0].key=x.key;/*將關鍵字存放在第0號位置,防止越界*/
    while(S.list[i].key>x.key)/*從有序順序表的最後一個元素開始向前比較*/
        i--;
    return i;
}

  設表中有n個資料元素,且資料元素在表中出現的概率都相等,即1n

\frac{1}{n}
,則有序順序表在查詢成功時的平均查詢長度為ASL=ni=1PiCi=ni=11n(ni+1)=n+12
ASL_{成功}=\sum ^n_{i=1}P_iC_i=\sum ^n_{i=1}\frac{1}{n}*(n-i+1)=\frac{n+1}{2}
,即查詢成功時平均比較次數約為表長的一半。在查詢失敗時,即要查詢的元素沒有在表中,則有序順序表在查詢失敗時的平均查詢長度為ASL=ni=1PiCi=ni=11n(ni+1)=n+12
ASL_{失敗}=\sum ^n_{i=1}P_iC_i=\sum ^n_{i=1}\frac{1}{n}*(n-i+1)=\frac{n+1}{2}
,即查詢失敗時平均比較次數也同樣約為表長的一半。

折半查詢

  又稱為二分查詢,這種查詢演算法要求待查詢的元素序列必須是從小到大排列的有序序列。
  折半查詢即將待查詢的元素與表中的元素進行比較,如果兩者相等,在說明查詢成功,否則利用中間位置將表分成兩個部分;如果待查詢元素小於中間位置的元素值,則繼續與前一個子表的中間位置元素進行比較,否則與後一個子表的中間位置進行比較;不斷重複以上操作,直到找到與待查詢元素相等的元素,表明查詢成功;如果子表變為空表,表明查詢失敗。

  例如一個有序順序表為(9,23,26,32,36,47,56,63,79,81),如果要查詢的元素為56,利用折半查詢演算法的查詢過程如下:


這裡寫圖片描述

  圖中low和high兩個指標,分別指向待查詢元素的下界和上界,指標mid指向low和high的中間位置,即mid=(low+high)/2。

int BinarySearch(SSTable S,DataType x)
/*在有序順序表中折半查詢關鍵字為x的元素,如果找到返回該元素在表中的位置,否則返回0*/
{
    int low,high,mid;
    low=0,high=S.length-1;          /*設定待查詢元素範圍的下界和上界*/
    while(low<=high)
    {
        mid=(low+high)/2;
        if(S.list[mid].key==x.key)  /*如果找到元素,則返回該元素所在的位置*/
            return mid+1;
        else if(S.list[mid].key<x.key)/*如果mid所指示的元素小於關鍵字,則修改low指標*/
            low=mid+1;
        else if(S.list[mid].key>x.key)/*如果mid所指示的元素大於關鍵字,則修改high指標*/
            high=mid-1;
    }
    return 0;
}

  整個查詢過程可以用一個判定樹來描述。


這裡寫圖片描述

  如果表中有n個元素,折半查詢成功時,至多需要比較的次數為log2n+1

\lfloor log_2n \rfloor+1

  對於具有n個結點的有序表(恰好構成一個深度為h的滿二叉樹)來說,有h=log2(n+1)
h=\lfloor log_2(n+1) \rfloor
,二叉樹中第i層的結點個數為2i1
2^{i-1}
。假設表中每個元素的查詢概率相等,即Pi=1n
P_i=\frac{1}{n}
,則有序表在折半查詢成功時的平均查詢長度為

ASL=i=1nPiCi=n+1nlog2(n+1)+1
ASL_{成功}=\sum ^n_{i=1}P_iC_i=\frac{n+1}{n}log_2(n+1)+1
查詢失敗時,有序表的折半查詢失敗的平均查詢長度為
ASL=i=1nPiCi=log2(n+1)
ASL_{失敗}=\sum ^n_{i=1}P_iC_i=log_2(n+1)

3.索引順序表的查詢

  當順序表的資料量非常大時,無論使用前述哪種查詢演算法都需要很長的時間,此時提高查詢效率的一個常用方法就是在順序表中建立索引表。建立索引表的方法就是將順序表分成幾個單元,然後分別為這幾個單元建立一個索引,原來的順序表稱為主表,提供索引的表稱為索引表。索引表中只存放主表中要查詢的資料元素的主關鍵字和索引資訊。
  如下圖所示,其中索引表包括兩部分,即順序表中每個單元的最大關鍵字和順序表中每個單元的第一個元素的下標。


這裡寫圖片描述

  這樣的表稱為索引順序表,要使查詢效率高,索引表必須有序,但主表中的元素不一定要按關鍵字有序排列。索引順序表的查詢也稱為分塊查詢
  如果主表中米格單元中的元素個數是不相等,就需要在索引表中增加一項,即用來儲存主表中每個單元元素的個數,將這種利用索引表示的順序表稱為不等長索引順序表。


這裡寫圖片描述

int SeqIndexSearch(SSTable S,IndexTable T,int m,DataType x)
/*在主表S中查詢關鍵字為x的元素,T為索引表。如果找到返回該元素在表中的位置,否則返回0*/
{
    int i,j,bl;

    for(i=0;i<m;i++)    /*通過索引表確定要查詢元素所在的單元*/
        if(T[i].maxkey>=x.key)
            break;
    if(i>=m)            /*如果要查詢的元素不在索引順序表中,則返回0*/
        return 0;
    j=T[i].index;       /*要查詢的元素在的主表的第j單元*/
    if(i<m-1)           /*bl為第j單元的長度*/
        bl=T[i+1].index-T[i].index;
    else
        bl=S.length-T[i].index;
    while(j<T[i].index+bl)
        if(S.list[j].key==x.key)/*如果找到關鍵字,則返回該關鍵字在主表中所在的位置*/
            return j+1;
        else
            j++;
    return 0;
}

  因索引表中的元素的關鍵字是有序的,故在確定元素所在主表的單元時,既可採用順序查詢發也可採用折半查詢法,但對於主表,只能採用順序法查詢。索引順序表的平均查詢長度可以表示為ASL=Lindex+Lunit

ASL=L_{index}+L_{unit}
,則Lindex
L_{index}
是索引表的平均查詢長度,Lunit
L_{unit}
是單元中元素的平均查詢長度。
  假設主表中的元素個數為n,並將主表平均分為b個單元,且每個單元有s個元素,即b=n/s。如果表中的元素查詢概率相等,則每個單元中元素的查詢概率就是1/s,主表中每個單元的查詢概率是1/b。如果用順序查詢法查詢索引表中的元素,則索引順序表查詢成功時的平均查詢長度為

ASL=Lindex+Lunit=1bi=1bi+1sj=1sj=b+12+s+12=12(ns+s)+1
ASL=L_{index}+L_{unit}=\frac{1}{b}\sum_{i=1}^b i +\frac{1}{s}\sum _{j=1}^sj=\frac{b+1}{2}+\frac{s+1}{2}=\frac{1}{2}*(\frac{n}{s}+s)+1

如果用折半查詢法查詢索引表中的元素,則有
ASL=Lindex+Lunit=b+1blog2(b+1)1+1sj=1sjlog2(n/s+1)+s2
ASL=L_{index}+L_{unit}=\frac{b+1}{b}log_2(b+1)-1 +\frac{1}{s}\sum _{j=1}^sj \approx log_2(n/s+1)+\frac{s}{2}

4.靜態查詢應用例項

  給定一組元素序列,利用順序表查詢、有序順序表查詢和索引順序表查詢值x的元素。

  • 型別定義檔案
#define MaxSize 100
#define IndexSize 20
typedef int KeyType;
typedef struct  /*元素的定義*/
{
    KeyType key;
}DataType;
typedef struct  /*順序表的型別定義*/
{
    DataType list[MaxSize];
    int length;
}SSTable;
typedef struct  /*索引表的型別定義*/
{
    KeyType maxkey;
    int index;
}IndexTable[IndexSize];
  • 查詢函式檔案
int SeqSearch(SSTable S,DataType x)
/*在順序表中查詢關鍵字為x的元素,如果找到返回該元素在表中的位置,否則返回0*/
{
    int i=0;
    while(i<S.length&&S.list[i].key!=x.key) /*從順序表的第一個元素開始比較*/
        i++;
    if(S.list[i].key==x.key)
        return i+1;
    else
        return 0;
}
int BinarySearch(SSTable S,DataType x)
/*在有序順序表中折半查詢關鍵字為x的元素,如果找到返回該元素在表中的位置,否則返回0*/
{
    int low,high,mid;
    low=0,high=S.length-1;          /*設定待查詢元素範圍的下界和上界*/
    while(low<=high)
    {
        mid=(low+high)/2;
        if(S.list[mid].key==x.key)  /*如果找到元素,則返回該元素所在的位置*/
            return mid+1;
        else if(S.list[mid].key<x.key)/*如果mid所指示的元素小於關鍵字,則修改low指標*/
            low=mid+1;
        else if(S.list[mid].key>x.key)/*如果mid所指示的元素大於關鍵字,則修改high指標*/
            high=mid-1;
    }
    return 0;
}
int SeqIndexSearch(SSTable S,IndexTable T,int m,DataType x)
/*在主表S中查詢關鍵字為x的元素,T為索引表。如果找到返回該元素在表中的位置,否則返回0*/
{
    int i,j,bl;

    for(i=0;i<m;i++)    /*通過索引表確定要查詢元素所在的單元*/
        if(T[i].maxkey>=x.key)
            break;
    if(i>=m)            /*如果要查詢的元素不在索引順序表中,則返回0*/
        return 0;
    j=T[i].index;       /*要查詢的元素在的主表的第j單元*/
    if(i<m-1)           /*bl為第j單元的長度*/
        bl=T[i+1].index-T[i].index;
    else
        bl=S.length-T[i].index;
    while(j<T[i].index+bl)
        if(S.list[j].key==x.key)/*如果找到關鍵字,則返回該關鍵字在主表中所在的位置*/
            return j+1;
        else
            j++;
    return 0;
}
  • 主程式
#include<stdio.h>
#include<stdlib.h>
void main()
{
    SSTable S1={{265,55,15,67,9,64,88,50},8};
    SSTable S2={{20,28,37,40,46,55,64,76},8};
    SSTable S3={{12,8,26,20,19,40,29,43,35,33,56,50,63,55,58,75,64,66,85,79},20};
    IndexTable T={{26,0},{43,5},{63,10},{85,15}};
    DataType x={64};
    int pos;
    if((pos=SeqSearch(S1,x))!=0)
        printf("順序表的查詢:關鍵字32在主表中的位置是:%2d\n",pos);
    else
        printf("查詢失敗!\n");
    if((pos=BinarySearch(S2,x))!=0)
        printf("折半查詢:關鍵字32在主表中的位置是:%2d\n",pos);
    else
        printf("查詢失敗!\n");
    if((pos=SeqIndexSearch(S3,T,4,x))!=0)
        printf("索引順序表的查詢:關鍵字32在主表中的位置是:%2d\n",pos);
    else
        printf("查詢失敗!\n");
}
  • 測試結果


這裡寫圖片描述

相關文章