数据结构篇九:散列表

本文介绍了散列表的基本概念,包括映射函数和常见的冲突解决方法——开放地址法和链地址法。通过实例展示了这两种方法在插入和查找数据时的具体操作,并提供了相应的C++代码实现。此外,还讨论了自动扩容策略以应对装载因子过大的情况。
摘要由CSDN通过智能技术生成

散列表

1.什么是散列表?
散列表就是通过一个映射函数,把每个数据映射为一个数组下标,按照下标存储起来,当我们访问这个数据时,只需要通过映射函数计算出下标,就可以找到这个数。

2.散列表映射函数
从散列表定义来看,散列表最重要的是设计映射函数,映射函数决定效率和减少地址冲突,常见的映射函数有下面的几个。。。

  • 直接定址法
  • 数学分析法
  • 除留取余法
  • 随机数法

3.散列表的冲突问题
不管我们设计的散列函数多牛逼。都无法避免数据映射冲突问题,冲突就是两个数据经过映射函数处理后得到的数据是一样的。这个时候不可能把它两放一起吧。因此发明了解决地址冲突问题的办法

  • 开放地址法
  • 拉链法
  • 多哈希法

下面我们用两种解决冲突的办法去实现一个完整的散列表。

一:开放地址法

1.首先准备大小为9的数组。设计映射函数是除留取余法。

映射函数:key = data % size(size就是散列表长度,data就是数据)
在这里插入图片描述

2.假设我们有数据5,4,8,15,6,7时,对应散列表情况,我们按照设计的映射函数逐个将数据放入到散列表,整个过程如下图所示。

  • 首先元素5,5%9=5,所以找到下标是5的位置放5,
  • 接着元素4,4%9=4,所以找到下标是4的位置放4,
  • 接着元素8,8%9=8,所以找到下标是8的位置放8,
  • 接着元素15,15%9=6,所以找到下标是6的位置放15
  • 接着元素6,6%9=6,发现下标6号位置又有了元素,发生了冲突,继续往后找第一个空的位置,来到了下标是7的位置,直接放进去。
  • 接着元素7,7%9=7,发现下标7号位置有了元素,发生了冲突,继续往后找第一个空的位置,来到了下标是0的位置,直接放进去。(开放地址法可以从后搜索到前面)

在这里插入图片描述

3.现在我们已经学会了如何插入数据到散列表,现在开始发挥它的威力了。散列表设计的本质就是用于搜索查找记录。

假设我们还是用上面已经插入好数据的散列表,我们分别要查找数据6,7,9

  • 查找元素6,我们按照映射函数,直接代入6计算,6%9=6,直接去下标是6的位置找,发现此位置有元素,继续往后找,来到了下标是7的位置,找到了数组6,查找成功。
  • 查找元素5,我们按照映射函数,直接带入5计算,5%9=5,直接去下标是5的位置找,发现找到了,查找成功。
  • 查找元素9,我们按照映射函数,直接带入9计算,9%9=0,直接去下标是0的位置找,发现此位置有元素,继续往后找,发现此位置是空,停止查找,查找失败。(停止原因你可以好好想想,它插入数据时会挨个放,不会跳着放)。

开放地址法的代码

//具有自动扩容性的散列表(装载因子最大为0.75时自动扩容)
Hash_Table_Array::Hash_Table_Array()
{
	this->maxSize = 10;
	this->data = new int[this->maxSize];
	this->size = 0;
	for (int i = 0; i < this->maxSize; i++)
	{
		this->data[i] = 99999;//标记数组初始值(标空)
	}
}
//数据的插入
void Hash_Table_Array::Insert(int data)
{
	int *newData,i,index;
	//1.每次插入数据前,先检测是否超过了最大装载因子
	if (this->size*1.0 >= this->maxSize*0.75)
	{
		newData = new int[this->maxSize * 2];
		for (i = 0; i < this->maxSize * 2; i++)
		{
			newData[i] = 99999;
		}
		//将原数据重新映射地址
		for (i = 0; i < this->maxSize;i++)
		{
			
			if (this->data[i] != 99999)
			{
				index = this->data[i] % (2 * this->maxSize);
				while (newData[index] != 99999)
				{
					index = (index + 1) % (2 * this->maxSize);
				}
				newData[index] = this->data[i];
			}
		}
		this->maxSize = 2 * this->maxSize;
		delete[] this->data;
		this->data = newData;
		index = data % this->maxSize;
		//开放地址法
		while (this->data[index] != 99999)
		{
			index = (index + 1) % this->maxSize;
		}
		this->data[index] = data;
		this->size++;
	}
	else {
		index = data % this->maxSize;
		//开放地址法
		while (this->data[index]!=99999)
		{
			index = (index + 1) % this->maxSize;
		}
		this->data[index] = data;
		this->size++;
	}
}

