23.树表和哈希表的查找

news2024/9/29 5:24:30

当表插入、删除操作频繁时,为维护表的有序性,需要移动表中很多记录。基于此,我们可以改用动态查找表——几种特殊的树。表结构在查找过程中动态生成。对于给定值key,若表中存在,则成功返回;否则,插入关键字key的记录。

一. 树表的查找

(1)二叉排序树

二叉排序树(Binary Sort Tree)又称为二叉搜索树、二叉查找树;采用递归定义:

  • 若其左子树非空,则左子树上所有结点的值均小于根结点的值
  • 若其右子树非空,则右子树上所有结点的值均大于等于根结点的值
  • 其左右子树本身又各是一棵二叉排序树

对左边的二叉树进行中序遍历(LDR),它正好是递增序列!中序遍历非空的二叉排序树所得到的数据元素序列是一个按关键字排列的递增有序序列。

下面我们研究在二叉排序树上的查找操作。若查找的关键字等于根结点,成功。否则。若小于根结点,查其左子树;大于根结点,查其右子树。在左右子树上的操作类似。

我们首先定义二叉排序树的存储结构:

typedef struct{
    KeyType key;  //关键字项
    InfoType otherinfo;  //其他数据域
}ElemType;

typedef struct BSTNode{
    ElemType data;  //数据域
    struct BSTNode *lchild,*rchild;  //左右孩子指针
}BSTNode,*BSTree;

BSTree T;  //定义二叉排序树T

然后我们写出递归的算法:

BSTree SearchBST(BSTree T,KeyType key){
    if((!T) || key==T->data.key)   //“||”逻辑或
        return T;  //找不到的时候T是空,返回空指针,找到了就直接返回结点
    else if(key<T->data.key)
        return SearchBST(T->Ichild,key);  //在左子树中继续查找
    else 
        return SearchBST(T->rchild,key);  //在右子树中继续查找
}  //SearchBST

下面分析时间效率。二叉排序树上查找某关键字等于给定值的结点过程,其实就是走了一条从根到该结点的路径。比较的关键字次数=此结点所在层次数;最多的比较次数=树的深度;由下面的分析我们也可以看出,二叉排序树的平均比较长度和树结构本身有关。

最好情况:初始序列{45,24,53,12,37,93},ASL=log_2(n+1)-1,树的深度为:\left \lfloor log_2n \right \rfloor+1;与折半查找中的判定树相同,时间复杂度为O (lg n)。
最坏情况:初始序列{12,24,37,45,53,93},插入的n个元素从一开始就有序,变成单支树的形态。此时树的深度为n,ASL=\frac{n+1}{2},查找效率与顺序查找情况相同,均为O(n)。

我们研究二叉排序树的生成和插入操作。若二叉排序树为空,则插入结点作为根结点插入到空树中。否则,继续在其左、右子树上查找:树中已有,不再插入;树中没有,查找直至某个叶子结点的左子树或右子树为空为止。插入结点应为该叶子结点的左孩子或右孩子。

一个无序序列可通过构造二叉排序树而变成一个有序序列。构造树的过程就是对无序序列进行排序的过程。插入的结点均为叶子结点,故无需移动其他结点。相当于在有序序列上插入记录而无需移动其他记录。但是,输入序列的顺序不同,则生成的排序二叉树也不同。

最后我们讨论二叉排序树的删除操作。删除比插入麻烦的地方在于:从二叉排序树中删除一个结点,不能把以该结点为根的子树都删去,只能删掉该结点,并且还应保证删除后所得的二叉树仍然满足二叉排序树的性质不变。由于中序遍历二叉排序树可以得到一个递增有序的序列。那么,在二叉排序树中删去一个结点相当于删去有序序列中的一个结点。

  • 被删除的结点是叶子结点:直接删去该结点。
  • 被删除的结点只有左子树或者只有右子树:用其左子树或者右子树替换它(结点替换)。其双亲结点的相应指针域的值改为“指向被删除结点的左子树或右子树”。
  • 被删除的结点既有左子树又有右子树:以其中序前趋值替换之(值替换),然后再删除该前趋结点。前趋是左子树中最大的结点。也可以用其后继替换之,然后再删除该后继结点。后继是右子树中最小的结点。

