数据结构--散列表查找(哈希表)

对于查找,有顺序表直接遍历,有折半查找直到查找成功。但是这都需要不断的比较,每一次查找都需要重新遍历,所以当数据庞大时是非常耗时的。散列表是一种可以避免多次比较,直接通过关键字就可以得到要查找的记录内存存储位置。例如就像是一个函数,每一个自变量都对应着一个函数值,即

                                                                    存储位置=f(关键字)

这属于散列技术,不需要比较就可以获得需要查找的存储位置。

散列表查找步骤:

  1. 在存储时,通过自定义设置的散列函数计算记录的散列地址,并按照此地址存储该记录。
  2. 在查找记录时,可以通过同样的散列函数计算散列地址,按照此地址访问散列表

 对于如何前面提到的散列函数,要求是计算简单,散列地址分布均匀。下面有几种方法:

  1. 直接定址法:例如现在要求对0~100岁的人口数字统计表。那么我们对年龄这个关键字就可以直接用年龄的数字作为地址。此时f(key)=key;也就是说可以取关键字的某个线性函数值为散列地址。但此方法只适用于知道关键字的分布情况,适合查找表较小而且连续的情况。
  2. 数字分析法:可以直接去某一段数字的一小截,例如手机号码136xxxx8744其中8744是真正的用户号,就可以用来当关键字。这个方法适用于关键字位数比较大而且关键字分布均匀,位数的均匀的情况。
  3. 平方取中法:这个方法最简单直接平方再加起来然后取中间部分。例如1234,则平方为1522756,中间部分为227,则277就是散列地址。此方法适用于不知道关键字的分布,而且位数不是很大的情况。
  4. 折叠法:将关键字从左到右分割成几段相等的部分再把这几部分相加起来,然后取后几位作为散列地址。例如:9876543210,987|654|321|0,求和987+654+321+0=1962,再取后3位962为散列地址。此方法适用于关键字位数比较多的情况且不需要知道关键字的分布。
  5. 除留余数法:这种是最常用的构造散列函数方法。对于散列表长为m的散列函数:   f(key)=key mod p(p≤m);但是这种方法容易产生冲突。冲突就是两个不同的关键字的散列地址相同,产生冲突的两个关键字称为同义词。例如:12mod11,144mod11都是为1,即散列地址相同。                                                                 

处理散列冲突:

代码详解:

  1. 开放定址法:所谓开放定址就是一旦发生了冲突就去寻找下一个空的散列地址来存储。例如:散列表长为11,f(12)=1;当需要存储144时,因为f(144)=1出现冲突,则继续寻找下一个空地址f(f(144)+1)mod12。
  2. 链地址法:此法是将所有同义词都存储在同一个单链表中,在散列表中只存储所有同义词子表的头指针。不需要因为冲突就换地址,但这也带来了查找时遍历单链表的性能损耗。
#include<iostream>
using namespace std;

#define OK 1
#define SUCCESS 1
#define FAIL 0
#define HASHSIZE 12 //哈希表大小
#define NULLKEY '#'

struct Node {//元素结点
	char name[100];
	float high;
	float weight;
};

struct HashTable {//哈希表
	Node *elem;//声明元素结点
	int count;//结点数量
};

int m;

//初始化
bool InitHashTable(HashTable *H) {
	int i;
	H->count = HASHSIZE;
	m = HASHSIZE;
	H->elem = new Node[HASHSIZE];//建立元素对象
								 //初始化元素
	for (i = 0; i < H->count; i++)
		H->elem[i].name[0] = NULLKEY;//把名字初始化为 # 
	return OK;
}

//获取对应的地址
int Hash(int key) {//散列函数
	return key%HASHSIZE;  //除留余数法
}

//插入元素到哈希表
void InsertHash(HashTable *H) {
	char key[10]; //关键字(姓名)
	cin >> key;

	int addr = Hash(key[0]);//获取哈希地址

	while (H->elem[addr].name[0] != NULLKEY)//如果该地址已存在元素,则利用公式找到下一个地址,然后进行存储
		addr = (addr + 1) % m;

	//对元素进行赋值
	strcpy_s(H->elem[addr].name, key);
	cin >> H->elem[addr].high;
	cin >> H->elem[addr].weight;
}

//利用关键字在哈希表搜索
bool SearchHash(HashTable *H, int &addr) {
	char key[10];
	cin >> key; //输入关键字
	addr = Hash(key[0]);//利用散列公式获取关键字对应的地址

	while (strcmp(key, H->elem[addr].name) != 0) {//如果该地址没有存放当前搜索的关键字,
		addr = (addr + 1) % m;//则利用冲突公式找到下一个地址,再进行判断
		if (addr == Hash(key[0]) || H->elem[addr].name[0] == '#') {//如果遍历直到返回到原来的地址,或者找到该地址存储的关键字为空,则查找失败
			return FAIL;
		}
	}
	return SUCCESS;
}

int main() {
	HashTable *h = new HashTable;
	InitHashTable(h);

	int num, addr;
	cin >> num;//插入元素数量
	while (num--)InsertHash(h);
	cin >> num;//查找次数
	while (num--) {
		if (SearchHash(h, addr))
			cout << h->elem[addr].high << " " << h->elem[addr].weight << endl;
		else
			cout << "Not Found!" << endl;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_41676901/article/details/82468303