文章目录
- 顺序哈希
- 链式哈希
- 链式哈希示意图
- 链式哈希头文件设计
- 链式哈希函数接口实现
- HashNode* Buynode(KeyType kx, HashNode* narg = nullptr)
- void Freenode(HashNode* p)
- void InitHashTable(HashTable* pt)
- int GetSize(HashTable* pt)
- bool IsEmpty(HashTable* pt)
- int Hash(KeyType kx)
- HashNode* FindValue(HashTable* pt, KeyType kx)
- bool Insert(HashTable* pt, KeyType kx)
- void DesetoryHashTable(HashTable* pt)
- bool Remove(HashTable* pt, KeyType kx)
- void PrintHashTable(HashTable* pt)
顺序哈希
顺序哈希示意图
头文件设计
这里是定长顺序哈希表,无法扩容,如果要设计不定长顺序哈希表,则可以用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");
}
}