(2)平衡二叉树

由上面的讨论,二叉排序树的ASL变大是因为排序树不够“均衡”,如何提高形态不均衡的二叉排序树的查找效率?这就是我们下面要讲的平衡化和平衡二叉树。

平衡二叉树(balanced binary tree),又称AVL树(Adelson-Velskii and Landis)。一棵平衡二叉树或者是空树,或者是具有下列性质的二叉排序树:

  • 左子树与右子树的高度之差的绝对值小于等于1;
  • 左子树和右子树也是平衡二叉排序树。

为了方便起见,给每个结点附加一个数字,给出该结点左子树右子树的高度差。这个数字称为结点的平衡因子(BF):平衡因子=结点左子树的高度-结点右子树的高度;根据平衡二叉树的定义,平衡二叉树上所有结点的平衡因子只能是-1,0或1。

当我们在一棵AVL树上插入一个结点,就可能造成失衡。为了方便起见,我们先说明几个点:

  • A:失衡结点不止一个失衡结点时,为最小失衡子树的根结点
  • B:A结点的孩子,C结点的双亲
  • C:插入新结点的子树
上图中插入新结点9导致7和16都失衡,所以我们选择最小失衡子树的根结点16

所以我们可以划分出四种平衡调整类型:

例题:给定关键字序列(16,3,7,11,9,26,18,14,15),写出构造AVL树的过程。

二. 散列表

(1)基本术语

基本思想:记录的存储位置与关键字之间存在对应关系。对应关系——hash(哈希)函数;LOC(i)=H(keyi)

例如:数据元素序列(21,23,39,9,25,11),若规定每个元素k的存储地址H(k)=k,画出存储结构图如下:

散列表的查找:根据散列函数H(key)=k,查找key=9,则访问H(9)=9号地址,若内容为9则成功;若查不到,则返回一个特殊值,如空指针或空记录。

散列表具有查找效率高,空间效率低的特点。所以我们后面的研究都是为了“省空间”。下面介绍一些散列表的有关术语:

  • 散列方法(杂凑法):选取某个函数,依该函数按关键字计算元素的存储位置,并按此存放;查找时,由同一个函数对给定值k计算地址,将k与地址单元中元素关键码进行比对,确定查找是否成功。
  • 散列函数(杂凑函数)︰散列方法中使用的转换函数。
  • 散列表:由散列方法构造出的表。
  • 冲突:不同的关键码映射到同一个散列地址,也就是key1≠key2,但是H(key1)=H(key2)。
  • 同义词:起冲突的多个关键字。

例如:数据元素序列仍同上,但是散列函数H(k)=k\, mod\, 7,则有H(25)=25%7=4,H(11)=11%7=4。他们指向了同一块地址。因此,使用散列表应该解决好两个问题:

  • 构造好的散列函数:所选函数尽可能简单,以便提高转换速度;所选函数对关键码计算出的地址,应在散列地址集中致均匀分布,以减少空间浪费。
  • 制定一个好的解决冲突的方案:查找时,如果从散列函数计算出的地址中查不到关键码,或者冲突,则应当依据解决冲突的规则,有规律地查询其它相关单元。

(2)构造散列函数

构造散列函数应该考虑以下几点:执行速度(即计算散列函数所需时间);关键字的长度;散列表的大小;关键字的分布情况;查找频率。

根据元素集合的特性构造:n个数据原仅占用n个地址虽然散列查找是以空间换时间,但仍希望散列的地址空间尽量小;无论用什么方法存储,目的都是尽量均匀地存放元素,以避免冲突。

常用的构造方法:直接定址法;数字分析法;平方取中法;折叠法;除留余数法;随机数法

