代码面试之哈希表

哈希表是种数据结构,它能够提供快速的插入操做和查找操做。第一次接触哈希表时,它的优势多得让人难以置信。不论哈希表中有多少数据,插入和删除(有时包括侧除)只须要接近常量的时间即0(1)的时间级。实际上,这只须要几条机器指令。html

  对哈希表的使用者一一人来讲,这是一瞬间的事。哈希表运算得很是快,在计算机程序中,若是须要在一秒种内查找上千条记录一般使用哈希表(例如拼写检查器)哈希表的速度明显比树快,树的操做一般须要O(N)的时间级。哈希表不只速度快,编程实现也相对容易。算法

  哈希表也有一些缺点它是基于数组的,数组建立后难于扩展某些哈希表被基本填满时,性能降低得很是严重,因此程序虽必需要清楚表中将要存储多少数据(或者准备好按期地把数据转移到更大的哈希表中,这是个费时的过程)。编程

  并且,也没有一种简便的方法能够以任何一种顺序〔例如从小到大〕遍历表中数据项。若是须要这种能力,就只能选择其余数据结构。数组

然而若是不须要有序遍历数据,井且能够提早预测数据量的大小。那么哈希表在速度和易用性方面是无与伦比的。数据结构

 

哈希表算法-哈希表的概念及做用dom

  通常的线性表,树中,记录在结构中的相对位置是随机的,即和记录的关键字之间不存在肯定的关系,所以,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法创建在“比较“的基础上,查找的效率依赖于查找过程当中所进行的比较次数。函数

  理想的状况是能直接找到须要的记录,所以必须在记录的存储位置和它的关键字之间创建一个肯定的对应关系f,使每一个关键字和结构中一个惟一的存储位置相对应。性能

哈希表最多见的例子是以学生学号为关键字的成绩表,1号学生的记录位置在第一条,10号学生的记录位置在第10条...spa

若是咱们以学生姓名为关键字,如何创建查找表,使得根据姓名能够直接找到相应记录呢?3d

 

哈希表算法 哈希表算法

 

用上述获得的数值做为对应记录在表中的位置,获得下表:

哈希表算法 哈希表算法

 

上面这张表即哈希表。

若是未来要查李秋梅的成绩,能够用上述方法求出该记录所在位置:

李秋梅:lqm 12+17+13=42 取表中第42条记录便可。

问题:若是两个同窗分别叫 刘丽 刘兰 该如何处理这两条记录?

这个问题是哈希表不可避免的,即冲突现象:对不一样的关键字可能获得同一哈希地址。

 

 

 哈希表算法-哈希表的构造方法

1、直接定址法

例如:有一个从1到100岁的人口数字统计表,其中,年龄做为关键字,哈希函数取关键字自身。

           但这种方法效率不高,时间复杂度是O(1),空间复杂度是O(n),n是关键字的个数

 

哈希表算法 哈希表算法

 

2、数字分析法

有学生的生日数据以下:

年.月.日

75.10.03
75.11.23
76.03.02
76.07.12
75.04.21
76.02.15
...

经分析,第一位,第二位,第三位重复的可能性大,取这三位形成冲突的机会增长,因此尽可能不取前三位,取后三位比较好。

3、平方取中法

取关键字平方后的中间几位为哈希地址

4、折叠法

将关键字分割成位数相同的几部分(最后一部分的位数能够不一样),而后取这几部分的叠加和(舍去进位)做为哈希地址,这方法称为折叠法。

例如:每一种西文图书都有一个国际标准图书编号,它是一个10位的十进制数字,若要以它做关键字创建一个哈希表,当馆藏书种类不到10,000时,可采用此法构造一个四位数的哈希函数。若是一本书的编号为0-442-20586-4,则:

 

哈希表算法 哈希表算法

 

5、除留余数法

取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。

H(key)=key MOD p (p<=m)

6、随机数法

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

H(key)=random(key) ,其中random为随机函数。一般用于关键字长度不等时采用此法。

5、除留余数法

取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。

H(key)=key MOD p (p<=m)

6、随机数法

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

H(key)=random(key) ,其中random为随机函数。一般用于关键字长度不等时采用此法。

5、除留余数法

取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。

H(key)=key MOD p (p<=m)

6、随机数法

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

H(key)=random(key) ,其中random为随机函数。一般用于关键字长度不等时采用此法。

 

哈希表算法-处理冲突的方法


哈希表算法

