天天看點

順序查找--二分查找--靜态樹表的查找--分塊查找

一、順序表的順序查找

1、順序查找

由表的一端開始,逐個檢測每個記錄是不是要查的記錄。找到則查找成功,一直找到另一端還沒找到則失敗。

1)順序存儲結構下的順序查找

#include <iostream>
using namespace std;

typedef int KeyType;
struct RecType
{
    KeyType key;//關鍵字域
};
//崗哨設在r[0]
int SeqSearch(RecType r[],KeyType k,int n)
{//順序表為r[1~n]
    int i=n;
    r[0].key=k;
    while(r[i].key!=r[0].key) i--;
    return i;
}
//崗哨設在r[n+1]
int SeqSearch1(RecType r[],KeyType k,int n)
{
    r[n+1].key=k;
    int i=1;
    while(r[i].key!=k) i++;
    return i%(n+1);
}
int main()
{
    RecType r[]={0,1,2,3,4,5,6};
    int length=sizeof(r)/sizeof(int);
    KeyType key=3,key1=4;
    int i=SeqSearch(r,key,length-1);
    int j=SeqSearch1(r,key1,length-1);
    cout <<i<<' '<<j<< endl;
    return 0;
}
           

順序查找--二分查找--靜态樹表的查找--分塊查找

2)以連結清單為存儲結構的順序查找

隻能從頭節點順連結清單查到尾節點,逐個比較。

#include <iostream>
using namespace std;

typedef int KeyType;

struct Node
{
    KeyType key;//關鍵字域
    Node *next;//指針
};
Node *LinkSearch(Node *first,KeyType k,int &j)
{
    Node *p=first->next;
    j=1;
    while(p)
    {
        if(p->key==k)
            return p;
        else
        {
            p=p->next;
            j++;
        }
    }
    j=0;//沒找到則j=0
    return NULL;//沒找到傳回空指針
}
           

2、時間複雜度分析

執行時間主要取決于關鍵字的比較次數。 平均查找長度 (Average Search Length,ASL)      需和指定key進行比較的關鍵字的個數的期望值,成為查找算法在查找成功時的平均查找長度。

     對于含有n個資料元素的查找表,查找成功的平均查找長度為:ASL = Pi*Ci的和。

     Pi:查找表中第i個資料元素的機率。

     Ci:找到第i個資料元素時已經比較過的次數。

     順序查找 查找 成功時的平均查找長度為:

     (假設每個資料元素的機率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;

     當查找 不成功時,需要n次比較,時間複雜度為O(n);

二、有序表的折半查找(二分查找)

1、描述

給定有序表按關鍵字有序,先确定待查記錄所在範圍,然後逐漸縮小範圍,直到找到或找不到為止。

#include <iostream>
using namespace std;
typedef int KeyType;
struct RecType
{
    KeyType key;//關鍵字域
};
int BinSearch(RecType r[],int n,int k)
{//r[0~n-1]
    int low=0,high=n-1;
    while(low<high)
    {
        int mid=(low+high)/2;
        if(r[mid].key==k) return mid;//找到
        else if(r[mid].key>k) high=mid-1;//在左半段
        else low=mid+1;//在右半段
    }
    return 0;
}

int main()
{
    RecType r[]={1,2,3,4,5,6};
    int length=sizeof(r)/sizeof(int);
    KeyType key=3;
    int i=BinSearch(r,length,key);
    cout <<"下标:"<<i<< endl;
    return 0;
}
           

2、性能分析

把目前查找區間的中間位置上的結點作為根,左子表和右子表中的結點分别作為根節點的左子樹和右子樹。由此得到的二叉樹,稱為描述二分查找樹的判定樹。判定樹隻與表中元素總數有關,與各個元素數值無關。

在判定樹中,将所有節點的空左右孩子處加一個方形節點,并用線條連接配接起來,陳這些方形節點為判定樹的外部節點,由n個節點構成的判定樹外部節點數目為n+1。二分查找中,查找不成功就是走了一條從根節點到外部節點的路徑,比較次數為該路徑上内部節點的個數。 例:具有10個元素的有序表的二分查找的判定樹為 1    2    3    4    5    6    7    8    9    10

順序查找--二分查找--靜态樹表的查找--分塊查找
順序查找--二分查找--靜态樹表的查找--分塊查找

     對于此圖,我麼可以得出:

      查找成功的最少次數:1

      查找成功最多的次數:4

      查找成功的平均次數:ASL=(1*1+2*2+3*4+4*3)/10=2.9=3次;

      查找不成功的最少次數:3

      查找不成功的最多次數:4

      查找不成功的平均次數:ASLunsucc=(3*5+4*6)/(5+6)=39/11=4次;

n個節點的判定樹的深度為[lo g 2 n ]+1,故二分查找在查找不成功時,比較次數最多不超過[log2n]+1。

--->斐波那契查找

斐波那契序列F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n>=2):1,1,2,3,5,8,13,21,.......... 方法:在斐波那契數列找一個等于略大于查找表中元素個數的數F[n],将原查找表擴充為長度為F[n](如果要補充元素,則補充重複最後一個元素,直到滿足F[n]個元素),完成後進行斐波那契分割,即F[n]個元素分割為前半部分F[n-1]個元素,後半部分F[n-2]個元素,找出要查找的元素在哪一部分并遞歸,直到找到。

