一.什么是哈希表

哈希表(Hash Table),也叫散列表,是一种根据关键码值(Key)直接访问记录的数据结构。在理想情况下,哈希表进行数据查找、插入和删除操作的时间复杂度接近 O(1),是目前计算机世界中查找速度最快的结构之一,当然其也存在缺点:空间利用率不高。

二.哈希表的核心原理

1.哈希函数

哈希函数是哈希表的核心,它的作用是把任意长度的输入(Key)转换成固定的数组下标。

哈希函数有很多不同的构造方法,但核心都是将输入的key尽量均匀的地“散列”到整个数组的各个位置上,减少哈希冲突的可能。

最常见的哈希函数是除留余数法:其原理是:index = key % p(这里的p简单的话可以取数组的长度,但在实际应用中,p 通常不等于数组长度 m,而是取小于或等于 m 的最大质数(素数)。)

2.哈希冲突

上面我们说到,哈希函数的一个重要作用就是减少哈希冲突的可能,那么什么是哈希冲突呢?

我们拿最常见的除留余数法来说,由于我们使用的是相除然后取余数的做法,假设我们找到的p为5,我们需要存储6和11两个数字,按照除留余数法,两个结果都取1,那么这两个数字都应该存储在数组的1号位置,但是毫无疑问,我们数组的1号位置只能存储一个元素,那么这两个数字就会发生冲突,当然如果存储的数据量大于数组的长度,冲突就 100% 会发生。

总结一下就是由于数组的长度是有限的,当不同的键(Key)通过哈希函数计算后,得到了相同的数组下标时,就会发生“哈希冲突”(Hash Collision)。

3.哈希冲突的解决方法

目前主流的解决方案有两种:

(1)链地址法(拉链法)

原理:数组的每个位置不直接存储数据,而是挂着一个链表(或红黑树)。当发生冲突时,直接把新数据添加到对应位置的链表末尾即可。
特点:实现简单,删除方便,是目前最主流的做法(C++ 的 unordered_map 就是采用的此法)。

(2)开放寻址法

原理:所有数据都必须存在数组内部。如果算出的位置被占了,就按照某种规则(比如往后挪一位、跳着挪等)去寻找下一个空闲的位置。

这里的挪位规则也存在很多:最简单的是线性探测,就是发生哈希冲突后,向后挪一位,但是这种做法的性能并不好,所以我们通常采用二次探测,向后面挪1²、2²、3²...位。

特点:没有额外的指针开销,内存连续,缓存友好;但删除数据比较麻烦,且数组容易存满。

4.减少哈希冲突的方法

当处理大量数据时,哈希冲突通常无法避免,但是我们能够通过适当的操作尽量减少哈希冲突发生的概率,核心操作有两个。

(1)选择合适的哈希函数

(2)合理的控制动态扩容

为了合理的控制动态扩容,我们定义了负载因子(Load Factor),其是衡量哈希表“拥挤程度”的指标(已存元素数 / 数组总长度),负载因子越大,冲突概率越高,所以我们通常会设定一个阈值(一般为0.75),当元素数量超过阈值时,哈希表会自动创建一个更大的数组(通常是翻倍,更好的做法是按照一个素数标扩容),并将所有旧数据重新计算位置搬运过去。这能有效降低负载因子,从根本上减少后续插入时的冲突概率。

三.代码实现

1.基于开放寻址法线性探测的哈希表

//桶的状态
enum State
{
	STATE_UNUSE,//桶从未被使用过
	STATE_USING,//桶正在被使用
	STATE_DEL//桶元素被删除了

};

//桶的类型
struct Bucket
{
	Bucket(int key = 0, State state = STATE_UNUSE)
		:m_key(key)
		, m_state(state)
	{
	}
	int m_key;//存储的元素
	State m_state;//桶的状态
};

//线性探测哈希表
class HashTable
{
public:
	HashTable(int size = prime[0], double loadFactor = 0.75)
		: m_loadFactor(loadFactor)
		, m_useBucketnum(0)
		, primeIdx(0)
	{

		if (size != prime[0])
		{
			for (; primeIdx < PRIME_SIZE; primeIdx++)
			{
				if (prime[primeIdx] > size) {
					size = prime[primeIdx];
					break;
				}
				if (primeIdx == PRIME_SIZE)
				{
					primeIdx--;
				}
			}
		}
		m_tableSize = prime[primeIdx];
		m_table = new Bucket[m_tableSize];

	}
	~HashTable()
	{
		delete[] m_table;
		m_table = nullptr;
	}

public:
	//插入元素(key可重复)
	bool insert(int key)
	{
		//扩容
		double factor = m_useBucketnum*1.0 / m_tableSize;
		if (factor >= m_loadFactor)
		{
			expend();
		}
		int idx = key % m_tableSize;
		int i = idx;
		do
		{
			if (m_table[i].m_state!= STATE_USING)
			{
				m_table[i].m_state = STATE_USING;
				m_table[i].m_key = key;
				m_useBucketnum++;
				return true;
			}
			i = (i + 1) % m_tableSize;
		} while (i!=idx);
		return false;
	}

