哈希表
来源:互联网 发布:java工程师培训价格 编辑:程序博客网 时间:2024/06/16 05:23
数据结构我们了解好几个了,其中写的最多的就是顺序表和链表,在这个阶段,还模拟实现了库中的vector和list,随后我们又了解了队列和栈这些都是线性的数据结构,所以想要查找一个元素的话,这些的时间复杂度都是O(N)。
除了线性结构还有树形结构,在这里我们从二叉树开始,先后了解了二叉树的实现以及线索化,不过我们发现一个问题,那就是如果二叉树写成单支的情况,那就是线性结构了,所以我们又了解了平衡二叉树、红黑树。我们实现这些代码的时候,最常用的就是递归,但是如果树的深度很大的时候,递归的深度也会很大,所以要开辟很多的栈空间,所以,我们又实现了非递归的形式,最后我们还用了B-树。
树形结构的时间复杂度为log以2为底N
不过,要是对这个时间复杂度还是不满意的话,那么还能继续减小吗?
答案是可以的,用哈希表就可以,将时间复杂度减到O(1)
今天我们就来看一下,哈希表是什么以及怎么实现哈希表
哈希表又称为散列表,是用Key直接访问内存位置的数据结构,它的时间复杂度低的原理是使用空间换取时间。通过开辟多个内存空间来实现高效率的查找。
实现哈希表的关键是哈希函数的选择,选择哈希函数有几个点需要注意:
1、哈希函数的定义域必须包含所有的关键码,如果哈希表允许有m个地址,那么哈希函数的值域必须在0~(m-1)之间
2、哈希函数计算出来的地址最好均匀分布在空间中
3.哈希函数应该比较简单,否则,只计算哈希函数就需要大量的时间,不划算
哈希函数有很多,我们常用的也就两个,一个是直接定址法,一个是除留余数法:
1、直接定址法:
取关键字的某个线性函数为散列地址,Hash(Key)=Key 或 Hash(Key)= A*Key+B。
利用数组下标可以很好的将对应的数据存入哈希表对应的位置。例如:在一个字符串中找出第一次只出现一次的字符,字符串为abcdabcdefg,需要找到e,利用下标统计可以很好地解决这个问题,对于这个问题,你必须开辟对应的256个空间。
不过,如果需要查找的数中出现了一个特别大的数(1000000),你必须要开辟1000000个空间,会造成大量空间的浪费。所以,这个方法只适合查找的数据较小,而且连续的情况。
2、除留余数法:
取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地。Hash(Key)= Key % P。
由于“直接定址法”的缺陷,于是下面引入“除留余数法”,该方法提高的空间的利用率,但不同的Key值经过哈希函数Hash(Key)处理以后可能产生相同的值哈希地址,我们称这种情况为哈希冲突。
任意的散列函数都不能避免产生冲突。
那么如何处理哈希冲突呢?
1、线性探测
2、二次探测
#include<iostream>using namespace std;#include<vector>enum State{ EMPTY, EXIST, DELETE};template<class K, class V>struct KVNode{ pair<K, V> _kv; State _s;//保存某个位置的状态信息 KVNode() : _s(EMPTY) {}};template<typename K>struct _GetK{ size_t operator()(const K& key) { return key; }};//获取字符串keystruct _GetStrK{ static size_t BKDRHash(const char * str) { unsigned int seed = 131; // 31 131 1313 13131 131313 unsigned int hash = 0; while (*str) { hash = hash * seed + (*str++); } return (hash & 0x7FFFFFFF); } size_t operator()(const string& str) { return BKDRHash(str.c_str()); }};template<class K, class V, typename GetK=_GetK<K>>class HashTable{public: HashTable(size_t size = 12) : _size(0) { _table.resize(GetPrime(size)); } //// 线性探测 //bool Insert(const K& key, const V& value) //{ // _CheckTable();//判断哈希表是不是满的 // int index = _HashFunc(key); // while (EXIST == _table[index]._s) // { // if (key != _table[index]._kv.first)//要插入的值,在原来的表中已存在 // { // return false; // } // ++index; // if (index == _table.size())//遍历到末尾,需要将索引置开头 // { // index = 0; // } // } // //找到合适的位置,插入 // _table[index]._kv.first = key; // _table[index]._kv.second = value; // _table[index]._s = EXIST; // ++_size; //return true; //} //二次探测 bool Insert(const K& key, const V& value) { _CheckTable();//判断哈希表是不是满的 int i = 0; int index = _HashFunc(key); //如果存在哈希冲突,即哈希地址上不为空,但是元素不在那个位置 while (_table[index]._s == EXIST) { if (_table[index]._kv.first == key) return false; //二次探测 index = HashFunc2(index, ++i); if (index > _table.size()) index -= _table.size(); } //找到合适的位置,插入 _table[index]._kv.first = key; _table[index]._kv.second = value; _table[index]._s = EXIST; ++_size; return true; } pair<KVNode<K, V>*, bool> Find(const K& key,const V& value) { size_t index = _HashFunc(key); while (EMPTY != _table[index]._s) { if (key == _table[index]._kv.first) { //如果那个元素的状态是存在,而不是已删除的,返回true if (EXIST == _table[index]._s) return make_pair(&_table[index],true); else return make_pair((KVNode<K, V>* )0, false); } else index++; //如果遍历到末尾,那么重头开始 if (index == _table.size()) { index = 0; } } return make_pair((KVNode<K, V>*)0, false); } bool Remove(const K& key) { //如果哈希表为空,返回false if (_size == 0) { return false; } //哈希函数找到带删除的元素的地址 size_t index = _HashFunc(key); int begin = index;//标记其实位置 while (_table[index]._s != EMPTY) { //如果这个元素找到了,并且状态是存在的,那么删除 if (key == _table[index]._kv.first && _table[index]._s==EXIST) { _table[index]._s = DELETE; --_size; return true; } index++; //如果到末尾 if (index == _table.size()) { index = 0; } //走了一圈还没有找到,返回false if (begin == index) return false; } }private: // 线性探测处理函数 size_t _HashFunc(const K& key) { GetK getK; return getK(key)% (_table.size()); } // 二次探测处理函数 size_t HashFunc2(size_t hashAddr, size_t i) { return hashAddr + (2 * i - 1);//通过二次探测的算法优化后的结果 } void _CheckTable() { if (_size * 10 / (_table.size()) > 7) { int newSize = GetPrime(_table.size());//获取一个新的size HashTable<K, V,GetK> hash; hash._table.resize(newSize);//创建一张新的哈希表 //将原来的表中元素按照新的哈希函数计算的地址,放入新表中 for (size_t i = 0; i < _size; ++i) { if (_table[i]._s == EXIST) { hash.Insert(_table[i]._kv.first, _table[i]._kv.second); } this->Swap(hash); } } else return; } int GetPrime(int data) { const int _primesize = 28; static const unsigned long _PrimeList[_primesize] = { 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul }; for (int i = 0; i < _primesize; i++) { if (_PrimeList[i]>data)//找出比data大的素数, return _PrimeList[i]; } return _PrimeList[_primesize - 1];//如果没有找到,那么返回最大的那个素数 } void Swap(HashTable<K, V,GetK>& ht) { //_table.swap(ht._table); _table.swap(ht._table); swap(_size, ht._size); }private: std::vector<KVNode<K, V>> _table;//hashtable have _kv and status size_t _size; // 有效元素的个数};void TestHashTable(){ HashTable<int, int> ht; int array1[] = { 89, 18, 8, 58, 2, 43, 74, 9, 20 }; for (int i = 0; i < sizeof(array1) / sizeof(array1[0]); ++i) { ht.Insert(array1[i], 0); } ht.Remove(8); ht.Remove(20); HashTable<string, int, _GetStrK> ht2; char* array2[] = { "hello", "world", "what", "find", "sort", "sort" }; for (int i = 0; i < sizeof(array2) / sizeof(array2[0]); ++i) { //找带插入的元素,看是否在哈希表中 KVNode<string, int>* node = (ht2.Find(array2[i], 0)).first; if (node)//如果结已存在,那么将 node->_kv.second++; else ht2.Insert(array2[i], 0); }}int main(){ TestHashTable(); return 0;}
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- .哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- 哈希表
- MySQL索引
- 关于素数筛法的一点讨论
- 远程桌面无法复制文件解决方法
- Longest Substring Without Repeating Characters
- properties配置文件的使用
- 哈希表
- banner listview (无限轮播+ listview)
- Linux系统Oracle定时删除归档日志
- Source Insight基本使用和快捷键
- 守护进程以及fork两次的问题
- 二项堆与斐波那契堆
- ubuntu17.04插上网线没反应(设备未托管)
- perparestatement在mysql中的普通使用
- window.onload与$(document).ready()的区别