《数据结构、算法与应用C++语言描述》-代码实现散列表(线性探查与链式散列)

散列表

完整可编译运行代码:Github:Data-Structures-Algorithms-and-Applications/_22hash/

定义

字典的另一种表示方法是散列(hashing)。它用一个散列函数(也称哈希函数)把字典的数对映射到一个散列表(也称哈希表)的具体位置。如果数对p的关键字是k,散列函数为f,那么在理想情况下,p 在散列表中的位置为f(k)。暂时假定散列表的每一个位置最多能够存储一个记录。为了搜索关键字为 k 的数对,先要计算f(k),然后查看在散列表的f(k)处是否已有一个数对。如果有,便找到了该数对。如果没有,字典就不包含该数对。在前一种情况下,可以删除该数对,为此只需使散列表的f(k)位置为空。在后一种情况下,可以把该数对插在f(k)的位置。

初始化一个空字典需要的时间为 O(b)(b 为散列表拥有的位置数),查找、插入、删除操作的时间均为0(1)。

散列函数和散列表

桶和起始桶

当关键字的范围太大,不能用理想方法表示时,可以采用并不理想的散列表和散列函数:散列表位置的数量比关键字的个数少,散列函数把若干个不同的关键字映射到散列表的同一个位置。散列表的每一个位置叫一个桶(bucket);对关键字为 k的数对,f(k)是起始桶(home bucket);桶的数量等于散列表的长度或大小。因为散列函数可以把若干个关键字映射到同一个桶,所以桶要能够容纳多个数对。本章我们考虑两种极端情况。第一种情况是每一个桶只能存储一个数对。第二种情况是每一个桶都是一个可以容纳全部数对的线性表。

除法散列函数

f ( k ) = k % D ( 10 − 2 ) f(k)=k\%D (10-2) f(k)=k%D(102)

其中 k 是关键字,D 是散列表的长度(即桶的数量),%为求模操作符。散列表的位置索引从0到D-1。当D=11时,与关键字3、22、27、40、80和96分别对应的起始桶是f(3)=3,f(22)=0,f(27)=5,f(40)=7,f(80)=3 和f(96)=8。

冲突和溢出

散列表有 11 个桶,序号从 0 到 10,而且每一个桶可以存储一个数对。除数D为11。因为80%11=3,则 80 的位置为3,40的位置为40%11=7,65的位置为65%11=10。每个数对都在相应的起始桶中。散列表余下的桶为空。
现在假设要插入 58。58 的起始桶为f(58)=58%11=3。这个桶已被另一个关键字占用。这时就发生了冲突。**当两个不同的关键字所对应的起始桶相同时,就是冲突(collision)发生了。**因为一个桶可以存储多个数对,因此发生碰撞也没什么了不起。只要起始桶足够大,所有对应同一个起始桶的数对都可存储在一起。如果存储桶没有空间存储一个新数对,就是溢出(overflow)发生了。

我需要一个好的散列函数

当映射到散列表中任何一个桶里的关键字数量大致相等时,冲突和溢出的平均数最少。**均匀散列函数(uniform hash function)**便是这样的函数。

在实际应用中,关键字不是从关键字范围内均匀选择的,因此有的均匀散列函数表现好一点,有一些就差一些。那些在实际应用中性能表现好的均匀散列函数被称为良好散列函数(good hash function)

对于除法散列函数,假设 D 是偶数。当 k是偶数时,f(k)是偶数;当k 是奇数时,f(k)是奇数。例如 k%20,当k是偶数时为偶数,当k是奇数时为奇数。如果你的应用以偶数关键字为主,那么大部分关键字都被映射到序号为偶数的起始桶里。如果你的应用以奇数关键字为主,那么大部分关键字都被映射到序号为奇数的起始桶里。因此,选择D为偶数,得到的是不良散列函数。

当 D 可以被诸如 3、5 和 7 这样的小奇数整除时,不会产生良好散列函数。因此,要使除法散列函数成为良好散列函数,你要选择的除数D应该既不是偶数又不能被小的奇数整除。理想的D是一个素数。当你不能用心算找到一个接近散列表长度的素数时,你应该选择不能被2和19之间的数整除的D。

因为在实际应用的字典中,关键字是相互关联的,所以你所选择的均匀散列函数,其值应该依赖关键字的所有位(而不是仅仅取决于前几位,或后几位,或中间几位)。当D既是素数又不能被小于20的数整除,就能得到良好散列函数。

除法和非整型关键字

为使用除法散列函数,在计算f(k)之前,需要把关键字转换为非负整数。

需要将串数据、结构和算法转换为整数。

冲突和溢出的解决方案

线性探查

  1. 插入元素

散列表有 11 个桶,序号从 0 到 10,而且每一个桶可以存储一个数对。除数D为11。因为80%11=3,则 80 的位置为3,40的位置为40%11=7,65的位置为65%11=10。每个数对都在相应的起始桶中。散列表余下的桶为空。
现在假设要插入 58。58 的起始桶为f(58)=58%11=3。这个桶已被另一个关键字占用。这时就发生了冲突

在这里插入图片描述

要在图 10-2a 的散列表中把 58放进去,最简单方法是找到下一个可用的桶。这种解决溢出的方法叫作线性探查(Linear Probing)。

