哈希表(顺序哈希和链式哈希)

顺序哈希

顺序哈希示意图

在这里插入图片描述

头文件设计

这里是定长顺序哈希表,无法扩容,如果要设计不定长顺序哈希表,则可以用KeyType*data; 初始化的时候再去申请空间;需要扩容再扩容

//顺序哈希
#include<stdio.h>
#include<assert.h>
#define Nil -1
typedef int KeyType;
const int M = 13;
typedef struct
{
	KeyType data[M];
	int cursize;
}HashTable;

接口函数实现

void InitHashTable(HashTable* pt)

注意需要将该哈希表填入初始值-1,方便后面在哈希表中查找值

void InitHashTable(HashTable* pt)
{
	assert(pt != nullptr);
	pt->cursize = 0;
	for (int i = 0; i < M; ++i)
	{
		pt->data[i] = Nil; // -1
	}
}

int GetSize(HashTable* pt)

int GetSize(HashTable* pt)
{
	assert(pt != nullptr);
	return pt->cursize;
}

bool IsEmpty(HashTable* pt)

bool IsEmpty(HashTable* pt)
{
	assert(pt != nullptr);
	return GetSize(pt) == 0;
}

bool IsFull(HashTable* pt)

bool IsFull(HashTable* pt)
{
	assert(pt != nullptr);
	return GetSize(pt) == M;
}

int Hash(KeyType kx)

int Hash(KeyType kx)
{
	return kx % M;
}

int Hash_One(KeyType kx, int i)

该函数用来解决哈希冲突,每次取到的地址,如果该位置被占用,则往后移动一位,如果到了尾部,可以对M取模,可以回到头部,充分利用空间。

int Hash_One(KeyType kx, int i)
{
	return (Hash(kx) + i) % M;
}

bool Insert(HashTable* pt, KeyType kx)

只要有空间,线型探查一定能找到一个位置将元素放进去,如果该位置非空,循环遍历 线型探查

bool Insert(HashTable* pt, KeyType kx)
{
	assert(pt != nullptr);
	if (IsFull(pt)) return false;
	for (int i = 0; i < M; ++i)
	{
		int pos = Hash_One(kx, i); // 2
		if (pt->data[pos] == Nil)
		{
			pt->data[pos] = kx;
			pt->cursize += 1;
			break;
		}
	}
	return true;
}

int FindValue(HashTable* pt, KeyType kx)

线型探查,逐个扫描,实现思路类似Insert

int FindValue(HashTable* pt, KeyType kx)
{
	int pos = -1;
	if (nullptr == pt) return pos;
	for (int i = 0; i < M; ++i)
	{
		int index = Hash_One(kx, i);
		if (pt->data[index] == kx)
		{
			pos = index;
			break;
		}
	}
	return pos;
}

链式哈希

链式哈希示意图

在这里插入图片描述

链式哈希头文件设计

定义一个大小为M的数组,该数组存储的内容是指向哈希节点的指针,即定义一个指针数组。

//链式哈希
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>  // #include<malloc.h>
#define M 13
typedef int KeyType;
typedef struct HashNode
{
	KeyType key;
	struct HashNode* next;
}HashNode, * PHashNode;
//定义一个大小为M的数组,该数组存储的内容是指向哈希节点的指针
//HashNode*base[M];
typedef struct
{
	HashNode* base[M];
	int cursize;
}HashTable;

链式哈希函数接口实现

HashNode* Buynode(KeyType kx, HashNode* narg = nullptr)

HashNode* Buynode(KeyType kx, HashNode* narg = nullptr)
{
	HashNode* s = (HashNode*)malloc(sizeof(HashNode));
	if (nullptr == s) exit(EXIT_FAILURE);
	s->key = kx;
	s->next = narg;
	return s;
}

void Freenode(HashNode* p)

void Freenode(HashNode* p)
{
	assert(p != nullptr);
	free(p);
}

void InitHashTable(HashTable* pt)

