几种经典查找算法

几种经典查找算法

  • 顺序查找法
  • 二分查找法
    • 判定树
  • 二叉查找树(BST)
  • 索引查找
  • B-树
  • B+树
  • 散列表(hash)查找

顺序查找法

顺序查找的平均查找长度为:
在这里插入图片描述在这里插入图片描述
时间复杂度为0(n);

二分查找法

int binsearch(datatype* a,int n,datatype target)
{
    int left=0;
    int right=n-1;
    int mid=(left+right)/2;
    while(left<=right)
    {
        if(target<a[mid])
        {
            right=mid-1;
            mid=(left+right)/2;
        }
        else if(target>a[mid])
        {
            left=mid+1;
            mid=(left+right)/2;
        }
        else
            return mid;
    }
    if(left>right)
        return -1;
}

我们来在主函数中测试一下:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef char datatype;
int binsearch(datatype*,int,datatype);
int main()
{
    char arr[10]={'a','c','e','g','i','k','m','o','q','s'};
    char x='m';
    char y='z';
    int xn=binsearch(arr,10,x);
    int yn=binsearch(arr,10,y);
    printf("%c:%d\n",x,xn);
    printf("%c:%d\n",y,yn);
    return 0;
}
int binsearch(datatype* a,int n,datatype target)
{
    int left=0;
    int right=n-1;
    int mid=(left+right)/2;
    while(left<=right)
    {
        if(target<a[mid])
        {
            right=mid-1;
            mid=(left+right)/2;
        }
        else if(target>a[mid])
        {
            left=mid+1;
            mid=(left+right)/2;
        }
        else
            return mid;
    }
    if(left>right)
        return -1;
}

在这里插入图片描述
结果正确。

下面是递归算法:

int binsearch2(datatype* a,int L,int R,datatype tar)
{
    if(L<R)
        return -1;
    int mid=(L+R)/2;
    if(a[mid]==tar)
        return mid;
    else
    {
        if(tar>a[mid])
            return binsearch2(a,mid+1,R,tar);
        else
            return binsearch2(a,L,mid-1,tar);
    }
}

判定树

二分查找法的平均查找长度和查找效率可以从判定树来看:
若把当前查找范围内居中的记录的位置作为根结点,前半部分与后半部分的记录的位置 分别构成根结点的左子树与右子树,于是就得到了一棵成为“判定树”的二叉树。(此处的位置不是下标,位置从1开始)
例如:数组【2,5,7,11,14,16,19,23,27,32,50】,其判定树为:
在这里插入图片描述我们发现:成功的查找过程正好等于走了一条从根结点到被查找结点的路径,经历的比较次数恰好是被查找结点在二叉树中所处的层数。

由此,我们可以得到二分查找法的平均查找长度:
在这里插入图片描述则上述案例的ASL为:(1 * 1 + 2 * 2 + 4 * 3 + 4 * 4)/ 11 = 23/11
时间复杂度为O(logn)。

二分查找法适用于:一经建立就很少改动(插入、删除),而又经常需要查找的查找表。
要使用二分查找法,必须满足:必须采用顺序存储结构,元素按值有序排列。

二叉查找树(BST)

二叉查找树可以同时兼顾查找和插入、删除操作的效率。
二叉树平衡的情况下,时间复杂度为:O(logn),退化的二叉树查找时间复杂度为:O(N)。

void searchBST(BTNodeptr p,int n)
{
    BTNodeptr cur=p;
    while(cur!=NULL)
    {
        if(n>cur->val)
        {
            cur=cur->right;
        }
        else if(n<cur->val)
        {
            cur=cur->left;            
        }
        else
        {
            printf("%d ",cur->val);
            break;
        }
    }
}

这个查找算法经过一些微改,还可以用于打印根结点到某结点的路径等。

索引查找

类似于牛津字典旁边的ABCD……把首字母一样的分为一类,首字母即为所谓的“索引”。当然,在实际运用的过程中,索引可以根据具体情况具体分析。
索引表是一个DataType*类型的数组,索引表数组的每一个元素都是一个数组,当然你也可以用链表来记录每一个索引下的数据。这里的代码以数组中的数组为例(因为可以两次运用二分查找法,如果内部用链表的话内部的查找就只能用顺序查找了)。