58 因此被存储在 4 号桶。假设下一个要插入的数对关键字为 24,24%11=2,2 号桶为空,于是把24放入2号桶。这时的散列表如图 10-2b所示。现在要插入35。35的起始桶2已满。用线性探查,结果如图 10-2c 所示。最后一例,插入 98。它的起始桶 10已满,而下一个可用桶是 0 号桶,于是 98 被插入在此。由此看来,在寻找下一个可用桶时,散列表被视为环形表。

  1. 查找元素

假设要查找关键字为k的数对,首先搜索起始桶f(k),然后把散列表当做环表继续搜索下一个桶,直到以下情况之一发生为止:

1)存有关键字 k 的桶已找到,即找到了要查找的数对;

2)到达一个空桶;

3)又回到起始桶f(k)。

后两种情况说明关键字为k的数对不存在。

  1. 删除一个元素

删除一个记录要保证上述的搜索过程可以正常进行。若在图 10-2c 中删除了关键字 58,不能仅仅把4号桶置为空,否则就无法找到关键字为35的数对。删除需要移动若干个数对。从删除位置的下一个桶开始,逐个检查每个桶,以确定要移动的元素,直至到达一个空桶或回到删除位置为止。在做删除移动时,一定要注意,不要把一个数对移到它的起始桶之前,否则,对这个数对的查找就可能失败。

实现删除的另一个策略是为每个桶增加一个域 neverUsed。在散列表初始化时,这个域被置为true。当一个数对存入一个桶中时,neverUsed域被置为false。现在,搜索的结束条件2)变成:桶的 neverUsed域为 true。不过在删除时,只是把表的相应位置置为空。一个新元素被插入在其对应的起始桶之后所找到的第一个空桶中。注意,在这种方案中,neverUsed不会重新置为true。用不了多长时间,所有(或几乎所有)桶的neverUsed域都等于false,这时搜索是否失败,只有检查所有的桶之后才可以确定。为了提高性能,当很多空桶的neverUsed域等于false时,必须重新组织这个散列表。例如,可把所有余下的数对都插到一个空的散列表中。这种方案不太可行

  1. 性能分析

我们只分析时间复杂度。设b为散列表的桶数,D为散列函数的除数,且b=D。散列表初始化的时间为0(b)。当表中有n个记录时,最坏情况下的插入和查找时间均为0(n)。当所有关键字都对应同一个起始桶时,便是最坏情况。把字典的散列在最坏情况下的复杂度与线性表在最坏情况下的复杂度相比较,二者完全相同。

然而,就平均性能而言,散列远远优于线性表。令 U n U_n Un,和 S n S_n Sn,分别表示在一次成功搜索和不成功搜索中平均搜索的桶数,其中 n是很大的值。这个平均值是由插入的 n个关键字计算得来的。对于线性探查,有如下近似公式:
U n ≈ 1 2 ( 1 + 1 ( 1 − α ) 2 ) ( 10 − 3 ) U_n\approx\frac{1}{2}\left(1 + \frac{1}{(1-\alpha)^2}\right)(10-3) Un21(1+(1α)21)(103)

S n ≈ 1 2 ( 1 + 1 1 − α ) ( 10 − 4 ) S_n\approx\frac{1}{2}\left(1 + \frac{1}{1-\alpha}\right)(10-4) Sn21(1+1α1)(104)

其中 α \alpha α=n/b为负载因子(loading factor)。

从上述公式可以证明,当 α \alpha α=0.5时,一次不成功的搜索将平均查找2.5 个桶,一次成功的搜索将平均查找 1.5 个桶。当 α \alpha α=0.9时,这些数字分别为 50.5 和 5.5。当然,这时假定 n 比 51 大得多。当负载因子比较小时,使用线性探查,散列的平均性能比线性表的平均性能要优越许多。一般情况下都是 α \alpha α<0.75。可以根据 α \alpha α和n的值确定b的值,从什么样的 α \alpha α能接受上考虑。

随机探查分析

用随机数生成器产生一个桶的搜索序列,然后用这个序列去确定插入位置。

理 10-1 设p 是某一事件发生的概率。为了该事件发生,独立试验的期望次数是 1/p。

U n U_n Un,公式按如下方式导出:当装载因子是a时,一个桶有数对的概率是a,没有数对的概率为 p=1-a。在随机探查中,使用一个独立试验序列进行搜索,一次失败的搜索是找到一个空桶,为此,我们期望搜索的桶数是:
U n ≈ a p = 1 1 − α ( 10 − 5 ) U_n\approx\frac{a}{p}=\frac{1}{1-\alpha}(10-5) Unpa=1α1(105)
S n S_n Sn,公式可以从 U n U_n Un公式推导出来。按照插入顺序,给散列表的 n个记录编号为1,2,…,n。当插入第i个数对时,首先通过一次不成功的搜索找到一个空桶,然后把该数对插到这个空桶里。在插入第i个数对时,装载因子是(i-1)/b,其中b是桶数。从公式(10-5)得出,在搜索第i个桶时,查看的期望桶数是:
a 1 − i − 1 b \frac{a}{1-\frac{i-1}{b}} 1bi1a
假设每一个数对搜索的概率是相等的,由此可得:

在这里插入图片描述

就需要查看的桶数而言,线性探查的性能不如随机探查。例如,当a=0.9时,使用线性探查进行一次不成功的搜索而期望查看的桶数是50.5,而用随机探查时,这个数降到10。

为什么不使用随机探查?

  • 真正影响运行时间的不是要查看的桶数。计算一个随机数比查看若干个桶更需要时间。
  • 随机探查是用随机方式搜索散列表,高速缓存使运行时间增大。因此,尽管随机探查需要查看的桶数要比线性探查少,但是它实际使用的时间很多,除非装载因子接近1。

