查找算法
写的断断续续的,中间事情多,自己也是懒狗。
(ps:虽说现在很多的编程都是封装好了,直接调库,我觉得查找的思想还是可以用到很多地方的,所以想了想还是把传统的查找算法总结一下,也是自己回顾回顾。)
1.顺序查找
2.二分(折半)查找
3.插值查找
4.斐波那契查找
5.树表查找
6.分块查找
7.哈希查找
(xdm,我就用简单通俗的话来解释解释了)
1.顺序查找
思想:从表中的第一个元素开始到表中的最后一个元素一次比较查找,如果等于目标元素则输出。
c++实现:
class Solution{
public:
bool findTarget(vector<int> &nums,int target){
for (int num : nums) {
if (num==tarhget)
return true;
}
return false;
}
};
python实现:
不说了,现在python这么火,得学点。再次强调一下,这种算法基本都封好了,直接调用就好了,比如index(),count()。
#举个例子
List=[1,2,3,4,5,6,7]
signal=false
a=input("请输入查找的值:") #不想加判断了,就这样看吧,我懒哈哈,也可以强转
for (i in List):
if (i==a):
signal=true
print("%d在表中呢,为第%d个",%a,%i)
if (signal==false):
print("不在")
2.折半查找
思想:
从顺序表中取中间值与所要查找的值进行比较,如果中间值(mid)>查找值则取前半段范围,若中间值(mid)<查找值,则取后半段,依次进行比较,直到查找到或者范围变为相邻。
代码:
#include<iostream>
#include<vector>
using namespace std;
int Find_bin(vector<int> nums,int target) {
int i = 0, j = nums.size()-1,mid=j/2;
while (i <= j) {
mid = (i + j) / 2;
if (nums[mid] > target)
j = mid - 1;
else if (nums[mid] < target)
i = mid + 1;
else
return mid;
}
return -1;
}
int main()
{
vector<int> nums1 { 1, 2, 3, 4, 5,6 };
cout << Find_bin(nums1, 2) << endl;
cout << Find_bin(nums1, 6) << endl;
cout << Find_bin(nums1, 1) << endl;
cout << Find_bin(nums1, 8) << endl;
system("pause");
return 0;
}
3.插值查找
思想:
插值思想是在折半查找的基础上优化的,比如举个例子A[10]={1,2,3,4,5,6,7,8,9,10},如果我的目标值是2,用二分查找我的mid先要取5,然后到3,然后找到。这是非常不合理的,为什么呢?因为我的查找值偏小,我的数组是有序的我为什么还要从中间开始找。
所以插值查找就是在这个基础上,改变我的mid值,如果我的目标值偏向左边,我的mid就往左偏,如果我的目标值偏向右边(是大数),我的mid就往右偏,所以,插值查找二分段并不是等长的。
代码:
int insertFind(vector<int> nums2, int target) {
int i = 0, j = nums2.size()-1;
while (i<=j)
{
//这边就是把本来二分查找的1/2 改为一个权重不一定是1/2
int mid = i + ((target - nums2[i]) *(j-i)) / (nums2[j] - nums2[i]);
if (mid >= 0 && mid <= (nums2.size() - 1)) { //防止越界报错
if (nums2[mid] == target) return mid;
else if (nums2[mid] > target) j = mid - 1;
else i = mid + 1;
}
else
return -1;
}
}
4.斐波那契查找
思想:
斐波那契查找思想也是在折半查找的基础上更改的,首先斐波那契数列是{1,1,2,3,5,8,13,21…}
也就是从第二个开始F(n)=F(n-1)+F(n-2),越往后F(n)/F(n-1)越接近0.618的黄金比例。
斐波那契查找就是以数列为分割点,进行二分查找。(简单理解一下)
斐波那契查找的时间复杂度还是O(log2n),但是与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间,因此,斐波那契查找的运行时间理论上比折半查找小
代码:
注意:我的测试代码因为是共享的一个vector nums 所以为了不影响最后的10的输出我加了还原操作,如果友友自己用的话注意一下这边自己是不是共享了nums,可以选择删除。
然后这边为了看的清晰,我就把main函数放在上面了,使用的时候可以加个声明,我自己另外开了头文件,这边只是随便放的哈哈。
int main()
{
vector<int> nums = { 1,2,3,4,5,6,7,8,9,10 };
//共享了数据内存
cout << FibSearch(nums, 1)<<endl;
cout << FibSearch(nums, 2) << endl;
cout << FibSearch(nums, 3) << endl;
cout << FibSearch(nums, 4) << endl;
cout << FibSearch(nums, 5) << endl;
cout << FibSearch(nums, 6) << endl;
cout << FibSearch(nums, 7) << endl;
cout << FibSearch(nums, 8) << endl;
cout << FibSearch(nums, 9) << endl;
cout << FibSearch(nums, 6.5) << endl;
cout << FibSearch(nums, 11) << endl;
cout << FibSearch(nums, -2) << endl;
cout << FibSearch(nums, 10);
}
/*
* 生成斐波那契数列
*/
void ProduceFib(vector<int>& Fib, int n)
{
Fib.resize(n); //初始化n个0
int i;
/*Fib.push_back(1);
Fib.push_back(1);*/
Fib[0] = 1;
Fib[1] = 1;
for (i = 2; i < n; ++i) {
Fib[i] = Fib[i - 1] + Fib[i - 2];
}
}
/*
*斐波拉契查找原理也是在二分查找的基础上改进的,
*不过斐波拉契的mid划分是划分成斐波拉契数列{1,1,2,3,5,8,13,21...}
*/
int FibSearch(vector<int>& data, double target) {
int exsize = data.size();
vector<int> Fib;
ProduceFib(Fib, N); //先生成数列
int a = 0;
//查找最接近data的最大数列值
while (data.size() > Fib[a])
a++;
//扩展原始数组到契合斐波那契数列
while (Fib[a] > data.size())
data.push_back(*data.rbegin()); //最后一个元素
int i = 0, j = data.size() - 1;
//可以区分了
--a;
int mid = Fib[a] - 1;
while (i <=j) {
if (target < data[mid]) {
--a;
if (a < 0) {
//复原操作
vector<int>::iterator x1 = data.begin() + exsize;
vector<int>::iterator y1 = data.end() - 1;
data.erase(x1, y1);
//删除最后一个元素
data.erase(data.end() - 1);
return -1;
}
j = mid;
mid = Fib[a] - 1+i;
}
else if (target > data[mid])
{
i = mid + 1;
a -= 2;
if (a < 0) {
vector<int>::iterator x1 = data.begin() + exsize;
vector<int>::iterator y1 = data.end() - 1;
data.erase(x1, y1);
//删除最后一个元素
data.erase(data.end() - 1);
return -1;
}
mid = Fib[a] + mid;
}
else
{
//复原操作
vector<int>::iterator x1 = data.begin() + exsize;
vector<int>::iterator y1 = data.end()-1;
data.erase(x1, y1);
data.erase(data.end() - 1);
if (mid > data.size() - 1) {
mid--;
return mid;
}
return mid;
}
}
}
5.树表查找
思想:
emm…是不是要先将数据构造成一棵树,然后按照树的数据结构进行查找。
用最简单的二叉排序树(二叉查找树)来进行例子讲解。
二叉排序树(也称二叉查找树):或者是一棵空的二叉树,或者是具有下列性质的二叉树:
⑴若它的左子树不空,则左子树上所有结点的值均小于根结点的值;
⑵若它的右子树不空,则右子树上所有结点的值均大于根结点的值;
⑶ 它的左右子树也都是二叉排序树。
这同时还是一个完美二叉树。正常的二叉排序树不是这样的。
tree.h
#pragma once
#include<iostream>
template<class dataType>
struct BiNode // 结点定义
{
dataType data; //结点值
BiNode* lchild, * rchild;
};
class BiTree
{
public:
BiTree(int a[],int n); //建立二叉排序树
void insertBiNode(BiNode<int> *&node,int x); //插入结点
bool deletBiNode(); //删除结点
bool searchBiNode(BiNode<int> *node,int x); //检索结点
~BiTree();
//private:
BiNode<int>* root;
};
//static BiNode<int>* root;
BiTree::BiTree(int a[], int n)
{
root = NULL;
for (int i = 0; i < n; i++) {
insertBiNode(root, a[i]);
}
}
void BiTree::insertBiNode(BiNode<int>* &node, int x) {
if (node == NULL) {
BiNode<int>* s = new BiNode<int>; //申请内存空间
s->data = x;
s->lchild = NULL;
s->rchild = NULL;
node = s;
}
else if (node->data > x) {
insertBiNode(node->lchild, x);
}
else
insertBiNode(node->rchild, x);
}
bool BiTree::deletBiNode() {
return true;
}
bool BiTree::searchBiNode(BiNode<int>* node, int x) {
if (node == NULL)
return false;
else if (node->data == x)
return true;
else if (node->data > x)
searchBiNode(node->lchild, x);
else
searchBiNode(node->rchild, x);
}
BiTree::~BiTree()
{
}
main.cpp
#include<iostream>
#include"Tree.h"
using namespace std;
int main()
{
int a[] = { 2,3,1,7,8,5,4 };
BiTree bt(a,7);
cout<<boolalpha<< bt.searchBiNode(bt.root, 9);
return 0;
6.分块查找
思想:
分块查找就是将一堆数据分成一块一块的,每个块里面存储的数据可以无序,但是块与块之间必须有序,在插入、查找、删除的时候必须维护一个索引表。
比如,我的索引表10、20、30,代表三个索引块第一个索引块元素<=10,第二个索引块(10,20],第三个索引块(20,30],第四个索引块>30。
代码:
先在索引表中用顺序查找的二分法之类的,然后在相应块中顺序查找。
#include<stdio.h>
//索引表
typedef struct
{
int key; //最大关键字
int start; //块开始下标
int end; //块结束下标
}Node;
typedef struct
{
Node idx[10];//表项
int len; //表长
}IdxTable;
//分块查找
IdxTable table; //索引表
int BlockingSearch(int key,int a[])
{
//折半查找
int low, high, mid;
low = 1;
high = table.len;
while (low <= high){
mid = (low + high) / 2; //找到中间位置
if (key<=table.idx[mid].key){
//顺序查找
if (key <= table.idx[mid - 1].key)
high = mid - 1;
else{
for (int i = table.idx[mid].start; i <= table.idx[mid].end; i++){
if (key == a[i])
return (i + 1); //从1开始算起
}
return 0;
}
}
else
low = mid + 1; //如果小于查找的记录则重新定位mid指针
}
return 0;
}
int main()
{
int i;
int a[] = {22,12,13,8,9,20,33,42,44,38,24,48,60,58,74,49,86,53 };
//索引表
table.idx[1].key = 22; table.idx[1].start = 0; table.idx[1].end = 5;
table.idx[2].key = 48; table.idx[2].start = 6; table.idx[2].end = 11;
table.idx[3].key = 86; table.idx[3].start = 12; table.idx[3].end = 17;
table.len = 3;
//查找结果
int key;
printf("请输入要查找的记录:");
scanf("%d", &key);
i = BlockingSearch(key, a);
printf("查找记录在表中的位置是(从1开始,0为未找到):%d\n", i);
}
7.哈希查找
思想:
哈希表查找又叫散列表查找,通过查找关键字不需要比较就可以获得需要记录的存储位置,它是通过在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。即:存储位置=f(关键字),其中f为哈希函数。
比如我的哈希函数是(关键字)/10,假设{10,20,30,40,50,60,70,80,90}是待查找数组,则存储位置就是{10,20,30,40,50,60,70,80,90}和10做hash。当我要查找x时,x/10,就知道存储位置在哪,或者在不在。
当然我可能会有哈希碰撞,可以使用开放定址法、再哈希法、链地址法等。
代码:
//呜呜我是懒狗,大家去搜一搜吧很多的。