一文搞定哈希表

哈希表(Hash Table)

概念

不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
向该结构中插入元素时:根据待插入元素的关键码,以此函数计算出该元素的存储位置。
在该结构中搜索元素时:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中寻找此位置的元素与欲查找的元素的关键码比较,若相等则代表搜索成功

上述方式即为哈希方法,此方法中用到的函数称为“哈希函数”,构造出来的这种结构称为“哈希表”(Hash Table)。

在这里插入图片描述

映射关系

哈希函数
把元素/键值映射到空间的一个位置
特点:

  1. 映射的位置范围要小于等于空间范围
  2. 映射的位置要尽量均匀
  3. 映射关系尽量简单
    一些常用的哈希函数:
    1.除留余数法:元素/键值 % 空间大小(通用方法)
    2.直接定址法:线性函数,A*X(元素//键值)+ B(只适合范围比较紧凑的数据,例如字符)

哈希冲突

不同的数据,经过哈希函数,映射到了同一个位置

注意:只要空间小于数据范围,那么就必然会出现哈希冲突

解决哈希冲突:

  1. 闭散列(开放定址法)(出现哈希冲突,无法保证效率)
    线性探测,二次探测
    线性探测
    插入
    a. 通过哈希函数计算哈希位置
    b. 如果当前位置为空,则进行插入操作
    c. 如果当前位置不为空,则从该位置开始,向后找到第一个为空的位置,再插入
    查找
    a. 通过哈希函数计算哈希位置
    b. 查看当前位置的数据是否和查找的数据相同,相同则查找成功并结束
    c. 如果不相同,则从当前位置开始向后查找,直到找到数据或者走到了空的位置,则查找结束
    删除(不是真正的删除)
    a. 先进行查找操作
    b. 如果找到了需要删除的数据,把该数据所在位置标记为删除状态
    二次探测
    与线性探测基本相同,区别在于,每次偏移的长度是上一次的平方,可以减少数据扎堆出现的情况,从而减少哈希冲突出现的情况

闭散列代码具体如下

//定义空间位置的三种状态
enum State {
	Empty,
	Exist,
	Delete
};

template<class K,class V>
//定义哈希节点
struct HashNode{
	pair<K, V> _val;
	State _state;

	HashNode(const pair<K, V>& val = pair<K, V>()) 
		:_val(val)
		,_state(Empty)
	{}
};

template<class K,class V>
//定义哈希表
class HashTable {
public:
	HashTable(size_t n = 10) {
		_table.resize(n);
		_size = 0;
	}

	//插入
	bool insert(const pair<K, V>& val) {
		//检查容量
		checkCapacity();
		//计算哈希位置
		int id = val.first%_table.size();
		//检查该位置的状态,看是否可用,以及检查要插入的数据是否已经存在
		while (_table[id]._state == Exist) {
			if (_table[id]._val.first == val.first) {
				return false;
			}
			id++;
			//走到空间的结尾,回到空间的开始位置
			if (id == _table.size()) {
				id = 0;
			}
		}
		//找到了合适位置
		_table[id]._val = val;
		_table[id]._state = Exist;
		_size++;
		return true;
	}

	//检查容量以及扩容
	void checkCapacity() {
		//当空间被占百分之80的时候进行扩容
		if (_size * 10 / _table.size() >= 8) {
			HashTable Newht(2 * _table.size());

			//旧表元素要重新插入到扩容之后的新表之中
			for (int i = 0; i < _table.size(); i++) {
				if (_table[i]._state == Exist) {
					Newht.insert(_table[i]._val);
				}
			}
			//新表替换旧表,完成扩容
			swap(_table, Newht._table);
		}
	}

	//查找
	HashNode<K, V>* find(const K& key) {
		int id = key % _table.size();
		while (_table[id]._state != Empty) {
			if (id == _table.size()) {
				id = 0;
			}
			if (_table[id]._state == Exist && _table[id]._val.first == key) {
				cout << "查找成功,K值为" << key << "的数据在下标为" << id << "的位置" << endl;
				return &_table[id];
			}
			else {
				id++;
			}
		}
		cout << "没有找到K值为" << key << "的数据" << endl;
		return nullptr;
	}

	//删除
	bool erase(const K& key) {
		HashNode<K, V>* result = nullptr;
		int id = key % _table.size();
		while (_table[id]._state != Empty) {
			if (id == _table.size()) {
				id = 0;
			}
			if (_table[id]._state == Exist && _table[id]._val.first == key) {
				 result = &_table[id];
				 break;
			}
			else {
				id++;
			}
		}
		if (result) {
			result->_state = Delete;
			_size--;
			return true;
		}
		return false;
	}
private:
	vector<HashNode<K, V>> _table;
	size_t _size;
};
  1. 开散列(拉链法,哈希桶)
    哈希桶
    每个空间节点下面都会挂一个单链表或者红黑树,如下图
    在这里插入图片描述
    开散列代码具体如下
template<class K>
struct KeyofValue {
	const K& operator()(const K& key) {
		return key;
	}
};

//开散列,哈希桶方法,指针数组 + 单链表
template<class V>
struct HashNode {
	V _val;
	HashNode<V>* _next;
	
	HashNode(const V& val = V()) 
		:_val(val)
		,_next(nullptr)
	{}
};

template<class K,class V,class KeyofValue>
class HashTable {
public:
	typedef HashNode<V> Node;

	//插入(要么是K,V键值对,要么是V)
	bool insert(const V& val) {
		//检查容量及扩容
		checkCapacity();

		//计算位置
		KeyofValue kov;
		int id = kov(val) % _table.size();

		//检查kov是否已经存在
		Node* cur = _table[id];
		while (cur) {
			if (kov(cur->_val) == kov(val)) {
				return false;
			}
			cur = cur->_next;
		}
		//插入:使用头插
		cur = new Node(val);
		cur->_next = _table[id];
		_table[id] = cur;

		_size++;
		return true;
	}
	//检查容量及扩容
	void checkCapacity() {
		if (_size == _table.size()) {
			size_t newSize = _size == 0 ? 5 : 2 * _size;
			vector<Node*> Newht;
			Newht.resize(newSize);
			KeyofValue kov;
			//遍历旧表中的非空单链表
			for (int i = 0; i < _table.size(); i++) {
				Node* cur = _table[i];
				//遍历当前单链表
				while (cur) {
					//记录旧表中的下一个元素
					Node* next = cur->_next;
					//计算在新表中的位置
					int Newid = kov(cur->_val%Newht.size());

					//头插
					cur->_next = Newht[Newid];
					Newht[Newid] = cur;

					//处理下一个元素
					cur = next;
				}
				_table[i] = nullptr;
			}
			_table.swap(Newht);
		}
	}
	//查找
	Node* find(const K& key) {
		if (_table.size() == 0) {
			return nullptr;
		}
		int id = key % _table.size();
		Node* cur = _table[id];
		KeyofValue kov;
		while (cur) {
			if (kov(cur->_val) == key) {
				return cur;
			}
			cur = cur->_next;
		}
		return nullptr;
	}
	//删除
	bool erase(const K& key) {
		if (_table.size() == 0) {
			return false;
		}
		int id = key % _table.size();
		Node* cur = _table[id];
		Node* prev = nullptr;
		KeyofValue kov;
		while (cur) {
			if (kov(cur->_val) == key) {
				//如果删除的是头结点,直接更新头结点
				if (prev == nullptr) {
					_table[id] = cur->_next;
				}
				else {
					prev->_next = cur->_next;
				}
				delete cur;
				_size--;
				return true;
			}
			prev = cur;
			cur = cur->_next;
		}
		return false;
	}
private:
	vector<Node*> _table;
	size_t _size = 0;
};

负载因子:实际存放的元素个数/空间大小

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值