当前位置: 首页 > news >正文

数据结构(查找算法)

1. 查找的概念

在一堆数据中,找到我们想要的那个数据,就是查找,也称为搜索,很容易想到,查找算法的优劣,取决于两个因素:

  • 数据本身存储的特点
  • 查找算法本身的特点

比如,如果数据存储是无序的,那么当我们想要在其中找到某个节点时,一般就只能对它们逐个比对。如果数据存储是有序且存放在一片连续的内存中,那么我们可以考虑从中间开始找(二分法)。因此可以看到,在实际应用中如果需要优化数据的查找(搜索)性能,我们主要从以上两方面入手,当然,有时数据的存储特性是无法更改的,那么此时就只能靠优化算法本身去达到提供程序性能的目的了。

2. 经典查找算法

2.1 顺序查找

顺序查找顾名思义,就是挨个找,这是一种不是算法的“算法”,最没办法的“办法”,简单直接,童叟无欺。时间复杂度就是 O(n)

适用情况:

  • 无序的数据
  • 无法(或不便于)对这些数据进行有效的整理

以下是示例代码

// 数据规模:100万
#define SCALE 1000*1000// 查找次数统计
static int count;int sequentialSearch(unsigned data[], int len, int n)
{for(int i=0; i<SCALE; i++){if(data[i] == n)return i;count++;}return -1;
}int main(int argc, char const *argv[])
{// 产生一系列无序数据srand(time(NULL));unsigned *data = calloc(SCALE, sizeof(unsigned));for(int i=0; i<SCALE; i++)data[i] = rand()%((int)pow(10, rand()%8+5));// 不进行任何数据整理,直接进行顺序查找unsigned n;printf("请输入你要找的正整数:\n");while(1){scanf("%u", &n);int pos = sequentialSearch(data, SCALE, n);if(pos == -1)printf("找不到你要的数据");elseprintf("你要找的数据第%d行", pos);printf("【找了%d次】\n", count);count = 0;}return 0;
}

以下是执行效果:

gec@ubuntu: ~$ ./sequential_search
请输入你要找的正整数:
132096806
你要找的数据第87行【找了87次】
951578948
你要找的数据第999999行【找了999999次】
951578949
找不到你要的数据【找了1000000次】

很显然,除非数据完全无序且无法整理,否则顺序查找的效率在大规模数据面前是非常低下的,因此一般情况下,对于大数据量的查找,我们都会尽量先对数据进行不同程度的整理。

2.2 分块查找

在一本字典中查找某个汉字的时候,一般是先找目录,在目录中找到对应拼音或笔画,然后直接翻到对应的页面再往下找,很显然这能大大提高查找的效率。

分块查找实质就是给数据建立索引,将查找的过程分成两个步骤:

  1. 在索引(目录)中找到数据所属分块
  2. 在所属分块中查找到该数据

以下例子中,先是产生系列长度和内容都随机的字符串,作为待查数据集。然后对这些字符串按首字符进行整理,形成字母表索引(目录)。最后利用索引提高查找效率。

核心代码示例

