算法精解(五):哈希表

1.個人理解

關於哈希表,通過看書與資料,有了一些自己的理解。

我更喜歡將哈希表比作是一個二維數組void chtbl[type][N]

同一數據類型的數據存儲在一個一維數組裏面,type表示不同類型的數據類型。

比如圓形,方形,心形,六邊形代表四種類型的框,如果一個物體數據某種類型,

則將該物體放入對應的框中。而N則代表相應數據類型的個數,

而如何辨別該物體是什麼形狀,則需要一套方法,也就是哈希函數。

下面所說的數據類型,並非是Int,double等基礎數據類型,而是具有相同規則的數據的稱呼。

例如:取餘後,餘數相同的數據。最後一位是都是A的數據等等

2.解決衝突

       哈希表存在一個衝突問題,比如上圖,只有長方形或圓形持續增多的情況下,如果要搜索圓形或長方形,那麼也是要花費的時間也會越來越長。

    理想情況下,所以類型的數據,應該是同步增長的。也就是說儘可能的將數據隨機分佈到各個類型中去,也就是均勻分佈。但實際上只能是儘可能達到。

   如果想插入表中的數據數量遠遠大於表中數據類型的數量,那麼及時是均勻分佈的情況,表的性能也會大大降低。這種情況下,每個數據類型下的數據量會越來越大。哈希表中將數據n,和數據類型m的商稱爲,負載因子。

                 即:a=n/m

例如:一個哈希表的數據量爲1000,數據類型爲100種,那麼負載因子就是10.這樣1000個數據,查找數據的最壞情況,也不過是查找10次,就必定能查找到。

3.代碼實現

//鏈式哈希表的實現
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef struct ListElmt   //定義節點
{
	void *data;
	struct ListElmt  *next;
};

typedef struct List  //定義鏈表
{
	int size;
	int(*match)(const void *key1, const void *key2); //構造函數
	void(*destroy)(void *data);  //析構函數
	ListElmt *head;
	ListElmt *tail;
};

//新建鏈表
void list_init(List *list, void(*destroy)(void *data))
{
	list->size = 0;
	list->destroy = destroy;
	list->head = NULL;
	list->tail = NULL;

	return;
}
void list_destroy(List *haed);
typedef struct CHTBL_
{
	int buckets;     //數據類型的個數
	int (*h)(const void *key);  //自定義的哈希函數
	int (*match)(const void *key1, const void *key2);  //判斷兩個鍵是否匹配
	void (*destroy)(void *data);    //釋放動態分配的內存
	int size;  //數據數量
	List *table;  //存儲數據類型的數組
}CHTBL;

void chtbl_destroy(CHTBL *htbl)
{
	int i;
	for (i = 0; i < htbl->buckets; i++)
	{
		list_destory(&htbl->table[i]);
	}

	free(htbl->table);
	memset(htbl, 0, sizeof(CHTBL));
	return;
}
//哈希表初始化
int chtbl_init(CHTBL *htbl, int buckets, int(*h)(const void *key),
	int(*match)(const void *key1, const void *key2), void(*destroy)(void *data))
{
	int i;
	if ((htbl->table == (List *)malloc(sizeof(buckets * sizeof(List)))) == NULL)
	{
		return -1;
	}

	htbl->buckets = buckets;
	//初始化哈希表數據類型
	for (i = 0; i < htbl->buckets; i++)
	{
		list_init(&htbl->table[i], destroy);
	}

	htbl->h = h;
	htbl->match = match;
	htbl->destroy = destroy;
	htbl->size = 0;
	return 0;
}

//銷燬哈希表
void chtbl_destroy(CHTBL *htbl)
{
	int i;
	for (i = 0; i < htbl->buckets; i++)
	{
		list_destroy(&htbl->table[i]);  //鏈表銷燬函數
	}

	memset(htbl, 0, sizeof(CHTBL));
	return;
}

//查找元素,找到返回0,否則,返回-1
int chtbl_lookup(const CHTBL *htbl, void **data)
{
	ListElmt *element;
	int bucket;

	//直接對數據類型數取餘,得到key
	bucket = htbl->h(*data) % htbl->buckets;

	//從獲得數據類型的一維數組開始尋找data是否存在
	for (element = list_head(&htbl->table[bucket]); element != NULL; element = list_next(element))
	{
		if (htbl->match(*data, list_data(element)))
		{
			*data = list_data(element);
			return 0;
		}
	}
	return -1;
}

//插入數據
int chtbl_insert(CHTBL *htbl, const void *data)
{
	void *temp;
	int bucket, retval;

	temp = (void *)data;
	//如果數據存在,則返回1,不進行插入操作
	if (chtbl_lookup(htbl,&temp) == 0)   
		return 1;

	//獲取傳入數據的key(數據類型)
	bucket = htbl->h(data) % htbl->buckets;
	//如果數據不存在,將這個數據連接在這個數據類型的鏈表上
	if ((retval = list_ins_next(&htbl->table[bucket], NULL, data)) == 0)
		htbl->size++;
	return retval;
}

int chtbl_remove(CHTBL *htbl, void **data)
{
	ListElmt *element,*prev;
	int bucket;

	bucket = htbl->h(data) % htbl->buckets;
	prev = NULL;

	//根據key尋找需要刪除的數據
	for (element = list_head(&htbl->table[bucket]);
		element != NULL; element = list_next(element))
	{
		//如果找到數據
		if (htbl->match(*data, list_data(element)))
		{
			//開始刪除,刪除成功返回0,刪除失敗返回-1
			if (list_rem_next(&htbl->table[bucket], prev, data) == 0)
			{
				htbl->size--;
				return 0;
			}
			else
			{
				return -1;
			}
		}
		prev = element;
	}
	return -1;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章