【數據結構】先排序后查找的查找


 

2021-11-30 14:46:14 星期二

序言

查找,顧名思義,就是從某一集體中找出一個或一種元素。又稱檢索。
其中,在計算機語言學習中,怎么利用機器對數據進行簡便查找更是一項重要的工程。
根據對查找表操作不同,查找又分靜態查找和動態查找。
根據查找表的特點,我們可以利用不同的方法進行找到我們所需的那個唯一關鍵字。

對於靜態表的查找方法,這里我們主要介紹
一、順序查找(線性查找)
二、折半查找(二分或對分查找)
三、靜態樹表的查找
四、分塊查找(索引順序查找)

對於動態表,我們主要介紹:
典型的動態表—— 二叉排序樹

既然有方法了,那么怎么能判斷方法的優劣呢?
這樣就需要一個工具,來簡單的判斷該方法的優劣,它就是ASL(平均查找長度)
image

基本概念

查找表:——————由同一類型的數據元素(或記錄)構成的集合
查 找:——————查詢(Searching)特定元素是否在表中。
查找成功:——————若表中存在特定元素,稱查找成功,應輸出該記錄或位置;
查找不成功:————(與查找成功對立)否則,稱查找不成功(也應輸出失敗標志或失敗位置)
靜態查找表:————只查找,不改變集合內的數據元素。
動態查找表:——既查找,又改變(增減)集合內的數據元素。
關鍵字:——記錄中某個數據項的值,可用來識別一個記錄
主關鍵字:——可以唯一標識一個記錄的關鍵字
次關鍵字:——識別若干記錄的關鍵字

工具:ASL

如何評估查找方法的優劣?
明確:查找的過程就是將給定的K值與文件中各記錄的關鍵字項進行比較的過程。所以用比較次數的平均值來評估算法的優劣。稱為平均查找長度(ASL:average search length)。
image
其中:
n是記錄個數;
Pi是查找第i個記錄的查找概率(通常取等概率,即Pi =1/n);
Ci是找到第i個記錄時所經歷的比較次數。

物理意義:假設每一元素被查找的概率相同,則查找每一元素所需的比較次數之總和再取平均,即為ASL。
顯然,ASL值越小,時間效率越高。


例如:
給定一個集合:1 3 7 4 9 0,順序查找的方法,查找成功的ASL是多少?
解:對於每一個元素的查找成功的概率相同,都是1/6 既pi = 1/6,然后每個元素被查找成功的比較次數分別為(1,2,3,4,5,6)
ASL = (1+2+3+4+5+6)/6 = 21/6

靜態查找表

抽象數據類型的靜態查找表的定義為:

ADT StaticSearchTable
{數據對象D:D是具有相同特性的數據元素的集合。每個數據元素均含有類型相同、可唯一標識數據元素的關鍵字。
 數據關系R:數據元素同屬一個集合。
 基本操作P: 
    Create(ST,n);   操作結果:構造一個含n個數據元素的靜態查找表ST。
    Destroy(ST);    操作結果:銷毀表ST。
    Search(ST,key); 操作結果:若ST中存在其關鍵字等於key的數據元素,則函數值為該元素的值或者在表中的位置,否則為空。
    Traverse(ST,Visit()); 操作結果:按照某種次序對ST的每個元素調用函數VIsit()一次且僅一次,一旦visit()失敗,則操作失敗
    
}

(1)順序表的機內存儲結構:

typedef  ElemType{
    keyType  key;
    anyType  otherItems;
} ElemType
typedef struct {
          ElemType   *elem; //表基址,0號單元留空。表容量為全部元素
          int               length;     //表長,即表中數據元素個數
}SSTable;

存儲圖例:
image

順序查找

利用上圖
如果我們要找出其中的元素四,
首先,要找到其關鍵字(這里是注關鍵字)------數組序號也就是長度
然后,選取方法。
這里我們采用第一種方法,也就是順序查找

Linear search,又稱線性查找,即用逐一比較的辦法順序查找關鍵字,這顯然是最直接的辦法。
(2)算法的實現:
技巧:把待查關鍵字key存入表頭或表尾(俗稱“哨兵”),
若將待查找的key存入順序表的首部(如0號單元),則順序查找的實現方案為:從后向前逐個比較!