// 数据规模: 100万
#define SCALE  1000*1000 // 字符串总数
#define MAXLEN 20        // 字符串最大长度// 查找次数统计
static int count;char *random_string()
{int len = rand()%(MAXLEN);len = ((len<2)? 2 : len); // len: 2~19char *s = calloc(1, len);char letter[] = {'a', 'A'};for(int i=0; i<len-1; i++) // i: 0~[1~18]s[i] = letter[rand()%2]+rand()%26;return s;
}void create_index(char data[][MAXLEN], int **index)
{// 统计各个首字符出现的频次int n[52]={0}; // ['a', 'b', ... 'z', 'A', 'B', ... 'Z']for (int k = 0; k < SCALE; k++){// 小写字母[00~25],大写字母[26~51]int pos = ((data[k][0] >= 'a') ? (data[k][0]-'a') : (data[k][0]-'A'+26));n[pos]++;}// 给index分配内存// 每个字母分配一段存储以该字母为首的字符串所在的行号的内存// 第一个位置存储总行数,因此所需分配的内存单元数是1+n[i]。// 例如:// index[2] --> [ 242     3     22    213 ... ... 42513 46698]//   'c'    -->  总行数  第3行 第22行     ... ...for(int i=0; i<52; i++)index[i] = calloc(1+n[i], sizeof(int));// 记录每个字母出现的行号for(int i=0; i<SCALE; i++){int pos = ((data[i][0] >= 'a') ? (data[i][0]-'a') : (data[i][0]-'A'+26));int k = ++index[pos][0];index[pos][k] = i;}
}int main(int argc, char const *argv[])
{// 1. 产生随机字符串数据集//    假设每个字符串长度不超过MAXLEN个字符char (*data)[MAXLEN] = calloc(SCALE, MAXLEN);srand(time(NULL));for(int i=0; i<SCALE; i++){char *s = random_string();strncpy(data[i], s, strlen(s));free(s);}// 2. 按首字母建立索引(分块)int **index = calloc(52, sizeof(int *));create_index(data, index);// 3. 利用索引,进行查找char str[32];printf("请输入你要查找的字符串:\n");while(1){// 从键盘接收一个待查找的字符串并去掉回车符bzero(str, 32);fgets(str, 32, stdin);strtok(str, "\n");bool done = false;for(int i=1; i<SCALE; i++){// 小写字母[00~25],大写字母[26~51]int pos = ((str[0]>='a') ? (str[0]-'a') : (str[0]-'A'+26));count++;if(i<=index[pos][0] && strcmp(data[index[pos][i]], str) == 0){printf("你要找的字符串在第%d行", index[pos][i]);done = true;break;}else if(i > index[pos][0])break;}if(!done)printf("没有你要的字符串");printf("【找了%d次】\n", count);count=0;}return 0;
}

以下是执行效果:

gec@ubuntu: ~$ ./index_search
请输入你要查找的字符串:
e
你要找的字符串在第8行【找了1次】
sdf
你要找的字符串在第206096行【找了4078次】
ssHcbSJC
你要找的字符串在第396828行【找了7891次】

由于对数据进行了分块,引入了索引,查找效率大大提高,对于上述的100万个数据来说,假设以各个大小写字母开头的字符串概率相等,那么相当于将整体数据分成了52块,整体效率平局提升52倍,这个性能的提升的相当显著的,所付出的代价是:需要额外的内存空间存储索引表index,这是以空间换时间的经典思路。

2.3 二分查找

分块查找是在不对数据进行排序的情况下采用的颇为有效的查找办法,但如果待查找的数据本身是有序的,或者在查找前,可以对数据先进行排序(比如数据量虽然较大,但短期较稳定,无大面积更新),这种情况下使用二分查找可以进一步提升效率。

二分法的思路相当朴实无华:从中间开始找。既然数据是有序的,那么如果将待查找的节点跟中间节点对比,就可以以排除掉一半的数据,接着再在剩余的数据的中间开始找,又可以很快排除掉剩下的一半的数据,这种一半一半筛查数据的办法,就是所谓的二分法。

例如下图,想要在一系列有序(从小到大)的数据中,查找45,中间的节点数据是22,很显然左侧数据可以立即排除,45只可能存在于22右侧的序列中(若有):

img
二分查找算法示意图

假设有一系列有序数据,以下是二分查找算法的核心代码

// 数据规模: 100万
#define SCALE  1000*1000// 查找次数统计
static int count;int main(int argc, char const *argv[])
{// 1. 产生SCALE个随机整数序列unsigned *data = calloc(SCALE, sizeof(unsigned));srand(time(NULL));for(int i=0; i<SCALE; i++)data[i] = rand()%((int)pow(10, rand()%8+5));// 2. 排序quick_sort(data, SCALE);// 3. 进行二分查找unsigned n;printf("请输入你要查找的正整数:\n");while(1){scanf("%u", &n);int low, high, mid;low = 0, high = SCALE-1;bool found = false;while(low <= high){count++;mid = (low+high)/2;if(n == data[mid]){printf("你要找的数据在第%d行", mid);found = true;break;}if(n < data[mid])high = mid - 1;elselow = mid + 1;} if(!found)printf("找不到你要的数据");printf("【找了%d次】\n", count);count=0;}return 0;
}

以下是执行效果:

