哈希表——数据结构——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);
		}
	}
	
}
相关推荐
小明说Java7 小时前
常见排序算法的实现
数据结构·算法·排序算法
小熳芋12 小时前
验证二叉搜索树- python-递归&上下界约束
数据结构
不穿格子的程序员15 小时前
从零开始写算法——链表篇2:从“回文”到“环形”——链表双指针技巧的深度解析
数据结构·算法·链表·回文链表·环形链表
诺....16 小时前
C语言不确定循环会影响输入输出缓冲区的刷新
c语言·数据结构·算法
长安er17 小时前
LeetCode876/141/142/143 快慢指针应用:链表中间 / 环形 / 重排问题
数据结构·算法·leetcode·链表·双指针·环形链表
workflower18 小时前
PostgreSQL 数据库的典型操作
数据结构·数据库·oracle·数据库开发·时序数据库
仰泳的熊猫18 小时前
1140 Look-and-say Sequence
数据结构·c++·算法·pat考试
EXtreme3518 小时前
栈与队列的“跨界”对话:如何用双队列完美模拟栈的LIFO特性?
c语言·数据结构·leetcode·双队列模拟栈·算法思维
松涛和鸣18 小时前
29、Linux进程核心概念与编程实战:fork/getpid全解析
linux·运维·服务器·网络·数据结构·哈希算法