	//删除(同值的全部元素)
	bool erase(int key)
	{
		if (m_useBucketnum == 0)
		{
			throw "HashTable is empty";
		}
		else
		{
			bool flag = false;
			int idx = key % m_tableSize;
			int i = idx;
			do
			{
				if (m_table[i].m_state== STATE_USING&&m_table[i].m_key==key)
				{
					m_table[i].m_state = STATE_DEL;
					m_useBucketnum--;
					flag = true;
				}
				i = (i + 1) % m_tableSize;
			} while (i!=idx&&m_table[i].m_state!= STATE_UNUSE);
			return flag;
		}
	}

	//查找
	bool find(int key)
	{
		if(m_tableSize==0)
			throw "HashTable is empty";
		else
		{
			int idx = key % m_tableSize;
			int i = idx;
			do
			{
				if (m_table[i].m_state == STATE_USING && m_table[i].m_key == key)
				{
					return true;


				}
				i = (i + 1) % m_tableSize;
			} while (i != idx && m_table[i].m_state != STATE_UNUSE);
			return false;
		}
		
	}

private:
	//扩容
	void expend()
	{
		primeIdx++;
		if (primeIdx == PRIME_SIZE)
		{
			throw "HashTable is too large can not expend";
		}
		else
		{
			

			Bucket* new_m_table = new Bucket[prime[primeIdx]];
			for (size_t i = 0; i < m_tableSize; i++)
			{
				if (m_table[i].m_state== STATE_USING)
				{
					int idx =m_table[i].m_key % prime[primeIdx];
					int k = idx;
					do
					{
						if (new_m_table[k].m_state != STATE_USING)
						{
							new_m_table[k].m_key = m_table[i].m_key;
							new_m_table[k].m_state = STATE_USING;
							break;
						}
						k = (k + 1) % prime[primeIdx];
					} while (k!=idx);
				}
			}
			m_tableSize = prime[primeIdx];
			delete[] m_table;
			m_table = nullptr;
			m_table = new_m_table;

		}
		
	}

private:
	Bucket* m_table;//指向动态开辟的哈希表
	int m_tableSize;//哈希表的长度
	int m_useBucketnum;//已经使用的桶的个数
	double m_loadFactor;//装载因子

	static const int PRIME_SIZE = 10;//素数表元数个数
	static  int prime[PRIME_SIZE];//素数表
	int primeIdx;//当前使用的素数表中的下标

};

2.基于链地址法实现的哈希表

class HashTable
{
public:

	HashTable(int size = prime[0], double  loadFactor = 0.75)
		:m_useBucketnum(0)
		, m_loadFactor(loadFactor)
		, primeIdx(0)
	{
		if (size != prime[0])
		{
			for (; primeIdx < PRIME_SIZE; primeIdx++)
			{
				if (prime[primeIdx] > size) {
					size = prime[primeIdx];
					break;
				}
				if (primeIdx == PRIME_SIZE)
				{
					primeIdx--;
				}
			}
		}
		m_table.resize(size);
	}

	//增加元素(key不可重复)
	void insert(int key)
	{
		double factor = m_useBucketnum * 1.0 / m_table.size();
		if (factor >= m_loadFactor)
		{
			expend();
		}
		int idx = key % m_table.size();
		if (m_table[idx].empty())
		{
			m_useBucketnum++;
			m_table[idx].emplace_front(key);
		}
		else
		{
			auto it = ::find(m_table[idx].begin(), m_table[idx].end(), key);
			if (it == m_table[idx].end())
			{
				m_table[idx].emplace_front(key);
			}
		}
	}

	//删除
	void erase(int key)
	{
		if (m_useBucketnum == 0)
			throw "HashTable is empty";
		else
		{
			int idx = key % m_table.size();
			auto it = ::find(m_table[idx].begin(), m_table[idx].end(), key);
			if (it != m_table[idx].end())
			{
				m_table[idx].erase(it);
				if (m_table[idx].empty())
					m_useBucketnum--;
			}
		}

	}

	//查找
	bool find(int key)
	{
		if (m_useBucketnum == 0)
			throw "HashTable is empty";
		else
		{
			int idx = key % m_table.size();

			auto it = ::find(m_table[idx].begin(), m_table[idx].end(), key);
			if (it != m_table[idx].end())
			{
				return true;
			}
			else
				return false;
		}

	}

private:
	//扩容
	void expend()
	{
		if (primeIdx + 1 == PRIME_SIZE)
		{
			throw "HashTable is too large can not expend";

		}
		else
		{
			primeIdx++;
			m_useBucketnum = 0;
			vector<list<int>>new_m_table;
			m_table.swap(new_m_table);
			m_table.resize(primeIdx);
			for (auto list:new_m_table)
			{
				for (auto key:list)
				{
					int idx = key % m_table.size();
					if (m_table[idx].empty())
					{
						m_useBucketnum++;
					}
					m_table[idx].emplace_front(key);

				}
			}
			
		}
	}

private:
	vector<list<int>>m_table;//哈希表数据结构
	int m_useBucketnum;//记录使用的桶的个数
	double m_loadFactor;//装载因子

	static const int PRIME_SIZE = 10;//素数表元数个数
	static  int prime[PRIME_SIZE];//素数表
	int primeIdx;//当前使用的素数表中的下标

};

更多推荐