链式散列

如果散列表的每一个桶可以容纳无限多个记录,那么溢出问题就不存在了。实现这个目标的一个方法是给散列表的每一个位置配置一个线性表。这时,每一个数对可以存储在它的起始桶线性表中。

在这里插入图片描述

为搜索关键字值为 k的记录,首先计算其起始桶,k%D,然后搜索该桶所对应的链表。为插入一个记录,首先要保证散列表没有一个记录与该记录的关键字相同,为此而进行的搜索仅限于该记录的起始桶所对应的链表。要删除一个关键字为k的记录,我们要搜索它所对应的起始桶链表,找到该记录,然后删除。

在这里插入图片描述

在图 10-4的每个链表上增加一个尾节点,可以改进一些程序的性能。尾节点的关键字值最起码要比插入的所有数对的关键字都大。在图10-4中,尾节点的关键字用正无穷来表示。在实际应用中,当关键字为整数时,可以用limits.h文件中定义的INT_MAX常量来替代。有了尾节点,就可以省去在sortedChain的方法中出现的大部分对空指针的检验操作。注意,在图 10-4 中,每个链表都有不同的尾节点,而实际上,所有链表可共用一个尾节点。

链式散列与线性探查相比:

线性探查占用的空间小于链式散列的空间,因为链式散列需要存储一些指针。

使用链表时要检查的节点数比使用线性和随机探查时要检查的桶数少。(详细证明见《数据结构、算法与应用C++语言描述》原书第二版链式散列介绍中 与线性探查比较 公式详细介绍)

链式散列与跳表相比:

通过使用随机过程,跳表和散列操作的平均复杂度分别是对数级和常数级。跳表方法的最坏时间复杂度为O(n+maxLevel),而散列的最坏时间复杂度为O(n)。跳表中指针平均占用的空间约为maxLevel+n/(1-p);最坏情况需要maxLevel*(n+1)个指针空间。就最坏情况下的空间需求而言,跳表远远大于链式散列。链式散列需要的指针空间为D+n。

不过,跳表比散列更灵活。例如,只需简单地沿着 0 级链就可以在线性时间内按关键字升序输出所有的元素。使用链式散列时,需要O(D)时间去搜索最多D个非空链表,另外需要O(nlogD)时间把有序链表按关键字升序合并。合并过程如下:

1)把链表放到一个队列中;

2)从队列中提取一对链表,把它们合并为一个有序链表,然后放入队列;

3)重复步骤2),直至队列中仅剩一个链表。其他的操作,如查找或删除其关键字最大或最小的数对,使用散列也要花费更多的时间(仅考虑平均复杂度)。

代码测试

main.cpp

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月13日17点38分
Last Version:			V1.0
Descriptions:			main()函数,控制运行所有的测试函数
*/
#include <iostream>
#include "_25hashTable.h"
#include "_26hashChains.h"int main()
{hashTableTest();hashChainsTest();return 0;
}

