1. 程式人生 > >雜湊表(HashTable)的開放定址法和鏈地址法的實現

雜湊表(HashTable)的開放定址法和鏈地址法的實現



散列表(Hash table,也叫雜湊表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表。引用(百度)

演算法時間複雜度分析:採用雜湊表作為資料結構的儲存系統中,根據關鍵字的值可快速定位到相應的地址上,在採用開放定址法時僅需O(1)複雜度就可找到,在採用鏈地址法時,需要O(N)複雜度,主要在連結串列中搜索,相對於搜尋樹的O(lg(N))的複雜度,開放定址法顯然來得快,但是雜湊表的長度會變得非常長,採用鏈地址法時快速定位到相應的頭結點中,只需在連結串列中迴圈遍歷即可,程式設計難度比樹降低了不少,還可以將鏈地址法中雜湊表陣列中的指標指向一個樹,這樣在搜尋時,快速定位到搜尋樹的根節點,根據樹的對數搜尋複雜度,更可快速的找到元素,比如說,紅黑樹,B樹..

關於雜湊表中的元素指標只想為樹的結點時,相應的結構如下:

                                                   

下面採用開放定址法和鏈地址法實現雜湊表:

1. 開放定址法:

include<stdio.h>
#include<stdlib.h>
#pragma warning(disable:4996)
typedef int KeyType;

int hashsize[] = { 11, 19, 29, 37 };//雜湊表容量遞增表,一個合適的素數序列
int m;//雜湊表的表長,全域性變數

struct ElemType{
	KeyType key;
	int order;
	struct ElemType*next;//方便在採用鏈地址法時用的
};
typedef struct HashTable1{
	ElemType *elem;//資料元素的基址
	int count;
	int hashindex;
}HashTable;

/*
	初始化雜湊表
*/
void InitHashTable(HashTable&H){
	H.count = 0;
	H.hashindex = 0;
	m = hashsize[H.hashindex];//初始化雜湊表表長為hashsize[H.hashindex]
	if ((H.elem = (ElemType*)malloc(sizeof(ElemType)*m)) == NULL){
		printf("初始化HashTable基址失敗\n");
		exit(-1);
	}
	for (int i = 0; i < m; i++){
		H.elem[i].key = 0;//未填充的記錄
	}
}

/*
Hash函式
採用取餘法,用關鍵字的值餘上表的長度,作為雜湊儲存的地址
*/
unsigned Hash(KeyType K){
	return K%m;
}
int d(int i){//增量序列是衝突次數i的函式
	return i;//線性探測再雜湊
	//return rand() 隨機探測再雜湊,一班用線性探測再雜湊就夠了
}


/*
開放定址法處理衝突
*/
void collision(KeyType K, int&p, int i){
	p = (Hash(K) + d(i)) % m;//最後得到的結果一定在0~m-1之間
}

/*
	在雜湊表中查詢關鍵字為K的記錄,若找到了則返回success,p表示資料在表中的位置
	否則以p指示插入位置,並返回Unsuccess.i為衝突次數,傳出引數
	*/
int SearchHash(HashTable&H, KeyType K, int &p, int &i){
	p = Hash(K);//根據關鍵字計算雜湊地址
	while (H.elem[p].key != 0 && K != H.elem[p].key){//在所得的地址上關鍵字不為空,且不等於該位置上的關鍵字,則產生衝突
		++i;//衝突次數自增一次
		if (i < m){//還有可能找到位置
			collision(K, p, i);//計算下一個位置/根據開放定址法
		}
		else break;//否則找不到位置了s
	}
	if (K == H.elem[p].key){//找到了位置
		return 1;
	}
	else return 0;
}
int InsertHashTable(HashTable&H, ElemType e);
void RecreateHashTable(HashTable&H){
	int i, count = H.count;
	ElemType *p, *elem = (ElemType*)malloc(sizeof(ElemType)*count);
	p = elem;
	for (i = 0; i < m; i++){
		if (H.elem[i].key != 0){//H在該單元有資料
			*p++ = H.elem[i];
		}
	}//將H中的資料臨時存放到elem中對應的位置上去
	H.count = 0;
	++H.hashindex;

	m = hashsize[H.hashindex];//新的儲存容量
	H.elem = (ElemType*)realloc(H.elem, m*sizeof(ElemType));//利用H.elem重新分配表長大小為m雜湊表長空間
	for (i = 0; i < m; i++){
		H.elem[i].key = 0;//賦初值
	}
	for (p = elem; p < elem + count; p++)
		InsertHashTable(H, *p);//將臨時的資料再次插入到新構造的HashTable中
	free(elem);
}
/*
	在雜湊表中插入資料項為e的元素
	*/
int InsertHashTable(HashTable&H, ElemType e){
	int p, c = 0;//c為衝突的次數
	if (SearchHash(H, e.key, p, c)){//如果找到了要插入的元素
		return -1;
	}
	else if (c < hashsize[H.hashindex] / 2){//未找到,衝突次數未達到上限
		H.elem[p] = e;
		H.count++;
		return 1;//插入成功
	}
	else {
		RecreateHashTable(H);//需重新建表
		return 0;
	}
}
/*
	遍歷雜湊
*/
void TraverseHashTable(HashTable H){
	for (int i = 0; i < m; i++){
		if (H.elem[i].key != 0)//第i個單元有資料
			printf("%d ,(%d,%d)\n", i, H.elem[i].key, H.elem[i].order);
	}
}

2. 接下來為鏈地址法處理衝突:

typedef int KeyType;

int hashsize[] = { 11, 19, 29, 37 };//雜湊表容量遞增表,一個合適的素數序列
int m;//雜湊表的表長,全域性變數
struct ElemType{
	KeyType key;
	int order;
	struct ElemType*next;
};
typedef struct HashTable2{
	ElemType **elem;//二級指標型向量,採用動態分配空間大小
	int count;//當前的頭指標向量的元素
	int hashindex;//hashsize[H.hashindex]為當前容量
}HashTableLinkList;
//表的容量永遠不會擴充,只是鏈地址的連結串列會很長,於是選擇合適的表長變得 很重要了
/*
	在ELemTYpe形成的連結串列中查詢關鍵字等於K的元素,L指向頭結點
	動態查詢的連結串列
	*/
int SearchHashTableElemType(ElemType *L, KeyType K, ElemType *&v){
	ElemType *s = NULL;
	s = (ElemType*)malloc(sizeof(ElemType));
	s->key = K;
	if (!L->next){//一開始為空,插入一個元素
		s->next = L->next;
		L->next = s;
		v = s;
		return 0;
	}
	else{
		//	printf("\n進入了尾插法\n");
		ElemType* p = L->next;
		ElemType *q = L;
		while (p&&p->key != K){
			q = p;
			p = p->next;
		}
		if (p&&p->key == K){
			v = p;
			return 1;//找到了資料元素
		}

		else{//插入一個數據,此時p為空,需要她的前驅指標q指向最後一個元素,採用尾插法插入元素
			s->next = q->next;
			q->next = s;
			v = s;
			return 0;
		}
	}

}
/*
	鏈地址法解決雜湊衝突
	*/
void InitHashTableLinkListHash(HashTableLinkList &H){
	H.count = 0;
	m = hashsize[0];
	H.elem = (ElemType**)malloc(m*sizeof(ElemType*));
	for (int i = 0; i < m; i++){
		H.elem[i] = (ElemType*)malloc(sizeof(ElemType));
		H.elem[i]->next = NULL;
	}
}
/*
	計算雜湊地址
	*/
int HashLinkList(KeyType K){
	return K%m;
}
/*
	在雜湊表中查詢元素,從雜湊鏈地址中查詢資料項值等於e的元素
	,不需要衝突次數
	*/
ElemType* SearchHashTableLinkList(HashTableLinkList H, KeyType K, int &p){
	//ElemType *v = NULL;
	p = Hash(K);//p為根據關鍵字計算的處的頭結點所在的位置,關鍵
	//printf("%d\n",p);
	ElemType *head = H.elem[p];//記下頭結點,關鍵字記錄肯定在以head為頭結點的單鏈表中
	return head;
	//SearchHashTableElemType(head, K, v);
	//return v;
}

/*
	遍歷採用鏈地址法的雜湊表
	*/
void TraverseHashTableLinkList(HashTableLinkList H){
	ElemType *p = NULL, *q = NULL;
	for (int i = 0; i < m; i++){
		if ((p = H.elem[i])->next){//頭結點不空
			printf("\n進入了新的連結串列:\n");
			q = p->next;//指向首節點
			while (q){
				printf("%d ", q->key);
				q = q->next;
			}
		}
	}
	printf("\n");
}
/*
	在雜湊表中插入一個元素
	*/
ElemType* InsertHashTableLinkList(HashTableLinkList&H, ElemType e){
	int p = 0;//為插入的位置
	ElemType*v = NULL;
	ElemType*head = SearchHashTableLinkList(H, e.key, p);//找到資料項e應該插入的頭結點所在的連結串列
	//SearchHashTableLinkListElemType;
	SearchHashTableElemType(head, e.key, v);//動態查詢連結串列
	return v;//返回這個結點,不管找沒找到,找到了返回,沒找到會自動插入這個元素也返回
	//在以head為頭結點的連結串列中查詢e.key關鍵字的元素
}