引言
本文的主要内容为几种查找算法,并使用C++语言实现了几种查找算法。本文记录如有错误,万望指正。笔者的邮箱为:wuxiaofang555555@163.com 。代码详见笔者的GitHub:https://github.com/wuerfang/search。
顺序表查找
-
Sequential_Search
int Sequential_search(std::vector<int> &v, int key) { for (int i = 0; i < v.size(); ++i) { if (v[i] == key) return i; } return -1; }
有序查找
二分查找
-
Binary_Search
int Binary_search(std::vector<int> &v, int key) { int low, high, mid; low = 0; high = v.size() - 1; while (low <= high) { mid = (low + high) / 2; if (v[mid] < key) low = mid + 1; else if (v[mid] > key) high = mid - 1; else return mid; } return -1; }
插值查找
-
Interpolation_search
int Interpolation_search(std::vector<int> &v, int key) { int low, high, mid; low = 0; high = v.size() - 1; while (low <= high) { mid = low + (key - v[low]) / (v[high] - v[low])*(high - low);//与二分查找的区别是mid取法不同 if (v[mid] < key) low = mid + 1; else if (v[mid] > key) high = mid - 1; else return mid; } return -1; }
斐波那契查找
-
Fibonacci_search
int Fibonacci_search(std::vector<int> &v, int key) { const int max_size = 20; //用于构造斐波那契(Fibonacci)数组长度 std::vector<int> F(max_size); //斐波那契(Fibonacci)数组 Fibonacci(F); int low, high, mid, k; low = 0; high = v.size() - 1; k = 0; int n = v.size(); while (n > (F[k] - 1)) { ++k; } std::vector<int> temp(F[k] - 1); for (int i = 0; i < v.size(); ++i) { temp[i] = v[i]; } for (int i = v.size(); i < F[k] - 1; ++i) { temp[i] = v[v.size() - 1]; } while (low <= high) { mid = low + F[k - 1] - 1; if (temp[mid] < key) { low = mid + 1; k -= 2; } else if (temp[mid] > key) { high = mid - 1; k -= 1; } else { if (mid < v.size()) return mid; else return v.size() - 1; } } return -1; }
-
Fibonacci
void Fibonacci(std::vector<int> &F) { //构造斐波那契数组 F[0] = 0; F[1] = 1; for (int i = 2; i < F.size(); ++i) { F[i] = F[i - 1] + F[i - 2]; } }
线性索引查找
索引按照结构可以分为线性索引、树形索引、多级索引。
所谓的线性索引就是将索引项集合组织为线性结构,也称为索引表。
这里只说明一下三种线性索引:稠密索引、分块索引、倒排索引。
稠密索引
- 稠密索引指在线性索引中,将数据集中的每个记录对应一个索引项
- 其索引表中的索引项一定是按照关键码有序的排列
分块索引
- 分块有序是把数据集的记录分成为了若干块,并且这些块需要满足两个条件
- 块内无序
- 块间无序
- 分索引块的索引项结构分为三个数据项
- 最大关键码:存储此块的最大关键字,使得在它下一块中的最小关键字也能比此块的最大关键字要大
- 存储了块中的记录个数:便于循环时使用
- 用于指向块首数据元素的指针:便于开始对这一块中的记录进行遍历
倒排索引
- 索引表的通用结构为
- 此关键码:例如下图中的“英文单词”
- 记录号表:例如下图中的“文章编号”
二叉排序树
- 二叉排序树结构,非常有利于查找,特别是平衡二叉树,详解见https://blog.csdn.net/qq_41713091/article/details/106610822
哈希表(散列表)
概述
存储位置 = f ( 关键字 )
f
称为散列函数,又称为**哈希(hash)函数
**。
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f
,使得每个关键字key对应一个存储位置f(key)
。散列技术既是一种存储方法,也是一种查找方法。
散列技术最适合查找与给定值相等的记录,查找效率非常高。
散列函数构造方法
构造散列函数的两个原则:1)计算简单;2)散列地址分布均匀。
- 直接定址法
- 取关键字的某个线性函数值为散列地址,即 f ( k e y ) = a ∗ k e y + b ( a 、 b 为 常 数 ) f(key)=a*key+b(a、b为常数) f(key)=a∗key+b(a、b为常数)
- 数字分析法
- 抽取关键字的一部分来计算散列存储位置。例如号码取后四位。
- 平方取中法
- 先将关键字平方,再取中间的几位作为散列地址。例如关键字 k e y = 1234 , k e y 2 = 1522756 , 取 中 间 三 位 227 作 为 散 列 地 址 key=1234, key^2=1522756,取中间三位227作为散列地址 key=1234,key2=1522756,取中间三位227作为散列地址。
- 折叠法
- 将关键字从左到右分割成位数相等的几部分,然后叠加求和,再取后几位作为散列地址。例如关键字 k e y = 9876543210 , 分 为 四 组 , 987 ∣ 654 ∣ 321 ∣ 0 , 叠 加 求 和 987 + 654 + 321 + 0 = 1962 , 取 后 三 位 为 962 作 为 散 列 地 址 key=9876543210,分为四组,987|654|321|0, 叠加求和987+654+321+0=1962,取后三位为962作为散列地址 key=9876543210,分为四组,987∣654∣321∣0,叠加求和987+654+321+0=1962,取后三位为962作为散列地址
- 除留余数法
- f ( k e y ) = k e y m o d p ( p ≤ m ) , 其 中 m 为 散 列 表 长 度 f(key)=key\;mod \;p \; (p\leq m),其中m为散列表长度 f(key)=keymodp(p≤m),其中m为散列表长度
- 若散列表表长为 m m m,通常p为小于或等于表长的最小指数或不包含小于20质因子的合数
- 随机数法
- f ( k e y ) = r a n d o m ( k e y ) f(key)=random(key) f(key)=random(key)
处理散列冲突的方法
使用前述散列函数构造方法,会出现不同的关键字出现相同的散列存储地址,即 k e y 1 ≠ k e y 2 , 而 f ( k e y 1 ) = f ( k e y 2 ) key_1 \neq key_2,而f(key_1)=f(key_2) key1=key2,而f(key1)=f(key2),这种情况称为冲突。
-
开放定址法
-
线性探测法: f i ( k e y ) = ( f ( k e y ) + d i ) m o d m ( d i = 1 , 2 , 3 , . . . , m − 1 ) f_i(key)=(f(key)+d_i)\;mod \;m\;(d_i=1,2,3,...,m-1) fi(key)=(f(key)+di)modm(di=1,2,3,...,m−1)
-
二次探测法: f i ( k e y ) = ( f ( k e y ) + d i ) m o d m ( d i = 1 2 , − 1 2 , 2 2 , − 2 2 , . . . , q 2 , − q 2 , q ≤ m / 2 ) f_i(key)=(f(key)+d_i)\;mod \;m\;(d_i=1^2,-1^2,2^2,-2^2,...,q^2,-q^2,q\leq m/2) fi(key)=(f(key)+di)modm(di=12,−12,22,−22,...,q2,−q2,q≤m/2)
-
随机探测法: f i ( k e y ) = ( f ( k e y ) + d i ) m o d m ( d i 是 一 个 随 机 数 列 ) f_i(key)=(f(key)+d_i)\;mod \;m\;(d_i是一个随机数列) fi(key)=(f(key)+di)modm(di是一个随机数列)
(说明:这里的随机为伪随机,故种子设置相同,可以获得相同的随机数列)
-
-
再散列函数法
- f i ( k e y ) = R H i ( k e y ) ( i = 1 , 2 , . . . , k ) f_i(key)=RH_i(key)\;(i=1,2,...,k) fi(key)=RHi(key)(i=1,2,...,k)
- 使用不同的散列函数,即 R H i RH_i RHi为不同的散列函数
-
链地址法
- 将关键字为同义词的记录存储在一个单链表中,称其为同义词子表
- 如集合,使用12为除数,采用除留余数法,如图所示
-
公共溢出区法
- 为那些有冲突的关键字建立一个公共的溢出区
- 如集合,使用12为除数,采用除留余数法,如图所示
散列表查找
以下均为伪代码
-
SearchHas
bool SearchHash(HashTable H, int key, int *addr){ *addr=Hash(key); while(H.elem[*addr]!=key){ //如果不为空,则冲突 *addr=(*addr+1)%m; //开放定址法的线性探测 if(H.elem[*addr]==null||+*addr==Hash(key)){ return false; } } return true; }
-
HashTable
typedef struct{ int *elem; //数据元素存储基址 int count; //当前元素个数 }HashTable;
-
Hash
int Hash(int key){ return key%m; //除留余数法 }
-
map: C++11中的哈希表可用关联容器map实现