斐波那契查找的時間複雜度還是O(log 2 n ),但是 與折半查找相比,斐波那契查找的優點是它隻涉及加法和減法運算,而不用除法,而除法比加減法要占用更多的時間,是以,斐波那契查找的運作時間理論上比折半查找小,但是還是得視具體情況而定。 折半查找的middle = (low + hight)/2,除法可能會影響效率,而斐波那契的middle = low + F[k-1] -1,純加減計算,速度要快一些。 對于斐波那契數列:1、1、2、3、5、8、13、21、34、55、89……(也可以從0開始),前後兩個數字的比值随着數列的增加,越來越接近黃金比值0.618。比如這裡的89,把它想象成整個有序表的元素個數,而89是由前面的兩個斐波那契數34和55相加之後的和,也就是說把元素個數為89的有序表分成由前55個資料元素組成的前半段和由後34個資料元素組成的後半段,那麼前半段元素個數和整個有序表長度的比值就接近黃金比值0.618,假如要查找的元素在前半段,那麼繼續按照斐波那契數列來看,55 = 34 + 21,是以繼續把前半段分成前34個資料元素的前半段和後21個元素的後半段,繼續查找,如此反複,直到查找成功或失敗,這樣就把斐波那契數列應用到查找算法中了。

順序查找--二分查找--靜态樹表的查找--分塊查找
順序查找--二分查找--靜态樹表的查找--分塊查找

有序表序列個數n=F(k)-1, 當有序表的元素個數不是斐波那契數列中的某個數字時,需要把有序表的元素個數長度補齊,讓它成為斐波那契數列中的一個數值,當然把原有序表截斷肯定是不可能的,不然還怎麼查找。

 開始将k值與第F(k-1)位置的記錄進行比較(及mid=low+F(k-1)-1),比較結果也分為三種

  1)相等,mid位置的元素即為所求

  2)>,low=mid+1,k-=2;

  說明:low=mid+1說明待查找的元素在[mid+1,high]範圍内,k-=2 說明範圍[mid+1,high]内的元素個數為

n-(F(k-1))= F(k)-1-F(k-1)=F(k)-F(k-1)-1=F(k-2)-1個,是以可以遞歸的應用斐波那契查找。

  3)<,high=mid-1,k-=1。

  說明:high=mid-1說明待查找的元素在[low,mid-1]範圍内,k-=1 說明範圍[low,mid-1]内的元素個數為F(k-1)-1個,是以可以遞歸 的應用斐波那契查找。

#include <iostream>
#include <vector>
using namespace std;
const int MAX_SIZE = 20;

int a[] = { 1, 5, 15, 22, 25, 31, 39, 42, 47,49,59,68,88};
 //a[0~n-1]
void Fibonacci(int F[])
{//斐波那契序列
    F[0] = 0;
    F[1] = 1;
    for (size_t i = 2; i < MAX_SIZE; i++)
        F[i] = F[i - 1] + F[i - 2];

}