gec@ubuntu: ~$ ./binary_search
请输入你要查找的正整数:
1
你要找的数据在第6行【找了17次】
534
你要找的数据在第731行【找了12次】
6996
你要找的数据在第9534行【找了16次】
5863827
你要找的数据在第333334行【找了20次】

可见,二分查找的查找效率得到了质的飞跃!在最不利的情况下,100万个数据最多仅需查找20次就能锁定结果,这个效率大大优于顺序查找和分块查找。但别忘记,适合于用二分查找的场合是有条件的:数据是严格有序的。

相关文章:

数据结构(查找算法)

1. 查找的概念 在一堆数据中&#xff0c;找到我们想要的那个数据&#xff0c;就是查找&#xff0c;也称为搜索&#xff0c;很容易想到&#xff0c;查找算法的优劣&#xff0c;取决于两个因素&#xff1a; 数据本身存储的特点查找算法本身的特点 比如&#xff0c;如果数据存储…...

private前端常见算法

1.数组 合并两个有序数组&#xff08;简单-5&#xff09; https://leetcode.cn/problems/merge-sorted-array/description/?envTypestudy-plan-v2&envIdtop-interview-150 移除元素&#xff08;简单-4&#xff09; https://leetcode.cn/problems/remove-element/descr…...

Go语言之十条命令(The Ten Commands of Go Language)

Go语言之十条命令 Go语言简介 Go语言&#xff08;又称Golang&#xff09;‌是由Google开发的一种开源编程语言&#xff0c;首次公开发布于2009年。Go语言旨在提供简洁、高效、可靠的软件开发解决方案&#xff0c;特别强调并发编程和系统编程‌。 Go语言的基本特征 ‌静态强类…...

Residency 与 Internship 的区别及用法解析

Residency 与 Internship 的区别及用法解析 在英文中&#xff0c;“residency” 和 “internship” 都与职业培训相关&#xff0c;但它们的使用场景和具体含义存在显著差异。本文将详细解析这两个词的区别&#xff0c;以及它们在不同语境下的应用。 Residency 的定义及使用场景…...

成品电池综合测试仪:电子设备性能与安全的守护者|鑫达能

在现代科技和工业领域&#xff0c;电池作为能量储存和转换的关键组件&#xff0c;其性能的稳定性和可靠性至关重要。为了确保电池在各种应用场景中都能发挥最佳性能&#xff0c;成品电池综合测试仪应运而生。这一设备不仅能够对电池的各项性能指标进行全面、准确的检测&#xf…...

Taro地图组件和小程序定位

在 Taro 中使用腾讯地图 1.首先在项目配置文件 project.config.json 中添加权限&#xff1a; {"permission": {"scope.userLocation": {"desc": "你的位置信息将用于小程序位置接口的效果展示"}} }2.在 app.config.ts 中配置&#x…...

深入了解 SSL/TLS 协议及其工作原理

深入了解 SSL/TLS 协议及其工作原理 一. 什么是 SSL/TLS?二. SSL/TLS 握手过程三. SSL/TLS 数据加密与传输四. 总结 点个免费的赞和关注&#xff0c;有错误的地方请指出&#xff0c;看个人主页有惊喜。 作者&#xff1a;神的孩子都在歌唱 一. 什么是 SSL/TLS? 安全套接层&am…...

【计算机操作系统:二、操作系统的结构和硬件支持】

第2章 操作系统的结构和硬件支持 2.1 操作系统虚拟机 操作系统虚拟机是一种通过软件技术对硬件资源进行抽象和虚拟化的机制&#xff0c;使用户能够以逻辑方式访问和使用计算机资源。 定义与概念&#xff1a; 虚拟机是操作系统虚拟化技术的核心产物&#xff0c;通过模拟硬件资…...

51单片机——步进电机模块

直流电机没有正负之分&#xff0c;在两端加上直流电就能工作 P1.0-P1.3都可以控制电机&#xff0c;例如&#xff1a;使用P1.0&#xff0c;则需要把线接在J47的1&#xff08;VCC&#xff09;和2&#xff08;OUT1&#xff09;上 1、直流电机实验 要实现的功能是&#xff1a;直…...

当算法遇到线性代数(四):奇异值分解(SVD)

