简单查找算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/angle_chen123/article/details/79124272
一、顺序查找:(静态查找)
 一个一个查找,基于线性表的查找,时间复杂度为O(n),查找成功时次数不超过n
 int SortSearch(int a[],int c)
 {
 for(int i=0;i<a.length;i++){
  if(a[i]==c){
      printf("查到了你想要的结果:%d",c);
      return i;
  }else{
   printf("没有查到你想要的结果!");
   return -1;
  }
 }
 }
二、折半查找、二分查找:(静态查找
 这个需要查找对象是有序的,每次都找1/2的部分,查找次数大大的减少了,时间复杂度是O(logN).
 折半查找其实就是一颗二叉树的遍历,其中的元素就是二叉树的根。
 注:折半查找方法使用于不经常变动而查找频繁的有序列表
int binarySearch(int a[],int c){
//注这里查找需要先排序,假设已经为有序数组了
 int low=0;
int high=a.length-1;
int middle;
while(low<=high){
 middle=(high+low)/2;
if(c==b[middle]){
printf("您要找的结果%d",middle);
 return middle;
  }else if(c>b[middle]){
     low=middle+1;
   }else if(c<b[middle]){
     high=middle-1;
   }
}
}
三、二叉树查找
二叉树用二叉链表作为存储结构,数据类型描述,
   typedef int KeyType;
   typedef struct Node
 {
 KeyType key;
struct Node *Lchild,*Rchild;
}BSTNode,*BSTree;
1.二叉排序树查找
特点: 如果它的左子树不空,那么左子树上的所有结点的值均小于它的根节点值;
 如果它的右子树不空,那么右子树上的所有结点值均大于它的根结点值;
它的左右子树均可以分别为二叉树
  时间复杂度:最好O(n)[所有结点构成一个二叉树]
    最差O(longn)[所有的结点都在一边]
算法实现:
(1)非递归实现
 BSTree SearchBST(BSTree bstKeyType k)
 {
 BSTree q;
q=bst;
while(q){
 if(q->key==k)
       return q;
    if(k<q->key )
    q=q->lchild;
     esle q=q->rchild;
}
return null;
}
 (2)递归实现
BSTree SearchBST(BSTree bstKeyType k)
{
   if(!bst)
  return null;
else if(bst->key==k)
 returnSearchBST(bst->lchild,key);
   else
  returnSearchBST(bst-rchild,key);
}
2.平衡二叉树
 若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的
最小子树根结点的指针。然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树。
当失去平衡的最小子树被调整为平衡子树后,原有其他所有不平衡子树无需调整,整个二叉排序树就
又成为一棵平衡二叉树
 先要调整二叉树,使其成为平二叉树,二叉树的调
 //平衡二叉树的结构体  
 typedef int elementType;  
 typedef struct AVLNODE   
 {  
  elementType data;  
   struct AVLNODE * left;  
  struct AVLNODE * right;  
  int height;  //以此节点为根,树的高度;  
  unsigned int freq;//此节点保存的数据出现的频率  
 }AvlNode,*PtrToNode; </ span >   
 //平衡处理   
 void  RightBalance(PtrToNode &node)  
 {  
   PtrToNode ptrTmp=node->right;  
    if (NodeHeight(ptrTmp->right)-NodeHeight(ptrTmp->left)==-1)  
  {   //左子树比右子树高,说明在左子树插入的   
      DoubleRotateWithRight(node);  //RL  
     } else   {  
         RotateWithRight(node);   //RR       
  }  
 }
 //查找
 PtrToNode AVL_Find(PtrToNode & node,elementType x)  
 {  
      if  (node==NULL)   //没找到元素   
     {  
          return  NULL;  
     }  
      else   if (x<node->data)  
     {  
          return  AVL_Find(node->left,x);  //在左子树里面查找   
     }  
      else   if (node->data<x)  
     {  
          return  AVL_Find(node->right,x);  //在右子树里面查找   
     }  
      else   //相等   
          return  node;  
 }  

主要有这么几个缺陷:
1、为了保证高度平衡,动态插入和删除的代价也随之增加,我们可以通过红黑树来实现更高效率的查找结构;
2、所有二叉查找树结构的查找代价都与树高有紧密的联系,可以通过减少树高来进一步的降低查找代价,我们可以通过
 多路查找树的结构来做到这一点;
3、在大数据量查找环境下,所有的二叉查找树结构(BST,AVL,RBT)都不合适,如此大规模的数据,全部组织成平衡二叉树
放入到内存中是不可能的。那么把这棵树放到磁盘中吧,问题又来了。假如构造的平衡二叉树深度有1W层,那么从根节点出
发到叶子节点很可能需要1W次的硬盘I/O读写。查找效率在IO读写过程中将会付出巨大的代价。
四、索引查找

线性表的长度为n,每块有i个记录,平均查找次数[log2(n/2+1)]-1+(i+1)/2

如果线性表既希望查找速度快,又需要冬天变化,则可以用索引查找

过程:

首先根据给定的索引值k1,在索引表上查找索引值等于k1的索引项,以确定对应字表在表中的开始位置

和长度,然后关键字等于k2的元素,

设置数组A是具有mainlist类型的一个主表,数组B是具有indexlist类型的在主表A上建立的一个索引表,m为索引

B的实际长度,即所含的索引项的个数,k1和k2分别为给定

算法实现;

int Indsch(mainlist A, indexlist B, int m, IndexKeyType K1, KeyType K2)  

{//利用主表A和大小为 m 的索引表B索引查找索引值为K1,关键字为K2的记录  

 //返回该记录在主表中的下标位置,若查找失败则返回-1  

    int i, j;  

    for (i = 0; i < m; i++)  

        if (K1 == B[i].index)  

            break;  

    if (i == m)  

        return -1; //查找失败      

 j = B[i].start;  

    while (j < B[i].start + B[i].length)  

    {  

        if (K2 == A[j].key) break;  

        else  

            j++;  

    }  

    if (j < B[i].start + B[i].length)  

        return j; //查找成功  

    else  

        return -1; //查找失败  

}  

//若 IndexKeyType 被定义为字符串类型,则算法中相应的条件改为  

 //strcmp (K1, B[i].index) == 0;  

//同理,若KeyType 被定义为字符串类型  

//则算法中相应的条件也应该改为    

//strcmp (K2, A[j].key) == 0    

//若每个子表在主表A中采用的是链接存储,则只要把上面算法中的while循环  

//和其后的if语句进行如下修改即可:  

while (j != -1)//用-1作为空指针标记  

{  

    if (K2 == A[j].key) break;  

    else  

        j = A[j].next;  

}  

return j;  

}