直接定址法:Hash(key) = a*key + b (a、b为常数)
优点:以关键码key的某个线性函数值为散列地址,不会产生冲突。缺点:要占用连续地址空间,空间效率低。例如:给定数据{100,300,500,700,800,900},构造散列函数Hash(key)=key/100(a=1/100,b=0)。

除留余数法:Hash(key)= key mod p(p是一个整数)

选取余数应该小于表长,且尽量选质数。例如,给定数据{15,23,27,38,53,61,70},散列函数Hash(key)=key mod 7。

(3)处理冲突的方法

a.开放定址法(开地址法)

基本思想:有冲突时就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将数据元素存入。

例如:除留余数法可以改进为H_i=(Hash(key)+d_i)mod m;其中d_i为增量序列:

1.线性探测法:d_i为1,2,....m-1线性序列

例:关键码集为{47,7,29,11,16,92,22,8,3},散列表长度为m=11;散列函数为Hash(key)=key mod 11;拟用线性探测法处理冲突。建散列表如下:

过程:

  • 47、7均是由散列函数得到的没有冲突的散列地址;
  • Hash(29)=7,散列地址有冲突,需寻找下一个空的散列地址:由H1=(Hash(29)+1) mod 11=8,散列地址8为空,因此将29存入。
  • 11、16、92均是由散列函数得到的没有冲突的散列地址;
  • 另外,22、8、3同样在散列地址上有冲突,也是由H1找到空的散列地址的。

本例中平均查找长度ASL=(1+2+1+1+1 +4+1 +2+2)/9=1.67,例如3,3mod11=3,然后依次找4,5,6发现6号能对上,所以查找3需要查找4次。

2.二次探测法:d_i为1^2,-1^2,2^2,-2^2,...,q^2二次序列

例:关键码集同上,当3位已经有元素47时,再存入3。Hash(3)=3 mod 11=3,散列地址冲突,由H_1=(3+1^2)\, mod\,11=4,仍然冲突;那么再计算H_2=(3-1^2)\, mod\,11=2,找到空的散列地址,存入。

3.伪随机探测法:d_i为伪随机数序列

b.链地址法(拉链法)

基本思想:相同散列地址的记录链成一单链表,m个散列地址相同就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构

链地址法建立散列表的步骤:取数据元素的关键字key,计算其散列函数值(地址)。若该地址对应的链表为空,则将该元素插入此链表;否则根据选择的冲突处理方法,计算关键字key的下一个存储地址。若该地址对应的链表为不为空,则利用链表的前插法或后插法将该元素插入此链表。

链地址法的优点:非同义词不会冲突,无“聚集”现象(当发生哈希冲突时,开地址法会尝试将冲突的元素插入到哈希表中的下一个可用槽位。如果下一个槽位也被占用了,继续寻找下一个可用槽位,直到找到一个空槽位为止。这种方式可能导致冲突的元素在哈希表中形成一串聚集)。链表上结点空间动态申请,更适合于表长不确定的情况。

例如:一组关键字为{19,14,23,1,68,20,84,27,55,11,10,79},散列函数为Hash(key)=key mod 13。

(4)散列表的查找

例题:已知一组关键字(19,14,23,1,68,20,84,27,55,11,10,79),给定散列函数为:H(key)=key MOD 13,散列表长为m=16,设每个记录的查找概率相等。

(1)用线性探测再散列处理冲突,即Hi=(H(key)+di) MOD m,可得散列表如下:

 从而计算得到ASL=(1*6+2+3*3+4+9)/12=2.5

(2)用链地址法解决冲突,计算ASL得到:ASL=(1*6+2*4+3+4)/12=1.75

使用平均查找长度ASL来衡量查找算法,ASL取决于:散列函数的选择,处理冲突的方法,散列表的装填因子α(装填因子=表中填入的记录数/哈希表的长度,α越大,表中记录数越多,说明表装得越满,发生冲突的可能性就越大,查找时比较次数就越多)。ASL与装填因子α有关,既不是严格的O(1),也不是O(n)。

  • 拉链法:ASL=1+\frac{\alpha }{2}
  • 线性探测法:ASL=\frac{1}{2}\left ( 1+\frac{1}{1-\alpha } \right )
  • 随机探测法:ASL=-\frac{1}{\alpha }ln(1-\alpha )

