【数据结构】哈希

本文深入解析哈希表的原理,包括哈希函数的作用、哈希冲突的解决策略,如闭散列与开散列,以及如何通过控制负载因子和优化哈希函数降低冲突。详细介绍了闭散列中的线性探测和二次探测,以及开散列的实现方式。

什么是哈希?

将元素的存储位置和该元素的关键码通过某种函数建立一一对应的关系,构造出来的存储结构称之为哈希表,转换时借助的函数称之为哈希函数,在理想情况下,根据关键码搜索元素时可以不经过任何比较,一次性从表中查找到所要搜索的元素

但是在通过哈希函数进行元素存储位置确立的时候会出现,不同元素的关键码通过哈希函数计算出来的存储位置是相同的,这便是哈希冲突

解决哈希冲突有两种方式:

  • 开散列
  • 闭散列

闭散列

当发生哈希冲突时,如果哈希表还允许做插入,就把该元素放到“下一个”空位置上去,怎么找“下一个”位置,常见的有线性探测和二次探测,这两种方式仅在插入元素发生冲突找“下一个”存储位置有区别

  • 线性探测:
while (发生哈希冲突)
{
    探测位置 = (当前位置 + 1% 哈希表容量
}
  • 二次探测:
int i = 1;
while (发生哈希冲突)
{
    探测位置 = (当前位置 + (i的二次方)) % 哈希表容量
    i++;
}

在这里插入图片描述
用闭散列的方式解决哈希冲突后会出现一个问题,当需要删除元素时,该元素恰巧导致了哈希冲突,如果直接删除该元素,那么因冲突重新确立存储位置的元素将无法根据哈希函数再次找到

定义一个动态线性表来构造hash表,由于在表中删除元素的时候不能直接删除,所以每个存储空间不止要存放元素的关键码,还需要标记当前该存储位置的状态,需要定义一个枚举常量来表示状态,分三种:未存放、存在有效元素、删除状态;hash函数也以函数指针的形式添加在结构体中

typedef int Key;
typedef int(*HashFunc)(Key, int);

typedef enum 
{
	EXIST,
	DELETE,
	EMPTY
} State;

typedef struct
{
	Key key;
	State state;
} Element;

typedef struct
{
	Element *arr;
	int size;
	int capacity;
	HashFunc hashfunc;
} HT;

哈希表的初始化&销毁

根据要求的容量动态开辟空间,别忘了对开辟的每一个空间状态初始值的设置

void HTInit(HT *pHT, int capacity, HashFunc hashFunc)
{
	int i = 0;
	pHT->size = 0;
	pHT->capacity = capacity;
	pHT->hashFunc = hashFunc;
	Element *tmp = (Element *)malloc(sizeof(Element) * capacity);
	assert(tmp);
	pHT->arr = tmp;

	for (; i<capacity; i++)
	{
		pHT->arr[i].state = EMPTY;
	}
}

void HTDestroy(HT *pHT)
{
	pHT->capacity = 0;
	pHT->size = 0;
	pHT->hashFunc = NULL;
	free(pHT->arr);
}

查找

根据元素的关键码和哈希计算出元素的存储位置,判断当前位置的状态和关键码是否都符合,否则根据探测原则持续向后查找,如过遇到的存储位置状态为空表示查找失败

int HTSearch(HT *pHT, Key key)
{
	int index = pHT->hashFunc(key, pHT->capacity);
	while (pHT->arr[index].state != EMPTY)
	{
		if (pHT->arr[index].state == EXIST && pHT->arr[index].key == key)
		{
			return 0;
		}
		index = (index + 1) % pHT->capacity;
	}
	return -1;
}

删除

删除其实上也是一个查找的过程,不同的是在进行删除后,哈希表中元素个数减1,删除位置的状态发生变化

int HTDelete(HT *pHT, Key key)
{
	int index = pHT->hashFunc(key, pHT->capacity);
	while (pHT->arr[index].state != EMPTY)
	{
		if (pHT->arr[index].state == EXIST && pHT->arr[index].key == key)
		{
			pHT->arr[index].state = DELETE;
			pHT->size--;
			return 0;
		}
		index = (index + 1) % pHT->capacity;
	}
	return -1;
}

插入

插入元素的时需要考虑一个问题,是否需要扩容,虽然哈希冲突不可避免,但可以使用一些方法来降低哈希冲突

  • 控制负载因子:
负载因子 = pHT->size / pHT->capacity

根据经验得知,将负载因子控制在0.8以下,可以有效降低哈希冲突,超过0.8冲突将会呈指数曲线增长

  • 优化哈希函数
  • 素数表:使用素数表对齐做哈希表的容量

以下采用控制负载因子,降低哈希冲突
所以在插入元素之前先要判断当前的哈希表负载因子,决定需不需要扩容的问题

需要扩容时可以分为以下三步:申请新空间、搬移数据、释放旧空间
扩容后的空间与旧空间容量发生变化,所以在数据搬移的过程中需要对元素的存储位置重新做调整

void IsExpand(HT *pHT)
{
	int i = 0;
	HT tHT;
	Element *tmp = NULL;
	int newCapacity = 0;
	if (pHT->size * 10 / pHT->capacity < 7)
	{
		return;
	}
	newCapacity = pHT->capacity * 2;
	HTInit(&tHT, newCapacity, pHT->hashFunc);
	for (; i<pHT->capacity; i++)
	{
		if (pHT->arr[i].state == EXIST)
		{
			HTInsert(&tHT, pHT->arr[i].key);
		}
	}
	free(pHT->arr);
	pHT->arr = tHT.arr;
	pHT->capacity = tHT.capacity;
}

插入之前先查找一遍,如果需要插入元素已经存在,不再进行重复插入,如果不存在,状态为EMPTY或者DELETE的位置均可以做插入

int HTInsert(HT *pHT, Key key)
{
	int index = 0;
	if (!HTSearch(pHT, key))
	{
		return -1;//元素已经存在
	}
	IsExpand(pHT);
	index = pHT->hashFunc(key, pHT->capacity);
	while (pHT->arr[index].state == EXIST)
	{
		index = (index + 1) % pHT->capacity;
	}
	pHT->arr[index].key = key;
	pHT->arr[index].state = EXIST;
	pHT->size++;
	return 0;
}

开散列

开散列又称哈希桶,解决哈希冲突的方式是构造一个存放链表头指针的顺序表,顺序表的存储位置是通过哈希函数计算得出,也称为桶号,每条链表都是产生哈希冲突的结点
在这里插入图片描述

typedef int Key;
typedef int(*HashFunc)(Key, int);

typedef struct Node
{
	Key key;
	struct Node* next;
} Node;

typedef struct
{
	Node **arr;
	int size;
	int capacity;
	HashFunc hashFunc;
} HB;

初始化&销毁

  • 初始化
    初始化的时候开辟的空间都需要存放结点地址,故将其全部初始化成NULL
void HBInit(HB *pHB, int capacity, HashFunc hashFunc)
{
	int i = 0;
	Node **tmp = (Node **)malloc(sizeof(Node *) * capacity);
	assert(tmp);
	pHB->arr = tmp;
	pHB->capacity = capacity;
	pHB->size = 0;
	pHB->hashFunc = hashFunc;

	for (; i<capacity; i++)
	{
		pHB->arr[i] = NULL;
	}
}
  • 销毁
    动态开辟的空间不止用于存放链表地址的动态顺序表,还有每一条链表的每一个结点,所以应该全部释放
void HBDestroy(HB *pHB)
{
	int i = 0;
	Node *cur = NULL;
	Node *del = NULL;
	for (; i<pHB->capacity; i++)
	{
		cur = pHB->arr[i];
		while (cur != NULL)
		{
			del = cur;
			cur = cur->next;
			free(del);//删除每条链表的每一个结点
		}
	}
	free(pHB->arr);//删除掉存放链表头指针的动态顺序表
}

查找

用哈希函数计算出桶号后,在每一个桶中去查找元素,实际上是通过遍历链表的方式查找

int HBSearch(HB *pHB, Key key)
{
	Node *cur = NULL;
	int index = pHB->hashFunc(key, pHB->capacity);
	cur = pHB->arr[index];
	while (cur != NULL)
	{
		if (cur->key == key)
		{
			return 0;
		}
		cur = cur->next;
	}
	return -1;
}

删除

删除的时候也是先做查找,当需要删除的元素是链表的第一个结点,直接让桶里的头指针指向链表的第二个结点,如果非第一个结点,就需要借助prev指针来完成链表的删除

int HBDelete(HB *pHB, Key key)
{
	Node *cur = NULL;
	Node *prev = NULL;
	int index = pHB->hashFunc(key, pHB->capacity);
	cur = pHB->arr[index];
	while (cur != NULL)
	{
		if (cur->key == key)
		{
			if (prev == NULL)
			{
				pHB->arr[index] = cur->next;
			}
			else
			{
				prev->next = cur->next;
			}
			free(cur);
			pHB->size--;
			return 0;
		}
		prev = cur;
		cur = cur->next;
	}
	return -1;
}

插入

插入之前同样需要判断是否扩容,扩容是借助另一个扩容后的临时桶来完成,因为需要进行数据的搬移,在搬移过程中由于桶的容量已经扩容,所以通过哈希函数计算出的桶号已经发生变化,数据搬移结束后,释放掉旧桶空间,启用新桶即可

int HBInsert(HB *pHB, Key key);
void IsExpandHB(HB *pHB)
{
	int i = 0;
	int newCapacity = 0;
	Node *cur = NULL;
	HB tHB;
	if (pHB->size * 10 / pHB->capacity < 9)
	{
		return;
	}
	newCapacity = pHB->capacity * 2;
	HBInit(&tHB, newCapacity, pHB->hashFunc);

	for (; i<pHB->capacity; i++)
	{
		cur = pHB->arr[i];
		while (cur != NULL)
		{
			HBInsert(&tHB, cur->key);
			cur = cur->next;
		}
	}
	HBDestroy(pHB);
	pHB->arr = tHB.arr;
	pHB->capacity = tHB.capacity;
}

int HBInsert(HB *pHB, Key key)
{
	Node *node = NULL;
	int index = 0;
	if (!HBSearch(pHB, key))
	{
		return -1;//不再重复插入
	}
	IsExpandHB(pHB);
	index = pHB->hashFunc(key, pHB->capacity);

	node = (Node *)malloc(sizeof(Node));
	node->key = key;

	node->next = pHB->arr[index];
	pHB->arr[index] = node;

	pHB->size++;
	return 0;
}
【电力系统】单机无穷大电力系统短路故障暂态稳定Simulink仿真(带说明文档)内容概要:本文档围绕“单机无穷大电力系统短路故障暂态稳定Simulink仿真”展开,提供了完整的仿真模型与说明文档,重点研究电力系统在发生短路故障后的暂态稳定性问题。通过Simulink搭建单机无穷大系统模型,模拟不同类型的短路故障(如三相短路),分析系统在故障期间及切除后的动态响应,包括发电机转子角度、转速、电压和功率等关键参数的变化,进而评估系统的暂态稳定能力。该仿真有助于理解电力系统稳定性机理,掌握暂态过程分析方法。; 适合人群:电气工程及相关专业的本科生、研究生,以及从事电力系统分析、运行与控制工作的科研人员和工程师。; 使用场景及目标:①学习电力系统暂态稳定的基本概念与分析方法;②掌握利用Simulink进行电力系统建模与仿真的技能;③研究短路故障对系统稳定性的影响及提高稳定性的措施(如故障清除时间优化);④辅助课程设计、毕业设计或科研项目中的系统仿真验证。; 阅读建议:建议结合电力系统稳定性理论知识进行学习,先理解仿真模型各模块的功能与参数设置,再运行仿真并仔细分析输出结果,尝试改变故障类型或系统参数以观察其对稳定性的影响,从而深化对暂态稳定问题的理解。
本研究聚焦于运用MATLAB平台,将支持向量机(SVM)应用于数据预测任务,并引入粒子群优化(PSO)算法对模型的关键参数进行自动调优。该研究属于机器学习领域的典型实践,其核心在于利用SVM构建分类模型,同时借助PSO的全局搜索能力,高效确定SVM的最优超参数配置,从而显著增强模型的整体预测效能。 支持向量机作为一种经典的监督学习方法,其基本原理是通过在高维特征空间中构造一个具有最大间隔的决策边界,以实现对样本数据的分类或回归分析。该算法擅长处理小规模样本集、非线性关系以及高维度特征识别问题,其有效性源于通过核函数将原始数据映射至更高维的空间,使得原本复杂的分类问题变得线性可分。 粒子群优化算法是一种模拟鸟群社会行为的群体智能优化技术。在该算法框架下,每个潜在解被视作一个“粒子”,粒子群在解空间中协同搜索,通过不断迭代更新自身速度与位置,并参考个体历史最优解和群体全局最优解的信息,逐步逼近问题的最优解。在本应用中,PSO被专门用于搜寻SVM中影响模型性能的两个关键参数——正则化参数C与核函数参数γ的最优组合。 项目所提供的实现代码涵盖了从数据加载、预处理(如标准化处理)、基础SVM模型构建到PSO优化流程的完整步骤。优化过程会针对不同的核函数(例如线性核、多项式核及径向基函数核等)进行参数寻优,并系统评估优化前后模型性能的差异。性能对比通常基于准确率、精确率、召回率及F1分数等多项分类指标展开,从而定量验证PSO算法在提升SVM模型分类能力方面的实际效果。 本研究通过一个具体的MATLAB实现案例,旨在演示如何将全局优化算法与机器学习模型相结合,以解决模型参数选择这一关键问题。通过此实践,研究者不仅能够深入理解SVM的工作原理,还能掌握利用智能优化技术提升模型泛化性能的有效方法,这对于机器学习在实际问题中的应用具有重要的参考价值。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值