若是两个同窗分别叫 刘丽 刘兰,当加入刘兰时,地址24发生了冲突,咱们能够以某种规律使用其它的存储位置,若是选择的一个其它位置仍有冲突,则再选下一个,直到找到没有冲突的位置。选择其它位置的方法有:

1、开放定址法

Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1)

其中m为表长,di为增量序列

若是di值可能为1,2,3,...m-1,称线性探测再散列。

若是di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2)

称二次探测再散列

若是di取值可能为伪随机数列。称伪随机探测再散列。

例:在长度为11的哈希表中已填有关键字分别为17,60,29的记录,现有第四个记录,其关键字为38,由哈希函数获得地址为5,若用线性探测再散列,以下:

 

哈希表算法 哈希表算法

 

2、再哈希法

当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增长。

3、链地址法

将全部关键字为同义词的记录存储在同一线性链表中。

哈希表算法 哈希表算法

 

4、创建一个公共溢出区

假设哈希函数的值域为[0,m-1],则设向量HashTable[0..m-1]为基本表,另外设立存储空间向量OverTable[0..v]用以存储发生冲突的记录

例如,按上面例9.4所给的关键字序列,用拉链法构造散列表如图9.14所示。 


  
  用拉链法处理冲突,虽然比开放定址法多占用一些存储空间用作连接指针,但它能够减小在插入和查找过程当中同关键字平均比较次数(平均查找长度),这是由于,在拉链法中待比较的结点都是同义词结点,而在开放定址法中,待比较的结点不只包含有同义词结点,并且包含有非同义词结点,每每非同义词结点比同义词结点还要多。
  如前面介绍的例9.4中,用线性探测法构造散列表的过程,咱们知道,对前5个关键字的查找,每个仅须要比较一次,对关键字49和24的查找,则须要比较2次,对关键字38的查找则须要比较4次,而对43的查找则须要比较3次。所以,对用线性探测法构造的散列表的平均查找长度为:
    ASL=(1×5+2×2+3×1+4×1)/9 ≈1.78
而用拉链法构造的散列表上查找成功的平均查找长度为:
    ASL=(1×5+2×3+3×1)/9≈1.55
显然,开放定址法处理冲突的的平均查找长度要高于拉链法处理冲突的平均查找长度。但它们都比前面介绍的其它查找方法的平均查找长度要短。

 

 

 


#define HASHSIZE 32    


//待存入表格数据

char *keywords[] = {
        "auto", "break", "case", "char", "const", "continue", "default", 
        "do",
        "double", "else", "enum", "extern", "float", "for", "goto", 
        "if",
        "int", "long", "register", "return", "short", "signed", "sizeof", 
        "static",
        "struct", "switch", "typedef", "union", "unsigned", "void", "volatile",
        "while"
};

char keybuf[HASHSIZE][10];
static char val_flag[HASHSIZE];
//标致已占用存储单元


void ClearFlag()
{
    int i;
    
    for (i = 0;i < HASHSIZE;i++)
    {
        val_flag[i] = (HASHSIZE+1);
//清标致位

    }
}

//哈希函数,从数据中抽出某个成员用于哈希值的计算

unsigned int hash(char *s)
{
    unsigned int hashval;
    int i = 0;

    for (hashval = 0; *s != '\0'; s++)
        hashval = *s + 31 * hashval;
    hashval = hashval % HASHSIZE; 
//计算下标


    while ((val_flag[hashval] != (HASHSIZE+1)) && (i<32))
    {
        i++;
        hashval = (hashval + i)%HASHSIZE;    
//冲突处理,存储单元(下标)偏移

    }
    if (i<HASHSIZE)
    {
        printf("\n元素下标(%d): 冲突次数: %d -- ",hashval,i);
        val_flag[hashval] = hashval; 
//表示该单元被占用

        return hashval;
    }
    return -1;
}

int main(void) 
{
  int i, size, pos;

  size = sizeof(keywords) / sizeof(keywords[0]);
//计算关键字数量

 
  
//将数据存入哈希表

  ClearFlag(); 
  for(i = 0;i < size; i++)
     strcpy(keybuf[hash(keywords[i])],keywords[i]);

  
//根据数据结构中某个成员做为索引值,查找对应数据  ClearFlag();   for(i = 0; i < size; i++)  {    pos = hash(keywords[i]);        printf("%-10s: %-3d\n", keybuf[pos], pos);  }  return 0;}

相关文章
相关标签/搜索