typedef struct node
{
    char index;//存放每个索引
    int num;//存放该索引下的元素个数
    char* stuff[100];//这个里面放该索引下的内容
    //也可用链表:
    //DataType* link;
}NODE;
void indexsearch(NODE a[],int n,char* target)
{
    char index=target[0];
    int left=0;
    int right=n-1;
    int mid;
    while(left<=right)
    {
        //索引如果有序的话,可以直接用二分查找法
        mid=(left+right)/2;
        if(target>a[mid].index)
        {
            left=mid+1;
        }
        else if(target<a[mid].index)
        {
            right=mid-1;
        }
        else
            break;
    }
    //有两种情况会跳出循环,第一种是找到了匹配的索引,通过break跳出
    //第二种是没找到,通过left>right跳出循环,这一种可以直接返回没找到
    //因为索引都没找到,那内容肯定更找不到了
    if(left>right)
    {
        printf("no find");
        return;
    }
    //如果找到索引了,就在索引里再用二分查找(顺序存储)
    int left=0;
    int right=a[mid].num-1;
    while(left<=right)
    {
        mid2=(left+right)/2;
        int cmp=strcmp(target,a[mid].stuff[mid2]);
        if(cmp>0)
            left=mid+1;
        else if(cmp<0)
            right=mid-1;
        else
        {
            //找到了,根据具体要求做具体操作就行
        }
    }
}

这个示例代码的作用类似字典,即索引表为单词首字母,然后现在索引表中找目标查找单词的索引,如果没找到(比如x开头的单词基本没有),那就直接返回,如果找到了,那就在找到的那部分索引中继续查找。

B-树

B-树的基本原理强烈大家去看b站
在这里插入图片描述
这个博主的视频!!!!ppt看了好久没搞明白但是博主动画一做就立马明白了真的讲的很清楚!

笔记这部分配图均来自上述博主及ppt,非本人原创↓
在这里插入图片描述一棵n阶B-树的特点:

  1. 根结点最少要有两个分支,最少要有一个元素
  2. 各个除根结点外分支节点最多有m个分支,m-1个元素;最少有【m/2】(向上取整)个分支,【m/2】-1个元素。
  3. 所有的叶结点都在同一层。教材们对B-树叶结点说法不统一,有些说上面红框子是叶子结点(也叫失败节点)。有些说是红框的上一层是叶结点。
  4. 各节点内部的元素是有序的

B树麻烦的地方在于它的插入
比如说,我们现在要在一个已有的3阶B-树中插入元素20
在这里插入图片描述经过比较,发现20比23小,那就应该插在它的左边:
在这里插入图片描述插入之后,发现这个节点的元素个数是2,没有超过每个结点的最大元素个数2,所以性质并未被破坏,则无需调整。

但是,我们再来插入17:
在这里插入图片描述
通过比较,发现应该插入14的右边
在这里插入图片描述
此时发现,该结点有3个元素,超出3阶B-树的最大元素个数,那么这个时候就需要分裂节点

以第【m/2】(上取整)个元素为中间结点,分为14左边,14,和14右边。

将14上移至父节点,而原来14的左边和右边,分别变为14在父节点中的左分支和右分支:
在这里插入图片描述但如果,调整之后,父节点又超过最大元素了,怎么办呢?
我们现在来插入元素53:
在这里插入图片描述那么这个时候,需要以53为分割点,进行上移:
在这里插入图片描述这个时候【36,53,70】这个节点又溢出了,那我们继续分裂这个节点。
这个时候要注意,此时分裂节点时,被分裂的就不仅是一个元素了,而是一个子树

在这里插入图片描述
ok此时父节点没有发生溢出。但如果根结点也溢出了,就需要再向上分裂,此时树层数加一。

B+树

在这里插入图片描述B+树的特点:
(前三点与B-树同)

  1. m阶B+树的各个分支节点最多有m棵子树
  2. 除根结点外,每个分支节点最少有【m/2】(上取整)棵子树
  3. 根结点至少有两棵子树(除非他就是叶子结点)

