雜湊表(HashTable)的開放定址法和鏈地址法的實現
阿新 • • 發佈:2019-02-15
散列表(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關鍵字的元素 }