哈希表的简述

什么是哈希表:

        哈希表是一种存放键值对(<key, value>)的数据结构,又称散列表。

哈希表的作用:

        能通过键快速地找到值,原理:哈希表的键值之间存在一种一对一的映射关系:哈希函数。

什么是哈希函数:

        哈希函数就是哈希表的键值之间存在的关系。哈希函数为了减少搜索时间一般都是简单函数,但是可能会造成哈希冲突。

常见的哈希函数:

以数组为例,i代表这个键所对应的值存放在数组下标为i的位置。

        ①直接定制法:

                取关键字的某个线性函数为散列地址:i = A*Key + B
                适合查找比较小且连续的情况。

        ②除留余数法:

                设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:i = key% p,将key转换成哈希地址。

什么是哈希冲突:

        当两个不同的键指向同一个值时就叫发生了哈希冲突,发生哈希冲突会导致根据键找到错误的值。

如何解决哈希冲突:

        ①闭散列(开放定址法)

这里要插入一个载荷因子的概念。载荷因子代表了哈希表中已存入数据占总空间的几分之几。当载荷因子接近于1时(一般大于0.7或0.8),就要考虑增大哈希表的空间大小,防止插入数据失败。

         通过闭散列方法,当产生哈希冲突时,将key存放在下一个空位置处。闭散列有如下两个方法寻找下一个空位置。

        闭散列方法较为简单便捷,但是容易产生数据“堆积”,即发生哈希冲突的数据将原本正常的数据位置占据了。而且在删除时不能直接删除数据,因为后面的数据可能是与该数据发生哈希冲突而存储的,如果删除了会使得后面的数据按照未发生哈希冲突的情况进行查找。所以删除要用带标记的伪删除法。

                一、线性探测:

                        i = i + j(j = 1, 2, 3, ...)。其中i是存放的值的位置,j是下一个空位置。

                二、二次探测:

                        i = i + j^2(j = 1, 2, 3, ...)。

#include<iostream>
#include<vector>
using namespace std;

enum assign {    //用于标记哈希表中的元素状态
	empty1 = 0,     //为空,未存放元素
	exist,     //存放元素
	cut         //存放的元素被删除(伪删除)
};

template<class T1, class T2> 
struct data1 {               //封装了哈希表的元素
	pair<T1, T2> k;      //键值对
	assign ass_ = empty1;         //该位置的状态
};

template<class T1, class T2>
class haxi {
private:
	int n = 0;        //哈希表的大小
	vector<data1<T1, T2> > hx;    //哈希表
	int yu;
	int t_n = 0;       //哈希表中已有的大小
public:

	haxi(int n = 10) {
		this->n = n;
		this->yu = n;
		extend(n);
	}

	int haxi_fun(T1 key) {      //存放哈希函数,根据key值返回其在哈希表中的下标
		return key % yu;
	}

	void extend(int t = 10) {          //扩容函数
		int newSize = n == 0 ? t : n * 2;
		hx.resize(newSize);
		n = newSize;
	}




	data1<T1, T2>* _find1(T1 key) {          //查找该键是否被存储了。
		int index = haxi_fun(key);
		if (n < index) { return nullptr; }
		while (hx[index].ass_ != 0) {
			if (hx[index].ass_ == exist && hx[index].k.first == key) {
				return &hx[index];
			}
			else {
				index++;   //线性探测
				//index += i*i;    //二次探测
			}
			index %= n;
		}
		return nullptr;
	}

	bool insert(data1<T1, T2> t1) {   //将t1插入
		int index = haxi_fun(t1.k.first);
		if (t_n * 1.0 / n >= 0.7 || index >= n) {      //索引大于现有空间,需要扩容
			extend();
		}
		if (_find1(t1.k.first) != nullptr) {
			return false;     //已经有这个键了,不能插入。
		}
		int i = 1; // i是二次探测用的。
		while (hx[index].ass_ != empty1) {     //找到不产生哈希冲突的位置
			index++;            //线性探测
			//index += i*i++;    //二次探测
			index %= n;
		}
		hx[index] = t1;        //插入
		hx[index].ass_ = exist;
		t_n++;           //记得要给加加。
	}

	bool erase(T1 key) {          //带标记的伪删除
		data1<T1, T2>* ret = find(key);
		if (ret == nullptr) {
			return false;
		}
		else {
			t_n--;    //存储的个数减一
			ret->ass_ = cut;    //伪删除
			return true;
		}
	}

	void print() {       //输出哈希表中的数据
		for (int i = 0; i < n; i++) {
			if (hx[i].ass_ == exist) {
				cout << hx[i].k.first << "  "<< hx[i].k.second << "  ";
			}
		}
		cout << endl;
	}
};




int main() {

	haxi<int, int> a;
	data1<int, int> b;
	b.k = make_pair(1, 2);
	a.insert(b);
	a.print();

	return 0;
}

        ②开散列(拉链法、链地址法)

        开散列将经过哈希函数后具有相同地址i的集合归并在同一子集中,称为一个桶(即发生哈希冲突的元素放同一个桶里)。将桶中的每个元素用单链表串联起来,头结点放在单链表中。

#include<iostream>
#include<vector>
#include<list>
using namespace std;

template<class T1, class T2>
struct data1 {               //封装了哈希表的元素
	pair<T1, T2> k;      //键值对
	data1* next = nullptr;         //在同一个位置的下一个节点
};

template<class T1, class T2>
class haxi {
private:
	int n = 0;        //哈希表的大小
	vector<data1<T1, T2> > hx;    //哈希表,采用链表的形式
	int yu;
	int t_n = 0;       //哈希表中已有的大小
public:

	haxi(int n = 10) {
		this->n = n;
		this->yu = n;
		extend(n);
	}

	int haxi_fun(T1 key) {      //存放哈希函数,根据key值返回其在哈希表中的下标
		return key % yu;
	}

	void extend(int t = 10) {          //扩容函数
		int newSize = n == 0 ? t : n * 2;
		hx.resize(newSize);
		n = newSize;
	}




	data1<T1, T2>* _find1(T1 key) {          //查找该键是否被存储了。
		int index = haxi_fun(key);

		if (n < index) { return nullptr; }
		data1<T1, T2>* p = hx[index].next;
		while (p != nullptr && p->k.first != key) {    //关键值不对,但下标对了。
			p = p->next;
		}
		return p;     //找到了,或者没找到返回nullptr。
	}

	bool insert(data1<T1, T2> *t1) {   //将t1插入
		int index = haxi_fun(t1->k.first);
		if (t_n * 1.0 / n >= 0.7 || index >= n) {      //索引大于现有空间,需要扩容
			extend();
		}
		if (_find1(t1->k.first) != nullptr) {
			return false;     //已经有这个键了,不能插入。
		}
		data1<T1, T2>* p = &hx[index];
		while (p->next != nullptr) {     //将其接到最后一个不为空的后面
			p = p->next;
		}
		p->next = t1;
		t_n++;          //记得要给加加。
		return true;
	}

	bool erase(T1 key){         //带标记的伪删除
		data1<T1, T2>* ret = _find1(key);
		if (ret == nullptr) {     //没找到
			return false;
		}
		else {
			t_n--;    //存储的个数减一
			int index = haxi_fun(key);
			data1<T1, T2>* p = hx[index].next;
			data1<T1, T2>* q = &hx[index];
			while (p->k.first != key) {
				q = p;
				p = p->next;
			}
			q->next = q->next->next;

			return true;
		}
	}

	void print() {       //输出哈希表中的数据
		for (int i = 0; i < n; i++) {
			data1<T1, T2>* p = _find1(i);
			int j = 0;
			while (p != nullptr) {
				cout << p->k.first << "  " << p->k.second << " ";
				p = p->next;
				j = 1;
			}
			if(j) cout << endl;
		}
		cout << endl;
	}
};




int main() {

	haxi<int, int> a;
	data1<int, int> b;
	b.k = make_pair(1, 2);
	a.insert(&b);
	a.print();
	a.erase(b.k.first);
	a.print();

	return 0;
}

        ③再哈希法

        当使用哈希函数后发生哈希冲突时,对结果再调用另一个哈希函数直到不发生冲突的方法。

        如:第一个哈希函数为 i = key%n; 发生哈希冲突后,第二个哈希函数为 i1 = 3i%(n+j);  其中j代表发生冲突的次数。

        ④公共溢出区法

        除哈希表外再建立一个溢出表,当发生哈希冲突时,将冲突的将其存放在溢出表中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值