最后做几点总结:

  • 散列表技术具有很好的平均性能,优于一些传统的技术
  • 链地址法优于开地址法
  • 除留余数法作散列函数优于其它类型函数

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

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

相关文章

HTML5-1-标签及属性

文章目录 语法规范标签规范标签列表通用属性基本布局 页面的组成&#xff1a; HTML&#xff08;HyperText Markup Language&#xff0c;超文本标记语言&#xff09;是用来描述网页的一种语言&#xff0c;它不是一种编程语言&#xff0c;而是一种标记语言。 HTML5 是下一代 HTM…

Linux内核数据结构 散列表

1、散列表数据结构 在Linux内核中&#xff0c;散列表&#xff08;哈希表&#xff09;使用非常广泛。本文将对其数据结构和核心函数进行分析。和散列表相关的数据结构有两个&#xff1a;hlist_head 和 hlist_node //hash桶的头结点 struct hlist_head {struct hlist_node *first…

Linux学习笔记-Ubuntu系统下配置ssh免密访问

Ubuntu系统下配置ssh免密访问 一、基本信息二、ssh安装2.1 查看是否已经安装ssh2.2 安装ssh2.3 查看ssh安装状态 三、启动、停止&#xff0c;及开机自启动3.1 启动ssh3.2 关闭ssh3.3 使用systemctl设置ssh服务自启动3.4 使用systemctl关闭ssh开机启动 四、配置通过密钥进行免密…

Spring Authorization Server入门 (十六) Spring Cloud Gateway对接认证服务

前言 之前虽然单独讲过Security Client和Resource Server的对接&#xff0c;但是都是基于Spring webmvc的&#xff0c;Gateway这种非阻塞式的网关是基于webflux的&#xff0c;对于集成Security相关内容略有不同&#xff0c;且涉及到代理其它微服务&#xff0c;所以会稍微比较麻…

基于Spring Gateway路由判断器实现各种灰度发布场景

文章目录 1、灰度发布实现1.1 按随机用户的流量百分比实现灰度1.2 按人群划分实现的灰度1.2.1 通过Header信息实现灰度1.2.2 通过Query信息实现灰度1.2.3 通过RemoteAdd判断来源IP实现灰度 2、路由判断器2.1. After2.2. Before2.3. Between2.4. Cookie2.5. Header2.6. Host2.7.…

C++ Primer 第2章 变量和基本类型

C Primer 第2章 变量和基本类型 2.1 基本内置类型2.1.1 算术类型一、带符号类型和无符号类型练习 2.1.2 类型转换一、含有无符号类型的表达式 2.1.3 字面值常量一、整型和浮点型字面值二、字符和字符串字面值三、转义序列四、指定字面值的类型五、布尔字面值和指针字面值 2.2 变…

软考A计划-系统集成项目管理工程师-小抄手册(共25章节)-下

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

MySQL—MySQL主从如何保证强一致性

一、前言 涉及到的东西&#xff1a;两阶段提交&#xff0c;binlog三种格式 1、两阶段提交 在持久化 redo log 和 binlog 这两份日志的时候&#xff0c;如果出现半成功的状态&#xff0c;就会造成主从环境的数据不一致性。这是因为 redo log 影响主库的数据&#xff0c;binlog…

【大数据】Doris:基于 MPP 架构的高性能实时分析型数据库

Doris&#xff1a;基于 MPP 架构的高性能实时分析型数据库 1.Doris 介绍 Apache Doris 是一个基于 MPP&#xff08;Massively Parallel Processing&#xff0c;大规模并行处理&#xff09;架构的高性能、实时的分析型数据库&#xff0c;以极速易用的特点被人们所熟知&#xff…

Kali Linux中的ARP欺骗攻击如何进行