五、分块查找

 分块查找属于索引查找,其对应的索引表为稀疏索引,具体地说,分块查找要求主表中每个子表(又称为块)之间

是递增(或递减)有序的。即前块中最大关键字必须小于后块中的最小关键字,但块内元素的排列可无序。它还要求索

引值域为每块中的最大关键字。


算法实现:

int Blocksch(mainlist A, indexlist B, int m, KeyType K)
 {//利用主表A和大小为m的索引表B分块查找关键字为K的记录
    int i, j;
    for (i = 0; i < m; i++)
        if (K <= B[i].index)
            break;
    if (i == m)
        return -1; //查找失败
    j = B[i].start;
    while (j < B[i].start + B[i].length)
    {
        if (K == A[j].key)
            break;
        else
            j++;
    }
    if (j < B[i].start + B[i].length)
        return j;
    else
        return -1;
}
//若在索引表上不是顺序查找,而是二分查找相应的索引项,则需要把算法中的for循环
//语句更换为如下的程序段:
int low = 0, high = m - 1;
while (low <= high)
{
    int mid = (low + high) / 2;
    if (K == B[mid].index)
    {
        i = mid;
        break;
    }
    else if (K < B[mid].index)
        high = mid - 1;
    else
        low = mid + 1;
}
if (low > high)
    i = low;
这里当二分查找失败时,应把low的值赋给i,此时b[i].index是刚大于K的索引值
当然若low的值为m,则表示真正的查找失败。

六、散列表查找
散列函数构造:
1.开放地址法

 所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

    它的公式为: fi(key)=(f(key)+di) MOD m(d1=1,2,3,4.....,m-1)

 比如说,关键字集合为{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},表长为12。散列函数f(key) = key mod 12

当计算前5个数{12, 67, 56, 16, 25}时,都是没有冲突的散列地址,直接存入,如下表所示。

计算key = 37时,发现f(37) = 1,此时就与25所在的位置冲突。于是应用上面的公式f(37) = (f(37) + 1) mod 12 =2,。于是将37存入下标为2的位置。

如下:

下标  0 1 2 3 4 5 6 7 8 9 10 11

关键字12 25 37 16 67 56 22

    接下来22,29,15,47都没有冲突,正常的存入,如下

下标  0 1 2 3 4 5 6 7 8 9 10 11

关键字12 25 37 15 16 29 67 56 22   47

    到了48,计算得到f(48) = 0,与12所在的0位置冲突了,不要紧,我们f(48) = (f(48) + 1) mod 12 = 1,此时又与25所在的位置冲突。于是f(48) = (f(48) + 2)

mod 12 = 2,还是冲突......一直到f(48) = (f(48) + 6) mod 12 = 6时,才有空位,如下表所示。

,还是冲突......一直到f(48) = (f(48) + 6) mod 12 = 6时,才有空位,如下表所示。


    把这种解决冲突的开放定址法称为线性探测法

    考虑深一步,如果发生这样的情况,当最后一个key = 34,f(key) = 10,与22所在的位置冲突,可是22后面没有空位置了,反而它的前面有一个空位置,

尽管可以不断地求余后得到结果,但效率很差。因此可以改进di=12, -12, 22, -22.........q2, -q2(q<= m/2),这样就等于是可以双向寻找到可能的空位置。