SVD分解的理论与应用 线性代数系列相关文章&#xff08;置顶&#xff09; 1.当算法遇到线性代数&#xff08;一&#xff09;&#xff1a;二次型和矩阵正定的意义 2.当算法遇到线性代数&#xff08;二&#xff09;&#xff1a;矩阵特征值的意义 3.当算法遇到线性代数&#xff0…...

SASS 简化代码开发的基本方法

概要 本文以一个按钮开发的实例&#xff0c;介绍如何使用SASS来简化CSS代码开发的。 代码和实现 我们希望通过CSS开发下面的代码样式&#xff0c;从样式来看&#xff0c;每个按钮的基本样式相同&#xff0c;就是颜色不同。 如果按照传统的方式开发&#xff0c;需要开发btn &…...

40.TryParse尝试转化为int类型 C#例子

也许这个时候学有点晚&#xff0c;但是不管怎样都学了 尝试转化&#xff0c;不能转化就返回bool类型的假 它会直接给括号里面的int类型赋值 代码&#xff1a; using System; using System.Timers; public class Program {static void Main(){int a;bool i;while (true){Get…...

【微服务】2、网关

Spring Cloud微服务网关技术介绍 单体项目拆分微服务后的问题 服务地址问题&#xff1a;单体项目端口固定&#xff08;如黑马商城为8080&#xff09;&#xff0c;拆分微服务后端口各异&#xff08;如购物车808、商品8081、支付8086等&#xff09;且可能变化&#xff0c;前端难…...

红队-shell编程篇(上)

声明 通过学习 泷羽sec的个人空间-泷羽sec个人主页-哔哩哔哩视频,做出的文章如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 一、建立Shell文件 1. Shell简介 Shell是一种命令行界面&am…...

电子价签会是零售界的下一个主流?【新立电子】

电子价签&#xff0c;作为一种能够替代传统纸质标签的数字显示屏&#xff0c;已经在零售行业中展现出其巨大的潜力。它具有实时更新、集中管理、高效节能的特点&#xff0c;实现价格的实时更新&#xff0c;大大减少更新价格的工作量和时间。为消费者带来更加便捷、准确的购物体…...

5 分布式ID

这里讲一个比较常用的分布式防重复的ID生成策略&#xff0c;雪花算法 一个用户体量比较大的分布式系统必然伴随着分表分库&#xff0c;分机房部署&#xff0c;单体的部署方式肯定是承载不了这么大的体量。 雪花算法的结构说明 如下图所示: 雪花算法组成 从上图我们可以看…...

SpringBoot | @Autowired 和 @Resource 的区别及原理分析

关注&#xff1a;CodingTechWork 引言 在Spring框架中&#xff0c;Autowired 和 Resource 是两种常用的依赖注入注解&#xff0c;它们都用于自动装配Bean&#xff0c;简化了开发者手动创建和管理Bean的繁琐工作。然而&#xff0c;它们的实现机制和使用方式有所不同。理解这两者…...

『SQLite』解释执行(Explain)

摘要&#xff1a;本节主要讲解SQL的解释执行&#xff1a;Explain。 在 sqlite 语句之前&#xff0c;可以使用 “EXPLAIN” 关键字或 “EXPLAIN QUERY PLAN” 短语&#xff0c;用于描述表查询的细节。 基本语法 EXPLAIN 语法&#xff1a; EXPLAIN [SQLite Query]EXPLAIN QUER…...

0基础学前端-----CSS DAY12

视频参考&#xff1a;B站Pink老师 今天是CSS学习的第十二天&#xff0c;今天开始的笔记对应Pink老师课程中的CSS第七天的内容。 本节重点&#xff1a;CSS高级技巧 本章目录 本节目标1. 精灵图1.1 为什么需要精灵图1.2 精灵图使用案例&#xff1a;拼出自己的名字 2. 字体图标2.…...

(概率论)无偏估计

参考文章&#xff1a;(15 封私信 / 51 条消息) 什么是无偏估计&#xff1f; - 知乎 (zhihu.com) 首先&#xff0c;第一个回答中&#xff0c;马同学图解数学讲解得很形象&#xff0c; 我的概括是&#xff1a;“注意&#xff0c;有一个总体的均值u。然后&#xff0c;如果抽样n个&…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...