哈希表学习笔记及LeetCode编程练习之两数之和and快乐数

一、哈希表的定义

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

记录的存储位置=f(关键字),这里的对应关系f称为散列函数,又称为哈希(Hash函数),采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。

二、散列法

1、除法散列法

最直观的一种,公式:

  • index = value % 16

2、平方散列法

求index是非常频繁的操作,而乘法的运算要比除法省时,所以我们考虑把除法换成乘法和一个位移操作。公式:

  • index = (value * value) >> 28

(右移,除以2^28。记法:左移变大,是乘。右移变小,是除。)

3、斐波那契(Fibonacci)散列法

平方散列法的缺点是显而易见的,所以我们试图找出一个理想的乘数。

  • 对于16位整数而言,这个乘数是40503
  • 对于32位整数而言,这个乘数是2654435769
  • 对于64位整数而言,这个乘数是11400714819323198485

三、哈希冲突

哈希冲突是无可避免的,因为如果要完全避开这种情况,只能每个字典去新开一个页,每个字在索引里面都有对应的页码,这可以避免冲突,但是会导致空间增大(每个字都有一页)。因此,应注意如下几点:

  • 尽量使关键字对应的记录均匀分配在哈希表里。
  • 关键字极小的变化可以引起哈希值极大的变化。
  • 比较好的哈希函数是time33算法。PHP的数组就是把这个作为哈希函数。

核心代码如下:

unsigned long hash(const char* key){
    unsigned long hash=0;
    for(int i=0;i<strlen(key);i++){
        hash = hash*33+str[i];
    }  
    return hash;
}

四、哈希冲突的解决

1、开发定址法

找hash表剩下空余的空间,找到空余的空间然后插入。
哈希表学习笔记及LeetCode编程练习之两数之和and快乐数_第1张图片

2、链地址法

上面所说的开发定址法的原理是遇到冲突的时候查找顺着原来哈希地址查找下一个空闲地址然后插入,但是也有一个问题就是如果空间不足,就无法处理冲突也无法插入数据,因此需要装填因子(空间/插入数据)>=1。

解决这一问题可用链地址法,其原理是如果遇到冲突,就在原地址新建一个空间,以链表结点的形式插入到该空间。比如说我有一堆数据{1,12,26,337,353…},而我的哈希算法是H(key)=key mod 16,第一个数据1的哈希值f(1)=1,插入到1结点的后面,第二个数据12的哈希值f(12)=12,插入到12结点,第三个数据26的哈希值f(26)=10,插入到10结点后面,第4个数据337,计算得到哈希值是1,遇到冲突,但是依然只需要找到该1结点的最后链结点插入即可,同理353。
哈希表学习笔记及LeetCode编程练习之两数之和and快乐数_第2张图片

五、哈希表的优缺点

1、优点

  • 不论哈希表中有多少数据,查找、插入、删除(有时包括删除)只需要接近常量的时间即0(1)的时间级。实际上,这只需要几条机器指令。
  • 哈希表运算得非常快,在计算机程序中,如果需要在一秒种内查找上千条记录通常使用哈希表(例如拼写检查器)哈希表的速度明显比树快,树的操作通常需要O(N)的时间级。哈希表不仅速度快,编程实现也相对容易。
  • 如果不需要有序遍历数据,并且可以提前预测数据量的大小。那么哈希表在速度和易用性方面是无与伦比的。

2、缺点

  • 哈希表是基于数组的,数组创建后难于扩展,某些哈希表被基本填满时,性能下降得非常严重,所以必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)。

六、编程题

1、两数之和

  • 题目
    给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
  • 示例
    给定 nums = [2, 7, 11, 15], target = 9
    因为 nums[0] + nums[1] = 2 + 7 = 9
    所以返回 [0, 1]
  • 思路
    求差值,将差值存进字典里作为键,索引作为值,if 判断是否为所需要配对的值, 是则返回两个索引值。(补充:nums[x] in d 是判断值是否在字典某个key里面)
  • 代码如下
class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        n = len(nums)
        d = {}
        for x in range(n):
            if nums[x] in d:
                return d[nums[x]],x
            else:
                a = target - nums[x]
                d[a] = x
  • 运行结果
    哈希表学习笔记及LeetCode编程练习之两数之和and快乐数_第3张图片

2、快乐数

  • 问题
    编写一个算法来判断一个数是不是“快乐数”。
    一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
  • 示例
    输入: 19
    输出: true
    解释:
    12 + 92 = 82
    82 + 22 = 68
    62 + 82 = 100
    12 + 02 + 02 = 1
  • 思路
    定义字典record记录正整数每个位置上的数字的平方和,每次计算出来的平方和都保存。若平方和出现重复则不是快乐数,反之则是。变量sq_num记录每次的平方和。将n先求余10得到个位上的数并平方,然后整除10得到去除个位上数的其他数,n其次进行循环直到结束计算出平方和,然后判断平方和有没有出现过,若没有则继续算,反之则返回false。
  • 代码如下
class Solution:
    def isHappy(self, n):
        """
        :type n: int
        :rtype: bool
        """
        record = {}
        sq_sum = 0
        while n != 1:
            sq_sum = 0
            sub_num = n
            while sub_num > 0:
                sq_sum += (sub_num % 10) * (sub_num % 10)
                sub_num //= 10
            if sq_sum in record:
                return False
            else:
                record[sq_sum] = 1
            n = sq_sum
        return True
  • 运行结果
    哈希表学习笔记及LeetCode编程练习之两数之和and快乐数_第4张图片

附:部分内容参考

原文:https://blog.csdn.net/duan19920101/article/details/51579136
原文:http://www.cnblogs.com/s-b-b/

你可能感兴趣的:(哈希表学习笔记及LeetCode编程练习之两数之和and快乐数)