搜索
- 线性搜索
- 二分搜索
- 散列法
线性搜索
- 从数组开头顺次访问各元素,检查该元素是否与目标元素相等。
- 一旦相等则返回该元素的位置并结束搜索。
- 如果检查到数组末尾仍未发现目标值,则返回一个特殊值来说明该情况。
- 线性搜索的效率很低,但适用于任何算法。
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 i 从 0 到 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 | 末尾元素的后一个元素 |
mid | left 与 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 下标的函数,称作散列函数。
该函数的返回值 ,称作散列值。
散列函数求出的散列值范围在 0 到 m - 1 之间(m为数组 T 的长度)。
为满足这一条件,函数内需使用取余运算,保证输出值为 0 到 m - 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 的长度 m 与 h₂(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;
}