int FibonacciSearch(int value)
{//斐波那契查找
    int F[MAX_SIZE];//斐波那契序列F[0]~F[19]
    Fibonacci(F);
    int n = sizeof(a) / sizeof(int);//數組a所含元素個數

    int k = 0;
    while (n > F[k] - 1)//最後找到一個合适的k,使n<=F[k]-1
        k++;
    vector<int> temp;
    temp.assign(a, a + n);//把數組a中元素指派到目前容器temp中
    for (size_t i = n; i < F[k] - 1; i++)
        temp.push_back(a[n - 1]);//若n<F[k]-1,擴充數組temp,最後的元素都是a[n-1]
 //temp[0~n-1]
    int l = 0, r = n - 1;
    while (l <= r)
    {
        int mid = l + F[k - 1] - 1;//mid=low+F[k-1]-1
        if (temp[mid] < value){
            l = mid + 1;
            k = k - 2;
        }
        else if (temp[mid] > value){
            r = mid - 1;
            k = k - 1;
        }
        else{
            if (mid < n)
                return mid;
            else
                return n - 1;
        }
    }
    return -1;
}

int main()
{

    int index = FibonacciSearch(47);
    cout << index << endl;//index為數組下标(0~n-1)

}
           

--->插值查找

在介紹插值查找之前,首先考慮一個新問題,為什麼上述算法一定要是折半,而不是折四分之一或者折更多呢?   打個比方,在英文字典裡面查“apple”,你下意識翻開字典是翻前面的書頁還是後面的書頁呢?如果再讓你查“zoo”,你又怎麼查?很顯然,這裡你絕對不會是從中間開始查起,而是有一定目的的往前或往後翻。   同樣的,比如要在取值範圍1 ~ 10000 之間 100 個元素從小到大均勻分布的數組中查找5, 我們自然會考慮從數組下标較小的開始查找。   經過以上分析,折半查找這種查找方式,不是自适應的(也就是說是傻瓜式的)。二分查找中查找點計算如下:   mid=(low+high)/2, 即mid=low+1/2*(high-low);   通過類比,我們可以将查找的點改進為如下:   mid=low+(key-a[low])/(a[high]-a[low])*(high-low),   也就是将上述的比例參數1/2改進為自适應的,根據關鍵字在整個有序表中所處的位置,讓mid值的變化更靠近關鍵字key,這樣也就間接地減少了比較次數。   基本思想:基于二分查找算法,将查找點的選擇改進為自适應選擇,可以提高查找效率。當然,內插補點查找也屬于有序查找。   注: 對于表長較大,而關鍵字分布又比較均勻的查找表來說,插值查找算法的平均性能比折半查找要好的多。反之,數組中如果分布非常不均勻,那麼插值查找未必是很合适的選擇。   複雜度分析:查找成功或者失敗的時間複雜度均為O(log2(log2n))。

#include <iostream>
#include <vector>
using namespace std;


int InsertionSearch(int r[],int n,int k)
{//r[0~n-1]
    int low=0,high=n-1;
    while(low<high)
    {
        int p=k-r[low],q=r[high]-r[low];
        int mid=low+(p/q)*(high-low);
        if(r[mid]==k) return mid;//找到
        else if(r[mid]>k) high=mid-1;//在左半段
        else low=mid+1;//在右半段
    }
    return 0;
}

int main()
{
    int r[]={1,2,3,4,5,6};
    int length=sizeof(r)/sizeof(int);
    int key=6;
    int i=InsertionSearch(r,length,key);
    cout <<"下标:"<<i<< endl;
    return 0;
}
           

三、靜态樹表的查找

靜态樹表為的是解決查找機率不等的記錄。一般情況下,我們都是預設各個記錄等機率查找的,但是有些記錄可能不是等機率的。我們可能會首先搜尋一些機率大的記錄。   例:

順序查找--二分查找--靜态樹表的查找--分塊查找

次優查找樹和最優查找樹的查找性能僅差1%-2%,而構造最優查找樹花費時間代價較高,構造次優查找樹的時間複雜度為O(nlog2n)。現構造一棵二叉樹, 使得二叉樹的帶權内部路 徑長度PH值在所有具有同樣權值的二叉樹中近似最小,稱為次優查找樹。   PH=w 1 h 1 +w 2 h 2 +.......,n個乘積的和,n為二叉樹上節點個數,hi為第i個節點在二叉樹上的層數,即深度,wi為節點的權。 次優查找二叉樹構造過程:

順序查找--二分查找--靜态樹表的查找--分塊查找
順序查找--二分查找--靜态樹表的查找--分塊查找
順序查找--二分查找--靜态樹表的查找--分塊查找

代碼:

