查找篇-散列表

189 阅读3分钟

散列表介绍

散列表是非比较查找表。散列表通过关键字直接查找数据结构,理论上的时间复杂度为O(1)。

  • 散列函数是一个将关键字映射成对应地址的函数。
  • 冲突 散列函数将不同的关键字映射到了相同的地址,这就是冲突。

散列函数构造方法

  • 直接定址法 H(key) = key或H(key) = a * key + b;适合关键字分布基本连续的场景。
  • 除留余数法 H(key) = key % p,p为质数
  • 数字分析法
  • 平方取中法 取关键字的平方的中间几位数作为散列地址

冲突解决方法

链接法不能有效利用CPU缓存,开放定址法能有效利用CPU缓存。

开放定址法

如果应用场景设计hash表数据的序列化,使用开放定址法可能更佳。

Hi = (H(key) + di) % m(表长) i = 1...k(k <= m - 1)

  • 线性探测法 di = ...m-1
  • 平方探测法 di = 0^2,1^2,...,(m-1)^2
  • 再散列法 di = H2(key)
  • 伪随机序列法 di = 伪随机数序列

链接法

线性探测法解决冲突的代码实现

散列函数使用除留余数法。

#include <stdio.h>
#include <stdlib.h>

#define NULLKEY -1
#define FOUND -2
#define NOTFOUND -3
#define FULL -4
#define INSERTED -5

typedef struct{
    int del;
    int key;
}Elem;

typedef struct{
    Elem *elem;
    int num;
    int size;
}HashTable;

//构造hash表
void createHashTable(HashTable *ht){
    int size = (*ht).size;
    ht->elem = (Elem*)malloc(sizeof(Elem) * size);
    if(ht->elem == NULL) exit(0);
    for(int i = 0; i < size; i ++){
        ht->elem[i].key = NULLKEY;
        ht->elem[i].del = 0;
    }
}

//散列函数 除留余数法
int hashFunc(HashTable ht, int key){
    return key % ht.size;
}

// 冲突处理函数 -- 线性探测法
void collison(HashTable ht, int *pos){
    *pos = (*pos + 1) % ht.size;
}

//搜索关键字
int search(HashTable ht, int key, int *pos){
    int i = 0;
    *pos = hashFunc(ht, key);
    while(i < ht.size){
        if(ht.elem[*pos].key == NULLKEY){ //该元素不在散列表中
            return NOTFOUND;
        }else if(ht.elem[*pos].key == key && ht.elem[*pos].del == 0){//找到该元素
            return FOUND;
        }else{ //继续查找
            collison(ht, pos);
        }
        i ++;
    }
    return NOTFOUND;
}

//插入元素
int insert(HashTable *ht, int key){
    int i = 0;
    int pos;
    int ret = search(*ht, key, &pos);
    if(ret == FOUND) return FOUND;
    pos = hashFunc(*ht, key);
    while(i < (*ht).size){
        if((*ht).elem[pos].key == NULLKEY || (*ht).elem[pos].del == 1){ //找到空位,直接插入
            (*ht).elem[pos].key = key;
            (*ht).elem[pos].del = 0;
            return INSERTED;
        }else{
            collison(*ht, &pos);
        }
        i ++;
    }
    return FULL;
}

//删除元素。前面在查找元素时,有一段逻辑,如果key=NULLKEY则散列表中没有该元素。如果在删除元素时将key置为NULLKEY,将导致上述逻辑失效,因此这里使用伪删除法,不真正删除元素,而是通过del字段控制是否删除。
void delete(HashTable *ht, int key){
    int pos;
    int ret = search(*ht, key, &pos);
    if(ret == NOTFOUND) return;
    (*ht).elem[pos].del = 1;
}

void print(HashTable ht){
    for(int i = 0; i < ht.size; i ++){
        if(ht.elem[i].key != NULLKEY && ht.elem[i].del != 1){
            printf("%d %d\n", i, ht.elem[i].key);
        }
    }
}

