顺序查找--二分查找--静态树表的查找--分块查找

一、顺序表的顺序查找

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;
}
                                                 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值