哈希表——数据结构——day8

哈希表的定义

存储位置=f(关键字)
散列技术 (哈希表的技术)是在记录的存储位置和它的关键字之间建立一个确定的对应关系 f,使得每个关键字 key 对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值 key 的映射 f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。
这里我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。
采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。

散列表查找步骤

整个散列过程其实就是两步。

(1)在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录 。就像张三丰我们就让他在体育馆,那如果是'爱因斯坦'我们让他在图书馆,如果是'居里夫人',那就让她在化学实验室,如果是'巴顿将军',这个打仗的将军一一我们可以让他到网吧。总之,不管什么记录,我们都需要用同一个散列函数计算出地址再存储。

(2)当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录 。说起来很简单,在哪存的,上哪去找,由于存取用的是同一个散列函数,因此结果当然也是相同的。

所以说,散列技术既是一种存储方法,也是一种查找方法。

散列函数的构造方法

直接定址法

如果我们现在要对0~100 岁的人口数字统计表,如表所示,那么我们对年龄这个关键字就可以直接用年龄的数字作为地址。此时f(key)=key。

如果我们现在要统计的是80 后出生年份的人口数 ,如表所示,那么我们对出生年份这个关键字可以用年份减去1980来作为地址。此f(key)=key-1980。

也就是说,我们可以取关键字的某个线性函数值为散列地址 ,即f(key)=a×key+b (a、b为常数)

数字分析法

如果我们的关键字是位数较多的数字,比如我们的 11 位手机号 "130xxxx1234", 其中前三位是接入号,一般对应不同运营商公司的子品牌,如 130是联通如意通、136 是移动神州行、153 是电信等;中间四位是HLR 识别号,表示用户号的归属地;后四位才是真正的用户号,如表所示。

这里我们提到了一个关键词------抽取。抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段。

数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。

除留余数法

此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:
f(key)=key mod p(p≤m)

mod 是取模(求余数)的意思。事实上,这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。

很显然,本方法的关键就在于选择合适的p,p 如果选得不好,就可能会容易产生同义词。

例如表,我们对于有 12 个记录的关键字构造散列表时,就用了f(key) =key.mod 12的方法。比如 29 mod12=5,所以它存储在下标为5的位置。

我们不选用p=12来做除留余数法,而选用p=11,如表所示。

此就只有12和144有冲突,相对来说,就要好很多。

例子:

这里我们写了一个例子,看哈希表如何应用。

hash.h头文件

c 复制代码
#ifndef _HASH_H_
#define _HASH_H_


#define HASN_SIZE 27

typedef struct per		//创建数据内容
{
	char name[64];
	char tel[31];
	char addr[64];
	int age;
}DATA_TYPE;


typedef struct hsnode
{
	DATA_TYPE data;
	struct hsnode *pnext;
}HASH_NODE;		



extern int insert_hash_table(DATA_TYPE data);
extern void hash_for_each();
extern HASH_NODE *find_hash_table(char *name);
extern void destroy_hash_table();

#endif

hash.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hash.h"


HASH_NODE *hash_table[HASN_SIZE] = {NULL};


int hash_fun(char ch)	//判断,因为是通过首字母排序
{
	if (ch >= 'a' && ch <= 'z')
	{
		return ch-'a';
	}
	else if (ch >= 'A' && ch <= 'Z')
	{
		return ch-'A';
	}
	else
	{
		return HASN_SIZE-1;
	}
}


int insert_hash_table(DATA_TYPE data)	//插入数据
{
	HASH_NODE *pnode = malloc(sizeof(HASH_NODE));
	if (NULL == pnode)
	{
		perror("fail malloc");
		return -1;
	}
	pnode->data = data;
	pnode->pnext = NULL;

	
	int addr = hash_fun(data.name[0]);
	
	//hash_table[addr] ========>phead

	pnode->pnext = hash_table[addr];
	hash_table[addr] = pnode;

	return 0;
}


void hash_for_each()	//遍历哈希表
{
	for (int i = 0; i < HASN_SIZE; i++)
	{
		HASH_NODE *ptmp = hash_table[i];
		while (ptmp != NULL)
		{
			printf("%s %s %s %d\n", ptmp->data.name, ptmp->data.tel, ptmp->data.addr, ptmp->data.age);
			ptmp = ptmp->pnext;
		}
		printf("\n");
	}
}


HASH_NODE *find_hash_table(char *name)	//通过名字查找其其余信息
{
	int addr = hash_fun(name[0]);

	HASH_NODE *ptmp = hash_table[addr];
	
	while (ptmp != NULL)
	{
		if (!strcmp(name, ptmp->data.name))
		{
			return ptmp;
		}
		ptmp = ptmp->pnext;
	}
	
	return NULL;
}

void destroy_hash_table()	//删除哈希表
{
	HASH_NODE *ptmp = NULL;
	for (int i = 0; i < HASN_SIZE; i++)
	{
		while(hash_table[i] != NULL)
		{
			ptmp = hash_table[i];
			hash_table[i] = ptmp->pnext;
			free(ptmp);
		}
	}
	
}
相关推荐
爱跳舞的烤冷面4 小时前
自学嵌入式第19天(数据结构篇——栈)
数据结构
星轨初途5 小时前
LeetCode 热题 100——day11 滑动窗口最大值
数据结构·c++·算法·leetcode·职场和发展
营养充电站15 小时前
VS Code Git 工作树:解锁多分支并行开发的高效体验
leetcode·决策树·逻辑回归·散列表·模拟退火算法
LuminousCPP18 小时前
数据结构-双向循环链表
c语言·数据结构·笔记·链表
白狐_79819 小时前
408数据结构第5章:树与二叉树②——遍历、线索树、森林与哈夫曼
数据结构·算法·深度优先
Jasmine_llq21 小时前
《P15800 [GESP202603 六级] 选数》
数据结构·long long·逆序线性 dp·从后往前推导·转移严格遵循题意·防止总和溢出·复杂度on
疯狂打码的少年21 小时前
【数据结构】串(字符串):基本概念与存储
数据结构·笔记
纵有疾風起1 天前
线性表的定义与基本操作 — 从逻辑结构到 ADT 接口
数据结构·算法·408·线性表·adt
wuyk5551 天前
1.栈:后进先出的线性数据结构
c语言·数据结构·stm32·单片机
LuminousCPP1 天前
数据结构-时间与复杂度|时间/空间复杂度 + 两道力扣练习 + 二分查找复盘
c语言·数据结构·经验分享·算法·leetcode