数据结构-哈希表(哈希集合)

哈希表(本篇主要讲哈希集合)

部分内容来源于博主@SnailMann

前提

在实际编程中,我们常常面临着两个问题:存储和查询,这两个过程的效率往往制约着整个程序的效率,而我们常见的存储数据的数据结构比如线性表,树,图等,数据在结构中的位置往往是不明确的,当我们在这些数据结构中要查询一个数据,都避免不了去执行查询算法,去遍历数据结构,拿关键字和结构中的数据进行一一比较,从而得到想要的数据,我们就希望能不能不通过比较就能获得我们想要的结果呢?

答案是有的,不通过任何比较,一次存储便能取得所查记录,但这就必须在记录的存储位置和他的关键字之间建立一个确定的对应关系f,使得每个关键字和结构中的唯一的存储位置相对应,这个关系就是我们所说的哈希函数f(x),在这个思想上建立起来的表就成为哈希表。

介绍

哈希表是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。

有两种不同类型的哈希表:哈希集合和哈希映射。

哈希集合是集合数据结构的实现之一,用于存储非重复值。
哈希映射是映射 数据结构的实现之一,用于存储(key, value)键值对。
在标准模板库的帮助下,哈希表是易于使用的。大多数常见语言(如Java,C ++ 和 Python)都支持哈希集合和哈希映射。

通过选择合适的哈希函数,哈希表可以在插入和搜索方面实现出色的性能。

原理

哈希表的关键思想是使用哈希函数将键映射到存储桶。更确切地说,

当我们插入一个新的键时,哈希函数将决定该键应该分配到哪个桶中,并将该键存储在相应的桶中;
当我们想要搜索一个键时,哈希表将使用相同的哈希函数来查找对应的桶,并只在特定的桶中进行搜索。
在这里插入图片描述
在示例中,我们使用 y = x % 5 作为哈希函数。让我们使用这个例子来完成插入和搜索策略:

插入:我们通过哈希函数解析键,将它们映射到相应的桶中。

  • 例如,1987 分配给桶 2,而 24 分配给桶 4。

搜索:我们通过相同的哈希函数解析键,并仅在特定存储桶中搜索。

  • 如果我们搜索 1987,我们将使用相同的哈希函数将1987 映射到 2。因此我们在桶 2 中搜索,我们在那个桶中成功找到了 1987。
    例如,如果我们搜索 23,将映射 23 到 3,并在桶 3 中搜索。我们发现 23 不在桶 3 中,这意味着 23 不在哈希表中。

设计哈希集合

不使用任何内建的哈希表库设计一个哈希集合

具体地说,你的设计应该包含以下的功能

add(value):向哈希集合中插入一个值。
contains(value) :返回哈希集合中是否存在这个值。
remove(value):将给定值从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。

示例:

MyHashSet hashSet = new MyHashSet();
hashSet.add(1);
hashSet.add(2);
hashSet.contains(1); // 返回 true
hashSet.contains(3); // 返回 false (未找到)
hashSet.add(2);
hashSet.contains(2); // 返回 true
hashSet.remove(2);
hashSet.contains(2); // 返回 false (已经被删除)

注意:

所有的值都在 [1, 1000000]的范围内。
操作的总数目在[1, 10000]范围内。
不要使用内建的哈希集合库。

class MyHashSet {
public:
    /** Initialize your data structure here. */
    vector<bool> hash;
    MyHashSet() {
        hash = vector<bool>(1000001, false);
    }
    
    void add(int key) {
        hash[key] = true;
    }
    
    void remove(int key) {
        if(hash[key])
        {
            hash[key] = false;
        }
        return;
    }
    
    /** Returns true if this set contains the specified element */
    bool contains(int key) {
        if(hash[key])
        {
            return true;
        }
        return false;
    }
};

/**
 * Your MyHashSet object will be instantiated and called as such:
 * MyHashSet* obj = new MyHashSet();
 * obj->add(key);
 * obj->remove(key);
 * bool param_3 = obj->contains(key);
 */

设计哈希映射

不使用任何内建的哈希表库设计一个哈希映射

具体地说,你的设计应该包含以下的功能

put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。
get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。
remove(key):如果映射中存在这个键,删除这个数值对。

示例:

MyHashMap hashMap = new MyHashMap();
hashMap.put(1, 1);
hashMap.put(2, 2);
hashMap.get(1); // 返回 1
hashMap.get(3); // 返回 -1 (未找到)
hashMap.put(2, 1); // 更新已有的值
hashMap.get(2); // 返回 1
hashMap.remove(2); // 删除键为2的数据
hashMap.get(2); // 返回 -1 (未找到)

