哈希表也称为散列表,是根据关键字值(key value)而直接进行访问的数据结构。也就是说,它通过把关键字值映射到一个位置来访问记录,以加快查找的速度。这个映射函数称为哈希函数(也称为散列函数),映射过程称为哈希化,存放记录的数组叫做散列表。比如我们可以用下面的方法将关键字映射成数组的下标:arrayIndex = hugeNumber % arraySize。
哈希化之后难免会产生一个问题,那就是对不同的关键字,可能得到同一个散列地址,即同一个数组下标,这种现象称为冲突,那么我们该如何去处理冲突呢?一种方法是开放地址法,即通过系统的方法找到数组的另一个空位,把数据填入,而不再用哈希函数得到的数组下标,因为该位置已经有数据了;另一种方法是创建一个存放链表的数组,数组内不直接存储数据,这样当发生冲突时,新的数据项直接接到这个数组下标所指的链表中,这种方法叫做链地址法。下面针对这两种方法进行讨论。
1.开放地址法
线性探测法
所谓线性探测,即线性地查找空白单元。如果21是要插入数据的位置,但是它已经被占用了,那么就是用22,然后23,以此类推。数组下标一直递增,直到找到空白位。下面是基于线性探测法的哈希表实现代码:
- public class HashTable {
- private DataItem[] hashArray; //DateItem类是数据项,封装数据信息
- private int arraySize;
- private int itemNum; //数组中目前存储了多少项
- private DataItem nonItem; //用于删除项的
- public HashTable() {
- arraySize = 13;
- hashArray = new DataItem[arraySize];
- nonItem = new DataItem(-1); //deleted item key is -1
- }
- public boolean isFull() {
- return (itemNum == arraySize);
- }
- public boolean isEmpty() {
- return (itemNum == 0);
- }
- public void displayTable() {
- System.out.print("Table:");
- for(int j = 0; j < arraySize; j++) {
- if(hashArray[j] != null) {
- System.out.print(hashArray[j].getKey() + " ");
- }
- else {
- System.out.print("** ");
- }
- }
- System.out.println("");
- }
- public int hashFunction(int key) {
- return key % arraySize; //hash function
- }
- public void insert(DataItem item) {
- if(isFull()) {
- //扩展哈希表
- System.out.println("哈希表已满,重新哈希化..");
- extendHashTable();
- }
- int key = item.getKey();
- int hashVal = hashFunction(key);
- while(hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {
- ++hashVal;
- hashVal %= arraySize;
- }
- hashArray[hashVal] = item;
- itemNum++;
- }
- /*
- * 数组有固定的大小,而且不能扩展,所以扩展哈希表只能另外创建一个更大的数组,然后把旧数组中的数据插到新的数组中。但是哈希表是根据数组大小计算给定数据的位置的,所以这些数据项不能再放在新数组中和老数组相同的位置上,因此不能直接拷贝,需要按顺序遍历老数组,并使用insert方法向新数组中插入每个数据项。这叫重新哈希化。这是一个耗时的过程,但如果数组要进行扩展,这个过程是必须的。
- */
- public void extendHashTable() { //扩展哈希表
- int num = arraySize;
- itemNum = 0; //重新记数,因为下面要把原来的数据转移到新的扩张的数组中
- arraySize *= 2; //数组大小翻倍
- DataItem[] oldHashArray = hashArray;
- hashArray = new DataItem[arraySize];
- for(int i = 0; i < num; i++) {
- insert(oldHashArray[i]);
- }
- }
- public DataItem delete(int key) {
- if(isEmpty()) {
- System.out.println("Hash table is empty!");
- return null;
- }
- int hashVal = hashFunction(key);
- while(hashArray[hashVal] != null) {
- if(hashArray[hashVal].getKey() == key) {
- DataItem temp = hashArray[hashVal];
- hashArray[hashVal] = nonItem; //nonItem表示空Item,其key为-1
- itemNum--;
- return temp;
- }
- ++hashVal;
- hashVal %= arraySize;
- }
- return null;
- }
- public DataItem find(int key) {
- int hashVal = hashFunction(key);
- while(hashArray[hashVal] != null) {
- if(hashArray[hashVal].getKey() == key) {
- return hashArray[hashVal];
- }
- ++hashVal;
- hashVal %= arraySize;
- }
- return null;
- }
- }
- class DataItem {
- private int iData;
- public DataItem (int data) {
- iData = data;
- }
- public int getKey() {
- return iData;
- }
- }
线性探测有个弊端,即数据可能会发生聚集。一旦聚集形成,它会变得越来越大,那些哈希化后落在聚集范围内的数据项,都要一步步的移动,并且插在聚集的最后,因此使聚集变得更大。聚集越大,它增长的也越快。这就导致了哈希表的某个部分包含大量的聚集,而另一部分很稀疏。
为了解决这个问题,我们可以使用二次探测:二次探测是防止聚集产生的一种方式,思想是探测相隔较远的单元,而不是和原始位置相邻的单元。线性探测中,如果哈希函数计算的原始下标是x, 线性探测就是x+1, x+2, x+3, 以此类推;而在二次探测中,探测的过程是x+1, x+4, x+9, x+16,以此类推,到原始位置的距离是步数的平方。二次探测虽然消除了原始的聚集问题,但是产生了另一种更细的聚集问题,叫二次聚集:比如讲184,302,420和544依次插入表中,它们的映射都是7,那么302需要以1为步长探测,420需要以4为步长探测, 544需要以9为步长探测。只要有一项其关键字映射到7,就需要更长步长的探测,这个现象叫做二次聚集。二次聚集不是一个严重的问题,但是二次探测不会经常使用,因为还有好的解决方法,比如再哈希法。
再哈希法
为了消除原始聚集和二次聚集,现在需要的一种方法是产生一种依赖关键字的探测序列,而不是每个关键字都一样。即:不同的关键字即使映射到相同的数组下标,也可以使用不同的探测序列。再哈希法就是把关键字用不同的哈希函数再做一遍哈希化,用这个结果作为步长,对于指定的关键字,步长在整个探测中是不变的,不同关键字使用不同的步长、经验说明,第二个哈希函数必须具备如下特点:
1. 和第一个哈希函数不同;
2. 不能输出0(否则没有步长,每次探索都是原地踏步,算法将进入死循环)。
专家们已经发现下面形式的哈希函数工作的非常好:stepSize = constant - key % constant; 其中constant是质数,且小于数组容量。
再哈希法要求表的容量是一个质数,假如表长度为15(0-14),非质数,有一个特定关键字映射到0,步长为5,则探测序列是0,5,10,0,5,10,以此类推一直循环下去。算法只尝试这三个单元,所以不可能找到某些空白单元,最终算法导致崩溃。如果数组容量为13, 质数,探测序列最终会访问所有单元。即0,5,10,2,7,12,4,9,1,6,11,3,一直下去,只要表中有一个空位,就可以探测到它。下面看看再哈希法的代码:
- public class HashDouble {
- private DataItem[] hashArray;
- private int arraySize;
- private int itemNum;
- private DataItem nonItem;
- public HashDouble() {
- arraySize = 13;
- hashArray = new DataItem[arraySize];
- nonItem = new DataItem(-1);
- }
- public void displayTable() {
- System.out.print("Table:");
- for(int i = 0; i < arraySize; i++) {
- if(hashArray[i] != null) {
- System.out.print(hashArray[i].getKey() + " ");
- }
- else {
- System.out.print("** ");
- }
- }
- System.out.println("");
- }
- public int hashFunction1(int key) { //first hash function
- return key % arraySize;
- }
- public int hashFunction2(int key) { //second hash function
- return 5 - key % 5;
- }
- public boolean isFull() {
- return (itemNum == arraySize);
- }
- public boolean isEmpty() {
- return (itemNum == 0);
- }
- public void insert(DataItem item) {
- if(isFull()) {
- System.out.println("哈希表已满,重新哈希化..");
- extendHashTable();
- }
- int key = item.getKey();
- int hashVal = hashFunction1(key);
- int stepSize = hashFunction2(key); //用hashFunction2计算探测步数
- while(hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {
- hashVal += stepSize;
- hashVal %= arraySize; //以指定的步数向后探测
- }
- hashArray[hashVal] = item;
- itemNum++;
- }
- public void extendHashTable() {
- int num = arraySize;
- itemNum = 0; //重新记数,因为下面要把原来的数据转移到新的扩张的数组中
- arraySize *= 2; //数组大小翻倍
- DataItem[] oldHashArray = hashArray;
- hashArray = new DataItem[arraySize];
- for(int i = 0; i < num; i++) {
- insert(oldHashArray[i]);
- }
- }
- public DataItem delete(int key) {
- if(isEmpty()) {
- System.out.println("Hash table is empty!");
- return null;
- }
- int hashVal = hashFunction1(key);
- int stepSize = hashFunction2(key);
- while(hashArray[hashVal] != null) {
- if(hashArray[hashVal].getKey() == key) {
- DataItem temp = hashArray[hashVal];
- hashArray[hashVal] = nonItem;
- itemNum--;
- return temp;
- }
- hashVal += stepSize;
- hashVal %= arraySize;
- }
- return null;
- }
- public DataItem find(int key) {
- int hashVal = hashFunction1(key);
- int stepSize = hashFunction2(key);
- while(hashArray[hashVal] != null) {
- if(hashArray[hashVal].getKey() == key) {
- return hashArray[hashVal];
- }
- hashVal += stepSize;
- hashVal %= arraySize;
- }
- return null;
- }
- }
2.链地址法
在开放地址法中,通过再哈希法寻找一个空位解决冲突问题,另一个方法是在哈希表每个单元中设置链表(即链地址法),某个数据项的关键字值还是像通常一样映射到哈希表的单元,而数据项本身插入到这个单元的链表中。其他同样映射到这个位置的数据项只需要加到链表中,不需要在原始的数组中寻找空位。下面看看链地址法的代码:
- public class HashChain {
- private SortedList[] hashArray; //数组中存放链表
- private int arraySize;
- public HashChain(int size) {
- arraySize = size;
- hashArray = new SortedList[arraySize];
- //new出每个空链表初始化数组
- for(int i = 0; i < arraySize; i++) {
- hashArray[i] = new SortedList();
- }
- }
- public void displayTable() {
- for(int i = 0; i < arraySize; i++) {
- System.out.print(i + ": ");
- hashArray[i].displayList();
- }
- }
- public int hashFunction(int key) {
- return key % arraySize;
- }
- public void insert(LinkNode node) {
- int key = node.getKey();
- int hashVal = hashFunction(key);
- hashArray[hashVal].insert(node); //直接往链表中添加即可
- }
- public LinkNode delete(int key) {
- int hashVal = hashFunction(key);
- LinkNode temp = find(key);
- hashArray[hashVal].delete(key);//从链表中找到要删除的数据项,直接删除
- return temp;
- }
- public LinkNode find(int key) {
- int hashVal = hashFunction(key);
- LinkNode node = hashArray[hashVal].find(key);
- return node;
- }
- }
下面是链表类的代码,用的是有序链表:
- public class SortedList {
- private LinkNode first;
- public SortedList() {
- first = null;
- }
- public boolean isEmpty() {
- return (first == null);
- }
- public void insert(LinkNode node) {
- int key = node.getKey();
- LinkNode previous = null;
- LinkNode current = first;
- while(current != null && current.getKey() < key) {
- previous = current;
- current = current.next;
- }
- if(previous == null) {
- first = node;
- }
- else {
- node.next = current;
- previous.next = node;
- }
- }
- public void delete(int key) {
- LinkNode previous = null;
- LinkNode current = first;
- if(isEmpty()) {
- System.out.println("chain is empty!");
- return;
- }
- while(current != null && current.getKey() != key) {
- previous = current;
- current = current.next;
- }
- if(previous == null) {
- first = first.next;
- }
- else {
- previous.next = current.next;
- }
- }
- public LinkNode find(int key) {
- LinkNode current = first;
- while(current != null && current.getKey() <= key) {
- if(current.getKey() == key) {
- return current;
- }
- current = current.next;
- }
- return null;
- }
- public void displayList() {
- System.out.print("List(First->Last):");
- LinkNode current = first;
- while(current != null) {
- current.displayLink();
- current = current.next;
- }
- System.out.println("");
- }
- }
- class LinkNode {
- private int iData;
- public LinkNode next;
- public LinkNode(int data) {
- iData = data;
- }
- public int getKey() {
- return iData;
- }
- public void displayLink() {
- System.out.print(iData + " ");
- }
- }
在没有冲突的情况下,哈希表中执行插入和删除操作可以达到O(1)的时间级,这是相当快的,如果发生冲突了,存取时间就依赖后来的长度,查找或删除时也得挨个判断,但是最差也就O(N)级别。
相关推荐
ANSYS中空隙材料、多孔介质与随机骨料模型的CAD建模插件及应用研究,ansys空隙材料、孔隙材料、多孔介质模型,随机骨料。 CAD建模插件,可导入ansys workbench ,ansys空隙材料; 孔隙材料; 多孔介质模型; 随机骨料; CAD建模插件; 导入ansys workbench,"ANSYS空隙材料多孔介质模型及随机骨料CAD建模插件"
1、文件内容:perl-Image-Info-1.33-3.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/perl-Image-Info-1.33-3.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装
mysql55-mysql-bench-5.5.37-5.el6.centos.alt.x86_64.rpm
1、文件内容:perl-Module-Implementation-0.06-6.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/perl-Module-Implementation-0.06-6.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装
Video_587150722718831.mp4
1、文件内容:pcs-snmp-0.9.169-3.el7.centos.3.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/pcs-snmp-0.9.169-3.el7.centos.3.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
"GL8碰撞仿真CAE有限元模型文件:141份文档,总大小281M",GL8碰撞cae有限元模型 141 wenjian大小281m ,GL8碰撞; cae有限元模型; 大小281m; 141 wenjian,GL8碰撞CAE有限元模型:大型高精度281M文件
MATLAB高级仿真:储能系统在调峰调频中的联合优化模型——深度探索充放电策略与运行协同优势,MATLAB代码:储能参与调峰调频联合优化模型 关键词:储能 调频 调峰 充放电优化 联合运行 参考文档:《Using Battery Storage for Peak Shaving and Frequency Regulation: Joint Optimization for Superlinear Gains》完全复现 仿真平台:MATLAB+CVX 平台 优势:代码具有一定的深度和创新性,注释清晰,非烂大街的代码,非常精品 主要内容:代码主要做的是考虑储能同时参与调峰以及调频的联合调度模型,现有代码往往仅关注储能在调峰方面的能力,而实际上同时参与调峰调频将超线性的提高储能的收益,在建模方面,构建了考虑电池 化成本、充放电功率约束以及用户负荷不确定性的储能优化模型,整体复现结果和文档一致,该代码具有一定的创新性,适合新手学习以及在此基础上进行拓展,代码质量非常高,保姆级的注释以及人性化的模块子程序 ,关键词:储能; 调峰调频; 联合优化模型; 充放电优化; 电池退化成本; 用户负荷
1、文件内容:perl-IO-Compress-2.061-2.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/perl-IO-Compress-2.061-2.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装
1、文件内容:perl-IO-Socket-INET6-2.69-5.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/perl-IO-Socket-INET6-2.69-5.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装
电脑调音软件下载是专为汽车音响爱好者和专业人士设计的一款强大工具, 这款软件的主要功能在于帮助用户对车载音频系统进行精确的数字信号处理,以提升音乐播放效果,提供更丰富的听觉体验。
1、文件内容:perl-File-Fetch-0.42-2.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/perl-File-Fetch-0.42-2.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装
智慧园区管理系统-活动资源
基于电动汽车灵活性的微网多尺度协调调度模型研究与应用,计及电动汽车灵活性的微网多时间尺度协调调度模型 摘要:构建了含有电动汽车参与的微网 电厂多时间尺度协调优化模型,其中包括日前-日内-实时三阶段,日前阶段由于风光出力具有不确定性,结合风光预测值作初步经济调度;日内阶段,风光出力观测的更加准确,通过调节储能、需求响应等单元对调度方案作进一步调整,避免遭受高额的不平衡惩罚;实时阶段,风光出力的预测结果更准确,为了进一步降低微网与上级电网并网功率的波动性,充分利用电动汽车的灵活性,调度电动汽车的充放电以减少功率波动,兼顾调度的安全性与经济性。 本代码为代码,实现效果见下图 ,电动汽车灵活性; 微网多时间尺度; 协调调度模型; 风光出力; 储能调节; 需求响应; 功率波动性,《微网中电动汽车灵活性的多时间尺度协调调度模型》
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
转正汇报(3).pdf
星落最强稳定版pak(1).zip
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
辛几何模态分解技术:提升时间序列预测精度的SGMD平滑子序列分解法,尚待应用于风电、光伏及负荷预测领域。,辛几何模态分解 SGMD,一种新的时间序列分解技术,通过样本熵得到几个平滑的子序列,可以提高时间序列预测的准确度目前还没有用于风电,光伏,负荷预测,需要的赶紧入手吧不信的可以去知网查查 ,辛几何模态分解;SGMD;时间序列分解技术;样本熵;平滑子序列;预测准确度;风电预测;光伏预测;负荷预测,辛几何模态分解SGMD:提升时间序列预测准确度的新技术