STL关联式容器之hashtable(前言)

##摘要
我们前面介绍了所谓的二叉搜索树和平衡二叉搜索树,还有一种被广泛使用的平衡二叉搜索树——RB-tree(红黑树),红黑树不仅在树形的平衡上表现不错,在效率表现和实现复杂度上也保持相当的“平衡”,所以被广泛使用,也因此成为STL set和map的标准底层机制。
二叉搜索树具有对数平均时间(longarithmic average time)的表现,但这样的变现构造在一个假设上:输入数据有足够的随机性。本节将介绍一种名为hashtable(散列表)的数据结构,这种结构在插入、删除、搜寻等操作上也具有“常数平均时间”的表现,而且这种表现是以统计为基础,不需要依赖输入元素的随机性。
##hashtable概述
hash table可提供对任何有名项(name item)的存取操作和删除操作。由于操作对象是有名项,所以hashtable也可被视为一种字典结构(dictionary)。这种结构的用意在于提供常数时间之基本操作,就像stack或deque那样。咋听之下是不可能完成的任务,因为制约条件如此之少,而元素的增加,搜寻操作必定耗费更多的时间。但也不是绝对,如果所有的元素都是16位且不带正负号的整数,范围0—65535,那么简单地运用一个数组就可以满足上述期望。首先配置一个array A,拥有65535+1个元素,索引号码0~65535,初始值都为0,如下图。每一个元素值代表相应元素的出现次数。如果插入元素i,我们就执行A[i]++,如果删除元素i,我们就执行A[i]–,如果搜寻元素i,我们就检查A[i]是否为0.以上的每一个操作都是常数时间。这种解法的额外负担(overhead)是array的空间和初始化时间。在这里插入图片描述
但这个解法存在两个现实问题。一是如果元素是32位而非16位,我们所准备的array A,初始值大小就必须是2^32=4GB,这就大的不切实际了。第二,如果元素型态是字符串而非整数,将无法被拿来作为array的索引。
第二个问题(关于索引)不难解决,就像数值1234是由阿拉伯数字1,2,3,4构成一样,字符串“array”是由’a’,‘r’,‘r’,‘a’,'y’构成。那么既然数值1234是1101010+21010+310*+410^0,我们也可以把字符编码,每个字符以一个7-bits数值来表示(ASCLL编码),从而将字符串“array”表现为:'a’128128128128+'r’128128128+'r’128128+'a’128+'y’1
于是先前的array实现法就可适用于“元素型别为字符串”的情况了。但这并不实用,因为这会产生出非常巨大的数值。“array”的索引值将是:106
128
128128128+106128128128+104128128+111128+117*1=28678174709
这就太不实际了。更长的字符串会导致更大的索引值。这就回归到第一个问题:array的大小。
如何避免使用一个大得荒谬的array呢?办法之一就是使用某种映射函数,将大数映射为小数。负责将某一元素映射为一个“大小可接受值索引”,这样的函数称为hash function(散列函数)。假如x是任意整数,TableSize是array大小,则X%TableSize会得到一个整数,范围在0~TableSize之间,恰好可以作为表格的索引。
使用hash function会带来一个问题:可能有不同的元素被映射到相同的位置(亦有相同的索引)。这是无法避免的,因为元素个数大于array容量。这便是所谓的“碰撞(ollision)”问题。解决问题的方法有许多种,包括线性探测(linear probing)、二次探测(quadratic probing)、开链(separate chaining)…等做法。每一种方法都很容易,导出的效率各不相同——与array的填满程度有很大的关联。
##线性探测(linear probing)
负载系数(loading factor):意指元素个数除以表格大小。负载系数永远在0-1之间,除非使用开链策略。
当hashfunction计算出某个元素的插入位置,而该位置上的空间已不再可用时,最简单的办法就是循序往下一一寻找(如果达到尾端,就绕头部继续寻找),直到找到一个可用空间为止。只要表格(array)足够大,操作时,道理也相同,如果hash function计算出来的位置上的元素值与我们的搜寻目标不符,就循序往下一一寻找,直到找到吻合者,或直到遇到空格元素。止于元素的删除,必须采用惰性删除(lazy deletion),也就是只删除标号,实际删除操作侧待表格重新整理时在进行。因为hash table中的每一个元素不仅表述它自己,也关系到其它元素的排列。
如下图:依序插入5个元素,array的变化:
在这里插入图片描述
欲分析线性探测的表现,需要两个假设:(1)表格足够大;(2)每个元素都够独立。在此假设之下,最坏的情况是线性巡防整个表格,平均情况则是巡防一半表格,这已经和我们所期望的常数时间天差地远了,而实际情况更为糟糕。明显的问题出在第二个假设:若持续上图的最后状态,除非新元素经过hash function的计算之后直接落在位置#4-#7,否则位置#4-#7永远不可能被运用,因为位置#3永远是第一考虑。换句话说,新元素不论是8,9,0,1,2,3中的哪一个,都会落在#3位置上。新元素如果是4或者5,6,7,才会各自落在位置#4,#5,#6,#7上。这很清楚地突显了一个问题:平均插入成本的成长幅度,远高于负载系数的成长幅度——这样的现象在hashing过程中被称为主集团(primary clustering)。此时的我们手上有的是一大团被用过的方格,插入操作极有可能在主集团所形成的泥泞中奋力爬行,不断解决碰撞的问题,最后才射门得分,但却是又助长了主集团的泥泞面积。
##二次探测(quadratic probing)
二次探测可以消除主集团,却可能造成次集团(secondary clustering):两个元素经过hash function计算出来的位置若相同,则插入式所探测的位置也形同,形成某种浪费。另外,消除次集团可用复式散列(double hashing)。
##开链(separate chaining)
另一种与二次探测法相近的便是开链法。这种做法是在每一个表格中维护一个list:hash function为我们分配某一个list,然后我们在那个list身上执行元素的插入,搜寻、删除等操作。虽然针对list而进行的搜寻只能是一种线性操作,但list够短,速度还是够快。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布系统具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行错误和内存泄漏等问题。它还支持编译检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布系统具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行错误和内存泄漏等问题。它还支持编译检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值