【数据结构】哈希表及其模拟实现

十年热恋 提交于 2020-03-16 18:37:19

一.哈希及其概念

  • 通过一定的哈希函数,确定元素的存储位置。搜索效率较高,为O(1)
  • 负载因子:存储元素个数/总表格长度 若大于一定值需要扩容

二.常见的哈希函数

直接定址法:hashfunc(date)=A*date+B;【A,B为常数】

  • 除留余数法:hashfunc(date)=date%capacity【capacity为表格容量】
  • 平方取中法:将数据平方后取中间三位。
  • 折叠法:例 12345 12+34+5=51

三.哈希冲突

  • 不同元素计算出的存储位置(哈希地址)相同

四.解决哈希冲突

  • 1.前期需有效减少哈希冲突的发生--------》哈希函数设计要合理
    哈希函数的值域必须在表格范围内
    哈希函数要尽可能使元素分布均匀
    哈希函数要尽可能简单

  • 2.发生哈希冲突后如何解决
    闭散列:从哈希冲突的位置,找下一个空余位置插入元素
    开散列:将哈希冲突的元素按链表的形式挂在结点位置

五.闭散列

从哈希冲突的位置,找下一个空余位置插入元素。寻找方式有两种,线性探测和二次探测。

1.线性探测

直接向后+1查找。

  • 优点:简单
  • 缺点:容易造成数据堆积。一个冲突造成很多数据都冲突。

2.二次探测

H(i)=H0+i^2; 或者H(i)=H0- i^2;

  • 优点:解决了数据堆积问题
  • 缺点:可能要查找多次

3.初始化,需设置标志位【EX,EM,DE】

DE需要的原因,此位置不可以插入元素,也不可设置为EM 。查找时遇EM就停止了,可能出现错误。

4扩容方式

1.不可以按照vector方式扩容。表格长度增加,相应的元素位置也会改变。
2.先新建哈希表,将元素一个一个重新插入到新表格。

六.开散列

将哈希冲突的元素按链表的形式挂在结点位置。每个位置叫做一个哈希桶。

1.扩容方式

1.不按照vector方式扩容,也不按照闭散列方式扩容【新建结点,空间开销大】
2.将表格结点直接插入新表格即可。

2.扩容节点

当表格中每个位置都有元素时,扩容!

七.实现代码

    1. 闭散列头文件 haxi.hpp
#pragma once
#include<vector>
#include<iostream>
#include<string>
#include"common.h"
enum state{ EMPTY,EXIST,DELETE };
template<class T>
struct Elem{
	Elem(const T& date=T())
		:_date(date)
		,_state(EMPTY)
	{}
	T _date;
	state _state;
};
//约定:哈希表格中的元素必须唯一
template<class T,class DF=DFDef<T>,bool isline=true>
class hashtable {
public:
	hashtable(size_t capacity= Getnextprime(10))
	:_size(0)
	,_total(0)
	{
		_table.resize(capacity);
	}
	bool insert(const T& date) {
		checkcapacity();
		//1.通过哈希函数,计算哈希地址
		size_t hashaddr = hashfunc(date);
		size_t i = 0;
		while (_table[hashaddr]._state != EMPTY) {
			//元素已存在
			if (_table[hashaddr]._state == EXIST && _table[hashaddr]._date == date)
				return false;
			if (isline) {
				//线性探测
				hashaddr++;
				if (hashaddr == _table.capacity()) {
					hashaddr = 0;
				}
			}
			else {
				//二次探测
				i++;
				hashaddr += 2 * i + 1;
				//方式一
				hashaddr %= _table.capacity();
			}
		}
		//插入元素
		_table[hashaddr]._date = date;
		_table[hashaddr]._state = EXIST;
		++_size;
		++_total;
		return true;
	}
	int find(const T& date) {
		size_t hashaddr = hashfunc(date);
		size_t i = 0;
		while (_table[hashaddr]._state != EMPTY) {
			if (_table[hashaddr]._state == EXIST && _table[hashaddr]._date == date) {
				return hashaddr;
			}
			if (isline) {
				//线性探测
				hashaddr++;
				if (hashaddr == _table.capacity()) {
					hashaddr = 0;
				}
			}
			else {
				//二次探测
				i++;
				hashaddr += 2 * i + 1;
				//方式一
				hashaddr %= _table.capacity();
			}
		}
		return -1;
	}
	bool erase(const T& date) {
		int pos = find(date);
		if (pos == -1) {
			return false;
		}
		_table[pos]._state = DELETE;
		_size--;
		return true;
	}
	void swap(hashtable<T, DFstr, isline>& ht) {
		_table.swap(ht._table);
		std::swap(_size, ht._size);
		std::swap(_total, ht._total);
	}
	size_t size()const {
		return _size;
	}
private:
	void checkcapacity() {
		if (_total*10 / _table.capacity() >= 7) {
			//扩容方式:将元素重新哈希函数确定位置!然后插入新空间
			//1.新创建一个哈希表
			hashtable<T, DFstr, isline> newHt(Getnextprime(_table.capacity()));
			//插入元素
			for (auto e : _table) {
				if (e._state == EXIST) {
					newHt.insert(e._date);
				}
			}
			swap(newHt);

		}
	}
	size_t hashfunc(const T& date) {
		
		return DF()(date) % _table.capacity();
	}
	std::vector<Elem<T>> _table;
	size_t _size;
	size_t _total;

};

void test2(){
	hashtable<std::string, DFstr, false> ht;
	ht.insert("1111");
	ht.insert("2222");
	ht.insert("3333");
	ht.insert("4444");
}
  • 2.开散列头文件hashbucket.hpp
#pragma once
#include"common.h"
#include<vector>
#include<iostream>
template<class T>
struct HashNode {
	HashNode(const T& date=T())
		:_pnext(nullptr)
		,_date(date){}

	HashNode<T>* _pnext;
	T _date;
};
template<class T,class DF=DFDef<T>>
class hashbucket {
	typedef HashNode<T> Node;
public:
	hashbucket(size_t capacity = 9)
		:_size(0)
	{
		_table.resize(Getnextprime(capacity));
	}
	bool insertunique(const T& date) {
		checkcapacity();
		//1.通过哈希函数计算桶号
		size_t bucketnum = hashfunc(date);
		std::cout <<date<<"  :"<< bucketnum << "\n";
		//2.检测date元素在哈希桶中是否存在
		Node *pcur = _table[bucketnum];
		while (pcur) {
			if (pcur->_date == date)
				return false;
			pcur = pcur->_pnext;
		}
		//3.插入新节点
		//采用头插法
		pcur = new Node(date);
		pcur->_pnext = _table[bucketnum];
		_table[bucketnum] = pcur;
		_size++;
		return true;
	}	
	bool insertequal(const T& date){}
	bool eraseunique(const T& date) {
		//1.寻找桶号
		size_t bucketnum = hashfunc(date);
		Node * pcur = _table[bucketnum];
		Node * ppre = nullptr;
		while (pcur) {
			if (pcur->_date == date) {
				//erase
				if (ppre == nullptr) {
					//删除的是第一个节点
					_table[bucketnum] = pcur->_pnext;
				}
				else {
					ppre->_pnext = pcur->_pnext;
				}
				delete pcur;
				_size--;
				return true;
			}
			ppre = pcur;
			pcur = pcur->_pnext;
		}
		return false;
	}
	bool eraseequal(const T& date) {}
	Node* find(const T& date)const {
		//1.通过哈希函数计算桶号
		size_t bucketnum = hashfunc(date);
		//2.检测date元素在哈希桶中是否存在
		Node *pcur = _table[bucketnum];
		while (pcur) {
			if (pcur->_date == date)
				return pcur;
			pcur = pcur->_pnext;
		}
		return nullptr;
	}
	size_t size() {
		return _size;
	}
	bool empty()const {
		return 0 == _size;
	}
	void print() {
		for (size_t bucketnum = 0; bucketnum < _table.capacity();bucketnum++) {
			std::cout << "_table[" << bucketnum << "]:";
			Node* pcur = _table[bucketnum];
			while (pcur) {
				std::cout << pcur->_date << " ---->  ";
				pcur = pcur->_pnext;
			}
			std::cout << "\n";
		}
	}
private:
	size_t hashfunc(const T& date) const{

		return DF()(date) % _table.capacity();
	}
	//如果哈希桶存储元素个数和桶的个数相同
	//原因:最佳状态:每个桶中存储一个元素
	//每个桶中都有一个元素,就会哈希冲突。
	//桶的利用率最大
	void checkcapacity() {
		size_t bucketCount = _table.capacity();
		size_t flag = 1;
		for (size_t bucketnum = 0; bucketnum < bucketCount; ++bucketnum) {
			if (_table[bucketnum] == nullptr) {
				flag = 0;
				break;
			}
		}
		if (flag==1) {
			//扩容方式
			//可以用闭散列的扩容方式,但是不太好,创建新结点,空间花销大。
			//直接将旧哈希桶中元素结点,搬移到新哈希桶
			hashbucket<int> newHt(bucketCount);
			for (size_t bucketnum = 0; bucketnum < bucketCount; ++bucketnum)
			{
				Node* pcur = _table[bucketnum];
				while (pcur)
				{
					// 将该节点从原哈希表中拆出来
					_table[bucketnum] = pcur->_pnext;

					// 将该节点插入到新哈希表中
					size_t bucketNo = newHt.hashfunc(pcur->_date);
					pcur->_pnext = newHt._table[bucketNo];
					newHt._table[bucketNo] = pcur;
					pcur = _table[bucketnum];
				}
			}
			newHt._size = _size;
			_table.swap(newHt._table);
		}
	}
private:
	std::vector<Node*> _table;
	size_t _size;

};
void testbucket() {
	hashbucket<int> ht;
	int array[] = { 1,2,3,4,5,6,7,8,9,6,5,0 };
	for (auto e : array)
		ht.insertunique(e);
	ht.print();
	ht.insertunique(44);
	ht.insertunique(54);
	ht.print();
	ht.eraseunique(44);
	ht.print();
}
  • 3.寻找素数以及不同元素类型的实现头文件 commo.h
#pragma once
#include<string>
size_t Getnextprime(size_t capacity);
template<class T>
class DFDef {
public:
	size_t operator()(const T& date) {
		return date;
	}
};
class DFstr {
public:
	size_t operator()(const std::string& date) {
		return BKDRHash(date.c_str());
	}
private:
	size_t BKDRHash(const char*str)
	{
		register size_t hash = 0;
		while (size_t ch = (size_t)*str++)
		{
			hash = hash * 131 + ch;
		}
		return hash;
	}
};
  • common.cpp
#include"common.h"
const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] = {
 10ul,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
};
size_t Getnextprime(size_t capacity) {
	for (auto e : primeList) {
		if (e > capacity)
			return e;
	}
	return primeList[PRIMECOUNT];
}
  • 测试代码hashtes.cpp
#include<iostream>
#include"haxi.h"
#include"hasubucket.hpp"
int main() {
	testbucket();
	return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!