对于34来说,取di = -1即可找到空位置了。另外,增加平方运算的目的是为了不让关键字都聚集在某一块区域。称这种方法为二次探测法。

  fi(key)=(f(key)+di=1^2,-1^2,2^2,....q<=m/2)

     还有一种方法,在冲突时,对于位移量di采用随机函数计算得到,称之为随机探测法

    既然是随机,那么查找的时候不也随机生成di 吗?如何取得相同的地址呢?这里的随机其实是伪随机数。伪随机数就是说,如果设置随机种子相同,则不

 断调用随机函数可以生成不会重复的数列,在查找时,用同样的随机种子,它每次得到的数列是想通的,相同的di 当然可以得到相同的散列地址。

  fi(key)=(f(key)+di MOD m (di是一个随机数列))

    总之,开放定址法只要在散列表未填满时,总是能找到不发生冲突的地址,是常用的解决冲突的方法。

2 再散列函数法

    对于散列表来说,可以事先准备多个散列函数。

 fi(key)=RHi(key)(i=1,2,....k)

    这里RH就是不同的散列函数,可以把前面说的除留余数、折叠、平方取中全部用上。每当发生散列地址冲突时,就换一个散列函数计算。

    这种方法能够使得关键字不产生聚集,但相应地也增加了计算的时间。

3 链地址法

    将所有关键字为同义词的记录存储在一个单链表中,称这种表为同义词子表,在散列表中只存储所有同义词子表前面的指针。对于关键字集合

{12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34},用前面同样的12为余数,进行除留余数法,可以得到下图结构。




    此时,已经不存在什么冲突换地址的问题,无论有多少个冲突,都只是在当前位置给单链表增加结点的问题。

    链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保证。当然,这也就带来了查找时需要遍历单链表的性能损耗。

4 公共溢出区法

    这个方法其实更好理解,你冲突是吧?那重新给你找个地址。为所有冲突的关键字建立一个公共的溢出区来存放。

    就前面的例子而言,共有三个关键字37、48、34与之前的关键字位置有冲突,那就将它们存储到溢出表中。如下图所示。




    在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行比对,如果相等,则查找成功;如果不相等,则到溢出表中进行顺序查找。

如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。

 算法实现

#include <stdio.h>

#include <stdlib.h>

#define OK 1
#define ERROR 0
#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12 //定义散列表表未数组的长度
#define NULLKEY -32768
typedef struct
{
    int *elem;  //数据元素存储基地址,动态分配数组
    int count;  //当前数据元素个数
}HashTable;     
 int m = 0;     //散列表长,全局变量
 //初始化散列表
 int InitHashTable(HashTable *h)
 {
     int i;
     m = HASHSIZE;
     h->elem = (int *)malloc(sizeof(int) * m );
     if(h->elem == NULL)
     {
         fprintf(stderr, "malloc() error.\n");
         return ERROR;
     }
     for(i = 0; i < m; i++)
     {
         h->elem[i] = NULLKEY;
     }
     return OK;
 }
 //散列函数
 int Hash(int key)
 {
     return key % m;    //除留余数法
 }
 //插入关键字进散列表
 void InsertHash(HashTable *h, int key)
 {
     int addr = Hash(key);              //求散列地址
     while(h->elem[addr] != NULLKEY) //如果不为空,则冲突
     {
         addr = (addr + 1) % m;         //开放地址法的线性探测
     }
     h->elem[addr] = key;                //直到有空位后插入关键字
 }
 //散列表查找关键字
 int  SearchHash(HashTable h, int key)
 {
     int addr = Hash(key);                  //求散列地址
     while(h.elem[addr] != key)     //如果不为空,则冲突
     {  
         addr = (addr + 1) % m;     //开放地址法的线性探测
         if(h.elem[addr] == NULLKEY || addr == Hash(key))
         {
             //如果循环回原点
             printf("查找失败, %d 不在Hash表中.\n", key);
             return UNSUCCESS;
         }
     }
     printf("查找成功,%d 在Hash表第 %d 个位置.\n", key, addr);
     return SUCCESS;
 }
 int main(int argc, char **argv)
 {
      int i = 0;
      int num = 0;
      HashTable h;
      //初始化Hash表
      InitHashTable(
      //未插入数据之前,打印Hash表
      printf("未插入数据之前,Hash表中内容为:\n");
      for(i = 0; i < HASHSIZE; i++)
      {
          printf("%d  ", h.elem[i]);
      }
      printf("\\n"
      //插入数据
      printf("现在插入数据,请输入(A代表结束哦).\n");
      while(scanf("%d", &i) == 1 && num < HASHSIZE) 
      { 
          if(i == 'a') 
          {
              break;
          }
          num++;
          InsertHash(&h,i);   
          if(num > HASHSIZE)
          {
              printf("插入数据超过Hash表大小\n");
              return ERROR;
          }
      }
      //打印插入数据后Hash表的内容
      printf("插入数据后Hash表的内容为:\n");
      for(i = 0; i < HASHSIZE; i++)
      {
          printf("%d  ", h.elem[i]);
      }
      printf("\n");
     printf("现在进行查询.\n");
     SearchHash(h, 12);
     SearchHash(h, 100);
     return 0;
 }

散列技术最适合的求解问题是查找与给定值相等的记录。对于查找来说,简化了比较过程,效率会大大提高。








 







 






















猜你喜欢

转载自blog.csdn.net/angle_chen123/article/details/79124272