#include <iostream>
#include <vector>
#include <iomanip>
#include <cmath>
#include <stdlib.h>
using namespace std;
//樹節點結構
typedef struct treenode
{
    struct treenode *left;//左孩子
    char data;//資料
    int weight;//權重
    struct treenode *right;//右孩子
}Treenode,* Treep;
int low=1,high=10;//共9個節點
char *R;//字元數組
int *weight;//權重數組
int *sw;//累計權值表sw[i]=w1+w2+...+wi
//初始化二叉樹
void init_tree(Treep &root)
{
    root=NULL;
    cout<<"初始化成功!"<<endl;
}

//建立二叉樹
void SecondOptimal(Treep &rt, char R[],int sw[], int low, int high)
{//由有序表R[low....high]及其累積權值表sw(其中sw[0]==0)遞歸構造次優查找樹T
    int i=low;
    int min = fabs(sw[high] - sw[low]);//ΔP1
    int dw = sw[high] + sw[low-1];//dw為sw[high]
    for(int j=low+1; j<=high; j++)//選擇最小的ΔPi值
    {
        if(fabs(dw-sw[j]-sw[j-1]) < min)
        {
            i=j;//i記錄要選擇的根節點
            min=fabs(dw-sw[j]-sw[j-1]);
        }
    }
    cout<<"i="<<i<<' ';
    rt=(Treep)malloc(sizeof(Treenode));
    rt->data=R[i];        //生成節點
    if(i==low)            //左子樹為空
        rt->left = NULL;
    else                //構造左子樹
        SecondOptimal(rt->left, R, sw, low, i-1);

    if(i==high)            //右子樹為空
        rt->right = NULL;
    else                //構造右子樹
        SecondOptimal(rt->right, R, sw, i+1, high);
}//SecondOptimal

//前序周遊二叉樹
void pre_order(Treep &rt)
{
    if(rt!=NULL)
    {
        cout<<rt->data<<"  ";
        pre_order(rt->left);
        pre_order(rt->right);
    }
}


//查找二叉樹中是否存在某元素
int seach_tree(Treep &rt,char key)
{
    if(rt==NULL)
        return 0;
    else
    {
        if(rt->data==key)
        {
            return 1;
        }
        else
        {
            if(seach_tree(rt->left,key) || seach_tree(rt->right,key))
                return 1;    //如果左右子樹有一個搜尋到,就傳回1
            else
                return 0;    //如果左右子樹都沒有搜尋到,傳回0
        }
    }
}


int main()
{
    Treep root;
    init_tree(root);        //初始化樹

    R=(char *)malloc( high*sizeof(char) );
    for(int i=low; i<high; i++)
        R[i]='A'+i-1;//R[1]~R[9]:A~I
    cout<<"構造次優查找樹的點R[]:"<<endl;
    for(int i=low-1; i<high; i++)
        cout<<setw(3)<<R[i]<<"  ";//R[0]為空,
    cout<<endl;

    weight=(int *)malloc( high*sizeof(int) );
    weight[0]=0;
    weight[1]=1;
    weight[2]=1;
    weight[3]=2;
    weight[4]=5;
    weight[5]=3;
    weight[6]=4;
    weight[7]=4;
    weight[8]=3;
    weight[9]=5;
    cout<<"構造次優查找樹的點的權值weight[]:"<<endl;
    for(int i=low-1; i<high; i++)
        cout<<setw(3)<<weight[i]<<"  ";
    cout<<endl;

    sw=(int *)malloc( high*sizeof(int) );
    sw[0]=0;
    for(int i=low; i<high; i++)
    {
        sw[i]=sw[i-1]+weight[i];//算法描述中的si
    }
    cout<<"構造次優查找樹的點累積權值sw[]:"<<endl;
    for(int i=low-1; i<high; i++)
        cout<<setw(3)<<sw[i]<<"  ";
    cout<<endl;

    //建立二叉樹
    SecondOptimal(root, R, sw, low, high-1);

    //前序周遊二叉樹
    cout<<endl<<"前序周遊序列是:"<<endl;
    pre_order(root);
    cout<<endl;

    //查找二叉樹中是否存在某元素
    cout<<"輸入要查找的元素!"<<endl;
    char ch;
    cin>>ch;
    if(seach_tree(root,ch)==1)
        cout<<"yes!"<<endl;
    else
        cout<<"no!"<<endl;

    return 0;
}
           
