技术标签: STL源码剖析
hashtable(散列表)结构,在插入、删除、搜索等操作也具有”常数平均时间”的表现。
举个例子,假设所有元素都是16bits,范围0~65535,简单的使用一个array即可以满足常数时间的插入、删除、搜索。
创建数组array A,拥有65536个元素,索引号码0~65535,初始值全部为0
当插入元素i就执行A[i]++,删除元素就执行A[i]–,如果搜索元素i就检查A[i]是否为0
下图为插入了元素:5,8, 3, 8, 58, 65535, 65534 数组内容。
但是上面的解法存在两个问题:
如果元素是32-bits而非16-bits那么所需的array A的大小就是232=4GB,这么大就不切实际了。
如果元素是字符串而非整数,就无法拿来做array的索引了。
第二个问题不难解决:可以将字符编码,每个字符以7-bits的数值表示(也就是ASCII码),如字符串”jjhou”表现为:
数值太大了,这有回到了问题一。
那么,如何避免一个大的慌缪的array呢?办法之一就是使用某种映射函数(hash function散列函数),将任意的元素映射到TableSize范围之内。
二、常用的哈希函数
1. 直接寻址法
取关键字或者关键字的某个线性函数值作为哈希地址,即H(Key)=Key或者H(Key)=a*Key+b(a,b为整数),这种散列函数也叫做自身函数.如果H(Key)的哈希地址上已经有值了,那么就往下一个位置找,知道找到H(Key)的位置没有值了就把元素放进去.
数字分析法
分析一组数据,比如一组员工的出生年月,这时我们发现出生年月的前几位数字一般都相同,因此,出现冲突的概率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果利用后面的几位数字来构造散列地址,则冲突的几率则会明显降低.因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址.
平方取中法
取关键字平方后的中间几位作为散列地址.一个数的平方值的中间几位和数的每一位都有关。因此,有平方取中法得到的哈希地址同关键字的每一位都有关,是的哈希地址具有较好的分散性。
折叠法
折叠法即将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和…….
除留余数法
取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址.
使用hash function会带来一个问题:不同元素可能会被映射到相同的位置。这便是所谓的“碰撞(collision)”问题。解决的办法有很多种,包括线性探测(linear probing),二次探测(quadratic probing),开链(seperate chaining)…等做法。
哈希冲突的处理方法
线性探测法的地址增量di = 1, 2, … , m-1,其中,i为探测次数。该方法一次探测下一个地址,知道有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。
线性探测容易产生“聚集”现象。当表中的第i、i+1、i+2的位置上已经存储某些关键字,则下一次哈希地址为i、i+1、i+2、i+3的关键字都将企图填入到i+3的位置上,这种多个哈希地址不同的关键字争夺同一个后继哈希地址的现象称为“聚集”。聚集对查找效率有很大影响。
二次探测法的地址增量序列为 di = 12, -12, 22, -22,… , q2, -q2 (q <= m/2)。二次探测能有效避免“聚集”现象,但是不能够探测到哈希表上所有的存储单元,但是至少能够探测到一半。
链地址法特点:
拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
以上参考: https://blog.csdn.net/u011080472/article/details/51177412
SGI的hash table正是以开链(seperate chaining)实现。
节点定义如下:
template<typename T>
struct __hashtable_node{
T val;
__hashtable_node* next;
};
注意:bucket所维护的linked list ,并不是采用STL的list,而是自行维护。
hashtable的核心成员定义如下:
template<typename Key, typename Value, typename HashFun>
class hashtable{
public:
typedef size_t size_type;
typedef HashFun hasher;
typedef Value value_type;
typedef Key key_type;
public:
typedef __hashtable_node<value_type> node;
// 分配桶节点,分配器
typedef allocator<node> nodeAllocator;
// 成员就是vector维护的node*数组
std::vector<node*> buckets;
size_type num_elements;
};
下面给出的iterator和侯捷先生的书,有不同,我认为迭代器关联hashtable是没有必要的。
template <typename Value>
struct __hashtable_iterator{
typedef Value value_type;
typedef Value& reference;
typedef __hashtable_node<value_type> node;
typedef node* pointer;
typedef __hashtable_iterator<value_type> self;
// 成员
node* cur;
reference operator*()const {
return cur->val;}
pointer operator->()const{
return &(operator*());}
// 返回的是引用
self& operator++(){
cur = cur->next;
return *this;
}
// 返回的非引用
self operator++(int){
self tmp = *this;
cur = cur->next;
return tmp;
}
};
在看构造函数,之前先看下,buckets表格大小的计算问题。
虽然开链法并不要求表的大小(buckets)必须也质数,但是SGI STL仍然以质数来设计表格大小。 将28个质数(逐渐呈现大约两倍的关系)计算好,以备随时访问。同时提供一个函数,用来查询在这28个质数之中,”最接近某数,并大于某数” 的质数。
static const int __stl_num_primes = 28;
static const unsigned long __stl__prime_arr[__stl_num_primes] =
{
5, 53, 97, 193, 389,
769, 1543, 3079, 6151, 12289,
24593, 49157, 98317, 196613, 393241,
786433, 1572869, 3145739, 6291469, 12582917,
25165843, 50331653, 100663319, 201326611, 402653189,
805306457, 1610612741, 3221225473, 4294967291
};
// 找出上述28个质数之中,最接近并大于 n 的那个质数
inline unsigned long __stl_next_prime(unsigned long n){
const unsigned long* first = __stl__prime_arr;
const unsigned long* last = __stl__prime_arr + __stl_num_primes;
const unsigned long* pos = std::lower_bound(first, last, n);
return pos == last ? *(last - 1): *pos;
}
假设现在,创建含有50个bucket节点的hashtable:
hashtable<int, int, std::hash<int>> hash_table(50);
hashtable会返回53个节点(50的下一个质数是53)的bucket 数组:
插入6个元素后,hash table的状态如下:
构造函数如下:
template<typename Key, typename Value, typename HashFun>
hashtable<Key, Value, HashFun>::hashtable(size_type n){
initialize_buckets(n);
}
template<typename Key, typename Value, typename HashFun>
void hashtable<Key, Value, HashFun>::initialize_buckets(size_type n) {
// 例如传入 50 则返回 53
const size_type n_buckets = next_size(n);
buckets.reserve(n_buckets);
buckets.insert(buckets.end(), n_buckets, nullptr);
num_elements = 0;
}
下面重点是元素的插入,类型与红黑树有unique插入,以及equal插入:
template<typename Key, typename Value, typename HashFun>
std::pair<iterator, bool> hashtable<Key, Value, HashFun>::insert_unique(const value_type& obj){
// 判断是否需要重建表格,入需要则扩充
resize(num_elements + 1);
insert_unique_noresize(obj);
}
template<typename Key, typename Value, typename HashFun>
void hashtable<Key, Value, HashFun>::resize(const size_type n){
// 根据 n 决定是否重新建立表格
// 先省略......
}
可以插入重复的数据:
std::pair<iterator, bool> insert_equal(const value_type& obj);
// 根据 obj 定位到哪个buckets数组
size_type bkt_num(const value_type& obj, size_t n){
return btk_num_key(get_key(obj), n);
}
key_type& get_key(const value_type& obj){
// 这里面省略了从 obj 获取 key
return key_type();
}
size_type btk_num_key(const key_type& key, size_type n){
return std::hash(key) % n;
}
template<typename Key, typename Value, typename HashFun>
void hashtable<Key, Value, HashFun>::clear(){
for (size_type i = 0; i < buckets.size(); ++i) {
node* cur = buckets[i];
// 情况每个bucket的list
while (cur){
node* next = cur->next;
dealloc_dtor_node(cur);
cur = next;
}
buckets[i] = 0;
}
num_elements = 0; // 令总的个数为 0
// 注意, buckets vector并未释放掉空间,人保留原有的
}
find查询函数:
template<typename Key, typename Value, typename HashFun>
iterator hashtable<Key, Value, HashFun>::find(const key_type& key){
// 根据 obj 定位到哪个buckets数组
size_type idx = bkt_num(key, num_elements);
// 遍历查找bucket中的list
node* it = buckets[idx];
while (it++){
if (get_key(it->val) != key)
break;
}
return iterator(it);
}
链表中的节点的配置、构造、与析构。
// 产生(配置并构造)一个节点, 首先分配内存, 然后进行构造
node* alloc_ctor_node(const value_type& value){
node* tmp = nodeAllocator::allocate(); // 配置空间
construct(tmp, value); // 构造节点
return tmp;
}
// 析构结点元素, 并释放内存
void dealloc_dtor_node(node* p){
destroy(&p->val);
nodeAllocator::destroy(p);
}
//Cg 3.1 Toolkit Documentation 补充bitCount- return the number of bits set in a bitfield.bitfieldExtract- return an extracted range of bits from a bitfield.bitfieldInsert- returns an extrac...
#include "base/mutex.h"Mutex::Mutex() { pthread_mutex_init(&mutex_, NULL); }Mutex::~Mutex() { pthread_mutex_destroy(&mutex_); }void Mutex::Lock() { pthread_mutex_lock(&mutex_); }void Mutex::Unlock() { pthread_mutex_unlock(&mutex_);
http://blog.csdn.net/nero_2012/article/details/7550436转载于:https://www.cnblogs.com/yidijimao/p/5709829.html
1、Import-General-Project from Folder or Archive2、选择需要导入的项目(这里是一个多项目,选择主项目导入就行)3、选中后finish就行了。转载于:https://www.cnblogs.com/tt9527/p/7145982.html...
我的公众号「码农之屋」(id: Spider1818),分享的内容包括但不限于 Linux、网络、云计算虚拟化、容器Docker、OpenStack、Kubernetes、SDN、OVS、DPDK、Go、Python、C/C++编程技术等内容,欢迎大家关注。1.OOP中唯一关系的是对象的接口是什么,就像计算机的销售商她不管电源内部结构是怎样的,他只关系能否给你提供电就行了,也就...
1舞伴配對系统实训报告舞伴配对系统本题目设计目的是训练本人的基本编程能力,了解数据结构C++实现系统的开发流程,掌握数据结构和熟悉C++语言的面向对象各种基本操作。本程序中涉及结构体、单链表、类等方面的知识。通过本程序的训练,使本人能对C++语言的类操作有一个更深刻的了解,为进一步开发出高质量的有关数据结构方面系统打下坚实的基础。1、问题定义【内容与要求】假设在周末舞会上,男士们和女士们进入舞厅时...
爬取某页面的数据时,在我本机环境进行请求,返回的结果是正常的,即不乱码,但是把代码拷贝到其他电脑运行,返回的结果就是乱码了。如下:我本机的请求结果:其他电脑运行的结果:百度了好久,都是说返回结果中文乱码的解决方案,都没有把问题解决(原来是自己问题点没有搜索对,毕竟当时自己也想不到)。后面在技术群中请教大佬后,才知道是headers请求头的Accept-Encoding设置值的问题,即Accept-Encoding='gzip,deflate,br’。因为代码都一样,在自己本机运行都正常,所以压根
第一步:.yml文件添加配置:testTask: doTask: cron: 0 6 20 ? * * 第二步:新建配置文件spring-task.xml:&lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;beans xmlns="http://www.springframework.org/schema/...
极乐技术周报(第二十六期)摘要:1. 十个免费的web前端开发工具;2. 美团点评 Java 后端新人第一年总结&面试经验;3. 我们来看看尤大神谈Vue.js;4.如何基于 PHP-X 快速开发一个 PHP 扩展;5.Java进阶之路 — 从初级 …某程序员对书法十分感兴趣,退休后决定在这方面有所建树。于是花重金购买了上等的文房四宝。一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲
java实现与运算、或运算、异或运算、取反运算、无符号左右移运算、有符号左右移运算
StringBuilder stringBuilder = new StringBuilder();stringBuilder.append(person.getName()) .append("在") .append(DateTimes.toLocalDate(effected_date)) .append("从") .append(person.getOrg_name()) .append("离开");...
解析法实现一元线性回归代码:#加载样本数据x=[137.97,104.50,100.00,124.32,79.20,99.00,124.00,114.00,106.69,138.05,53.75,46.91,68.00,63.02,81.26,86.21]y=[145.00,110.00,93.00,116.00,65.32,104.00,118.00,91.00,62.00,133.00,51.00,45.00,78.50,69.65,75.69,95.30]meanX=sum(x)/len(x