Реализация шаблона класса C++ метода открытой адресации для хеш-таблиц (хеш-таблиц)

глубокое обучение

1. Введение

\

Хеш-таблица — это структура данных, доступ к которой осуществляется напрямую в соответствии со значением ключа.

Доступ к записям путем сопоставления значения ключа с местоположением в таблице для ускорения поиска.

Эта функция отображения называется хеш-функцией, а массив записей называется хеш-таблицей.

\

Чтобы сделать очень неточную аналогию:

NBA2K — это игра, которая пользуется большим спросом у любителей баскетбола. Он имеет боевую ценность каждого игрока НБА, чем выше, тем лучше.

Теперь предположим, что в компьютере размещено 2К значений боя от 1 до 100 (не все из них имеют значение 10, например, может не быть значения 10)

Используя боевое значение в качестве ключевого слова, я хочу найти человека, чье значение 2K равно 1? Следуйте традиционному методу:

Если 2К значений хранятся в компьютере не по порядку, чтобы найти 1, можно только вынуть их по одному и сравнить с 1, и тогда они смогут найти того, кто хранил там 1;

Если значения 2k хранятся в компьютере упорядоченным образом, вы можете найти место, где хранится 1, например, путем деления пополам, поиска и сравнения, а затем найти человека.

\

Есть ли способ узнать, кто тот человек, чье значение 2K равно 1, без больших затрат времени и без необходимости много раз сравнивать и искать?

Ответ — хранилище хеш-таблиц, где ключом является боевое значение, а расположение хранилища известно с учетом боевого значения! !

\

Но хэши иногда конфликтуют, то есть даже если ключевые слова разные, адреса, вычисляемые хеш-функцией, могут совпадать, что на данный момент смущает.

Существует множество способов разрешения конфликтов, таких как метод цепочек адресов (метод застежки-молнии), создание общедоступной области переполнения и т. д. В этой статье в основном используются следующие:

\

Метод открытой адресации: при возникновении коллизии в хеш-таблице формируется последовательность проб (проб) с использованием какой-либо техники проб (также называемой пробой).

