一、线性表的查找
1. 顺序查找(线性查找)
1.1 基本概念
1.2 代码
#include <iostream>
using namespace std;
// 全局变量 查找表
#define LEN (10)
int searchTable[LEN + 1] = { 0, 1, 16, 24, 35, 47, 59, 62, 73, 88, 99 }; // 查找表元素从下标1开始存储
/**
* @brief: 常规顺序查找
* @param a: 顺序表指针
* @param n: 顺序表长度
* @param key: 要查找的关键字
* @return: 若查找成功,则返回key所在下标,否则返回0
*/
int sequentialSearch(int* a, int n, int key)
{
for (size_t i = 1; i <= n; i++)
{
if (a[i] == key)
{
return i; // 查找成功
}
}
return 0; // 查找失败
}
int main(int argc, char* argv[])
{
int key = 199;
int pos = sequentialSearch(searchTable, LEN, key);
if (pos)
{
cout << "the index of " << key << " is " << pos << endl;
}
else
{
cout << "not found" << endl;
}
}
从上面代码可以看出,除了要进行 a [ i ] a[i] a[i]与关键字 k e y key key是否相等比较外,每次循环还需要对 i i i是否越界进行检查。为了避免每次循环的越界检查,可以设置一个哨兵,即设置 a [ 0 ] = k e y a[0]=key a[0]=key,优化后的代码如下。
#include <iostream>
using namespace std;
// 全局变量 查找表
#define LEN (10)
int searchTable[LEN + 1] = { 0, 1, 16, 24, 35, 47, 59, 62, 73, 88, 99 }; // 查找表元素从下标1开始存储
/**
* @brief: 带哨兵优化的顺序查找
* @param a: 顺序表指针
* @param n: 顺序表长度
* @param key: 要查找的关键字
* @return: 若查找成功,则返回key所在下标,否则返回0
*/
int sequentialSearchOpt(int* a, int n, int key)
{
a[0] = key; // 哨兵
int i = n; // 循环从尾部开始
while (a[i] != key)
{
i--;
}
// 由于设置了a[0]=key,所以即使在a[1]~a[n]处不等于key,那么while循环也会在i=0时结束,此时查找失败
return i;
}
int main(int argc, char* argv[])
{
int key = 199;
int pos = sequentialSearchOpt(searchTable, LEN, key);
if (pos)
{
cout << "the index of " << key << " is " << pos << endl;
}
else
{
cout << "not found" << endl;
}
}
2. 折半查找(二分或对分查找)
2.1 基本概念
2.2 代码
#include <iostream>
using namespace std;
// 全局变量 查找表
#define LEN (10)
int searchTable[LEN + 1] = { 0, 1, 16, 24, 35, 47, 59, 62, 73, 88, 99 }; // 查找表元素从下标为1处开始
/**
* @brief: 二分查找迭代形式
* @param a: 查找表指针
* @param n: 查找表长度
* @param key: 要查找的关键字
* @return: 若查找成功,则返回key所在下标,否则返回0
*/
int binarySearch(int* a, int n, int key)
{
int low = 1; // 记录最低下标
int high = n; // 记录最高下标
int mid; // 记录中值下标
while (low <= high)
{
mid = (low + high) / 2;
if (key > a[mid]) // 若查找值大于中值
{
low = mid + 1;
}
else if (key < a[mid]) // 若查找值小于中值
{
high = mid - 1;
}
else // 若查找值等于中值
{
return mid;
}
}
return 0;
}
int main(int argc, char* argv[])
{
int key = 59;
int pos = binarySearch(searchTable, LEN, key);
if (pos)
{
cout << "the index of " << key << " is " << pos << endl;
}
else
{
cout << "not found" << endl;
}
}
#include <iostream>
using namespace std;
// 全局变量 查找表
#define LEN (10)
int searchTable[LEN + 1] = { 0, 1, 16, 24, 35, 47, 59, 62, 73, 88, 99 }; // 查找表元素从下标为1处开始
/**
* @brief: 二分查找递归形式
* @param a: 查找表指针
* @param n: 查找表长度
* @param key: 要查找的关键字
* @return: 若查找成功,则返回key所在下标,否则返回0
*/
int binarySearchRec(int* a, int low, int high, int key)
{
if (low > high)
{
return 0;
}
int mid = (low + high) / 2;
if (key > a[mid]) // 若查找值大于中值
{
return binarySearchRec(a, mid + 1, high, key);
}
else if (key < a[mid]) // 若查找值小于中值
{
return binarySearchRec(a, low, mid - 1, key);
}
else // 若查找值等于中值
{
return mid;
}
}
int main(int argc, char* argv[])
{
int key = 59;
int pos = binarySearchRec(searchTable, 1, LEN, key);
if (pos)
{
cout << "the index of " << key << " is " << pos << endl;
}
else
{
cout << "not found" << endl;
}
}
3. 插值查找
3.1 基本概念
3.2 代码
#include <iostream>
using namespace std;
// 全局变量 查找表
#define LEN (10)
int searchTable[LEN + 1] = { 0, 1, 16, 24, 35, 47, 59, 62, 73, 88, 99 }; // 查找表元素从下标为1处开始
/**
* @brief: 插值查找
* @param a: 查找表指针
* @param n: 查找表长度
* @param key: 要查找的关键字
* @return: 若查找成功,则返回key所在下标,否则返回0
*/
int insertSearch(int* a, int n, int key)
{
int low = 1;
int high = n;
int mid;
while (low <= high)
{
mid = low + (key - a[low]) / (a[high] - a[low]) * (high - low); // 插值
if (key > a[mid])
{
low = mid + 1;
}
else if (key < a[mid])
{
high = mid - 1;
}
else
{
return mid;
}
}
return 0;
}
int main(int argc, char* argv[])
{
int key = 59;
int pos = insertSearch(searchTable, LEN, key);
if (pos)
{
cout << "the index of " << key << " is " << pos << endl;
}
else
{
cout << "not found" << endl;
}
}
4. 斐波拉契查找
4.1 基本概念
斐波拉契查找的前提要求有序查找表记录数n为某个斐波拉契数减1,即 n = F ( k ) − 1 n=F(k)-1 n=F(k)−1,这样mid可将查找表分成左右长度分别为 F ( k − 1 ) − 1 F(k-1)-1 F(k−1)−1和 F ( k − 2 ) − 1 F(k-2)-1 F(k−2)−1的两个有序表。至于为什么要求 n = F ( k ) − 1 n=F(k)-1 n=F(k)−1,可移步至下面的参考链接内容,对照图示理解。
4.2 代码
#include <iostream>
#include <vector>
using namespace std;
// 全局变量,斐波拉契数列
vector<int> FibArray = { 1, 1 }; // 初始第一、二项
vector<int> searchTable = { 0, 1, 16, 24, 35, 47, 59, 62, 73, 88, 99 }; // 查找表元素从下标为1处开始
/**
* @brief: 斐波拉契查找
* @param a: 查找表 vector
* @param key: 要查找的关键字
* @return: 若查找成功,则返回key所在下标,否则返回0
*/
int fibonacciSearch(vector<int>& a, int key)
{
int n = a.size() - 1; // 查找表长度,有效元素从下标1开始
int low = 1; // 记录最低下标
int high = n; // 记录最高下标
int mid; // 记录中值下标
int k = 0;
while (n > FibArray[k] - 1) // 计算n位于斐波拉契数列的位置
{
k++;
if (k >= FibArray.size())
{
FibArray.push_back(FibArray[k - 1] + FibArray[k - 2]);
}
}
for (size_t i = n; i < FibArray[k] - 1; i++) // 查找表起初长度可能不足 FibArray[k] - 1,将其补足
{
a.push_back(a[n]);
}
while (low <= high)
{
mid = low + FibArray[k - 1] - 1; // 分隔点mid位置
if (key < a[mid]) // 若查找值大于中值
{
high = mid - 1;
k -= 1;
}
else if (key > a[mid]) // 若查找值小于中值
{
low = mid + 1;
k -= 2;
}
else
{
if (mid <= n)
{
return mid;
}
else // 若mid>n说明是补全的数值,直接返回n
{
return n;
}
}
}
return 0;
}
int main(int argc, char* argv[])
{
int key = 59;
int pos = fibonacciSearch(searchTable, key);
if (pos)
{
cout << "the index of " << key << " is " << pos << endl;
}
else
{
cout << "not found" << endl;
}
}
二、树表的查找
1. 基本概念
1.1 二叉排序树操作——查找
1.2 二叉排序树操作——插入
1.3 二叉排序树操作——生成
1.4 二叉排序树操作——删除
2. 平衡二叉树
三、散列表的查找
1. 基本概念
2. 散列函数的构造方法
2.1 直接定址法
H a s h ( k e y ) = a ⋅ k e y + b ( a 、 b 为常数 ) Hash(key) = a·key + b \ (a、b为常数) Hash(key)=a⋅key+b (a、b为常数)
优点:以关键码
k
e
y
key
key的某个线性函数值为散列地址,不会产生冲突;
缺点:要占用连续地址空间,空间效率低。
2.2 除留余数法
H
a
s
h
(
k
e
y
)
=
k
e
y
m
o
d
p
(
p
是一个整数
)
Hash(key) = key \ mod \ p \ (p是一个整数)
Hash(key)=key mod p (p是一个整数)
关键:如何选取合适的
p
p
p?
技巧:设表长为
m
m
m,取
p
⩽
m
p \leqslant m
p⩽m 且为质数
3. 冲突处理方法
3.1 开放地址法(开地址法)
3.2 链地址法(拉链法)
4. 查找性能分析
平均查找长度ASL取决于
- 散列函数
- 处理冲突的方法
- 散列表的装填因子
α
α
α
α = 表中填入的记录数 哈希表的长度 α = \frac{表中填入的记录数}{哈希表的长度} α=哈希表的长度表中填入的记录数
α α α越大,表中记录数越多,说明表装得越满,发生冲突的可能性就越大,查找时比较次数就越多。
A S L ASL ASL与装填因子 α α α有关!既不是严格的 O ( 1 ) O(1) O(1),也不是 O ( n ) O(n) O(n):
A
S
L
≈
1
+
α
2
(
拉链法
)
ASL ≈ 1 + \frac{α}{2}\ (拉链法)
ASL≈1+2α (拉链法)
A
S
L
≈
1
2
(
1
+
1
1
−
α
)
(
线性探测法
)
ASL ≈ \frac{1}{2}(1 + \frac{1}{1 - α})\ (线性探测法)
ASL≈21(1+1−α1) (线性探测法)
A
S
L
≈
−
1
α
l
n
(
1
−
α
)
(
随机探测法
)
ASL ≈ -\frac{1}{α}ln(1 - α)(随机探测法)
ASL≈−α1ln(1−α)(随机探测法)
5. 代码
#include <iostream>
#include <vector>
using namespace std;
#define HASHSIZE (5) // 散列表表长,自定义
struct HashNode {
int key; // 关键字
HashNode* next; // 指向下一个结点
HashNode() {
key = -1;
next = nullptr;
};
HashNode(int key) : key(key), next(nullptr) {};
};
/* 哈希表结构 */
struct HashTable {
HashNode** elem; // 哈希表数组,每个元素都是一个指向HashNode结构的指针(每个链表的首地址)
};
/**
* @brief: 散列表初始化
* @param H: 散列表指针
* @return:
*/
void InitHashTable(HashTable *H)
{
H->elem = new HashNode*[HASHSIZE]; // 分配哈希表数组空间
for (size_t i = 0; i < HASHSIZE; i++)
{
H->elem[i] = nullptr;
}
}
/**
* @brief: 散列表销毁,回收空间
* @param H: 散列表指针
* @return:
*/
void DestroyHashTable(HashTable* H)
{
// 对每个链表的每个结点元素进行回收
for (size_t i = 0; i < HASHSIZE; i++)
{
HashNode* root;
HashNode* tmp;
root = H->elem[i];
while (root)
{
tmp = root->next;
delete root;
root = tmp;
}
}
delete[] H->elem;
}
/**
* @brief: 散列函数:除留余数法
* @param key: 关键字
* @return: 散列值
*/
int Hash(int key)
{
return key % HASHSIZE;
}
/**
* @brief: 将关键字插入散列表
* @param H: 散列表指针
* @param key: 关键字
* @return:
*/
void InsertHash(HashTable *H, int key)
{
int addr = Hash(key); // 散列值,应该放到散列表的哪个链表上
HashNode* node = new HashNode(key);
// 头插法
node->next = H->elem[addr];
H->elem[addr] = node;
}
/**
* @brief: 散列表查找
* @param H: 散列表指针
* @param key: 关键字
* @return: true/false
*/
bool SearchHash(HashTable* H, int key)
{
int addr = Hash(key); // 散列值,应该在散列表的哪个链表上查找
HashNode* root = H->elem[addr]; // 链表头结点
while (root)
{
if (key == root->key) // 找到记录
{
return true;
}
root = root->next;
}
return false;
}
int main()
{
HashTable* H = new HashTable;
// 初始化哈希表
InitHashTable(H);
// 往哈希表中插值
vector<int> searchTable = { 1, 16, 24, 35, 47, 59, 62, 73, 88, 99 }; // 查找表元素
for (size_t i = 0; i < searchTable.size(); i++)
{
InsertHash(H, searchTable[i]);
}
// 哈希表查找
int key = 98;
if (SearchHash(H, key))
{
cout << "found" << endl;
}
else
{
cout << "not found" << endl;
}
// 销毁散列表
DestroyHashTable(H);
delete H;
return 0;
}
参考链接
青岛大学数据结构-王卓
七大查找算法
查找算法—(图解)斐波那契查找
斐波拉契查找法的原理及实现