1、哈希表

  理想情况是不希望经过任何比较,一次存取,便可以直接定位,这是需要一种映射关系,我们把这个映射关系就叫为哈希函数;

  哈希表在对大数据的处理查找上,有优势;

  若数据有序,-------->我们一般会想到二分查找;但是哈希查找更加犀利;

  针对哈希查找,我们主要学习:

  i>如何构造哈希函数;

  ii>怎么解决冲突问题

2、哈希函数

  这个哈希函数的构造方法比较多:(1)、直接定址法 (2)、数字分析法 (3)、平方取中法 (4)、折叠法

(5)、除留余数法 (6)、随机数法;

  简单说一下,直接定址法,用的少,且需要空间过大;

  以上的哈希函数,最常用的是除留余数法;什么是除留余数法?

  在哈希表中,总空间大小为m,选一个p,p满足两个条件i>: p <= m(越接近m越好); ii>: p必须是质数;则哈希函数为:                                          

                  hash(key) = kep % p;      p<=m;

  p为什么一定要选质数呢?                                       

  :经过数学的理论推导,p为质数时,可以保证模之后概率均等,均匀分布;p要不是质数,则不满足随机、均匀这些条件,就不能保证每个数字均等存储。

3、解决冲突

  (1)、线性探测法:

  线性探测法存储,此时存放的位置如下图,因为在下标为1处已经存放数字了,此时其后的数据也要存放到这个位置,产生冲突;按照线性探测法的解决方案:有冲突一直往后探测,直到有空间可以放数据时,就存储在那个位置。

  (2)、二次探测法

  二次探测法存储,此时存放的位置如下图,因为在下标为1处已经存放数字了,此时其后的数据也要存放到这个位置,产生冲突;按照二次探测法的解决方案:有冲突先向左探测,若没有空间,再向右探测,来回左右探测,直到有空间可以放数据时,就存储在那个位置。

  以上的两种解决冲突的方案不是很好,缺点:定位准确度不高,查找起来效率较低。

  (3)、双散列法

  需要两个哈希函数,第一个元素直接就是记哈希表的下标,第二个记到达该数字的移位量,从而达到直接定位。如下图:

  (4)、链地址法

  哈希表中存放的是指针(指向结点的指针),在冲突处,通过指针连接起来;如下图:

  关于链表的连接问题:

  头插----->效率比较高

  尾插----->效率相对来说比较低,(因为的一个一个的查找最后一个结点);

  解决尾插效率低的方案:以空间换时间,就是在哈希表中存放的是一个结构体类型,成员如下