順序查找--二分查找--靜态樹表的查找--分塊查找

四、分塊查找

分塊查找又稱索引順序查找,它是順序查找的一種改進方法。   方法描述:将n個資料元素"按塊有序"劃分為m塊(m ≤ n)。每一塊中的結點不必有序,但塊與塊之間必須"按塊有序";即第1塊中任一進制素的關鍵字都必須    小于第2塊中任一進制素的關鍵字;而第2塊中任一進制素又都必須小于第3塊中的任一進制素,……。   操作步驟:   step1 先選取各塊中的最大關鍵字構成一個索引表;   step2 查找分兩個部分:先對索引表進行二分查找或 順序查找,以确定待查記錄在哪一塊中;然後,在已确定的塊中用順序法進行查找。 特點:   (1)比順序查找快很多嗎,但不如二分查找快   (2)也适合于線性連結清單存儲的查找表   (3)即可作為靜态查找法,也可作為動态查找法。因在塊内無序,若塊内沒有查找到,則可插入,此前該塊内應有空,且索引表應含每一塊的上下界。 時間複雜性分析: 時間複雜度:O(log(m)+N/m),N個元素,分為N/M塊,每塊含M個元素。                                                       

順序查找--二分查找--靜态樹表的查找--分塊查找

代碼:                        

順序查找--二分查找--靜态樹表的查找--分塊查找

代碼:

#include <iostream>
#include <vector>
using namespace std;
typedef struct
{
    int r[100];
    int length;
}SSTable; //資料表,被查找表
//分塊查找——索引查找
typedef struct
{
    int key;  //關鍵字域
    int stadr;//起始位址
}IndexItem; //索引表中索引項的結構類型
typedef struct
{
    IndexItem elem[51];
    int length;
}IndexTable;//索引表
int Search_Index(SSTable &ST,IndexTable &ID,int k)
{  //索引查找關鍵字k
    int low,high,mid;
    int p=0;//p用來儲存查找的關鍵字所屬的索引中的位置
    int s,t;//s,t分别用來儲存查找的關鍵字所在塊的起始和終點位置
    low=0;
    int found=0;
    high=ID.length-1;
    while(low<=high&&found!=1)
    {//該循環是用對半查找的方法,對索引表進行查找,進而定位要查找的元素所在的塊
        mid=(low+high)/2;
        if(k>ID.elem[mid-1].key&&k<=ID.elem[mid].key)
        {p=mid;found=1;}//判斷關鍵字對應哪個索引位置,就是k比前一個值大,比後一個值小,
                    //那個大的就是k對應的索引位置
        else if(k>ID.elem[mid].key)
            low=mid+1;
        //else if(k<ID.elem[mid].key)
        else
            high=mid-1;
    }
    cout<<"索引表下标p="<<p<<"即在第"<<p+1<<"塊中."<<endl;
    s=ID.elem[p].stadr;
    if(p==ID.length-1)
        t=ST.length-1;//這裡對p的判斷很重要,p若是索引中最後一個位置,則t應該為ST的表長
    else
        t=ID.elem[p+1].stadr-1; //終止位置為後一塊的起始位址減1
    while(k!=ST.r[s]&&s<=t)//這裡在塊裡進行順序查找
        s++;
    if(s>t)
        return -1;
    else
        return s;
}
//建立需要查找的表,和對半查找用的索引表
void CreateTable(SSTable &ST,IndexTable &ID,int n,int m)
{
    int i;
    cout<<"請輸入待查序清單的長度:";
    cin>>ST.length;
    cout<<"請輸入每一個元素:";
    for(i=0;i<n;i++)
        cin>>ST.r[i];
    cout<<"請輸入索引表的長度:";
    cin>>ID.length;
    cout<<"請輸入索引表的元素(資料和起始位址):";
    for(i=0;i<m;i++)
        cin>>ID.elem[i].key>>ID.elem[i].stadr;
}
int main()
{
    SSTable ST;
    IndexTable ID;
    CreateTable(ST,ID,16,4);
    int i=Search_Index(ST,ID,24);
    if(i==-1)
        cout<<"沒找到!"<<endl;
    else
        cout<<"ST.r["<<i<<"]="<<ST.r[i]<<endl;
    return 0;
}
           
順序查找--二分查找--靜态樹表的查找--分塊查找

繼續閱讀