在Kali Linux中进行ARP欺骗攻击是一种常见的网络攻击方法&#xff0c;它允许攻击者篡改局域网中的ARP表&#xff0c;以便将网络流量重定向到攻击者控制的位置。 步骤&#xff1a; 安装必要工具&#xff1a; 首先&#xff0c;确保 已经安装了Kali Linux&#xff0c;并在终端中安…

解除用户账户控制提醒

解决用户账户控制提醒 1. 前言2. 解决用户账户控制提醒2.1 控制面板2.2 注册表2.3 UAC服务 结束语 1. 前言 当我们使用电脑时&#xff0c;有时进行安装应用或者打开应用时&#xff0c;总会弹出一个提示框&#xff0c;要选择点击是否允许程序运行&#xff1b; 系统经常弹出用户…

流处理详解

【今日】 目录 一 Stream接口简介 Optional类 Collectors类 二 数据过滤 1. filter()方法 2.distinct()方法 3.limit()方法 4.skip()方法 三 数据映射 四 数据查找 1. allMatch()方法 2. anyMatch()方法 3. noneMatch()方法 4. findFirst()方法 五 数据收集…

azure data studio SQL扩展插件开发笔记

node.js环境下拉取脚手架 npm install -g yo generator-azuredatastudio yo azuredatastudio 改代码 运行 调试扩展&#xff0c;在visual studio code中安装插件即可 然后visual studio code打开进行修改运行即可 image.png 运行后自动打开auzre data studio了&#xff0c; 下面…

深度学习9:简单理解生成对抗网络原理

目录 生成算法 生成对抗网络&#xff08;GAN&#xff09; “生成”部分 “对抗性”部分 GAN如何运作&#xff1f; 培训GAN的技巧&#xff1f; GAN代码示例 如何改善GAN&#xff1f; 结论 生成算法 您可以将生成算法分组到三个桶中的一个&#xff1a; 鉴于标签&#…

6. 使用python将多个Excel文件合并到同一个excel-附代码解析

【目录】 文章目录 6. 使用python将多个Excel文件合并到同一个excel-附代码解析1. 目标任务2. 结果展示3. 代码示例4. 代码解析4.1 导入库4.2 调用库的类、函数、变量语法4.3 os.listdir-返回目录中的文件名列表4.4 startswith-用于判断一个字符串是否以指定的前缀开头4.5 ends…

如何评估开源项目的活跃度和可持续性?

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

深度学习1.卷积神经网络-CNN

目录 卷积神经网络 – CNN CNN 解决了什么问题&#xff1f; 需要处理的数据量太大 保留图像特征 人类的视觉原理 卷积神经网络-CNN 的基本原理 卷积——提取特征 池化层&#xff08;下采样&#xff09;——数据降维&#xff0c;避免过拟合 全连接层——输出结果 CNN …

postgresql-字符函数

postgresql-字符函数 字符串连接字符与编码字符串长度大小写转换子串查找与替换截断与填充字符串格式化MD5 值字符串拆分字符串反转 字符串连接 concat(str, …)函数用于连接字符串&#xff0c;并且忽略其中的 NULL 参数&#xff1b;concat_ws(sep, str, …) 函数使用指定分隔…

小研究 - Java虚拟机内存管理(三)

Java 语言的面向对象&#xff0c;平台无关&#xff0c;安全&#xff0c;开发效率高等特点&#xff0c;使其在许多领域中得到了越来越广泛的应用。但是由于Java程序由于自身的局限性&#xff0c;使其无法应用于实时领域。由于垃圾收集器运行时将中断Java程序的运行&#xff0c;其…

【手写promise——基本功能、链式调用、promise.all、promise.race】

文章目录 前言一、前置知识二、实现基本功能二、实现链式调用三、实现Promise.all四、实现Promise.race总结 前言 关于动机&#xff0c;无论是在工作还是面试中&#xff0c;都会遇到Promise的相关使用和原理&#xff0c;手写Promise也有助于学习设计模式以及代码设计。 本文主…