初始化链式哈希表。将指针数组中的每一个指针全部置为nullptr

void InitHashTable(HashTable* pt)
{
	assert(pt != nullptr);
	pt->cursize = 0;
	for (int i = 0; i < M; ++i)
	{
		pt->base[i] = nullptr;
	}
}

int GetSize(HashTable* pt)

该大小指的是该链式哈希表中结点的总数

int GetSize(HashTable* pt)
{
	assert(pt != nullptr);
	return pt->cursize;
}

bool IsEmpty(HashTable* pt)

bool IsEmpty(HashTable* pt)
{
	assert(pt != nullptr);
	return GetSize(pt) == 0;
}

int Hash(KeyType kx)

int Hash(KeyType kx)
{
	return kx % M;
}

HashNode* FindValue(HashTable* pt, KeyType kx)

注意:链式哈希存储的key是没有重复的
核心步骤:
先根据哈希函数定位到哪一个“桶”
定义一个p指针,遍历桶对应的链表
当p为空,则说明没有该key,跳出循环
当p->key==kx,则说明找到该值,跳出循环
注意:两个条件不能交换位置,否则当p为空p->key 对空指针解引用,程序会崩溃

HashNode* FindValue(HashTable* pt, KeyType kx)
{
	assert(pt != nullptr);
	int pos = Hash(kx);
	HashNode* p = pt->base[pos];
	while (p != nullptr && p->key != kx)
	{
		p = p->next;
	}
	return p;
}

bool Insert(HashTable* pt, KeyType kx)

注意需要判断是否有了该值,有的话就不用插入,直接返回false
没有的话可以插入,类似于链表头插

bool Insert(HashTable* pt, KeyType kx)
{
	assert(pt != nullptr);
	HashNode* p = FindValue(pt, kx);
	if (p != nullptr) return false;
	int pos = Hash(kx);
	//HashNode* s = Buynode(kx, pt->base[pos]);
	//pt->base[pos] = s;
	pt->base[pos] = Buynode(kx, pt->base[pos]);
	pt->cursize += 1;
	return true;
}

void DesetoryHashTable(HashTable* pt)

类似不带头节点链表的删除,将每一个桶都依次进行头删
核心步骤为:标记 贯穿 释放
每一个桶循环的终止条件是pt->base[i]==nullptr,和带头链表的区别是没有头结点所以不用释放头结点

void DesetoryHashTable(HashTable* pt)
{
	assert(pt != nullptr);
	for (int i = 0; i < M; i++) {
		while (pt->base[i]!=nullptr) {
			HashNode*q = pt->base[i];
			pt->base[i] = q->next;
			free(q);
		}
	}
	pt->cursize = 0;
}

bool Remove(HashTable* pt, KeyType kx)

删除链表中p点,并且只有指针p,一个思路就是再定义一个prev
本题非常巧妙的是,定义一个prev指针,该指针有两个作用,
标记p的前一个节点,用来删除p
标记第一个节点是否是kx,如果是,进行头删
如果不是kx,将p赋值给prev,即将prev指向p,p向后遍历

bool Remove(HashTable* pt, KeyType kx) {
	assert(pt != nullptr);
	int pos = Hash(kx);
	HashNode* prev = nullptr;
	HashNode* p = pt->base[pos];
	while (p != nullptr) {
		if (p->key == kx) {
			if (prev == nullptr) {
				pt->base[pos] = p->next;
			}
			else {
				prev->next = p->next;
			}
			pt->cursize--;
			return true;
		}
		prev = p;
		p = p->next;
	}
	return false;
}

void PrintHashTable(HashTable* pt)

两层循环实现链式哈希表元素的打印遍历

void PrintHashTable(HashTable* pt)
{
	assert(pt != nullptr);
	for (int i = 0; i < M; ++i)
	{
		printf("桶 %d ", i);
		HashNode* p = pt->base[i];
		while (p != nullptr)
		{
			printf("%5d", p->key);//  free(p);
			p = p->next;
		}
		printf("\n");
	}
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值