搜索

搜索

  • 线性搜索
  • 二分搜索
  • 散列法

线性搜索

  • 数组开头顺次访问各元素,检查该元素是否与目标元素相等。
    • 一旦相等则返回该元素的位置并结束搜索。
    • 如果检查到数组末尾仍未发现目标值,则返回一个特殊值来说明该情况。
  • 线性搜索的效率很低,但适用于任何算法。

AOJ : ALDS1_4_A

最容易想到的实现:

linearSearch()
  for i 从 0 到 n - 1
    for j 从 0 到 m - 1
      if T[i] == S[j] count++
  return count

对本题而言,这种方法的时间复杂度为 O(n²)。
注意到题目中对 T[ ] 的限制:a sequence of different q integers T
可知,T 中的元素互不相同,因此没有必要在另设一个数组来储存 T
只需在输入的循环中,不断将 T 的当前值,赋给 key 即可

  • 对于线性搜索,其代码实现为:
linearSearch()
  for i0 到 n - 1
    if A[i] 与 key 相等
      return i
    return NOT_FOUND

但可以注意到,如引入标记,可以提升算法效率数倍

标记

  • 标记即为数组等数据结构中设置的一个拥有特殊值的元素
  • 在线性搜索中,我们可以把含有目标关键字的数据放在数组末尾

含有标记的线性搜索可用如下方法实现:

linearSearch()
  i = 0
  A[n] = key
  while A[n] 与 key 不同
    i++
  if i 到达了 n
    return NOT_FOUND
  return i

例题实现:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int S[10001],key;
int N, M,count;
int search(){
    int i = 0;
    S[N] = key;
    while(S[i] != key) i++;
    return i != N;
}
int main(){
    scanf("%d",&N);
    for(int i = 0; i < N; i++){
        scanf("%d",&S[i]);
    }
    scanf("%d",&M);
    for(int j = 0; j < M; j++){
        scanf("%d",&key);
        if(search()) count++;
    }
    printf("%d\n", count);
    return 0;
}

二分搜索

  • 利用数据的大小进行高速搜索。
  • 如数据无序,可先用排序算法将其排序即可。

假设数组中存放的数据已按关键字升序排列,那么对其使用二分搜索的算法思路如下:

二分搜索的算法
1. 将整个数组作为搜索范围
2.检查位于搜索范围最中央的元素
3.如果中央元素的关键字与目标关键字一致则搜索结束
4.如果目标关键字小于中央元素的关键字,则以前半部分为搜索范围重新执行2;如果大于目标关键字则以后半部分为搜素范围重新执行2

AOJ : ALDS1_4_B

注意到题目中如下描述:Elements in S is sorted in ascending order
因此采用二分搜索:
这里假设有一个包含 n 个元素的数组 A ,利用二分搜索在其中寻找 key
算法伪代码如下:

binarySearch(A,key)
  left  = 0
  right = n
  while left < right
    mid = ( left + right) / 2
    if A[mid] == key
      return mid
    else if key < A[mid]
      right = mid
    else
      left = mid +1
  return NOT_FOUND

实现二分搜索需要表示搜索范围的变量 left 和 right ,以及指示变量 mid

变量含义
left搜索范围开头元素
right末尾元素的后一个元素
midleft 与 right 和的一半(小数点后直接舍去)
  • while 循环中,先通过 ( left + right) / 2 求出当前搜索范围的中间位置 mid
  • 再将 mid 所指中间元素 A[mid]key 进行比较,如果一致则返回 mid
  • key 小于 A[mid] 时,说明目标值位于 mid 前方,所以把 mid 赋给 right 将搜索范围缩小至前半部分
  • 反之把 mid + 1 赋给 left
  • while 的循环条件 left < right表示搜索范围仍不为空
  • 搜索范围为空,就代表在数组中没有找到 key,返回 NOT_FOUND
    对本题而言,时间复杂度为O(qlgn)
本题的数据都已完成了排序,今后再遇到无序的数据时,只要预先进行一次排序,就可以应用二分搜索

例题实现:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int S[100001], key, n;
int binarySearch(int key){
    int left = 0;
    int right = n;
    int mid;
    while(left < right){
        mid = (left + right) / 2;
        if(key == S[mid]) return 1;
        if(key > S[mid]) left = mid +1;
        else if(key < S[mid]) right = mid;
    }
    return 0;
} 

int main(){
    int m, count;
    scanf("%d",&n);
    for(int i = 0; i < n; i++){
        scanf("%d", &S[i]);
    }
    scanf("%d", &m);
    for(int i = 0; i < m; i++){
        scanf("%d", &key);
        if(binarySearch(key)) count++;
    }
    printf("%d\n",count);
    return 0;
}

散列法

  • 各元素的储存位置由散列函数决定
  • 只需将关键字(值)代入特定函数即可找出其对应位置
  • 散列是一种数据结构,同时也是一种使用散列表的算法

