搜索結構之哈希-----閉散列

搜索結構之哈希-----閉散列

解決哈希衝突兩種常見的方法是:閉散列和開散列

閉散列
閉散列:也叫開放地址法,當發生哈希衝突時,如果哈希表未被裝滿,說明在哈希表中必然還有空位置,那麼可以把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;
}

總結閉散列:

閉散列空間利用率不高,若是靜態的話,元素個數受限,動態的話,要考慮什麼時候擴容,負載因子解決,則就會降低空間利用率.

下一篇會介紹開散列哦吐舌頭

發佈了47 篇原創文章 · 獲贊 32 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章