前言
在前面,我们提到过了几种搜索结构的树,二叉搜索树是根据左孩子节点的值小于根节点,右孩子节点的值大于根节点而建立的,当我们把n个数据插入到二叉搜索树中,最好情况下(数据插入后是一颗完全二叉树)我们查找数据的时间复杂度为O(lg(N)),但是当我们按照一定的顺序往二叉搜索树中插入数据时,它会呈现一个线性的状态,这时候查找数据的时间复杂度为O(N).为了对这种情况进行优化,我们提出了AVL树的结构,AVL树给树的节点添加了平衡因子bf(里面存储的是这个节点右子树减去左子树的差值),当|bf|<=1时,我们不需要对树的结构进行调整,当插入一个新的节点导致上面的节点的|bf|=2时,我们就需要从这个新插入的节点开始进行调整,是平衡因子恢复到|bf|<=1的情况。随后,我们又提出了红黑树的结构,它通过一系列的规定是这棵树中最长的路径仅仅比最短的路劲长1倍,来保证树的平衡,我们发现AVL树和红黑树都具有一种自适应的属性,即当插入一个新节点导致树的平衡遭到破坏,会自己进行调整使这颗树保持平衡,从而减小这颗树的高度来使这颗树查找数据的时间复杂度尽可能的小。
今天,我们要介绍一种新的数据结构,它查找数据的时间复杂度一定情况下近似于O(C)。
1:什么是哈希表?
哈希表是根据关键字key而直接访问其在内存中存储位置的结构,它通过一个关键值得函数将所需要的数据映射到表中的位置来访问数据。这个映射函数叫做散列函数,存放记录的数组叫做散列表或者哈希表,这种数据结构是一种消耗空间来换取查找数据时间的结构。
2:构建哈希表的几种方法
1.直接定址法
直接定址法是以数据元素关键字k本身或它的线性函数作为它的哈希地址,即:H(k)=k 或 H(k)=a×k+b ; (其中a,b为常数)
2.折叠法
所谓折叠法是将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位),这方法称为折叠法。这种方法适用于关键字位数较多,而且关键字中每一位上数字分布大致均匀的情况。
3.除留余数法
取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址,即设定哈希函数为 Hash(key)=key % p (p≤m),其中,除数p称作模。
3:哈希冲突
如图是我们用除留余数法建立的哈希表,下面是我们所要往表中插入的元素,当我们插入51时,51会被放进表中51的内存中,当我们插入102时,经过除留余数函数,映射到表中的地址也是51,而这时51已经放入了元素,不同的Key 值经过哈希函数Has h(Key )处理以后可能产生相同的值哈希地址,我们称这种情况为哈希冲突。任意的散列函数都不能避免产生冲突。。
4:哈希冲突的解决方法
1:线性探测
所谓的线性探测,就是在你通过映射函数将key映射到哈希表的直接地址时,如果该直接地址里面的内存已经被占了,就检查这个直接地址的下一个地址是否存放的有数据,如果没有就讲key放在该地址的内存中,如果有就继续向后探测。
2:二次探测
二次探测与线性探测的区别就是每次他会向后探测i^2个单位(i为映射函数出来的直接地址相同的数据的个数)。
代码
哈希冲突的方式:线性探测
哈希映射函数:为除留余数法
#pragma once
#include <iostream>
using namespace std;
#include <vector>
#include <string>
enum STATUS
{
EXIST,
DELETE,
EMPTY,
};
template<class K,class V>
struct KVNoode
{
K _Key;
V _Value;
STATUS _status;
KVNoode(const K& key=0,const V& value=0)
:_Key(key)
, _Value(value)
, _status(EMPTY)
{}
};
template<class K,class V,class Hash=HashFunc<T>>
class HashTable
{
typedef KVNoode<K, V> Node;
/*typedef HashTable<K, V> HashTable;*/
public:
HashTable()
:_size(0)
{}
bool Insert(const K& key, const V& value)
{
_CheckSize();
int Key = Hash(key); //字符串的hash算法
size_t index = HashFunc(Key);
while (_tables[index]._status == EXIST)
{
index++;
if (index == _tables.size())
{
index = 0;
}
if (_tables[index]._Key == key)
{
return false;
}
}
_tables[index]._Key = key;
_tables[index]._Value = value;
_tables[index]._status = EXIST;
_size++;
return true;
}
Node* Find(const K& key)
{
unsigned long index = BKDRHash(key); //找到查找元素的下标
if (_tables[index]._Key != key) //如果直接地址没有该元素,则向后进行线性探测
{
while (_tables[index]._status == EXIST)
{
if (_tables[index]._Key == key)
return &_tables[index];
index++;
}
return NULL;
}
if (_tables[index]._status ==EXIST)
return &_tables[index];
else
return NULL;
}
//返回哈希表中的直接地址的下标
size_t HashFunc(const int& key)
{
return key%_tables.size();
}
size_t BKDRHash(const char* str)
{
unsigned int seed = 131;
unsigned int hash = 0;
while (*str)
{
hash = hash*seed + (*str++);
}
return (hash & 0x7fffffff);
}
bool Remove(const K& key)
{
//懒删除法删除节点
if (IsEmpty())
return false;
Node* ret = Find(key);
if (ret == NULL)
return false;
ret->_status = DELETE;
return true;
}
protected:
void _CheckSize()
{
//扩容条件
if (_tables.size() == 0 || ((_size * 10) / _tables.size()) >= 8)
{
size_t newSize = _GetSize(_tables.size());
HashTable ht;
ht._tables.resize(newSize);
for (unsigned long i = 0; i <_tables.size(); i++)
{
ht.Insert(_tables[i]._Key,_tables[i]._Value);//
}
Swap(ht);
}
}
void Swap(HashTable& ht)
{
_tables.swap(ht._tables);
swap(this->_size, ht._size);
}
bool IsEmpty()
{
if (_tables.size() == 0)
return true;
return false;
}
unsigned long _GetSize(unsigned long PrimeSize)
{
//使用素数表对齐做哈希表的容量,降低哈希冲突
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 (PrimeSize<_PrimeList[i])
{
return _PrimeList[i];
}
return _PrimeList[_PrimeSize - 1];
}
return _PrimeList[_PrimeSize - 1];
}
protected:
vector<Node> _tables;
unsigned long _size;
};