哈希表的認識和實現2(鏈地址法)

1、C實現哈希表的鏈地址法

(1)什麼是鏈地址法?
鏈地址法是用於處理哈希衝突的一種方法(什麼是哈希衝突,以及解決哈希衝突的開放地址法)
在鏈地址法的描述中,哈希表的結構更加複雜一點,但是查詢和存儲的性能都有進一步提升,尤其是面對超大量的數據時,將哈希表的每一個位置設計成鏈表,無疑加快了查詢速度,而且理解上也更加容易。
在開放地址法中,若是哈希表的位置已經被佔用,則向後尋找一個爲空的位置保存數據,在鏈地址法中,若是哈希表的位置被佔用也不需要向後尋找,因爲每個位置都是一個鏈表的節點,我們只需要將新節點插入就行(程序中採用的是尾插法)

2、頭文件
#pragma once
typedef struct HashTable HashTable;

HashTable* hash_table_new();

void hash_table_delete(HashTable *ht);

//大師的加強版,key用字符串類型表示更貼合實際應用,value給void*則可傳任意類型,同時增加value的釋放函數指針
int hash_table_put2(HashTable *ht, char *key, void* value, void(*free_value)(void*));

void* hash_table_get(HashTable *ht, char *key);

void hash_table_rm(HashTable *ht, char *key);
3、函數實現
#include "HashTable2.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define MAX_TABLE_SIZE 100

//哈希表中的鏈表的一個結點結構體
typedef struct kv
{
	struct kv *next;
	char *key;
	void *value;
	void(*free_value)(void*);
}KV;

struct HashTable
{
	KV **table; //這裏就是別人高手的思維,其實這麼寫會好理解一點
						//struct kv *table[MAX_TABLE_SIZE]
};

static void init_kv(KV *kv)
{
	kv->key = NULL;
	kv->next = NULL;
	kv->value = NULL;
	kv->free_value = NULL;
}

static void free_kv(KV *kv)
{
	if (kv){
		if (kv->free_value){
			//由於value結構不同,其應該有對應的free函數,且在相應free函數中應將指針置爲NULL
			kv->free_value(kv->value);
		}
		free(kv->key);
		kv->key = NULL;
		free(kv);
	}
}

//這裏是號稱最快的哈希函數 TIME33 算法
static unsigned int hash_33(char *key)
{
	unsigned int hash = 0;
	while (*key)
	{
		hash = (hash << 5) + hash + *key++;//左移5位相當於*32,再+hash則相當於*33;
	}
	return hash;
}

HashTable* hast_table_new()
{
	HashTable *ht = (HashTable *)malloc(sizeof(HashTable));
	if (ht == NULL) {
		hash_table_delete(ht);
		return NULL;
	}
	//上面創建了一個指向哈希表的指針,但是關鍵還是哈希表內的用於指向鏈表的指針(地址)
	ht->table = (KV **)malloc(sizeof(KV*) * MAX_TABLE_SIZE);
	if (ht->table == NULL) {
		hash_table_delete(ht);
		return NULL;
	}
	memset(ht->table, 0, sizeof(KV*)*MAX_TABLE_SIZE);

	return ht;
}

void hash_table_delete(HashTable *ht)
{
	if (ht) {
		if (ht->table) {
			for (int i = 0; i < MAX_TABLE_SIZE; i++) {
				KV *p = ht->table[i];
				KV *q = NULL;
				//將鏈表的節點一個一個free掉
				while (p)
				{
					q = p->next;
					free_kv(p);
					p = q;
				}
			}
			free(ht->table);
			ht->table = NULL;
		}
		free(ht);
	}
}

int hash_table_put2(HashTable *ht, char *key, void *value, void(*free_value)(void*))
{
	int i = hash_33(key) % MAX_TABLE_SIZE;
	KV *p = ht->table[i];
	KV *prep = p;

	while (p)
	{
		//key相等,則修改value的值,修改前先將原value內存釋放,也就是說key是唯一的
		if (strcmp(p->key, key) == 0) {
			if (p->free_value) {
				p->free_value(p->value);
			}
			p->value = value;
			p->free_value = free_value;
		}
		//prep指向最後一個節點
		prep = p;
		p = p->next;
	}
	//跳出while循環表示當前節點的next爲NULL,想保存數據需要建立新的鏈表節點
	if (p == NULL)
	{
		char *kstr = (char *)malloc(strlen(key) + 1);
		if (kstr == NULL) {
			return -1;
		}
		KV *kv = (KV *)malloc(sizeof(KV));
		if (kv == NULL) {
			free(kstr);
			kstr = NULL;
			return -1;
		}
		init_kv(kv);
		//kv->next = NULL;
		strcpy(kstr, key);
		kv->key = kstr;
		kv->value = value;
		kv->free_value = free_value;

		if (prep == NULL) {
			ht->table[i] = kv; //哈希表的這個位置第一次來數據節點
		}
		else
		{
			prep->next = kv;
		}
	}
	return 0;
}

void * hash_table_get(HashTable *ht, char *key)
{
	int i = hash_33(key) % MAX_TABLE_SIZE;
	KV *p = ht->table[i];
	while (p)
	{
		if (strcmp(key, p->key) == 0) {
			return p->value;
		}
		p = p->next;
	}
	return NULL;
}

void hash_table_rm(HashTable *ht, char *key)
{
	int i = hash_33(key) % MAX_TABLE_SIZE;
	KV *p = ht->table[i];
	KV *prep = p;
	while (p)
	{
		if (strcmp(key, p->key) == 0) {
			free_kv(p);
			//要知道,prep指向的就是p的上一個節點,p == prep表明此鏈表只有一個節點
			if (p == prep) {
				ht->table[i] = NULL;
			}
			else
			{
				//相當於刪除了鏈表中的某個節點
				prep->next = p->next;
			}
		}
		prep = p;
		p = p->next;
	}
}
4、測試

暫時沒有去測試

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