_25hash.h

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月23日10点18分
Last Version:			V1.0
Descriptions:			哈希---散列函数(将type K转换为非负整数)
// functions to convert from type K to nonnegative integer
// derived from similar classes in SGI STL
*/
#pragma once
#ifndef _HASH_H_
#define _HASH_H_
#include <iostream>
#include <string>
using namespace std;
/*之前名称时hash,实例化hash一直报错,然后我就各种找原因*/
/*后来发现原因可能是 C++STL源文件定义了hash类,所以我后面定义的模板就不作数,所以一直报错*/
/*因此解决方案就是:将类名称hash改为hashNode*/
template <class K>
class hashNode
{
public:size_t operator()(const int theKey) const{return size_t(theKey);}
};/*这是一个实例化*/
template<>
class hashNode<string>
{
public:size_t operator()(const string theKey) const{// Convert theKey to a nonnegative integer.unsigned long hashNodeValue = 0;int length = (int)theKey.length();for (int i = 0; i < length; i++)hashNodeValue = 5 * hashNodeValue + theKey.at(i);return size_t(hashNodeValue);}
};
/*这是一个实例化*/
template<>
class hashNode<int>
{
public:size_t operator()(const int theKey) const{return size_t(theKey);}
};
/*这是一个实例化*/
template<>
class hashNode<long>
{
public:size_t operator()(const long theKey) const{return size_t(theKey);}
};#endif

_25hashTable.h

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月23日10点18分
Last Version:			V1.0
Descriptions:			哈希表(数组存储)---使用线性探查的头文件---是无序的
// hash table using linear open addressing and division
// implements dictionary methods other than erase
*/
#pragma once
#ifndef _HASHTABLE_H_
#define _HASHTABLE_H_
#include <iostream>
#include "_1myExceptions.h"
#include "_25hash.h"  // mapping functions from K to nonnegative integer
using namespace std;
/*测试函数*/
void hashTableTest();template<class K, class E>
class hashTable
{
public:hashTable(int theDivisor = 11);~hashTable() { delete[] table; }bool empty() const { return dSize == 0; }int size() const { return dSize; }pair<const K, E>* find(const K&) const;void insert(const pair<const K, E>&);void erase(const K theKey);/*友元函数*//*输入字典*/istream& input(istream& in);
//    friend istream& operator>> <K, E>(istream& in, hashTable<K, E>& m);/*输出字典*/ostream& output(ostream& out) const;
//    friend ostream& operator<< <K, E>(ostream& out, const hashTable<K, E>& m);protected:int search(const K&) const;pair<const K, E>** table;  // hash tablehashNode<K> hashNode;              // maps type K to nonnegative integerint dSize;                 // number of pairs in dictionaryint divisor;               // hash function divisor
};/*输入字典*/
template<class K, class E>
istream& hashTable<K, E>::input(istream& in)
{int numberOfElement = 0;cout << "Please enter the number of element:";while (!(in >> numberOfElement)){in.clear();//清空标志位while (in.get() != '\n')//删除无效的输入continue;cout << "Please enter the number of element:";}K first;E second;for (int i = 0; i < numberOfElement; i++){cout << "Please enter the element " << i + 1 << ":";while (!(in >> first >> second)){in.clear();//清空标志位while (in.get() != '\n')//删除无效的输入continue;cout << "Please enter the element " << i + 1 << ":";}const pair<const K, E> element(first, second);insert(element);}return in;
}
template<class K, class E>
istream& operator>>(istream& in, hashTable<K, E>& m){m.input(in);return in;
}
/*输出字典*/
template<class K, class E>
ostream& hashTable<K, E>::output(ostream& out) const
{for (int i = 0; i < divisor; i++)if (table[i] == nullptr)cout << "nullptr" << " ";elsecout << "(" << table[i]->first << "," << table[i]->second << ")" << " ";cout << endl;return out;
}template<class K, class E>
ostream& operator<<(ostream& out, const hashTable<K, E>& m){m.output(out);return out;
}template<class K, class E>
hashTable<K, E>::hashTable(int theDivisor)
{divisor = theDivisor;dSize = 0;// allocate and initialize hash table arraytable = new pair<const K, E>*[divisor];for (int i = 0; i < divisor; i++)table[i] = nullptr;
}template<class K, class E>
int hashTable<K, E>::search(const K& theKey) const
{// Search an open addressed hash table for a pair with key theKey.// Return location of matching pair if found, otherwise return// location where a pair with key theKey may be inserted// provided the hash table is not full.int i = (int)hashNode(theKey) % divisor;  // home bucketint j = i;    // start at home bucketdo{if (table[j] == nullptr || table[j]->first == theKey)return j;j = (j + 1) % divisor;  // next bucket} while (j != i);          // returned to home bucket?return j;  // table full
}template<class K, class E>
pair<const K, E>* hashTable<K, E>::find(const K& theKey) const
{// Return pointer to matching pair.// Return nullptr if no matching pair.// search the tableint b = search(theKey);// see if a match was found at table[b]if (table[b] == nullptr || table[b]->first != theKey)return nullptr;           // no matchreturn table[b];  // matching pair
}template<class K, class E>
void hashTable<K, E>::insert(const pair<const K, E>& thePair)
{// Insert thePair into the dictionary. Overwrite existing// pair, if any, with same key.// Throw hashTableFull exception in case table is full.// search the table for a matching pairint b = search(thePair.first);// check if matching pair foundif (table[b] == nullptr){// no matching pair and table not fulltable[b] = new pair<const K, E>(thePair);dSize++;}else{// check if duplicate or table fullif (table[b]->first == thePair.first){// duplicate, change table[b]->secondtable[b]->second = thePair.second;}else // table is fullthrow hashTableFull();}
}/*删除关键字为theKey的元素*/
template<class K, class E>
void hashTable<K, E>::erase(const K theKey)
{int i = (int)hashNode(theKey) % divisor;int j = i;/*从起始桶开始寻找关键字为theKey的元素*/while (table[j] != nullptr && table[j]->first != theKey){j = (++j) % divisor;/*如果找完了所有桶都没找到以theKey为关键字的数对,说明该数对不存在,抛出异常*/if (j == i)throw illegalParameterValue("The key is not exist");}if(table[j] == nullptr)throw illegalParameterValue("The key is not exist");/*能到这里说明已经找到以theKey为关键字的数对了*/i = j;//使用i记录该数对的位置delete table[i];//将该数对删除table[i] = nullptr;j++;//找到下一个数对/*将后面的数对前移*//*不是空桶不是起始桶不是原始桶*/while (table[j] != nullptr && (int)hashNode(table[j]->first) % divisor != j && j != i){table[j - 1] = table[j];//移动后面的元素j = (++j) % divisor;}
}
#endif

_25hashTable.cpp

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月23日10点18分
Last Version:			V1.0
Descriptions:			哈希表(数组存储)---使用线性探查的cpp文件
*/
#include <iostream>
#include "_25hashTable.h"
using namespace std;void hashTableTest()
{cout << endl << "*********************************hashTableTest()函数开始**************************************" << endl;hashTable<int, int> a;//测试输入和输出cout << endl << "测试友元函数*******************************************" << endl;cout << "输入输出************************" << endl;cin >> a;cout << "hashTable a is:" << a;cout << endl << "测试成员函数*******************************************" << endl;cout << "empty()*************************" << endl;cout << "a.empty() = " << a.empty() << endl;cout << "size()**************************" << endl;cout << "a.size() = " << a.size() << endl;cout << "find()**************************" << endl;cout << "a.find(1)->second = " << a.find(1)->second << endl;cout << "insert()************************" << endl;pair<const int, int> insertData(3, 4);a.insert(insertData);cout << "hashTable a is:" << a;cout << "erase()*************************" << endl;a.erase(2);cout << "hashTable a is:" << a;cout << "*********************************hashTableTest()函数结束**************************************" << endl;
}

_23dictionary.h

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月22日09点17分
Last Version:			V1.0
Descriptions:			字典的抽象类
*/
/*
pair:介绍:是将2个数据组合成一组数据,是一个结构体,主要的两个成员变量first和second,分别存储两个数据.使用:使用std命名空间引入对组std::pair
*/
#pragma once
#ifndef _DICTIONARY_H_
#define _DICTIONARY_H_
using namespace std;
template<class K,class E>
class dictionary
{
public:virtual ~dictionary() {}/*返回为true,当且仅当字典为空*/virtual bool empty() const = 0;/*返回字典中数对的数目*/virtual int size() const = 0;/*返回匹配数对的指针*/virtual pair<const K, E>* find(const K&) const = 0;/*删除匹配的数对*/virtual void erase(const K&) = 0;/*往字典中插入一个数对*/virtual void insert(const pair<const K, E>&) = 0;
};
#endif

_23pairNode.h

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月22日09点17分
Last Version:			V1.0
Descriptions:			是字典的一个节点
*/
#pragma once
#ifndef _PAIRNODE_H_
#define _PAIRNODE_H_
using namespace std;
template <class K, class E>
struct pairNode
{typedef pair<const K, E> pairType;pairType element;pairNode<K, E>* next;pairNode(const pairType& thePair) :element(thePair) {}pairNode(const pairType& thePair, pairNode<K, E>* theNext):element(thePair) {next = theNext;}
};
#endif

_23dictionaryChain.h

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月22日09点17分
Last Version:			V1.0
Descriptions:			使用链表实现的字典的头文件---是有序的
*/
#pragma once
#ifndef _DICTIONARY_CHAIN_H_
#define _DICTIONARY_CHAIN_H_
#include <iostream>
#include "_23pairNode.h"
#include "_23dictionary.h"
using namespace std;
/*测试函数*/
void dictionaryChainTest();template<class K, class E>
class dictionaryChain : public dictionary<K, E>
{
public:/*成员函数*//*构造函数和析构函数*/dictionaryChain() { firstNode = nullptr; dSize = 0; }~dictionaryChain();/*其他成员函数*/bool empty() const { return dSize == 0; }int size() const { return dSize; }pair<const K, E>* find(const K&) const;void erase(const K&);void insert(const pair<const K, E>&);/*友元函数*//*输入字典*/istream& input(istream& in);
//    friend istream& operator>> <K,E>(istream& in, dictionaryChain<K,E>& m);/*输出字典*/ostream& output(ostream& out) const;
//    friend ostream& operator<< <K,E>(ostream& out, const dictionaryChain<K,E>& m);
protected:pairNode<K, E>* firstNode;  // pointer to first node in chainint dSize;                 // number of elements in dictionary
};/*输入字典*/
template<class K, class E>
istream& dictionaryChain<K, E>::input(istream& in)
//istream& operator>>(istream& in, dictionaryChain<K,E>& m)
{int numberOfElement = 0;cout << "Please enter the number of element:";while (!(in >> numberOfElement)){in.clear();//清空标志位while (in.get() != '\n')//删除无效的输入continue;cout << "Please enter the number of element:";}pair<K, E> element;for (int i = 0; i < numberOfElement; i++){cout << "Please enter the element " << i + 1 << ":";while (!(in >> element.first >> element.second)){in.clear();//清空标志位while (in.get() != '\n')//删除无效的输入continue;cout << "Please enter the element " << i + 1 << ":";}insert(element);}return in;
}
template<class K, class E>
istream& operator>>(istream& in, dictionaryChain<K,E>& m){m.input(in);return in;
}
/*输出字典*/
template<class K, class E>
ostream& dictionaryChain<K, E>::output(ostream& out) const
//ostream& operator<<(ostream& out, const dictionaryChain<K,E>& m)
{pairNode<K, E>* currentNode = firstNode;while (currentNode != nullptr){out << "(" << currentNode->element.first << " ," << currentNode->element.second << ")" << " ";currentNode = currentNode->next;}cout << endl;return out;
}
template<class K, class E>
ostream& operator<<(ostream& out, const dictionaryChain<K,E>& m){m.output(out);return out;
}
/*析构函数*/
template<class K,class E>
dictionaryChain<K,E>::~dictionaryChain()
{pairNode<K, E>* nextNode;while (firstNode != nullptr){nextNode = firstNode->next;delete firstNode;firstNode = nextNode;}
}
/*寻找关键字为key的数对并返回*/
template<class K, class E>
pair<const K, E>* dictionaryChain<K, E>::find(const K& key) const
{//返回匹配的数对的指针//如果不存在匹配的数对,则返回nullptrpairNode<K, E>* currentNode = firstNode;while (currentNode != nullptr && currentNode->element.first != key)currentNode = currentNode->next;if (currentNode != nullptr && currentNode->element.first == key)return &currentNode->element;return nullptr;//如果没找到就返回nullptr
}
/*删除关键字为key的数对*/
template<class K, class E>
void dictionaryChain<K, E>::erase(const K& key)
{//删除关键字为key的数对pairNode<K, E>* p = firstNode;//存储的是插入元素之后的位置pairNode<K, E>* tp = nullptr;//存储的是插入元素之前的位置//搜索关键字为key的数对while (p != nullptr && p->element.first < key){tp = p;p = p->next;}//确定是否匹配if (p != nullptr && p->element.first == key)if (tp == nullptr) firstNode = p->next;//p是第一个节点else tp->next = p->next;delete p;dSize--;
}
/*往字典中插入data,覆盖已经存在的匹配的数对*/
template<class K,class E>
void dictionaryChain<K, E>::insert(const pair<const K, E>& data)
{pairNode<K, E>* p = firstNode;//存储的是插入元素之后的位置pairNode<K, E>* tp = nullptr;//存储的是插入元素之前的位置while (p != nullptr && p->element.first < data.first){tp = p;p = p->next;}//检查是否有匹配的数对if (p != nullptr && p->element.first == data.first){p->element.second = data.second;return;}//无匹配的数对,为thePair建立新节点pairNode<K, E>* newNode = new pairNode<K, E>(data, p);//在tp之后插入新节点if (tp == nullptr) firstNode = newNode;else tp->next = newNode;dSize++;return;
}#endif

_26hashChains.h

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月24日11点16分
Last Version:			V1.0
Descriptions:			哈希表---链式散列(链表存储)---头文件---是无序的
// hash table using sorted chains and division
// implements all dictionary methods
*/
#pragma once
#ifndef _HASHCHAINDS_H_
#define _HASHCHAINDS_H_
#include <iostream>
#include "_23dictionary.h"
#include "_23dictionaryChain.h"
#include "_25hash.h"
using namespace std;
/*测试函数*/
void hashChainsTest();template<class K, class E>
class hashChains : public dictionary<K, E>
{
public:hashChains(int theDivisor = 11){divisor = theDivisor;dSize = 0;// allocate and initialize hash table arraytable = new dictionaryChain<K, E>[divisor];}~hashChains() { delete[] table; }bool empty() const { return dSize == 0; }int size() const { return dSize; }pair<const K, E>* find(const K& theKey) const{return table[hashNode(theKey) % divisor].find(theKey);}void insert(const pair<const K, E>& thePair){int homeBucket = (int)hashNode(thePair.first) % divisor;int homeSize = table[homeBucket].size();table[homeBucket].insert(thePair);if (table[homeBucket].size() > homeSize)dSize++;}void erase(const K& theKey){table[hashNode(theKey) % divisor].erase(theKey);}/*友元函数*//*输入字典*/istream& input(istream& in);
//    friend istream& operator>> <K, E>(istream& in, hashChains<K, E>& m);/*输出字典*/ostream& output(ostream& out) const;
//    friend ostream& operator<< <K, E>(ostream& out, const hashChains<K, E>& m);
protected:dictionaryChain<K, E>* table;  // hash tablehashNode<K> hashNode;              // maps type K to nonnegative integerint dSize;                 // number of elements in listint divisor;               // hash function divisor
};/*输入字典*/
template<class K, class E>
istream& hashChains<K, E>::input(istream& in)
{int numberOfElement = 0;cout << "Please enter the number of element:";while (!(in >> numberOfElement)){in.clear();//清空标志位while (in.get() != '\n')//删除无效的输入continue;cout << "Please enter the number of element:";}K first;E second;for (int i = 0; i < numberOfElement; i++){cout << "Please enter the element " << i + 1 << ":";while (!(in >> first >> second)){in.clear();//清空标志位while (in.get() != '\n')//删除无效的输入continue;cout << "Please enter the element " << i + 1 << ":";}const pair<const K, E> element(first, second);cout << element.first << endl;cout << element.second << endl;insert(element);}return in;
}
template<class K, class E>
istream& operator>>(istream& in, hashChains<K, E>& m){m.input(in);return in;
}
/*输出字典*/
template<class K, class E>
ostream& hashChains<K, E>::output(ostream& out) const
{for (int i = 0; i < divisor; i++){if (table[i].size() == 0)cout << "nullptr" << endl;elsecout << table[i] << " ";}return out;
}
template<class K, class E>
ostream& operator<<(ostream& out, const hashChains<K, E>& m){m.output(out);return out;
}#endif

_26hashChains.cpp

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月24日11点16分
Last Version:			V1.0
Descriptions:			哈希表---链式散列(链表存储)---cpp文件
// hash table using sorted chains and division
// implements all dictionary methods
*/
#include <iostream>
#include "_26hashChains.h"
using namespace std;void hashChainsTest()
{cout << endl << "*********************************hashChainsTest()函数开始*************************************" << endl;hashChains<int, int> a;//测试输入和输出cout << endl << "测试友元函数*******************************************" << endl;cout << "输入输出************************" << endl;cin >> a;cout << "hashChains a is:" << a;cout << endl << "测试成员函数*******************************************" << endl;cout << "empty()*************************" << endl;cout << "a.empty() = " << a.empty() << endl;cout << "size()**************************" << endl;cout << "a.size() = " << a.size() << endl;cout << "find()**************************" << endl;cout << "a.find(1)->second = " << a.find(1)->second << endl;cout << "insert()************************" << endl;pair<const int, int> insertData(3, 4);a.insert(insertData);cout << "hashChains a is:" << a;cout << "erase()*************************" << endl;a.erase(1);cout << "hashChains a is:" << a;cout << "*********************************hashChainsTest()函数结束*************************************" << endl;
}

_1myExceptions.h

/*
Project name :			allAlgorithmsTest
Last modified Date:		2022年8月13日17点38分
Last Version:			V1.0
Descriptions:			综合各种异常
*/
#pragma once
#ifndef _MYEXCEPTIONS_H_
#define _MYEXCEPTIONS_H_
#include <string>
#include<iostream>using namespace std;// illegal parameter value
class illegalParameterValue
{
public:illegalParameterValue(string theMessage = "Illegal parameter value"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};// illegal input data
class illegalInputData
{
public:illegalInputData(string theMessage = "Illegal data input"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};// illegal index
class illegalIndex
{
public:illegalIndex(string theMessage = "Illegal index"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};// matrix index out of bounds
class matrixIndexOutOfBounds
{
public:matrixIndexOutOfBounds(string theMessage = "Matrix index out of bounds"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};// matrix size mismatch
class matrixSizeMismatch
{
public:matrixSizeMismatch(string theMessage ="The size of the two matrics doesn't match"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};// stack is empty
class stackEmpty
{
public:stackEmpty(string theMessage ="Invalid operation on empty stack"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};// queue is empty
class queueEmpty
{
public:queueEmpty(string theMessage ="Invalid operation on empty queue"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};// hash table is full
class hashTableFull
{
public:hashTableFull(string theMessage ="The hash table is full"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};// edge weight undefined
class undefinedEdgeWeight
{
public:undefinedEdgeWeight(string theMessage ="No edge weights defined"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};// method undefined
class undefinedMethod
{
public:undefinedMethod(string theMessage ="This method is undefined"){message = theMessage;}void outputMessage() {cout << message << endl;}
private:string message;
};
#endif

运行结果

"C:\Users\15495\Documents\Jasmine\prj\_Algorithm\Data Structures, Algorithms and Applications in C++\_22hash\cmake-build-debug\_22hash.exe"*********************************hashTableTest()函数开始**************************************测试友元函数*******************************************
输入输出************************
Please enter the number of element:3
Please enter the element 1:1 2
Please enter the element 2:2 4
Please enter the element 3:5 6
hashTable a is:nullptr (1,2) (2,4) nullptr nullptr (5,6) nullptr nullptr nullptr nullptr nullptr 测试成员函数*******************************************
empty()*************************
a.empty() = 0
size()**************************
a.size() = 3
find()**************************
a.find(1)->second = 2
insert()************************
hashTable a is:nullptr (1,2) (2,4) (3,4) nullptr (5,6) nullptr nullptr nullptr nullptr nullptr 
erase()*************************
hashTable a is:nullptr (1,2) nullptr (3,4) nullptr (5,6) nullptr nullptr nullptr nullptr nullptr 
*********************************hashTableTest()函数结束***********************************************************************hashChainsTest()函数开始*************************************测试友元函数*******************************************
输入输出************************
Please enter the number of element:3
Please enter the element 1:1 2
1
2
Please enter the element 2:3 5
3
5
Please enter the element 3:6 9
6
9
hashChains a is:nullptr
(1 ,2)nullptr
(3 ,5)nullptr
nullptr
(6 ,9)nullptr
nullptr
nullptr
nullptr测试成员函数*******************************************
empty()*************************
a.empty() = 0
size()**************************
a.size() = 3
find()**************************
a.find(1)->second = 2
insert()************************
hashChains a is:nullptr
(1 ,2)nullptr
(3 ,4)nullptr
nullptr
(6 ,9)nullptr
nullptr
nullptr
nullptr
erase()*************************
hashChains a is:nullptr
nullptr
nullptr
(3 ,4)nullptr
nullptr
(6 ,9)nullptr
nullptr
nullptr
nullptr
*********************************hashChainsTest()函数结束*************************************Process finished with exit code 0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/167610.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html table样式的设计 表格边框修饰

<!DOCTYPE html> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetutf-8" /> <title>今日小说排行榜</title> <style> table {border-collapse: collapse;border: 4px double red; /*…

Python之Pygame游戏编程详解

一、介绍 1.1 定义 Pygame是一种流行的Python游戏开发库&#xff0c;它提供了许多功能&#xff0c;使开发人员可以轻松创建2D游戏。它具有良好的跨平台支持&#xff0c;可以在多个操作系统上运行&#xff0c;例如Windows&#xff0c;MacOS和Linux。在本文中&#xff0c;我们将…

单链表的反转?太细了哥们!细到离谱!

单链表的反转&#xff08;面试常出&#xff09;&#xff1a; ​ 单链表的反转&#xff0c;可以通过很多种方法实现。包括迭代法&#xff0c;递归法&#xff0c; 迭代法&#xff1a; 定义三个指针&#xff1a;prev、current和next&#xff0c;它们分别表示前一个节点、当前节点…

NSGA-III求解微电网多目标优化调度(MATLAB)

一、NSGA-III简介 NSGA-III算法由Kalyanmoy Deb和Himanshu Jain于 2014年提出。 参考文献&#xff1a;Deb K , Jain H . An Evolutionary Many-Objective Optimization Algorithm Using Reference Point-Based Nondominated Sorting Approach, Part I: Solving Problems With …

[chroot+seccomp逃逸] THUCTF2019 之 固若金汤

题目分析 附件为一个源码, 其中注释我都写好了, 主要就讲关键的知识点. #define _GNU_SOURCE#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <errno.h> #include <sched.h> #include <uni…

【C/PTA —— 10.函数1(课外实践)】

C/PTA —— 10.函数1&#xff08;课外实践&#xff09; 一.函数题6-1 符号函数6-2 求排列数6-3 求一个大于10的n位整数w的后n-1位的数&#xff0c;并作为函数值返回。6-4 其右上三角&#xff08;含主对角线&#xff09;元素之和。6-5 字符串比较6-6 使用函数求素数和6-7 使用函…

【电子通识】为什么说做产品不是简单的将不同的技术进行搭积木?

很多人说做产品的硬件工程师&#xff0c;其实就是将专项技术工程师已经调好的模块进行拼接。类似于小孩将积木搭成一个房子的形状&#xff0c;虽然不同人搭的房子风格迥异&#xff0c;但所使用的原材料却都是一样的。 首先我并不同意这种看法&#xff0c;原因是产品工程师是需要…

JVM深入理解

JVM深入理解&#xff08;一&#xff09; JVM是什么 JRE、JDK和JVM 的关系 JVM原理 1、JVM是什么&#xff1f; JVM是Java Virtual Machine&#xff08;Java虚拟机&#xff09;的缩写&#xff0c;由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组…

MediaCodec详解

MediaCodec 是Android平台提供的一个API&#xff0c;用于对音频和视频数据进行编码&#xff08;转换为不同的格式&#xff09;和解码&#xff08;从一种格式转换回原始数据&#xff09;。它是Android 4.1&#xff08;API级别16&#xff09;及以上版本的一部分&#xff0c;允许开…

【教3妹学编程-算法题】统计和小于目标的下标对数目

2哥 : 3妹&#xff0c;OpenAI的宫斗剧迎来了大结局&#xff01;OpenAI宣布阿尔特曼复职CEO&#xff0c;董事会重组 3妹&#xff1a;啊&#xff1f;到底谁才是幕后操纵者啊&#xff0c;有咩有揪出来 2哥 : 也不是很清楚&#xff0c;据说在被开除的几周前&#xff0c;前CEO曾谴责…

Linux 家目录和根目录

摘要&#xff1a; 在 Linux 操作系统中&#xff0c;家目录和根目录是两个非常重要的概念。它们是 Linux 文件系统中的两个关键节点&#xff0c;为用户和系统进程提供存储、管理和访问文件和目录的接口。本文旨在深入探讨和理解这两个目录的结构、功能和使用方式&#xff0c;同时…

行情分析 - - 加密货币市场大盘走势(11.24)

大饼昨日震荡幅度很小&#xff0c;而今天延续昨日的空头思路。当然如果从MACD日线来看&#xff0c;处于上涨趋势&#xff0c;稳健的可以选择观望等待。空头思路是因为目前EMA21均线和EMA55均线依然保持很远&#xff0c;最近两个月BTC上涨40%&#xff0c;而最近持续保持高位很快…

Twincat使用:EtherCAT通信扫描硬件设备链接PLC变量

EtherCAT通信采用主从架构&#xff0c;其中一个主站设备负责整个EtherCAT网络的管理和控制&#xff0c;而从站设备则负责在数据环网上传递数据。 主站设备可以是计算机、工控机、PLC等&#xff0c; 而从站设备可以是传感器、执行器、驱动器等。 EL3102:MDP5001_300_CF8D1684;…

关于爱普生L3219彩色喷墨打印机打印过程中噪声过大的几点缓解方法

故障描述&#xff1a; 一台新购买的爱普生L3219使用过程中出现了噪声过大的问题&#xff0c;每次打印或者复印都或有明显的噪音过大的现象&#xff0c;目测观察大概是打印机字车左右来回移动的时候剐蹭滑道的问题&#xff0c;与经销商沟通后由经销商联系上级供货商更换一台全新…

CAN实验

CAN 寄存器 HAL库函数 代码 #include "./BSP/CAN/can.h"CAN_HandleTypeDef g_can1_handle; CAN_TxHeaderTypeDef g_can1_txheader; CAN_RxHeaderTypeDef g_can1_rxheader;/* STM32F103 TS1 8 TS2 7 BRP 3 波特率&#xff1a;36000 / [(9 8 1) * 4] 500Kbps …

Qt学习(2)

1.QObject 只有继承了QObject类的类&#xff0c;才具有信号槽的能力。所以&#xff0c;为了使用信号槽&#xff0c;必须继承QObject。凡是QObject类&#xff08;不管是直接子类还是间接子类&#xff09;&#xff0c;都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽&…

【Java 进阶篇】Jedis 操作 String:Redis中的基础数据类型

在Redis中&#xff0c;String是最基础的数据类型之一&#xff0c;而Jedis作为Java开发者与Redis交互的利器&#xff0c;提供了丰富的API来操作String。本文将深入介绍Jedis如何操作Redis中的String类型数据&#xff0c;通过生动的代码示例和详细的解释&#xff0c;让你轻松掌握…

数字图像处理(实践篇)一 将图像中的指定目标用bBox框起来吧!

目录 一 实现方法 二 涉及的OpenCV函数 三 代码 四 效果图 一 实现方法 ①利用OTSU方法将前景与背景分割。 ②使用连通区域分析可以将具有相同像素值且位置相邻的前景像素点组成的图像区域识别。 ③画bbox。 ④显示结果。 二 涉及的OpenCV函数 ① OpenCV提供了cv2.th…

convertRect:toView 方法注意事项

这是在网上找到的一张图 我们开发中有时候会用到左边转换&#xff0c;convertRect:toView 通常情况下&#xff0c;我们回这样使用 CGRect newRect [a convertRect:originframe toView:c];其中newRect和 originframe的size相同&#xff0c;只改变origin newRect.origin a…

系统优化软件Bitsum Process Lasso Pro v12.4,供大家学习研究参考

1、自动或手动调整进程优先级;将不需要抑制的进程添加到排除列表; 2、设置动态提升前台运行的进程/线程的优先级 3、设置进程黑名单,禁止无用进程(机制为启动即结束,而非拦截其启动)。 4、优化I/O优先级以及电源模式自动化。 5、ProBalance功能。翻译成中文是“进程平衡…