Hi=(H(key) + di) MOD m,i=1,2,...,k(k1. di=1,2,3,…,m-1, что называется линейным обнаружением и перехешированием;
2. di=1^2,-1^2,2^2,-2^2, ⑶^2,...,±(k)^2,(k3. di = псевдослучайная числовая последовательность, называемая псевдослучайным обнаружением и повторным хешированием.


Метод повторного хеширования: то есть вычисление другого адреса хеш-функции, когда синоним имеет конфликт адресов, до тех пор, пока конфликт не исчезнет. \

\

\

Две программные реализации

\

Подготовьте тестовые файлы:

Имеется 10 записей, начиная со строки 2, в каждой строке есть два числа, первое число — значение 2K, а второе — связанное лицо.

Например, в строке 2 человека, соответствующего значению 2K 6, называют роскошно богатым.

Все, что нужно сделать сейчас, это знать эту информацию и сохранить ее в компьютере в подходящем месте.

В следующий раз, когда вы спросите, кто тот человек, чье значение 2K равно 6, компьютер сразу же узнает, где взять соответствующую запись.

\

\

\

\

1. Создайте HashTable.h

\

#ifndef HASH.H
#define HASH.H
const int SUCCESS=1;//插入元素成功
const int UNSUCCESS=0;
const int EXIST=-1;//哈希表中有相同关键字元素,不再插入
const int MAX=3;
//hashsize数组存放哈希表需要重建时下一个容量大小
int hashsize[MAX]={11,19,37};

template <typename T>
class HashTable{
private:
	T *elem;
	int count,length;//数据个数,哈希表容量
	int sizeindex;//hashsize[sizeindex]为当前容量
	int *random_d;//增量随机数数组指针
	int Hash(KeyType key){
		return key%length;
	}
	int Hash2(KeyType key){
		return key%(length-1);//双散列函数探测法第二个哈希函数
	}
	void Random(){//随机探测法中建立伪随机数组
		bool *a=new bool[length];
		random_d=new int[length];
		int i;
		for(i=1;i<length;i++)
			a[i]=false;
		srand(time(0));
		for(i=1;i<length;i++){
			do{
				random_d[i]=rand()%(length-1)+1;//给rando[i]赋值为1到length-1
				if(!a[random_d[i]])
					a[random_d[i]]=true;
				else
					random_d[i]=0;
			}while(random_d[i]==0);//赋值失败重新赋值
//			cout<<"random_d["<<i<<"]="<<random_d[i]<<"  ";
		}
		cout<<endl;
		delete []a;
	}
	int d(int i,KeyType key){//增量序列函数,返回第i次冲突的增量
	    switch(d_type){
			//线性探测法:1,2,3···
	    	case 0:return i;
	    	//二次探测法:1,-1,4,-4,9,-9···
			case 1:return ((i+1)/2)*((i+1)/2)*(int)pow(-1,i-1);
			//双散列探测法
			case 2:return i*Hash2(key);
			//随机探测法
			case 3:return random_d[i];
			//默认线性探测
			default: return i;
	    }
		
	}
	void FindAnotherPos(KeyType key,int &p,int i){//开地址法求关键字key的第i次冲突地址p
		p=(Hash(key)+d(i,key))%length;
		if(p<0)//求余是负,在二次探测可能出现
			p=p+length;
	}
	void RecreateHashTable(){
		int i,len=length;//哈希表原容量
		T *p=elem;
		sizeindex++;//取存储容量为hashsize[]中下一个序列数
		if(sizeindex<MAX){
			length=hashsize[sizeindex];
			elem=new T[length];
			assert(elem!=NULL);
			for(i=0;i<length;i++)
				elem[i].key=EMPTY;//未填有数据
			for(i=0;i<len;i++)//将p所指原elem中的数据插入到重建的哈希表中
				if(p[i].key!=EMPTY&&p[i].key!=REMOVE)//原哈希表【i】有数据
					InsertHash(p[i]);
			delete []p;
			if(d_type==3)
				Random();//重建伪随机增量
		}
	}
public:
	int d_type;//探测法类型
	HashTable(){
		count=0;
		sizeindex=0;
		length=hashsize[sizeindex];
		elem= new T[length];
		assert(elem!=NULL);
		for(int i=0;i<length;i++)
			elem[i].key=EMPTY;//还没填充元素
		cout<<"     依下面提示序号选择冲突时探测法:"<<endl;
		cout<<"(0:线性;1:二次;2:再散列;3:随机):"<<endl;
		cin>>d_type;
		switch(d_type){
			case 0:cout<<"产生冲突时你选择使用线性探测法解决!"<<endl;break;
			case 1:cout<<"产生冲突时你选择使用二次探测法解决!"<<endl;break;
			case 2:cout<<"产生冲突时你选择用再散列探测法解决!"<<endl;
				  if(d_type==3)
				     Random();
				  else random_d=NULL;
					break;
			case 3:cout<<"产生冲突时你选择使用随机探测法解决!"<<endl;break;
			default:cout<<endl;
		}
		
	}
	~HashTable(){
		if(elem!=NULL)
			delete []elem;
		if(d_type==3)
			delete []random_d;
	}
	bool SearchHash(KeyType key,int &p,int &c){
		/*  查找关键字为key的元素,若查找成功
			p代表待查数据在表中位置,返回success;
            否则,p代表插入位置,返回unsuccess;
            c冲突次数,初值0,在建表插入时用
		*/
		int c1,remove=-1;//存放第一个删除地址
		p=Hash(key);//求得哈希地址
		//该位置被删除或者该位置有数据但不相同
		while(elem[p].key==REMOVE||(elem[p].key!=EMPTY)&&(key!=elem[p].key))
		{
			//该位置数据被删除且是第一个
			if(elem[p].key==REMOVE&&remove==-1){
				remove=p;//该位置存放在remove中
				c1=c;//冲突次数存放在c1中
			}
			//冲突数加1
			c++;
			//在哈希表中可能找到插入位置
			if(c<=hashsize[sizeindex]/2)
				FindAnotherPos(key,p,c);
			else
				break;
		}
		if(key==elem[p].key)
			return true;
		else{
				if(remove!=-1){//在查找过程中碰到曾删除位置
					p=remove;//设为插入位置
					c=c1;//找到此位置时的冲突次数
				}
				return false;//p指示的是插入位置
		}
	}
	int InsertHash(T e){
		int p,c=0;
		//先查找,有不插入且p指向查到的位置;没有则P指向该插入的位置
		if(SearchHash(e.key,p,c))
			return EXIST;
		else{
			if(c<hashsize[sizeindex]/2){
				elem[p]=e;
				++count;
				return SUCCESS;
			}
			else{
				cout<<"插着插着可能不够,暂停!!"<<endl;
				cout<<"哈希地址顺序遍历此时哈希表,准备重建:"<<endl;
				TraverseHash(Visit);
				cout<<"重建哈希表如下:"<<endl;
				RecreateHashTable();
				cout<<endl;
				return UNSUCCESS;
				
			}
		}
	}
	bool DeleteHash(KeyType key,T &e){
		int p,c;
		if(SearchHash(key,p,c)){
			e=elem[p];
			elem[p].key=REMOVE;//设置删除标志!
			--count;
			return true;
		}
		else return false;
	}
	T GetElem(int i)const{
		return elem[i];
	}
	void TraverseHash(void(*visit)(int ,T *))const{
		//按照哈希地址顺序遍历表
		int i;
		cout<<"哈希地址0~"<<length-1<<endl;
		for(i=0;i<length;i++)
			if(elem[i].key!=EMPTY&&elem[i].key!=REMOVE)
				visit(i,&elem[i]);
		cout<<endl;
	}
};
#endif


\

\

2. Создайте заголовочный файл func.cpp

\

struct DATA{
	KeyType key;//关键字必有
	string star;
};
void Visit(int i,DATA *c){
	cout<<"["<<i<<"]: "<<'('<<c->key<<", "<<c->star<<")"<<endl;
}
void Visit(DATA c){
	cout<<'('<<c.key<<","<<c.star<<")";
}
void InputFromFile(ifstream &f,DATA &c){
	
	f>>c.key>>c.star;
	
}
void InputKey(int &k){
	cin>>k;
}


\

\

\

\

3. Процедура испытаний

\

#include <iostream>
#include <fstream>
#include <cmath>
#include <string>
#include <ctime>
#include <assert.h>
using namespace std;
const int EMPTY=0;//尚未填充数据标志
const int REMOVE=-1;//数据删除标志
typedef int KeyType;//关键字类型
#include "func.cpp"
#include "HashTable.h"
int main()
{
	HashTable<DATA> h;
	int i, j, n, p=0;
	bool m;
	DATA e;
	KeyType k;
	ifstream fin("test.txt");
	fin>>n;
	for(i=0; i<n; i++)
	{
		InputFromFile(fin, e);
		j=h.InsertHash(e);
		if(j==EXIST)
		{
			cout<<"有人抢着要(已经有主)的关键字(2K战斗力):"<<e.key<<",妄图再插入成为: ";
			Visit(e);
			cout<<endl;
		}
		if(j==UNSUCCESS)
			j=h.InsertHash(e);
	}
	fin.close();
	cout<<endl;
	cout<<"按哈希地址的顺序遍历哈希表:"<<endl;
	h.TraverseHash(Visit);
	if(h.d_type==1)
	{
		cout<<"请输入待删除球星的关键字(2K战斗力):";
		InputKey(k);
		m=h.DeleteHash(k, e);
		if(m)
		{
			cout<<"成功删除该元素";
			Visit(e);
			cout<<endl;
		}
	}
	cout<<"请输入待查找球星的关键字(2K战斗力):";
	InputKey(k);
	n=0;
	j=h.SearchHash(k, p, n);
	if(j==SUCCESS)
	{
		Visit(h.GetElem(p));
		cout<<endl;
	}
	else
		cout<<"未找到"<<endl;
	if(h.d_type==1)
	{
		cout<<"插入球星,请输入待插入球星的关键字(2K战斗力):";
		InputKey(e.key);
		cout<<"请输入待插入球星的名字:";
		cin>>e.star;
		j=h.InsertHash(e);
		cout<<j<<endl;
		cout<<endl;
		cout<<"按哈希地址的顺序遍历哈希表:"<<endl;
		h.TraverseHash(Visit);
	}
	
	return 0;
}


\

\

4. Частичные результаты испытаний

\

\

\

\

\

\

\

\