目录
1.了解哈希表
1.哈希表
1.他的实现原理就是: 编辑
2.写单个数据的类型(这边先模拟map的kv类型,后面会再一起改,这边先一步步的先简单实现他)
3.封装整个类:
4.哈希表中存储string
2.哈希桶
3.封装unordered中的哈希桶
4.迭代器的实现
5.封装unordered_map和unordered_set
1.了解哈希表
其实了解这两个库,就知道底层其实是一个哈希表的一个功能。所以我们首先要了解哈希表。
他其实就是解决在一堆数据里,取寻找某一个数据在不在的一个问题。想想如果让他先排序,然后在找,排序的时间复杂度其实很大了,那有没有办法用o(N)的时间复杂度将这个数据拷贝下来,在以后查找这个数据在不在,时间复杂度都是在0(1)呢?
其实这个就是哈希实现的功能。
1.哈希表
1.他的实现原理就是:
注意看这个18这个数字是不是和2这个位置冲突了,所以我们需要往后面移一个位置,那我们找也一样,也是要往后找,那找到什么时候结束?(就是找到空格结束,还没有找到就是没有;或则最坏的结果就是把这个数组都找完,因为这个数组都填满了这个数据,但是这个情况不会发生,因为我们在写这个底层是,会不断的给他扩容。你想想看,如果都快填满了,那查找它的效率就会明显下降,那就失去了他高效功能的意义了)
2.写单个数据的类型(这边先模拟map的kv类型,后面会再一起改,这边先一步步的先简单实现他)
我上面讲的数组除了存储它的数据,但我举一个例子:
如果我们删除6,再去寻找44就找不到了,所以我们就需要一个状态值了:
所以我们就可以开始第一步了:
3.封装整个类:
先看成员变量:
现在来讲解上面HashFunc是干嘛用的,他其实是一个仿函数,为什么需要仿函数呢?你要知道我们不知道key中存的是什么数据,可以无法整除整数,那就和哈希完全不相关联,所以我们要引入这个模板,当其他人使用这个类时,想存储自定义类型也是可以的,只需要让他写一个仿函数就可以了。
最后还有一点就是扩容不能超过0.7,其实每一个库实现的都不一样,这边其实没有一个统一的划分。
4.哈希表中存储string
这个为什么要单独拿出来讲呢?因为这个会出错:因为字符串转化为整形,很有可能会重叠,所以大佬们也是想了很多办法,但也只能不断地减小误差。
各种字符串Hash函数 - clq - 博客园 (cnblogs.com)
可以去这个网站上了解一下:
我就用最高评分的那种了:
就用一个模板的特例化取解决:
2.哈希桶
能明白我的意思把,就是这个数组变成了指针数组,下面是一个链表,只有next的链表。但是库里面比我这个模拟实现还要复杂,下面挂的不是单链表,而是红黑树,其实也不是很难实现,有兴趣的可以自己实现一下:
3.封装unordered中的哈希桶
这是单个数据的结点:
下面这个我就先连迭代器一起写进去了,还有一些知识因为在set和map模拟里面我有说过,这些基本是一样的,我就不累赘了,C++模拟实现set和map-CSDN博客
4.迭代器的实现
其实在我们模拟实现中,不应该按照我这个顺序来的,这在set和map那节也说过,这是因为我是已经模拟完了,才过来写这篇博客的。其实正确的模拟顺序是:
1.模拟实现哈希桶
2.初步封装unordered_map和unordered_set。
3.模拟实现迭代器
4.在迭代器中加入const迭代器
5.insert返回值, operator[]
6.map中的key和set不能修改的问题
如果一起直接写完,那必然很容易就会报错,那么就会让你很无从下手,甚至想放弃。
然后我们继续说迭代器。这个迭代器还是比较特殊的。
首先一点就是,我们要想清楚,我们成员变量只有一个Node* 的结点指针是否就够了,看上面那张图,如果结点指针指向了44,我们怎么跳到5?因为我们这个结点只有next,所以只能找到下一个,不能找到上一个,那执行oeprator++就不怎么好执行了,所以我们必须要再加一个成员变量,这个哈希桶的头指针。
但现在其实还有一个问题,我们下面的迭代器类需要_table, HashTable类需要iterator,这个相互牵扯的,每一个类都在在另一个类上面去实现他。所以就需要声明一个类了。
所以:
而且,迭代器中的_pht需要拜访_table,所以还要加一个友元:
最后看一下整体的: