搜索结构之哈希-----闭散列
解决哈希冲突两种常见的方法是:闭散列和开散列
闭散列: 也叫开放地址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到表中“下一个” 空位中去
那如何寻找下一个空余位置?
线性探测
设关键码集合为{37, 25, 14, 36, 49, 68, 57, 11},散列表为HT[12],表的大小m = 12,假设哈希函数为:Hash(x) = x %p(p = 11,是最接近m的质数),就有:
Hash(37) = 4
Hash(25) = 3
Hash(14) = 3
Hash(36) = 3
Hash(49) = 5
Hash(68) = 2
Hash(57) = 2
Hash(11) = 0
其中25,14,36以及68,57发生哈希冲突,一旦冲突必须要找出下一个空余位置
线性探测找的处理为:从发生冲突的位置开始,依次继续向后探测,直到找到空位置为止
【插入】
1. 使用哈希函数找到待插入元素在哈希表中的位置
2. 如果该位置中没有元素则直接插入新元素;如果该位置中有元素且和待插入元素相同,则不用插入;如果该位置中有元素但
不是待插入元素则发生哈希冲突,使用线性探测找到下一个空位置,插入新元素;
采用线性探测处理哈希冲突:(本人喜欢凌乱美哈)
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。
采用线性探测,实现起来非常简单,缺陷是:
一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键
码的位置需要许多次比较,导致搜索效率降低。
如何缓解呢?
我们引入二次探测:
在此之前呢,引入负载因子的概念
二次探测
若当前key与原来key产生相同的哈希地址,则当前key存在该地址后偏移量为(1,2,3...)的二次方+1地址处
key1:hash(key)
key2:hash(key)+1^2+1
key3:hash(key)+2^2+1
哈希表(闭散列)
Hash.h
#pragma once typedef void(*explore)(int*, int); typedef int DataType; typedef enum state { Empty, Exist, Delete, }state; typedef struct HashElemt { DataType _data; state _state; }HashElemt; typedef struct Hash { HashElemt * _table; int _size;//有效元素的个数 int _capacity;//数组的容量; explore _exp;//线性探测还是二次探测 }Hash,*pHash; void HashInit(pHash Ht,int _capacity,explore exp); int Hash_Insert(pHash Ht,DataType data); void Hash_Delete(pHash Ht,DataType data); HashElemt* Hash_Find(pHash Ht,DataType data); void Hash_Destory(pHash Ht); void Hash_Print(pHash Ht); void Line_explore(int *addr,int capacity); void second_explore(int *addr, int capacity);
Hash.c
#include "Hash.h" #include<malloc.h> #include<stdlib.h> #include<assert.h> #include<stdio.h> int i = 0; int GetNextPrimeNum(int Old_Capacity){ int i = 0; const int _PrimeSize = 28; static const unsigned long _PrimeList[28] = { 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul }; for ( i = 0; i < _PrimeSize; i++) { if (_PrimeList[i]>Old_Capacity) return _PrimeList[i]; } return -1; } void HashInit(pHash Ht, int capacity, explore exp){ int i = 0; assert(Ht); Ht->_capacity = capacity; Ht->_table = (HashElemt*)malloc((Ht->_capacity)*sizeof(HashElemt)); assert(Ht->_table); for ( i = 0; i < Ht->_capacity; i++) { (Ht->_table)[i]._state = Empty; } Ht->_size = 0; Ht->_exp = exp; } //交换两个哈希表的内容 void Swap(pHash Ht,pHash New_Hash) { int temp = 0; HashElemt * ret = NULL; //交换容量 temp = Ht->_capacity; Ht->_capacity = New_Hash->_capacity; New_Hash->_capacity = temp; //交换有效元素的个数 temp = Ht->_size; Ht->_size = New_Hash->_size; New_Hash->_size = temp; //交换底层数组 ret = Ht->_table; Ht->_table = New_Hash->_table; New_Hash->_table = ret; } //扩容 void Increase_Capacity(pHash Ht){ Hash New_Hash; int i = 0; assert(Ht); int New_Capacity = GetNextPrimeNum(Ht->_capacity); //创建一个新的哈希表 HashInit(&New_Hash,New_Capacity,Ht->_exp); for (i = 0; i < Ht->_capacity; i++) { if (Ht->_table[i]._state==Exist) { Hash_Insert(&New_Hash, Ht->_table[i]._data); } } Swap(Ht, &New_Hash); Hash_Destory(&New_Hash); } int HashFun(pHash Ht, DataType data){ int ret = 0; ret = (int)(data%(Ht->_capacity)); return ret; } int Hash_Insert(pHash Ht, DataType data){ assert(Ht); if (((Ht->_size)*10/(Ht->_capacity))>=7) { Increase_Capacity(Ht); } int Hash_Addr = HashFun(Ht, data); while ((Ht->_table)[Hash_Addr]._state!=Empty) { if (((Ht->_table)[Hash_Addr]._state == Exist )&& ((Ht->_table)[Hash_Addr]._data == data)) return -1; Ht->_exp(&Hash_Addr, Ht->_capacity); } (Ht->_table)[Hash_Addr]._data = data; (Ht->_table)[Hash_Addr]._state = Exist; Ht->_size++; i = 0; return 0; } void Hash_Delete(pHash Ht, DataType data){ HashElemt* ret = NULL; assert(Ht); ret = Hash_Find(Ht, data); if (ret == NULL) return; ret->_state = Delete; Ht->_size--; } HashElemt* Hash_Find(pHash Ht, DataType data){ assert(Ht); int Hash_Addr = HashFun(Ht, data); while (Ht->_table[Hash_Addr]._state!=Empty) { if (((Ht->_table)[Hash_Addr]._state == Exist)&&(Ht->_table[Hash_Addr]._data == data)) { return &(Ht->_table[Hash_Addr]); } Hash_Addr++; if (Hash_Addr == Ht->_capacity) Hash_Addr = 0; } return NULL; } void Hash_Destory(pHash Ht){ assert(Ht); free(Ht->_table); Ht->_size = 0; Ht->_capacity = 0; } void Hash_Print(pHash Ht){ int i = 0; assert(Ht); for (i = 0; i < Ht->_capacity; i++) { if (Ht->_table[i]._state == Exist) printf("%d=Exist%d \n",i, Ht->_table[i]._data); //if (Ht->_table[i]._state == Delete) //printf("%d=Delete \n",i); //if (Ht->_table[i]._state == Empty) //printf("%d=Empty\n ",i); } printf("%d",Ht->_capacity); printf("\n"); } void Line_explore(int *addr, int capacity){ assert(addr); (*addr)++; if ((*addr) == capacity) *addr == 0; } void second_explore(int *addr, int capacity){ assert(addr); *addr = *addr+i*i+1; if ((*addr) == capacity) *addr = *addr%capacity; }
test.c
#include "Hash.h" #include<stdio.h> int main(){ Hash Ht; HashElemt* ret = NULL; //HashInit(&Ht,5,Line_explore); HashInit(&Ht, 5, second_explore); Hash_Insert(&Ht, 37); Hash_Insert(&Ht, 25); Hash_Insert(&Ht, 14); Hash_Insert(&Ht, 36); Hash_Insert(&Ht, 49); Hash_Insert(&Ht, 68); Hash_Insert(&Ht, 57); Hash_Insert(&Ht, 11); Hash_Insert(&Ht, 24); Hash_Insert(&Ht, 89); Hash_Print(&Ht); printf("%d\n", Ht._size); //Hash_Delete(&Ht, 25); //Hash_Delete(&Ht, 57); //Hash_Print(&Ht); //ret = Hash_Find(&Ht,24); //printf("%d\n",ret->_data); system("pause"); return 0; }
总结闭散列:
闭散列空间利用率不高,若是静态的话,元素个数受限,动态的话,要考虑什么时候扩容,负载因子解决,则就会降低空间利用率.
下一篇会介绍开散列哦