数据结构之散列表

 散列表(也叫哈希表)是一种查找算法,与链表、树等算法不同的是,散列表算法在查找时不需要进行一系列和关键字(关键字是数据元素中某个数据项的值,用以标识一个数据元素)的比较操作。

    散列表算法希望能尽量做到不经过任何比较,通过一次存取就能得到所查找的数据元素,因而必须要在数据元素的存储位置和它的关键字(可用key表示)之间建立一个确定的对应关系,使每个关键字和散列表中一个唯一的存储位置相对应。因此在查找时,只要根据这个对应关系找到给定关键字在散列表中的位置即可。这种对应关系被称为散列函数(可用h(key)表示)。

    根据设定的散列函数h(key)和处理冲突的方法将一组关键字key映像到一个有限的连续的地址区间上,并以关键字在地址区间中的像作为数据元素在表中的存储位置,这种表便被称为散列表,这一映像过程称为散列,所得存储位置称为散列地址。

    关键字、散列函数以及散列表的关系如下图所示:

 

《数据结构之散列表》

 

 

    1、散列函数

    散列函数是从关键字到地址区间的映像。

    好的散列函数能够使得关键字经过散列后得到一个随机的地址,以便使一组关键字的散列地址均匀地分布在整个地址区间中,从而减少冲突。

    常用的构造散列函数的方法有:

    (1)、直接定址法

    取关键字或关键字的某个线性函数值为散列地址,即:

    h(key) = key   或 h(key) = a * key + b

    其中a和b为常数。

    (2)、数字分析法

    (3)、平方取值法

    取关键字平方后的中间几位为散列地址。

    (4)、折叠法

    将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为散列地址。

    (5)、除留余数法

    取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址,即:

    h(key) = key MOD p    p ≤ m

    (6)、随机数法

    选择一个随机函数,取关键字的随机函数值为它的散列地址,即:

    h(key) = random(key)

    其中random为随机函数。

    2、处理冲突

    对不同的关键字可能得到同一散列地址,即key1 ≠ key2,而h(key1)= h(key2),这种现象称为冲突。具有相同函数值的关键字对该散列函数来说称作同义词。

    在一般情况下,散列函数是一个压缩映像,这就不可避免地会产生冲突,因此,在创建散列表时不仅要设定一个好的散列函数,而且还要设定一种处理冲突的方法。

    常用的处理冲突的方法有:

    (1)、开放定址法

    hi =(h(key) + di) MOD m     i =1,2,…,k(k ≤ m-1)

    其中,h(key)为散列函数,m为散列表表长,di为增量序列,可有下列三种取法:

    1)、di = 1,2,3,…,m-1,称线性探测再散列;

    2)、di = 12,-12,22,-22,32,…,±k2 (k ≤m/2),称二次探测再散列;

    3)、di = 伪随机数序列,称伪随机探测再散列。

    (2)、再散列法

    hi = rhi(key)   i = 1,2,…,k

    rhi均是不同的散列函数。

    (3)、链地址法

    将所有关键字为同义词的数据元素存储在同一线性链表中。假设某散列函数产生的散列地址在区间[0,m-1]上,则设立一个指针型向量void *vec[m],其每个分量的初始状态都是空指针。凡散列地址为i的数据元素都插入到头指针为vec[i]的链表中。在链表中的插入位置可以在表头或表尾,也可以在表的中间,以保持同义词在同一线性链表中按关键字有序排列。

    (4)、建立一个公共溢出区

 

    下面以除留余数法和链地址法构造散列表,代码如下: 

#include<stdio.h>
#include<stdlib.h>
#define LEN 13

typedef struct hash_node {  
    int count;  
    struct hash_node *next;  
}HashNode,*pNode;  
  
int hash(int num){  
    return num % LEN;  
}  

/**
 * 解决冲突
 * @param index vec数组的下标
 * @param vec[] 一个数组,数组的每一个元素是一个指向结构体的指针
 */
void collision(pNode vec[],int index,pNode newNode){
    if(vec[index] == NULL){
        vec[index] = newNode;  //无冲突,直接链接
    }else{
        newNode->next = vec[index];
        vec[index] = newNode;  //冲突,链表插入新元素
    }
}

/**
 * 创建Hash表
 * @param arr[] 待散列的数组
 * @param num 待散列数组的长度
 */
void create_hash(pNode vec[], int arr[],int num) {
    for(int i = 0; i < num; i++){
        pNode p = (pNode)malloc(sizeof(HashNode));
        p->count = arr[i];
        p->next = NULL;
        int temp = hash(arr[i]); //求余
        collision(vec,temp,p);
    }
}

void _print_hash(pNode node){
   printf("%d ",node->count); 
if(node->next != NULL){ _print_hash(node->next); } } /** * 打印Hash表 */ void print_hash(pNode vec[]){ for(int i = 0; i < LEN; i++){ printf("vec[%d]: ",i); if(vec[i]!=NULL){ _print_hash(vec[i]); } printf("\n"); } } int main(int argc,char* argv[]){ pNode vec[LEN]; int arr[] = {19,14,23,1,68,20,84,27,55,11,10,79}; //待散列的数组元素 for(int i=0;i<LEN;i++){ vec[i] = NULL; } create_hash(vec,arr,12); //arr数组的长度为12 print_hash(vec); return 0; }

 

运行结果截图:

《数据结构之散列表》

    原文作者:水火379
    原文地址: https://www.cnblogs.com/zhangming-blog/articles/5367494.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