//数据的查询
int Hash_Table_Array::isFind(int data)
{
	int index,sum=0;
	index = data % this->maxSize;
	//开放地址法
	while (this->data[index] != data)
	{
		index = (index + 1) % this->maxSize;
		sum++;
		//如果继续查找数据为空,或者回到原点,表示该数据不存在
		if (this->data[index] == 99999 || sum == this->maxSize)
		{
			return -1;
		}
	}
	return index;
}

二:链地址法

1.首先准备大小为10的边表数组,设计映射函数仍然是除留取余法,处理冲突的办法时链地址法

映射函数: key=data%size

在这里插入图片描述

2.假设我们有数据5,4,8,15,6按个插入,如下图所示

  • 首先元素5%10=5,所以链接到第5个边表节点,
  • 接着元素4%10=4,所以链接到第4个边表节点,
  • 接着元素8%10=8,所以链接到第8个边表节点,
  • 接着元素15%10=5,有冲突,直接链接到位置5的链表节点后面去
  • 接着元素6%10=6,所以链接到第6个边表节点

在这里插入图片描述

3.链地址法的查找

假设我们要查找数据15,8,9

  • 查找数据15,按照映射函数15%10=5,直接去对应边表下标是5下面找,逐个遍历该位置链表结构,找到了15,查找成功。
  • 查找数据8,按照映射函数8%10=8,直接去对应边表下标是8下面找,逐个遍历该位置链表结构,找到了8,查找成功。
  • 查找数据9,按照映射函数9%10=9,直接去对应边表下标是9下面找,逐个遍历该位置链表结构,发现链表是空,查找失败。

4.链地址法的代码

//初始化处理
Hash_Table_List::Hash_Table_List()
{
	int i;
	this->len = 0;
	this->length = 10;
	this->head = new Node[10];
	for (i = 0; i < 10; i++)
	{
		this->head[i].data = i;
		this->head[i].next = NULL;
	}
}
//插入数据
void Hash_Table_List::Insert(int data)
{
	//超过装载因子,自动扩容
	Node *newHead,*p,*q;
	int i,value,index;
	if (this->len*1.0 >= this->length * 0.75)
	{
		newHead = new Node[this->length * 2];
		for (i = 0; i < this->length * 2; i++)
		{
			newHead[i].data = i;
			newHead[i].next = NULL;
		}
		//将旧数据,逐个重新映射到新的散列表上
		for (i = 0; i < this->length; i++)
		{
			p = this->head[i].next;
			while (p)
			{
				//原数据
				value = p->data;
				//映射后地址
				index = p->data % (this->length * 2);
				//根据地址,放到新散列表的后面
				q = new Node;
				q->data = value;
				q->next = newHead[index].next;
				newHead[index].next = q;
				//删除旧散列表边表结构
				this->head[i].next = p->next;
				delete p;
				p = this->head[i].next;
			}
		}
		if (this->head)
		{
			delete[] this->head;
		}
		this->head = newHead;
		this->length = this->length * 2;

		//插入数据进去
		index = data % this->length;
		q = new Node;
		q->data = data;
		q->next = this->head[index].next;
		this->head[index].next = q;
		this->len++;
	}
	//没有超过装载因子,不需要扩容
	else
	{
		index = data % this->length;
		q = new Node;
		q->data = data;
		q->next = this->head[index].next;
		this->head[index].next = q;
		this->len++;
	}
}



//查找处理
int Hash_Table_List::isFind(int data)
{
	Node *p;
	int index;
	index = data %this->length;
	p = this->head[index].next;
	while (p != NULL && p->data != data)
	{
		p = p->next;
	}
	if (p == NULL)
	{
		return -1;//没找到
	}
	//返回链表的下标
	else {
		return index;
	}
}

获取完整代码

我分别用C,C++,JAVA三种主流语言编写了完整代码,有需要的在微信公众号搜索考研稳上岸获取完整代码

数据结构练习(同样适用于考研)

微信小程序搜索计算机刷题
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值