散列法是一种搜索算法,它可以根据各元素的值来确定存储位置,然后将位置保存在散列表中,从而实现数据的高速搜索。
散列表是一种数据结构,能对包含关键字的数据集合高效地执行动态插入、搜索、删除操作。

散列表 由容纳 m 个元素的数组 T ,以及数据根据关键字决定数组下标的函数( 散列函数)共同组成。我们要将数据的关键字输入该函数,由该函数决定数据在数组中的位置。

  • 散列表的实现(简单实现)
insert(data)
  T[h(data.key)] = data

search(data)
  return T[h(data.key)]

这里假设 散列函数的输入值data.key 为整数。

注意:当关键字为字符串等其他类型时,需要借助某些手法将其转换为相应的整数

h(k) 是根据k值求数组 T 下标的函数,称作散列函数
该函数的返回值 ,称作散列值
散列函数求出的散列值范围在 0m - 1 之间(m为数组 T 的长度)。
为满足这一条件,函数内需使用取余运算,保证输出值为 0m - 1之间的整数。
比如:

h(k) = k mod m

就是一种散列函数。
不过,单有这一个运算,会发生不同的 key 对应同一散列值的情况,即出现“冲突”。
开放地址法 是解决这类冲突的常用手段之一。此处介绍双散列结构中使用的开放地址法。
如下所示,在双散列结构中一旦出现冲突,程序会调用第二个散列函数来求散列值。

H(k) = h(k,i) = (h₁(k) + i × h₂(k)) mod m

散列函数h(k,i) 拥有关键字 k 和整数 i 两个参数,这里的 i 是发生冲突后计算下一个散列值的次数。
也就是说,只要散列函数 H(k) 起了冲突,就会依次调用 h(k,0)、h(k,1)、h(k,2)… 直到不发生冲突为止,然后返回这个 h(k,i) 的值作为散列值。
该算法先通过 h₁(k) 求出第一个下标,然后在发生冲突时将下标移动 h₂(k) 个位置,从而寻找仍然空着的位置。

要注意的是,因为下标每次都移动 h(k) 个位置,所以必须保证 T 的长度 mh₂(k) 互质,否则会出现无法生成下标的情况。这种时候,我们可以特意让 m 为质数,然后取一个小于 m 的值作为 h₂(k) ,从而避免上述情况发生。

比如,散列法可通过下列方法实现:

h1(key)
  return key mod m

h2(key)
  return 1 + (key) mod (m - 1)

h(key,i)
  return (h1(key) + i * h2(key)) mod m

insert(T,key){
  i = 0
  while true
    j = h(key,i)
    if T[j] == NIL
      T[j] = key
      return j
    else 
      i++
}

search(T,key){
  i = 0
  while true 
    j = h(key ,i)
    if T[j] == key
      return j
    else if T[j] == NIL or i >= m
      return NIL
    else 
      i = i + 1
}

这里用 T[j] 是否为 NIL 来判断当前位置是否为空。

AOJ : ALDS1_4_C

#include<stdio.h>
#include<string.h>

#define M 1046527
#define NIL (-1)
#define L 14

char H[M][L];

//将字符串转换为数值 
int getChar(char ch){
    if (ch == 'A') return 1;
    else if(ch == 'C') return 2;
    else if(ch == 'G') return 3;
    else if(ch == 'T') return 4;
    else return 0;
}

//将字符串转换为数值并生成key
long long getKey(char str[]){
    long long sum = 0, p = 1, i;
    for(i = 0; i < strlen(str); i++){
        sum += p * (getChar(str[i]));
        p *= 5;
    }
    return sum;
} 

int h1(int key){
    return key % M;
}

int h2(int key){
    return 1 + (key % (M - 1));
}

int find(char str[]){
    long long key, i, h;
    key = getKey(str);  //将字符串转换为数值
    for(i = 0; ;i++){
        h = (h1(key) + i * h2(key)) % M;
        if( strcmp(H[h],str) == 0) return 1;
        else if(strlen(H[h]) == 0) return 0;
    } 
    return 0;
}

int insert(char str[]){
    long long key, i, h;
    key = getKey(str); //将字符串转换为数值
    for(i = 0; ; i++){
        h = (h1(key) + i * h2(key)) % M;
        if( strcmp(H[h],str) == 0) return 1;
        else if( strlen(H[h]) == 0) {
            strcpy(H[h],str);
            return 0;
        }
    }
    return 0;
}

int main(){
    int i, n, h;
    char str[L],com[9];
    for(i = 0; i < M; i++) H[i][0] = '\0';
    scanf("%d",&n);
    for(i = 0; i < n; i++){
        scanf("%s %s",com,str);
        if( com[0] == 'i'){
            insert(str);
        }
        else{
            if(find(str)){
                printf("yes\n");
            }
            else printf("no\n");
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值