int Search_Seq( SSTable  ST , KeyType  key ){
   ST.elem[0].key =key;            //設立哨兵 
   for( i=ST.length; ST.elem[ i ].key!=key;  - - i  ); 
         //從后向前逐個比較
   return i; 
} // Search_Seq

設立哨兵:可免去查找過程中每一步都要檢測是否查找完畢。當n>1000時,查找時間將減少一半。
不要用for(i=n; i>0; - -i) 或 for(i=1; i<=n; i++) 了
到達0號單元(哨兵)結束循環,查找失敗,返回0(i=0)。查找成功,則返回該元素位置i。
圖例:
image

等概率下的平均比較查找長度:image

image

折半查找(二分查找 對分查找)

先給數據排序(例如按升序排好),形成有序表,然后再將key與正中元素相比,若比key小,則縮小至右半部內查找;再取其中值比較,每次縮小1/2的范圍,直到查找成功或失敗為止。
image
存儲圖例:
image
② 運算步驟:
1)low =1,high =11 ,mid =6 ,待查范圍是 [1,11];
2)若 ST.elem[mid].key < key,說明 key[ mid+1,high] ,
則令:low =mid+1;重算 mid= (low+high)/2;.
3)若 ST.elem[mid].key > key,說明key[low ,mid-1],
則令:high =mid–1;重算 mid ;
4)若 ST.elem[ mid ].key = key,說明查找成功,元素序號=mid;

③ 結束條件: (1)查找成功 : ST.elem[mid].key = key
(2)查找不成功 : high≤low (意即區間長度小於0)
image

最后(low+high)/2下取整,得mid = 4 得出查找結果:查找成功,21所在位置是4

給出代碼

int  bin_search(SSTable  ST,int  key)
{ int low,high,mid;
  low=1;  high=ST.length;
  while(low<=high)
  {
     mid=(low+high)/2;
     if  EQ(key,ST.elem[mid].key)   return mid;       //等於
     else if LT(key,ST.elem[mid].key)  high=mid-1;    //小於
          else  low=mid+1;
     }
     return 0;
}/*bin_search*/

算法效率:
image
image

分塊查找

又稱索引順序查找這是一種順序查找的另一種改進方法。
先讓數據分塊有序,即分成若干子表,要求每個子表中的數值(用關鍵字更准確)都比后一塊中數值小(但子表內部未必有序)。
然后將各子表中的最大關鍵字構成一個索引表,表中還要包含每個子表的起始地址(即頭指針)。

image

兩步走: 首先通過索引表查找待查記錄所在的塊(子表),然后在塊內進行順序查找

索引表的存儲結構定義如下,

#define MAX_SUBLIST_NUM 10
typedef struct{
    KeyType maxKey;           /*---子表中的最大關鍵字---*/
    int index;                /*---子表中第一個記錄在基本表中的位置---*/
    
}IndexItem;                 /*---索引項---*/
typedef IndexItem indexList[MAX_SUBLIST_NUM]; /*----索引表類型--*/

那么,可以得出分塊查找算法的平均查找長度
image

動態查找表

image

典型的動態查找表——————二叉排序樹

二叉排序樹的定義:
或是一棵空樹;或者是具有如下性質的非空二叉樹:
(1)左子樹的所有結點值均小於根的值;
(2)右子樹的所有結點值均大於根的值;
(3)它的左右子樹也分別為二叉排序樹。

從定義上可以看出,二叉排序樹是一個可以由遞歸創建的邏輯結構。
二叉排序的圖例:
image
二叉排序樹的存儲結構(以鏈表存儲為例):

/*---二叉排序樹的二叉鏈表存儲結構---*/
typedef struct BTNode{                  /*---元素的結點結構---*/
    Elemtype key;                       /*---記錄的關鍵字,忽略記錄的其他數據項---*/
    struct BTNode *lchild,*rchild;      /*---左右指針---*/
}BTNode,*BStree;

二叉排序樹的創建,插入,查找,刪除

創建與插入

根據定義創建二叉樹:
while(序列不為空){

  • 順序拿出元素
  • 元素比根結點大,放到根結點的右子樹。
  • 元素比根結點小,放到根結點的左子樹。
  • 重復步驟2-3,至成為葉子結點。
    }

給出代碼:

創建:

BStree CreateBST()
{
    BStree T,s;
    T = NULL;
    printf("輸入關鍵字key,輸入“-1”結束。\n");
    while(1)
    {
        scanf("%d",&key);               /*---這里假設key是int類型---*/
        if(key) break;
        s = (BTNode*)malloc(sizeof(BTNode));
        s->key = key;
        s->lchild = NULL;
        s->rchild = NULL;
        T  = InsertBST(T,s);             /*---插入二叉排序樹---*/
    }
	return T;
}

插入(1:遞歸版本)

BSTree InsertBST(BSTree T,BTNode *s)
{/*---在以T為根的二叉排序樹上插入一個指針s所指向的結點的遞歸算法---*/
    if(T==NULL) T = s;
    else
    {
        if(s->key > T->key) T->rchild = InsertBST(T->rchild,s);         /*---遞歸插入到T的右子樹中---*/
        else if(s->key < T->key) T->lchild = InsertBST(T->lchild,s);    /*---遞歸插入到T的左子樹中---*/

    }
    return T;

}

插入(2:非遞歸版本)

點擊查看代碼
BSTree InsertBST(BSTree T,BTNode *s)
{
    BSTree p = T;
    BTNode *f = NULL;
    if(T==NULL)
    {
        T = s;
        return T;
    }
    while(p)
    {
        if(p->key == s->key) return T;
        f = p;                      /*---記錄訪問的結點:便於連接s---*/
        if(s->key < p->key)
            p = p->lchild;
        else p = p->rchild;

    }//跳出循環:1.p = null;2.f就是s與之連接的結點

   if(s->key > p->key) f->rchild = s;
   if(s->key < p->key) f->lchild = s;

   return T;

}

image

查找與刪除

查找的基本過程如下:
當二叉樹為空時,返回0;
當二叉樹不為空時,首先將給定的值key與根結點關鍵字進行比較,若相等,則查找成功,返回1;否則將依據給定值key與根結點關鍵字值的大小關系,分別在其左子樹或者右子樹中繼續查找。可見,二叉排序樹的查找可以是一個遞歸的過程。

刪除的基本過程:
image
image
image

給出代碼:

查找代碼:

int BSTsearch(BSTree T, int key)
{
    BSTree p = T;
    if(!p) return 0; /*---空樹,返回0---*/
    else if(p->key == key) return 1;
    else if(p->key > key) BSTsearch(p->lchild,key);
    else BSTsearch(p->rchild,key);
}

查找非遞歸算法

點擊查看代碼
//二叉排序樹查找非遞歸算法
int BSTsearch2(BSTNode *T, int data)
{
    BSTNode *p = T;
    while(p)
    {
        if(p->key == data)
            return 1;
        p = (p->key > data)? p->lchild : p->rchild;
    }
    return 0;
}

刪除代碼:


//二叉排序樹刪除操作
void DelBST(BSTNode *T,int key)
{
    BSTNode *p = T, *f, *q, *s;
    while(p)
    {
        if(p->key == key) break; //找到關鍵字為key的結點
        f=p;//記下關鍵字key節點的父節點
        p=(key < p->key)? p->lchild : p->rchild;//分別在*p的左、右子樹中查找
    }
    if(!p) return;//二叉排序樹中無關鍵字為key的結點
    if(p->lchild == NULL && p->rchild == NULL)//p無左子樹無右子樹
    {
        if(p == T) T = NULL;//刪除的是根節點
        else
            if(p == f->lchild)
                f->lchild = NULL;
            else
                f->rchild = NULL;
    }
    else if(p->lchild == NULL && p->rchild != NULL)//p無左子樹有右子樹
    {
        if(f->lchild == p)
            f->lchild = p->rchild;
        else
            f->rchild = p->rchild;
    }
    else if(p->rchild == NULL && p->lchild != NULL)//p有左子樹無右子樹
    {
        if (f->lchild == p)
            f->lchild = p->lchild;
        else
            f->rchild = p->lchild;
    }
    else if(p->lchild != NULL && p->rchild != NULL)//p既有左子樹又有右子樹
    {
        q = p;
        s = p->lchild;//轉左
        while(s->rchild)
        {//然后向右到盡頭
            q = s;
            s = s->rchild;//s指向被刪節點的“前驅”(中序前驅)
        }
        p->key = s->key;//以p的中序前趨結點s代替p(即把s的數據復制到p中)
        if(q != p)
            q->rchild = s->lchild;//重接q的右子樹
        else
            q->lchild = s->lchild;//重接q的左子樹。
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM