算法(查找算法---二分查找/索引查找/哈希表查找)

二、查找算法

什么是查找算法:

在一个数据序列中,查找某个数据是否存在或存在的位置,在实际开发过程中使用的频率非常高,例如对数据常见的操作有增、删、改、查,增加数据时需要查询新增加的数据是否重复,删除数据时需要先查询到数据所在位置再删除,修改数据时也需要先查询到被修改的数据在什么位置,查找算法在编程中重要性排列在第一位。

顺序查找:
顺序表的顺序查找:
//  从顺序表中从前往后查找数据,找到返回下标,找不到返回-1
int order_search(int* arr,size_t len,int key)
{for(int i=0; i<len; i++){if(key == arr[i]) return i;}return -1;
}
链表的顺序查找:
ListNode* order_search(ListNode* head,int key)
{for(ListNode* n=head; NULL!=n; n=n->next){if(n->data == key) return n;}return NULL;
}
顺序查找的优点:
  • 对待查找的数据没有有序性的要求,无论是否有序都可以顺序查找

  • 对待查找的数据的存储方式也没有要求,无论是顺序表还是链表都可以顺序查找

顺序查找的缺点:
  • 相比于其他查找算法速度要慢,最优时间复杂度 O(1) 最差O(N) 平均O(N)

二分查找:

数据序列必须有序,然后关键字key与中间数据比较,如果相等则立即返回,如果key小于中间数据,则只需要在中间数据左边继续查找即可,如果key大于中间数据,则只需要在中间数据右边继续查找即可,重复该步骤,直到找到关键字,或中间数据左右两边为空,则查找失败。

//  循环实现  查找前已有序
int binary_search(int* arr,size_t len,int key)
{int left = 0, right = len-1;while(left <= right){int p = (left + right)/2;if(arr[p] == key)return p;if(key < arr[p])right = p-1;if(key > arr[p])left = p+1;}return -1;
}
​
//  递归实现
int _binary_search(int* arr,size_t len,int key,int l,int r)
{if(l > r) return -1;int p = (l+r)/2;if(key < arr[p])return _binary_search(arr,len,key,l,p-1);if(key > arr[p])return _binary_search(arr,len,key,p+1,r);return p;
}
​
int binary_search(int* arr,size_t len,int key)
{return _binary_search(arr,len,key,0,len-1);
}
二分查找的优点:

查询速度快,时间复杂度:O(log2N)。

二分查找的缺点:
  • 对待查找的数据有序性有要求,必须先排序才能二分查找

  • 对数据存储结构有要求,不适合链式表中直接使用,因为无法快速地访问中间位置,以及快速地让左右标杆位置进行移动

索引查找:

索引查找是在索引表和主表(即线性表的索引存储结构)上进行的查找,但需要先建立索引表,索引表就类似图书的目录,能大提高查找效率。

给顺序表创建索引表:
typedef struct Stduent
{int id;char name[20];char sex;short age;float score;
}Student;
​
Student stu[10000]; //  主表
​
//  索引表元素结构
typedef struct Index
{int id;void* addr;
}Index;
​
//  索引表
Index indexs[10000];
​
//  对主表与索引表之间建立索引
void create_index(Student* stu,Index* indexs,size_t len)
{for(int i=0; i<len; i++){indexs[i].id = stu[i].id;indexs[i].addr = stu+i;}
}
//  注意:建立索引表后,后面使用索引查找的是索引表,再通过找到的索引表中记录的主表元素的位置信息,来最终找到待查找的数据元素
//  注意:索引表如何建立索引,根据实际需求来选择
索引表的顺序查找:
//  它比直接查询数据的主表的跳转速度要快,如果使用stu[i]查找,i每加1,要跳转过sizeof(Student)个字节数,如果使用索引表indexs[i]查询,i每加1,则只需要跳转sizeof(Index)个字节数,比主表跳转字节数要少。
//  如果主表的数据不存在内存而是存储在磁盘上,而索引表是建立在内存中,通过索引表访问效率、速度远高于访问磁盘的主表
int order_index_search(Index* indexs,size_t len,int id)
{for(int i=0; i<len; i++){if(indexs[i].id == id)return i;}/*for(int i=0; i<len; i++){if(stu[i].id == id)return i;}*/
}
索引表二分查找:
//  需要对索引表先排序
//  对索引表排序的效率和速度要远比直接对主表的排序要快
void sort_index(Index* indexs,size_t len)
{for(int i=0; i<len-1; i++){int min = i;for(int j=i+1; j<len; j++){if(indexs[j].id < indexs[min].id)min = j;}if(min != i){Index temp = indexs[min];indexs[min] = indexs[i];indexs[i] = temp;}}
}
​
//  对索引表进行二分查找
//  因为索引表已经重新排序了,而主表没有排序过,所以不能返回在索引表中找到元素的下标,该下标与主表对应元素的下标很可能不一致了,所以需要直接返回主表对应元素的地址
Student* binary_index_search(Index* indexs,size_t len,int id)
{int l = 0, r = len-1;while(l <= r){int p = (l + r)/2;if(indexs[p].id == id)return indexs[p].addr;if(id < indexs[p].id)r = p-1;if(id > indexs[p].id)l = p+1;}return NULL;
}
给链表创建索引表:
//  给链表head创建一张顺序的索引表 表中的元素是ListNode* 用来指向链表中的节点 
//  返回值是返回索引表首地址,len_i输出型参数,返回索引表中元素的个数
ListNode** create_index_list(ListNode* head,size_t* len_i)
{if(NULL == head || NULL == len_i) return NULL;//  索引表的长度*len_i = 0;ListNode** indexs = NULL;//  遍历链表head 给每个节点建立普通索引for(ListNode* n=head; NULL!=n; n=n->next){//  申请索引表元素ListNode*的内存indexs = realloc(indexs,sizeof(ListNode*)*(*len_i+1));//  让索引表中的最后一个元素指向对应的链表节点indexs[(*len_i)++] = n;}//  对索引表进行排序,交换索引表中指针的指向for(int i=0; i<(*len_i)-1; i++){int min = i;for(int j=i+1; j<*len_i; j++){if(indexs[j]->data < indexs[min]->data)min = j;}if(min != i){//  交换索引表中指针的指向 不修改链表ListNode* temp = indexs[min];indexs[min] = indexs[i];indexs[i] = temp;}}
}
​
//  链表的二分查找,本质上是对顺序的索引表进行二分
int binary_list_index_search(ListNode** indexs,size_t len,int key)
{int l = 0, r = len - 1;while(l <= r){int p = (l + r)/2;if(indexs[p]->data == key)return p;if(key < indexs[p])r = p-1;if(key > indexs[p])l = p+1;}return -1;
}
索引查找的优点:
  • 对于顺序表的顺序查找,索引查找可以缩短数据的查找跳转范围

  • 对于顺序表的二分查找,通过排序索引表也能提高排序的速度

  • 对于链式表,可以先建立顺序的索引表后,进行之前无法实现的二分查找了

索引查找的缺点:
  • 使用了额外的存储空间来创建索引表,是一种典型的以空间换时间的算法策略

索引查找的使用场景:
  • 如果针对的是内存中的顺序表中的数据,通过索引查找提升的速度和效率其实并不是很明显,毕竟内存的运算速度很快

  • 但是对于存储在机械硬盘上的数据,通过在内存中建立对应硬盘数据的索引表,访问起来提升的效率就很多了,因此在一些常用的数据库中的索引查找使用非常多

分块查找:

先对数据进行分块,可以根据日期进行分块,可以对整形数据根据数据的最后一位分为10个分表,针对字符串数据可以根据第一个字母分为26个分表,然后再对这些分表进行二分查找、顺序查找,它是分治思想的具体实现。

分块查找的优点:

可以把海量数据进行分化,降低数据规模,从而提升查找速度。

分块查找的缺点:

操作比较麻烦,可能会有一定的内存浪费。

二叉排序树和平衡二叉树:

二叉排序树也叫二叉搜索树是根据数据的值进行构建的,然后进行前序遍历查找,它的查找原理还是二分查找,我在二叉搜索树中已经详细过,在此不再赘述。

平衡二叉树首先是一棵二叉排序树或二叉搜索树,二叉排序树在创建时,由于数据基本有序,会导致创建出的二叉排序树呈单枝状,或者随机数据的插入、删除导致二叉排序树左右失衡呈单枝状,就会导致二叉树排序的查询速度接近单向链表的O(N);

平衡二叉树要求左右子树的高度差不超过1,这样就会让二叉排序树左右均匀,让二叉排序树的查找速度接近二分查找(要了解AVL树和红黑树的区别)。

哈希表查找:Hash

在查找数据时需要进行一系列的比较运算,无论是顺序查找、二分查找、索引查找、这类的查找算法都是建立在比较的基础上的,但最理想的查找算法是不经过任何比较,一次存取就能查找到数据,那么就需要在存储位置和它的关键字之间建立一个确定的对应关系,使每个关键字和数据中的唯一的存储位置相对应。在查找时,根据对应关系找到给定的关键字所对应的数据,这样可以不经过比较可以直接取得所查找的数据。

我们称存储位置在关键字之间的对应关系为哈希函数,按这个思想建立的表为哈希表。

设计哈函数的方法:
直接定值法:

取关键字的值或某个线性函数作为k哈希地址:H(key) = key 或H(key)=a*key-b,但这种方法对数据有很高的要求。

问题:假设有10000个范围在0~255之间的随机整数,请任意输入0~255之间的一个整数,帮查询该整数总共出现了多少次?
#include <stdio.h>
#include <stdlib.h>
​
int main(int argc,const char* argv[])
{int arr[10000] = {};
​for(int i=0; i<10000; i++){arr[i] = rand() % 256;}
​//  建立哈希表int hash[256] = {};//  通过直接定值法 建立哈希函数for(int i=0; i<10000; i++){hash[arr[i]]++;}
​unsigned char num = 0;printf("请输入:");scanf("%hhu",&num);
​printf("次数:%d\n",hash[num]);
}  
​

时间复杂度:O(1)

但是有很大的局限性,因为很多数据是无法直接用做数组的下标的,其次可能会出现数据量少,但是数据值的差值较大,导致哈希表长度过大,造成内存浪费

数字分析法:

分析数据的特点设计哈希,常用方法是找到最大最小值,最大值-最小值+1 确定哈希表的长度,通过 数据-最小值 访问哈希表下标

以上两种方法局限比较大,但计算出的哈希地址没有冲突的可能,以下方法:平方取中法、折叠法、除留余数法等方法对关键字的要求不要,但计算出的哈希地址可能会有冲突。称为哈希冲突

解决哈希值冲突的方法:
  • 开方地址法

  • 再哈希法

  • 链地址法

  • 创建公共溢出区

哈希查找的优点:

查找速度极快,时间复杂度能达到O(1)。

哈希查找的缺点:

1、局限性比较大,对数据的要求比较高。

2、设计哈希函数麻烦,没具体的设计哈希函数的方法,只是有一些大致的思路。

3、需要建立哈希表,占用了额外的空间。

Hash函数的应用:MD5、SHA-1都属于Hash算法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/51496.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【日常设计案例分享】通道对账

今天跟同事们讨论一个通道对账需求的技术设计。鉴于公司业务线有好几个&#xff0c;为避免不久的将来各业务线都重复竖烟囱&#xff0c;因此&#xff0c;我们打算将通道对账做成系统通用服务&#xff0c;以降低各业务线的开发成本。 以下文稿&#xff08;草图&#xff09;&…

驾驭Mojo模型:处理大规模数据集的艺术

驾驭Mojo模型&#xff1a;处理大规模数据集的艺术 引言 在现代的数据分析和机器学习领域&#xff0c;处理大规模数据集是一个常见且具有挑战性的任务。Mojo&#xff08;Model-as-a-Service&#xff09;模型&#xff0c;作为一种提供模型服务化的工具&#xff0c;允许开发者和…

vue中,当数据更之后,视图没有对应的更新

异步更新队列&#xff1a;Vue.js中对数据的更改是异步的&#xff0c;当你修改数据时&#xff0c;Vue并不会立即更新DOM&#xff0c;而是将更改放入一个队列中&#xff0c;然后在下一个事件循环中才会更新视图。这是为了优化性能。如果在同一个事件循环中进行多次数据修改&#…

局部变量,在使用时再定义

关于局部变量&#xff0c;适时定义局部变量&#xff0c;可提高代码清晰度和可读性&#xff0c;并能规避不必要的代码bug 局部变量&#xff0c;在使用时再定义&#xff0c;提高代码可读性 下面代码中的2个方法&#xff0c;第1个 verifyTaskApply 调用第2个 existAppliedTask 。…

20240730 每日AI必读资讯

&#x1f3ac;燃爆&#xff01;奥运8分钟AI影片火了&#xff0c;巴赫主席&#xff1a;感谢中国黑科技 - 短片名为《永不失色的她》&#xff08;To the Greatness of HER&#xff09;&#xff0c;由阿里巴巴和国际奥委会联合推出。 - 百年奥运史上伟大女性的影响故事在此被浓缩…

Rust语言入门第七篇-控制流

文章目录 Rust语言入门第七篇-控制流If 表达式基本结构特点和规则示例 let 语句中使用 ifloop 循环基本结构特点示例综合示例 while 循环基本结构特点示例综合示例 与 loop 循环的区别 for 循环基本结构详细说明特点示例综合示例 Rust语言入门第七篇-控制流 Rust 的控制流是指…

Oracle Database 23.5 - for Engineered Systems版本发布

要尝鲜的可以在https://edelivery.oracle.com/下载。对于x86的本地版本再等等吧。 安装可参考飞总的&#xff1a;oracle 23ai&#xff08;23.5.0.24.07&#xff09;完整功能版安装体验 – 提供7*24专业数据库(Oracle,SQL Server,MySQL,PostgreSQL等)恢复和技术支持Tel:1781323…

Python数值计算(12)

本篇说说Neville方法。Neville方法的基础是&#xff0c;插值多项式可以递归的生成&#xff0c;有时进行插值的目的是为了计算某个点的值&#xff0c;这个时候并不需要将拟合曲线完全求出&#xff0c;而是可以通过递归的方式进行计算&#xff0c;具体操作如下&#xff1a; 例如…

OpenGL学习 1

一些唠叨&#xff1a; 很多时候&#xff0c;都被Live2d吸引&#xff0c;去年想给网页加个live2d看板娘&#xff0c;结果看不懂live2d官方给的SDK&#xff0c;放弃了。今天又想弄个live2d桌宠&#xff0c;都已经在网上找到Python 的 Live2D 拓展库了&#xff0c;并提供了用QT实现…

可能是目前最全面的前端提测/自测标准

产品、测试总是抱怨你的开发质量太差&#xff0c;怎么办&#xff1f; 前端开发的质量直接影响用户体验。无论是一个简单的静态页面&#xff0c;还是复杂的动态应用&#xff0c;都需要经过严格的测试流程才能保证上线后的稳定和流畅。 这里整理了一份前端自测标准&#xff0c;…

【算法模板】数论:裴蜀定理

概念 裴蜀定理&#xff08;Bzout’s Identity&#xff09;是数论中的一个重要定理&#xff0c;涉及整数的线性组合。定理陈述如下&#xff1a; 对于任何整数 a 和 b&#xff0c;如果 d 是 a 和 b 的最大公约数&#xff0c;那么存在整数 x 和 y 使得&#xff1a; axbyd 换句话…

昇思25天学习打卡营第19天|ResNet50 图像分类案例:数据集、训练与预测可视化

目录 环境配置 数据集加载 数据集可视化 Building Block Bottleneck 构建ResNet50网络 模型训练与评估 可视化模型预测 环境配置 首先指出实验环境预装的 mindspore 版本以及更换版本的方法。然后&#xff0c;它卸载了已安装的 mindspore 并重新安装指定的 2.3.0rc1 版本…

值得买科技与MiniMax达成官方合作伙伴关系,共建融合生态

7月29日&#xff0c;值得买科技与大模型公司MiniMax宣布达成官方合作伙伴关系。 MiniMax旗下大模型产品海螺AI现已接入值得买“消费大模型增强工具集”&#xff0c;基于海螺AI比价策略&#xff0c;用户可通过海螺AI“悬浮球”功能实现快速比价及跳转购买。 此次合作也标志着值…

4.仓颉编程_使用Console.stdIn.read()实现c语言的控制台scanf或getchar()的读取输入字符功能

使用Console.stdIn.readln()实现c语言的控制台scanf或getchar()的读取输入字符功能 try catch捕捉异常 import std.console.*main() {println("请输入字符. 按q退出:")try {//读取一个字符var ch2 Console.stdIn.read().getOrThrow()//判断输入的是否是字符q,如果…

Day.32 | 518.零钱兑换 II 377.组合总和 Ⅳ

518.零钱兑换 II 要点&#xff1a;先遍历数组&#xff0c;后遍历背包&#xff0c;求的是组合数 class Solution { public:int change(int amount, vector<int>& coins) {vector<int> dp(amount 1, 0);dp[0] 1;for (int i 0; i < coins.size(); i) {for…

docker compose 和 docker-compose 两个命令等价吗

docker compose 和 docker-compose 这两个命令虽然看起来很相似,但实际上有一些重要的区别: 实现方式不同: docker-compose 是用 Python 编写的独立工具[2][4]。docker compose 是用 Go 语言重写的,作为 Docker CLI 的一个插件[1][4]。 集成度不同: docker compose 作为 Docke…

Window 下 Vim 环境安装踩坑问题汇总及解决方法

导航 Linux 下Mamba 及 Vim 安装问题参看本人之前博客&#xff1a;Mamba 环境安装踩坑问题汇总及解决方法Linux 下Vmamba 安装教程参看本人之前博客&#xff1a;Vmamba 安装教程&#xff08;无需更改base环境中的cuda版本&#xff09;Windows 下 VMamba的安装参看本人之前博客…

操作系统重点总结

文章目录 1. 操作系统重点总结1.1 操作系统简介1.1.1 操作系统的概念和功能1.1.2 操作系统的特征1.1.2.1 并发1.1.2.2 共享1.1.2.3 虚拟1.1.2.4 异步 1.1.3 操作系统的发展与分类1.1.4 中断和异常1.1.5 系统调用1.1.6 操作系统的体系结构1.1.7 操作系统简介总结 1.2 进程1.2.1 …

使用YApi平台来管理接口

快速上手 进入YApi官网&#xff0c;进行注册登录https://yapi.pro/添加项目 3. 添加分类 4. 添加接口 5. 添加参数 添加返回数据 可以添加期望 验证 YAPI&#xff08;Yet Another Practice Interface&#xff09;是一个现代化的接口管理平台&#xff0c;由淘宝团队…

【Python Tips】使用func_timeout库实现os.system()命令行的超时报错检测

一、引言 有时候在python项目代码中使用 os.system() 使用命令行调用外部程序或者函数时&#xff0c;由于我们不知道外部程序会不会因为一些未知原因崩掉&#xff0c;但我们又不想项目代码因此中断&#xff0c;因此&#xff0c;我们可以使用已有的func_timeout库来实现超时检测…