注意:

所有的值都在 [1, 1000000]的范围内。
操作的总数目在[1, 10000]范围内。
不要使用内建的哈希库。

class MyHashMap {
public:
    /** Initialize your data structure here. */
    vector<int> hash;
    MyHashMap() {
        hash = vector<int>(1000001,-1);
    }
    
    /** value will always be non-negative. */
    void put(int key, int value) {
        hash[key] = value;
    } 
    
    /** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
    int get(int key) {
        if(hash[key] == -1)
        {
            return -1;
        }else{
            return hash[key];
        }
    }
    
    /** Removes the mapping of the specified value key if this map contains a mapping for the key */
    void remove(int key) {
        if(hash[key] == -1)
        {
            return;
        }else
        {
            hash[key] = -1;
        }
    }
};

/**
 * Your MyHashMap object will be instantiated and called as such:
 * MyHashMap* obj = new MyHashMap();
 * obj->put(key,value);
 * int param_2 = obj->get(key);
 * obj->remove(key);
 */

这两道题主要用于理解哈希集合和哈希映射的概念

哈希集合在c++ stl中的应用

#include <unordered_set>                // 当时用set时引用的模板库

int main() {
    // 创建一个哈希集合
    unordered_set<int> hashset;   
    // 插入新的关键字
    hashset.insert(3);
    hashset.insert(2);
    hashset.insert(1);
    // 删除关键字
    hashset.erase(2);
    // 判断关键字是否在哈希集合中,如果在方法返回负数
    if (hashset.count(2) <= 0) {
        cout << "关键字2不在哈希集合中" << endl;
    }
    // 得到哈希集合的大小
    cout << "The size of hash set is: " << hashset.size() << endl; 
    // 遍历哈希集合,注意要以指针的形式
    for (auto it = hashset.begin(); it != hashset.end(); ++it) {
        cout << (*it) << " ";
    }
    cout << "are in the hash set." << endl;
    // 清除哈希集合
    hashset.clear();
    // 判断哈希结合是否为空
    if (hashset.empty()) {
        cout << "hash set is empty now!" << endl;
    }
}
经典例题1
  • 存在重复元素
  • 给定一个整数数组,判断是否存在重复元素。
  • 如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
  • 示例 :

输入: [1,2,3,1]
输出: true
示例 2:

输入: [1,2,3,4]
输出: false
示例 3:

输入: [1,1,1,3,3,4,3,2,4,2]
输出: true

分析;这道题我们使用标准库中的查重模板,顺序将数组中的元素压入哈希集合中,在利用count()方法判断后序函数是否在哈希集合中即可

// leecode 存在重复元素
class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_set<int> hashset;
        for(int i = 0; i < nums.size(); i++)
        {
            if(hashset.count(nums[i]) > 0)
            {
                return true;
            }
            else{
                hashset.insert(nums[i]);
                
            }
        }
        return false;
    }
};
经典例题2
  • 只出现一次的数字

  • 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

  • 说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1
示例 2:

输入: [4,1,2,1,2]
输出: 4

分析:因为在数组中,元素出现的次数不是一次就是两次,我们这里利用哈希集合顺序将元素压入哈希集合中,利用count()方法判断后序元素是否在哈希集合中,如果在,便删除哈希集合中的元素,如果不在,将后序元素压入哈希集合中,到最后,哈希集合中只会存在一个元素,将此元素输出即可。

// leetcode 只出现一次的数字
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_set <int> hashset;
        for(int i = 0; i < nums.size(); i++)
        {
            if(hashset.count(nums[i]) > 0)
            {
                hashset.erase(nums[i]);
            }else{
                hashset.insert(nums[i]);
            }
        }
        return *hashset.begin();
      
    }
};
经典例题3
  • 两个数组的交集
  • 给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]
示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
说明:

输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序

分析:创建两个哈希集合,压入nums1和nums2的所有元素,因为哈希集合中不允许存在相同的元素,所以在全部压入的时候会自动甩出相同的元素,最后再一次判断重复元素即可

// leetcode 两个数组的交集
class Solution {
public:
    vector<int> intersection(vector<int> &nums1, vector<int> &nums2) {
        vector<int> ret{};
        unordered_set<int> nums1_set{nums1.cbegin(), nums1.cend()}; 
        unordered_set<int> nums2_set{nums2.cbegin(), nums2.cend()};
        for (auto &num : nums2_set) {
            if (nums1_set.count(num) > 0) 
            {
                ret.push_back(num);
            }
        }

        return ret;
    }
};
经典例题4
  • 快乐数问题(然而被这个问题搞得一点都不快乐)

  • 编写一个算法来判断一个数是不是“快乐数”。

  • 一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。

示例:

输入: 19
输出: true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

思路:利用哈希集合,将每次计算的和,例如82,68等等压入哈希集合中,如果某一次的计算出的总和出现在了哈希集合中,则证明这个数不是快乐数,这是跳出无限循环的关键,如果计算出的和等于1,则返回true

// leetcode 快乐数(今天你快乐了嘛)
class Solution {
public:
    unordered_set<int> hashset;
    int m;
    bool isHappy(int n) {
        while(n != 1)
        {
            if(hashset.count(n) > 0)
            {
                return false;
            }else
            {
                int sum = 0;
                hashset.insert(n);
                while( n != 0)
                {
                    sum += (n % 10) * (n % 10);
                    n = n / 10;
                }
                n = sum;
            }
        }
        return true;
    }
};
  • 41
    点赞
  • 143
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Java 中的哈希表(HashMap)数据结构是一种数组和链表结合的结构,通过使用哈希函数将键映射到数组中的桶中,从而实现快速查找和插入。每个桶中可以包含一个或多个键值对,并且如果有多个键映射到同一个桶,它们将通过链表连接在一起。这样,即使在最坏的情况下(即哈希冲突),查找时间复杂度仍然是常数级别的。 ### 回答2: Java中的哈希表数据结构在Java集合框架中被称为HashMap。它基于散列函数(Hash Function)和数组实现,用于存储键值对。以下是Java中哈希表的几个重要特点和数据结构组成部分: 1. 索引:Java的哈希表使用散列函数将键映射到相关的数组索引上。散列函数将键转换为数组中的一个位置,以便快速查找和访问对应的值。 2. 数组:哈希表内部使用一个数组来存储所有的键值对。这个数组称为哈希表的桶数组(bucket array)或哈希表的存储容器。每个桶都可以容纳一个或多个键值对。 3. 链表或红黑树:在同一个桶中,如果存在多个键值对,它们会被组织成一个链表或红黑树(Java 8之后使用红黑树进行优化)。如果链表或红黑树中存在相同的键,则哈希表会根据键来更新对应的值。 4. 散列冲突:由于可能存在不同的键对应相同的哈希值,即发生了散列冲突。为了解决冲突,桶中使用链表或红黑树来存储冲突的键值对,保证高效的查找和插入。 5. 扩容和重哈希:当哈希表的负载因子(即元素数量与桶数组大小的比值)超过一个设定的阈值时,哈希表会自动进行扩容。扩容过程会创建一个更大的桶数组,并将原有的键值对重新散列到新数组中,以保持散列函数的均匀性。 总之,Java的哈希表数据结构HashMap采用数组和链表或红黑树的组合来存储数据,并使用散列函数来快速定位和访问其中的键值对。这种数据结构可以提供高效的插入、查找和删除操作,并且在面对大量数据时也能保持较好的性能。 ### 回答3: Java中的哈希表数据结构是通过HashMap类实现的。它是基于数组和链表实现的散列表(Hash Table)。 在Java的哈希表中,数据存储在一个由固定大小的数组组成的表中。每个数组位置被称为桶(Bucket),每个桶可以存储一个或多个键值对。数组中的每个元素被称为哈希槽(Hash Slot)。 当需要将一个键值对插入哈希表时,Java使用哈希函数将键转换为其在数组中的索引位置。通过哈希函数,可以将键均匀地散布到数组中的不同位置,这样可以确保散列表内的数据分布更加均匀。 如果两个不同的键被哈希函数映射到同一个索引位置上,这样的情况被称为哈希冲突(Hash Collision)。为了解决冲突,Java中的哈希表使用链表或红黑树来存储具有相同索引位置的键值对。 当发生哈希冲突时,新插入的键值对将被添加到该索引位置的链表(或红黑树)的末尾。当需要从哈希表中获取或删除一个键值对时,Java会先通过哈希函数获取相应的索引位置,然后再在链表(或红黑树)中进行查找。 当哈希表的负载因子(Load Factor)达到一个阈值时,即数组中元素的个数与数组大小的比值超过了给定的阈值,Java会自动对数组进行扩容,从而减少哈希冲突的可能性,并提高哈希表的性能。 通过使用哈希表数据结构,Java的HashMap类允许快速地插入、查找和删除键值对,平均时间复杂度为O(1)。此外,Java哈希表还支持null键和null值的存储。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值