typedef struct HASH_TABLE{//哈希表也就是线性表结构,表中每个数据的类型;    HASH_NODE *first;  //指向哈希结点(链表结点)的指针;first始终指向第一个结点;    HASH_NODE *last;   //last始终指向最后一个结点;    int size;   //size记录其上挂了多少数据;可有可无,看实际需求;}HASH_TABLE;

4、除留余数法+开链法(重点实现)

  此次全部代码均用C语言实现,与C++不牵扯!!

  我把函数声明和定义放到了一个.h文件中,只是为了方便实现,本应该分开存放;

  除留余数法的重点是:hash()函数;开链法的重点是:链表的操作;

  哈希表上应有的操作函数(其实主要是链表的操作):

void initHashTable(HashTable ht);  //初始化哈希表void prevInsert(HashTable ht, ElemType value);  //数据的前插void showHashTable(HashTable ht);  //显示数据void endInsert(HashTable ht, ElemType value); //数据的后插HASH_NODE* find(HashTable ht, ElemType value); //查找函数boolean remove_(HashTable ht, ElemType value); //删除哈希表中的数据void destoryHashTable(HashTable ht);  //销毁哈希表void clearHashTable(HASH_NODE *p);    //真正的销毁函数

  (1)、数据的前插:

void prevInsert(HashTable ht, ElemType value){    int index = Hash(value);    HASH_NODE *p = (HASH_NODE *)malloc(sizeof(HASH_NODE));    p->data = value;    p->next = NULL;  //前插,这条语句可以不写;    p->next = ht[index];    ht[index] = p;}

  (2)、显示数据:

void showHashTable(HashTable ht){    int i;    HASH_NODE *p;    for(i = 0; i < P; i++){        printf("%d : ", i);        if(ht[i]){            p = ht[i];            while(p){                printf("%d-->", p->data);                p = p->next;            }        }            printf("NULL\n");    }}

  (3)、数据的后插:

void endInsert(HashTable ht, ElemType value){    int index = Hash(value);    HASH_NODE *p = (HASH_NODE *)malloc(sizeof(HASH_NODE));    HASH_NODE *q;    p->data = value;    p->next = NULL;    if(ht[index] == NULL){ //判断是不是第一个结点;        ht[index] = p;    }else{        q = ht[index];        while(q->next){            q = q->next;        }        q->next = p;    }}

  (4)、查找函数:

HASH_NODE* find(HashTable ht, ElemType value){    int index = Hash(value);    HASH_NODE *p = ht[index];    while(p && p->data != value){ //没找完并且没找到,特别注意,已免发生内存非法访问!        p = p->next;    }        return p;  //直接返回p;p为空就是没找到,否则就是找到了;/*        if(p){        return p;    }else{        return NULL;    }*/    }

  (5)、删除函数:

boolean remove_(HashTable ht, ElemType value){    int index = Hash(value);    HASH_NODE *q;    HASH_NODE *p = find(ht, value); //利用找到函数,求得要找值得地址    if(p == NULL){ //为空,就是没找到,直接退出        return FALSE;    }    q = ht[index];    if(ht[index] == p){  //是第一个结点        ht[index] = p->next;    }else{        while(q->next != p){ //其后结点的删除,也就是再找前驱节点;            q = q->next;        }        q->next = p->next;        free(p);    }    return TRUE;}

5、完整代码、测试代码、测试结果

  (1)、完整代码:

#ifndef _HASH_TABLE_H_#define _HASH_TABLE_H_typedef unsigned char boolean;#define TRUE    1#define FALSE    0#define P    7#include
//链表中的数据类型typedef struct HASH_NODE{    ElemType data;    struct HASH_NODE *next;}HASH_NODE;//哈希表中的每个元素是指针类型,但是哈希表又是数组;所以为指针数组类型;typedef HASH_NODE *HashTable[P];int Hash(ElemType key){    return key % P;}void initHashTable(HashTable ht);void prevInsert(HashTable ht, ElemType value);void showHashTable(HashTable ht);void endInsert(HashTable ht, ElemType value);HASH_NODE* find(HashTable ht, ElemType value);boolean remove_(HashTable ht, ElemType value);void destoryHashTable(HashTable ht);void clearHashTable(HASH_NODE *p);void clearHashTable(HASH_NODE *p){    HASH_NODE *q;    while(p){        q = p->next;        free(p);        p = q;    }}void destoryHashTable(HashTable ht){    int i;    for(i = 0; i < P; i++){        clearHashTable(ht[i]);        ht[i] = NULL;    }}boolean remove_(HashTable ht, ElemType value){    int index = Hash(value);    HASH_NODE *q;    HASH_NODE *p = find(ht, value); //利用找到函数,求得要找值得地址    if(p == NULL){ //为空,就是没找到,直接退出        return FALSE;    }    q = ht[index];    if(ht[index] == p){  //是第一个结点        ht[index] = p->next;    }else{        while(q->next != p){ //其后结点,也就是再找前驱节点;            q = q->next;        }        q->next = p->next;        free(p);    }    return TRUE;}HASH_NODE* find(HashTable ht, ElemType value){    int index = Hash(value);    HASH_NODE *p = ht[index];    while(p && p->data != value){ //没找完并且没找到,特别注意,已免发生内存非法访问!        p = p->next;    }    return p;/*        if(p){        return p;    }else{        return NULL;    }*/}void endInsert(HashTable ht, ElemType value){    int index = Hash(value);    HASH_NODE *p = (HASH_NODE *)malloc(sizeof(HASH_NODE));    HASH_NODE *q;    p->data = value;    p->next = NULL;    if(ht[index] == NULL){ //判断是不是第一个结点;        ht[index] = p;    }else{        q = ht[index];        while(q->next){            q = q->next;        }        q->next = p;    }}void showHashTable(HashTable ht){    int i;    HASH_NODE *p;    for(i = 0; i < P; i++){        printf("%d : ", i);        if(ht[i]){            p = ht[i];            while(p){                printf("%d-->", p->data);                p = p->next;            }        }            printf("NULL\n");    }}void prevInsert(HashTable ht, ElemType value){    int index = Hash(value);    HASH_NODE *p = (HASH_NODE *)malloc(sizeof(HASH_NODE));    p->data = value;    p->next = NULL;  //前插,这条语句可以不写;    p->next = ht[index];    ht[index] = p;}void initHashTable(HashTable ht){    int i;    for(i = 0; i < P; i++){        ht[i] = NULL;    }}#endif

  (2)、测试代码:

#include
typedef int ElemType;#include"hashTable.h"void main(void){    HashTable ht;    int ar[] = {1, 8, 15, 3, 6, 2, 8, 9, 22, 31,};    int n = sizeof(ar) / sizeof(int);    int i;    HASH_NODE *p; //C语言只能在所有的有效语句之前定义变量;    boolean k;    initHashTable(ht);//数组名称的传递退化为指针    for(i = 0; i < n; i++){        //prevInsert(ht, ar[i]);        endInsert(ht, ar[i]);    }    showHashTable(ht);    p = find(ht, 10);    if(p){        printf("找到了,地址为:%p\n", p);    }else{        printf("输入的数据不存在,没找到\n");    }    k = remove_(ht, 99);    if(k){        printf("删除成功\n");    }else{        printf("删除失败\n");    }    destoryHashTable(ht); //表空间在栈,数据存放在堆!随着程序结束,堆空间释放。    }

  (3)、测试结果: