u8,u8国际,u8国际官方网站,u8国际网站,u8国际网址,u8国际链接,u8体育,u8体育官网,u8体育网址,u8注册,u8体育网址,u8官方网站,u8体育APP,u8体育登录,u8体育入口在看本篇文章前大家尽量拿出上一篇文章的代码跟着一步步实现否则很容易引出大量模板错误而无法解决。
首先我们要解决映射的问题我们目前的代码只能映射整形那么如何支撑浮点数等的映射呢只需要多加一个模板参数就可以了
这个仿函数可以将任何支持隐式类型转换的key转换为size_t类型比如double类型会被隐式转换为size_t类型那么字符串该如何解决呢我们直接用模板特化来解决模板的特化就是有特化就走特化没有特化就走原类型
然后我们给Hash这个模板参数一个缺省参数默认使用我们的仿函数
然后我们在每个函数体内将取模的key值用仿函数包一下(这里我们只展示了修改后的代码并不是函数体就只有这些代码
只需要每次累乘因子31就能解决这样的问题为什么是31呢因为31是在大量测试中表现最好的一个值
可以看到确实成功解决了这个问题在这里我们补充一个上一篇遗留的问题哈希表增删查改的时间复杂度都是O1并且因为有着载荷因子的控制每个桶的元素不会太长下面我们验证一下
我们随机插入10000个随机数然后将每个桶插入几个元素就打印出来
结果就是每个桶基本就是0-2之间这就证明了我们刚刚说的由载荷因子控制每个桶的长度。那么如果面试官问出现了一个桶有很多的值这个时候查找变成O(N该怎么解决其实很简单当某个桶的长度超过规定的大小我们就将这个桶改为红黑树红黑树会大幅度减少高度这样就解决了这样的问题。除了刚刚挂红黑树可以优化哈希表还有一个方法能优化那就是每次开空间的大小都为素数为什么是素数呢因为素数只能被1和他本身整除如果空间大小是素数每次映射的时候就能减少冲突。代码如下
只需要在扩容的时候把素数表中的值给newsize即可。下面我们就开始封装unordered系列了这里很多地方都和红黑树的封装一样所以我们就不讲的那么细了
然后我们修改一下节点中的kv键值对用一个T类型来表示set中的key或者map中的pair:
用KeyOfT这个仿函数可以准确的拿到key值然后我们用key值经过哈希仿函数转化为可以取模的无符号整形这样就可以让unordered_set和map用哈希表做底层了然后我们再把其他接口修改一下
修改到find和eraser接口我们就应该能体会到刚刚在set中多传一个参数的作用了还记得我们怎么说的吗第一个参数是给Find等接口使用的因为这些接口用的参数一定是key。
下面我们给一组测试用例先跑一下在这里大家一定要注意因为模板参数很多一定要写一部分就编译一下否则模板参数的报错很容易把人搞崩溃。
经过编译后我们发现没有问题接下来就进行迭代器的实现了
那么迭代器该如何实现呢首先我们都知道要想遍历桶中的节点是一定需要节点的指针的还需要什么呢其实还需要一个哈希表的vector或者一整个哈希表因为当我们要从一个桶到另一个桶的时候是需要vector的我们实现的迭代器里面就直接存指针和哈希表好了大家也可以下去试试存vector步骤都是一样的STL源码中存的是哈希表可以给大家看看
首先我们实现的迭代器有哈希表的指针和节点的指针由于我们的迭代器实现在哈希表之前所以我们需要加上前置声明才能正常使用哈希表要注意的是哈希表参数中的Hash的缺省参数我们只需要在哈希表的地方给出在声明的时候不用带上缺省参数如果有重定义的报错那么一定是你将Hash给上HashFunc的缺省参数了。
如上图只需要在哈希表的位置给出缺省参数就可以了因为这里是定义。我们在初始化迭代器的时候不仅需要节点还需要哈希表的指针下面我们实现迭代器的主要功能
对于-的返回类型相信不用多说了吧引用本身就是指针实现的所有返回引用是没问题的。下面实现由于unordered系列的迭代器是单向迭代器所以我们实现个就可以了
首先我们要判断迭代器当前节点的next是否为空如果不为空那么我们直接走到next的那个节点就可以了如果next为空就说明我们后要变成下一个不为空的桶的头结点所以当next为空时我们算出当前节点映射出来的哈希桶位置因为当前的位置已经遍历完了毕竟刚刚next都为空了所以我们需要让当前位置变成下一个位置在这里要说明一下迭代器的起始位置是哈希表中第一个不为空的桶的头结点 所以我们需要判断算出来的位置是否小于总的表长度进入循环后判断当前位置的桶的头结点是否存在如果存在那么后的位置就是这个头结点然后break即可。因为有可能在的过程中后面有连续空的桶所以需要判断如果出循环后发现hashi和表长度一样大那么就说明表中没有不为空的桶所以我们让迭代器的节点为空即可。最后返回迭代器。
因为begin是哈希表中第一个不为空的桶的头结点所以我们挨个遍历哈希表找到不为空的桶我们就将头结点给cur,然后返回由cur这个节点构造的迭代器因为我们的迭代器还有哈希表而我们就是在哈希表中实现的begin所以直接返回this指针this指针就是我们当前的哈希表注意不是*this因为我们的迭代器要的就是哈希表的指针所以不需要解引用end的实现非常简单只需要返回空节点构造的迭代器即可因为哈希表的结束位置的节点就是为空下面我们将迭代器封装进set和map中
还记得typename的作用吗当我们使用模板的时候编译器会找不到iterator,这个时候需要typename让编译器知道iterator以后会由模板实例化生成所以不要报错。然后我们运行起来
这是因为我们的迭代器无法访问哈希表中的私有成员所以我们直接用友元函数搞一下
上图这样的方法不用声明模板参数下面我们再用一下传统方法
这里报错的原因是没加迭代器的模板参数我们加一下
map的迭代器测试也没问题下面我们实现一下map的特有功能:[]运算符
我们在修改哈希表中的insert前需要先修改一下find接口因为有了迭代器后find应该返回迭代器才对
当我们找不到的时候返回end()即可end就是nullptr构造的迭代器找到了就返回由找到的节点构造的那个迭代器。
插入的时候因为find接口返回的是迭代器所以我们也用迭代器来接收find的返回值如果it不等于end()说明就是要插入的值在哈希表找到了这个时候不在插入了因为我们不插入重复的值返回it这个迭代器和false即可。最后插入成功返回新节点构造的迭代器和哈希表即可。
【】不管怎么样都返回pair的second在插入的时候由于不知道V给的是什么所以用匿名对象就可以了。下面测试一下
经过测试没有问题下面我们要引出另一个问题如果是自定义类型该如何存入哈希表呢我们以日期类为例
运行时报错了因为日期类无法转化为size_t类型这也在我们意料之中我们用库中的哈希表要解决这样的问题实际上是自己提供一个将key转化为可以取模的整形就可以搞定了所以我们之前实现的Hash是有问题的因为这个参数应该是给map和set当有人调用map和set的时候自己传入Hash,所以我们修改一下
调用哈希表的时候必须显式的传Hash这个参数然后修改一下map和set:
写完后我们发现这个仿函数用不了日期类的私有成员下面将仿函数设为友元
运行后发生报错原因是日期类没有支持等于操作符下面我们实现一下
然后我们将运算符重载放到外面因为放在类里面会少一个参数无法直接打印日期类。
2.哈希表实现的unordered_map/set要用自定义类型需要一个支持取模转化为无符号整形的仿函数和支持运算符。
这里报的错与红黑树那部分一样因为我们已经将普通迭代器用const迭代器实现了所以begin()中返回的普通迭代器和begin的返回类型无法转换了因为begin的返回类型是经过typedef的const迭代器而返回值是调用哈希表中的普通迭代器无法从普通迭代器转化为const迭代器所以我们像之前一样直接增加一个支持将普通迭代器转化为const迭代器的构造函数
首先我们typedef一个普通迭代器这一定是普通迭代器。因为只有传Ref和Ptr的时候如果传参是const类型就会转化为const迭代器而我们用的T&,T*是不会转化的。
这次就解决刚刚的问题了我们再实现一下map的const迭代器:
哈希表的封装是比红黑树复杂的相信大家看出来了不过只要大家在红黑树的封装那边没有问题那么哈希表的封装也不会很困难的如果有不懂的完全可以查一下源码或者看上一篇红黑树封装的文章那篇文章将源码的实现都列出来了。