int main(int argc, const char * argv[]) {
    HashTable ht;
    ht.size = 17;
    ht.num = 0;
    ht.elem = NULL;
    createHashTable(&ht);
    insert(&ht, 8);
    insert(&ht, 5);
    delete(&ht, 8);
    insert(&ht, 8);
    insert(&ht, 22);
    print(ht);
    return 0;
}

链接法解决冲突代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FOUND -1
#define NOTFOUND -2
#define INSERTED -3

typedef struct keyType {
    int key;
    struct keyType* next;
}keyType;

typedef struct {
    keyType** elem;
    int size;
    int num;
}HashTable;

void createHashTable(HashTable** ht, int size) {
    *ht = (HashTable*)malloc(sizeof(HashTable));
    if (*ht == NULL) {
        exit(0);
    }
    (*ht)->elem = (keyType**)malloc(sizeof(keyType*) * size);
    if ((*ht)->elem == NULL) {
        exit(0);
    }
    memset((*ht)->elem, 0, sizeof(keyType*) * size);
    (*ht)->size = size;
    (*ht)->num = 0;
}

int htFunc(HashTable ht, int k) {
    return k % ht.size;
}

int search(HashTable ht, int k, keyType* ptr) {
    int pos = htFunc(ht, k);

    ptr = ht.elem[pos];
    while (ptr != NULL) {
        if (ptr->key == k) {
            return FOUND;
        }
        ptr = ptr->next;
    }
    return NOTFOUND;
}

//按升序插入
int insert(HashTable* ht, int k) {
    int pos = htFunc(*ht, k);
    keyType* p = ht->elem[pos], *q = NULL, * node;

    if (p == NULL) { //没有结点
        node = (keyType*)malloc(sizeof(keyType));
        if (node == NULL) exit(0);
        node->key = k;
        node->next = NULL;
        ht->elem[pos] = node;
        ht->num++;
        return INSERTED;
    }

    if (k < p->key && p->next == NULL) { //有一个结点且关键字大于k
        node = (keyType*)malloc(sizeof(keyType));
        if (node == NULL) exit(0);
        node->key = k;
        node->next = p;
        ht->elem[pos] = node;
        ht->num++;
        return INSERTED;
    }

    //查找结点的插入位置
    while (p != NULL) {
        if (k == p->key) {
            return FOUND;
        }
        else if (k > p->key) {
            q = p;
            p = p->next;
        }
        else { //找到第一个大于k的结点的前驱
            break;
        }
    }
    node = (keyType*)malloc(sizeof(keyType));
    if (node == NULL) exit(0);
    node->key = k;
    node->next = p;
    q->next = node;
    ht->num++;
    return INSERTED;
}

void printHt(HashTable ht) {
    keyType* p;
    for (int i = 0; i < ht.size; i++) {
        p = ht.elem[i];
        while (p != NULL) {
            printf("%d\t", p->key);
            p = p->next;
        }
    }
}

int main() {
    HashTable *ht;
    createHashTable(&ht, 17);
    insert(ht, 8);
    insert(ht, 25);
    insert(ht, 9);
    keyType key;
    int ret = search(*ht, 9, &key);
    if (ret == FOUND) {
        printf("OK");
        printf("\n");
    }
    ret = search(*ht, 10, &key);
    if (ret == NOTFOUND) {
        printf("NOTFOUND \n");
    }
    printHt(*ht);
    return 0;
}(keyType));
    	if(node == NULL) exit(0);
    	node->key = k;
    	node->next = p;
        ht->elem[pos] = node;
        ht->num ++;
        return INSERTED;
    }
    
    //查找结点的插入位置
    while(p != NULL){ 
    	if(k == p->key){
        	return FOUND;
        }else if(k > p->key){ 
        	q = p;
        	p = p->next;
        }else{ //找到第一个大于k的结点的前驱
			break;
        }
    }
    node = (keyType*)malloc(sizeof(keyType));
    if(node == NULL) exit(0);
    node->key = k;
    node->next = p;
  	q->next = node;
    ht->num ++;
    return INSERTED;
}