从第四点开始,就是B+树不同于B-树的地方:

  1. 具有n棵子树的结点中,必有n个元素(B-树只有n-1个
  2. 每个叶结点存放着记录的关键字以及指向记录的指针。(B-树是在每个结点中存储记录
  3. 所有分支节点可以看做是索引的索引,结点中仅包含它的各个孩子中的最大值或最小值以及指向孩子结点的指针。

这是B-树和B+树的区别:在这里插入图片描述

散列表(hash)查找

上述查找方式,多多少少都要进行关键字的比较,从而增加了时间复杂度。
那么有没有一种查找方式,不用经过任何关键字的比较,或者经过很少次关键字的比较,就能够达到目的的方法?

有,那就是哈希表,也叫散列表。
在这里插入图片描述
i ≠ j 但是,H(i) = H(j),也就是关键字不同,但散列地址相同,那么这种现象叫散列冲突

一个好的散列表应该具备的特点是:

  1. 散列函数的定义域必须包括将要存储的全部关键字;
    若散列表允许有m个位置时,则函数的值为[0,……,m-1](地址空间)
  2. 利用散列函数计算出来的地址应能尽可能均匀分布在整个地址空间中。
  3. 散列函数应该尽可能简单,应该在较短的时间内计算出结果。

建立散列表的步骤有:

  1. 确定散列的地址空间
  2. 构造合适的散列函数
  3. 选择处理冲突的方法

散列函数的构造方法有很多,我们最常用的就是:除留余数法

H(k)= k MOD p (p为小于等于m的素数)

冲突的处理方法也有很多,我们常用的是:链地址法,就是散列地址一样的链在后面。有点像日本鲤鱼旗那种感觉。。。
在这里插入图片描述
但是链表的查找效率比较低,所以我们可以换成一颗颗二叉搜索树。
下面的函数用于,查找哈希表中的某一个值,如果该值存在,则返回它的地址,如果不存在,则插入:

#define MAX 100
typedef struct node
{
    int data;
    struct node* next;
}NODE;
//因为有时经常需要按照一定的规律插入哈希表
//因此我们将哈希表每个位置的链表的头结点都设置为哨兵位
//这样方便头插
NODE* Hashtab[MAX];
NODE* find(int key)
{
    unsigned int h;
    NODE* cur;

    h=hash(key);//这是散列函数,根据具体情况做
    for(cur=Hashtab[h];cur->next!=NULL;cur=cur->next)
    {
        if(cur->next.data==key)
            return cur->next;
    }
    //如果函数能进行到这个地方,就说明没找到,那就需要插入了,我们这里就进行尾插就行
    NODE* new=(NODE*)malloc(sizeof(NODE));
    new->data=key;
    new->next=NULL;
    cur->next=new;
    cur=new;
    return cur;
}

OK,今天的分享就到这里了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CNN学习(7):用C++实现简单不同参数的卷积模型

目录 一、参数说明和计算公式 1、符号约定 2、输出大小计算公式 二、不同类型的卷积 1、输入3*3*1&#xff0c;卷积核3*3*1&#xff0c;输出1*1*1 &#xff08;1&#xff09;实现代码 &#xff08;2&#xff09;代码说明 2、输入4*4*1&#xff0c;卷积核3*3*1&#xff…

环保评A的意义与价值

环保评A&#xff0c;这个看似简单的称谓&#xff0c;背后却蕴藏着深厚的环保理念和实践标准。在当今社会&#xff0c;环保已经成为一项全球性的议题&#xff0c;各国都在努力推动绿色发展&#xff0c;实现可持续发展目标。那么&#xff0c;环保评A究竟是全国性的认证还是地方性…

Java SSTI服务端模版注入漏洞原理与利用

文章目录 前言Velocity基础语法基础示例命令执行 靶场实践漏洞代码漏洞验证检测工具 FreeMarker基础示例漏洞示例CMS案例 Thymeleaf基础示例漏洞示例安全方案 总结 前言 SSTI&#xff08;Server Side Template Injection&#xff09;全称服务端模板注入漏洞&#xff0c;在 Jav…

开放式耳机值得入手买吗?可以对比这几款开放式耳机看看

居家办公时&#xff0c;选择一款合适的耳机能够有效地提高工作效率。入耳式耳机虽然能够有效地隔绝外界噪音&#xff0c;但长时间佩戴会对耳朵造成负担&#xff0c;甚至引发耳道感染。而头戴式耳机虽然能够提供更好的音质&#xff0c;但体积较大&#xff0c;佩戴起来不够灵活。…

PyTorch -- Batch Normalization(BN) 快速实践

Batch Normalization 可以 改善梯度消失/爆炸问题&#xff1a;前面层的梯度经过多次传递后会变得非常小(大)&#xff0c;从而导致网络收敛速度慢(不收敛)&#xff0c;应用 BN 可缓解加速网络收敛&#xff1a;BN 使得每个神经元的输入分布更加稳定减少过拟合&#xff1a;BN 可减…

求导,积分

求导公式&#xff1a; 复合函数求导法则&#xff1a;两个函数导函数的乘积. 例如&#xff1a;f(x)2x1,f(x)2,g(x)x^24x4,g(x)2x4 那么复合函数&#xff1a; g(f(x))(2x1)^24(2x1)4 把&#xff08;2x1&#xff09;看做整体,则g2(2x1)4 然后再求&#xff08;2x1&#xff09;的导函…

LeetCode | 2879.显示前三行

在 pandas 中&#xff0c;可以使用 head() 方法来读取 DataFrame 的前几行数据。如果想读取指定数量的行&#xff0c;可以在 head() 方法中传入一个参数 n&#xff0c;读取前 n 行 import pandas as pddef selectFirstRows(employees: pd.DataFrame) -> pd.DataFrame:retur…

Dictionary 字典

文章目录 一、什么是字典1.1 字典的创建方式 一、什么是字典 字典&#xff1a; 用来存储数据&#xff0c;与列表和元组不一样的是&#xff0c;字典以键值对的形式对数据进行存储&#xff0c;也就是 key 和 value。相当于 Java 中的 Map。 注意&#xff1a; 1、 key 的值不可重…

C++进阶(一)

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 本篇博客是讲解函数的重载以及引用的知识点的。 文章目录 前言 1.函数重载 1.1何为函数重载 1.2函数重载的作用 1.3函数重载的实现 2.引用 2.1何为引用 2.2定义引用 2.3引用特性 2.4常引用 2…

认识一些分布函数-Frechet分布及其应用

1. 何为Frechet分布 Frechet分布也称为极值分布(EVD)类型II,用于对数据集中的最大值进行建模。它是四种常用极值分布之一。另外三种是古贝尔分布、威布尔分布和广义极值分布(Gumbel Distribution, the Weibull Distribution and the Generalized Extreme Value Distributi…

34 Debian如何配置ELK群集

作者:网络傅老师 特别提示:未经作者允许,不得转载任何内容。违者必究! Debian如何配置ELK群集 《傅老师Debian知识库系列之34》——原创 ==前言== 傅老师Debian知识库特点: 1、拆解Debian实用技能; 2、所有操作在VMware虚拟机实测完成; 3、致力于最终形成Debian知识手…

LVS-DR模式详解:提升网站性能的最佳解决方案

LVS-DR模式原理 用户请求到达Director Server&#xff1a; 用户请求到达Director Server&#xff08;负载均衡服务器&#xff09;&#xff0c;数据包首先到达内核空间的PREROUTING链。数据包源IP&#xff1a;CIP&#xff0c;目标IP&#xff1a;VIP&#xff0c;源MAC&#xff1a…

【内存管理之C语言数组】

1.栈空间上的C数组 糟糕的可用性&#xff0c;但是你将在遗留代码中见到它们 相同类型的对象的内存块 大小必须是常量表达式 第一个元素索引为0 2.指针和C数组 更奇怪的是&#xff1a;数组标识符退化为指向第一个元素的指针 3.访问数组 4.堆空间上的C数组 相同类型的对象的内…

数据库开发——并发控制(第十一章)

文章目录 前言并发执行例题一、封锁二、封锁协议三、可串行调度四、总结 学习目标&#xff1a;重点为并发控制的基本概念及几个基本协议 前言 数据库管理系统必须提供并发控制机制&#xff0c;保证事务的隔离性和一致性 并发执行例题 一、封锁 排他锁称为写锁&#xff0c;共…

智能化状态管理:自动状态流转处理模块

目录 基本背景介绍 具体实现 基本数据准备 基本数据表 状态转换常量 状态转换注解 任务处理模版 各任务实现逻辑 开启比对任务进行处理 降噪字段处理任务处理 开启业务数据比对处理 业务数据比对处理 开始核对数据生成最终报告处理 核对数据生成最终报告处理 状…

小红书教程简化版,从0开始走向专业,小红书-主理人培养计划 (13节)

课程目录 1-小红书分析与拆解.mp4 2-小红书电商玩法.mp4 3-小红书基础信息设置10_1.mp4 4-小红书如何开店&#xff1f;.mp4 5-小红书店铺设置&#xff08;1&#xff09;.mp4 5-小红书店铺设置.mp4 6-小红书笔记制作与产品发布.mp4 7-小红书运营的文案与标题.mp4 8-小红…

Spring Boot 自定义Starter

自定义starter 创建pom项目 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.ap…

MySQL的三种重要的日志

日志 Mysql有三大日志系统 Undo Log&#xff08;回滚日志&#xff09;&#xff1a;记录修改前的数据&#xff0c;用于事务回滚和 MVCC&#xff08;多版本并发控制&#xff09;。 Redo Log&#xff08;重做日志&#xff09;&#xff1a;记录数据变更&#xff0c;用于崩溃恢复&…

XAMPP PHP-CGI 远程代码执行漏洞(CVE-2024-4577)

漏洞概述&#xff1a; PHP 是一种被广泛应用的开放源代码的多用途脚本语言&#xff0c;PHP-CGI 是 PHP 自带的 FastCGI 管理器。是一个实现了 CGI 协议的程序&#xff0c;用来解释 PHP 脚本的程序&#xff0c;2024 年 6 月 7 日&#xff0c;推特安全上 orange 公开了其漏洞细节…

基于Wireshark实现对FTP的抓包分析

基于Wireshark实现对FTP的抓包分析 前言一、虚拟机Win10环境配置二、FileZilla客户端的安装配置下载FileZilla客户端安装FileZilla 三、FileZilla Server安装下载FileZilla Server安装 四、实现对FTP的抓包前置工作实现抓包完成抓包 前言 推荐一个网站给想要了解或者学习人工智…
最新文章