• HashTable、函数对象 学习笔记


    1 目标

    结合一道简单的题目Leetcode-两数之和,学习HashTable、和函数对象

    2 题意

    给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

    你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

    示例:

    给定 nums = [2, 7, 11, 15], target = 9
    因为 nums[0] + nums[1] = 2 + 7 = 9
    所以返回 [0, 1]
    

    3 思路

    author's blog == http://www.cnblogs.com/toulanboy/

    3.1 思路出发点

    这是昨天的打卡题(2020年10月3日),虽然之前做过,但是知道有更好解法,故昨晚学习了一下。

    谈下暴力法:只需双重循环,两两尝试匹配。复杂度是0(n^2)

    但,如果能拥有常数级别时间复杂度的查找find和插入insert的数据结构,那么结合该数据结构,我们可以使用以下逻辑来实现O(n)的解题。

    具体逻辑:从前往后遍历nums数组。对于nums[i],用O(1)查找该数据结构,查看之前是否出现过他的匹配数字。

    • 若有,找到答案,退出。
    • 若没有,则把当前数字用O(1)放入到数据结构,然后继续nums[i+1]。

    总体复杂度:O(n)。

    而hashtable就是能满足我们需求的数据机构!

    3.2 HashTable

    概述:通过数组+链表的形式,结合hash算法,查找和插入的时间复杂度为常数级。

    3.2.1 HashTable 结构

    (1)表面认识

    注释的内容会在后面解析,刚开始学习,我们先看大体,再看细节。

    组成架构:该数据结构包含多个桶bucket[1],然后每个桶里面可以放很多数值[2]。

    插入逻辑:对于一个新来的数值key[3],通过一个简单的运算[4],确定该数值key应该放那个桶,然后把它丢进去[5]即可。

    查找逻辑:对于需要被查找的数值key,参考插入逻辑(先通过一个运算确定它在哪个桶),再去这个桶里面逐一遍历出来。

    (2)稍微深入的学习

    [1] 多个桶bucket:这是通过顺序数组来实现的。

    [2] 每个桶里面可以放很多数值:实际上,每个桶存储的都是一个指针,该指针指向一条链表。

    [3] key:放入的数值,不限定类型。在C++层面,如果是单一类型,那么可以对应标准库的unodered_set。如果是键值对(结构体),那么可以对应标准库的unodered_map。

    [4] 简单的运算:这个是hash运算。给定指定数据,hash运算会将其转换为一串数字

    [5] 把它丢进去:这个是hash冲突的处理方法,如果多个数据都hash到同一个桶,那么我们将这个视为hash冲突。而这里处理冲突的方法,就是使用一条链表,将所有hash到这个桶的数据都串起来,然后只需把链表头指针放到桶里面就行,这个处理方案的方法被称为链地址法

    (3)结构总结

    hashtable的结构利用hash运算,将数值映射到某个桶。如果出现冲突,那么就使用链表处理冲突。由于hash运算不需要复杂的运算,所以使得他的查找效率和插入效率非常高。

    (4)其他

    Q:后期数据太多,链表太长影响效率?

    A:可以设定阈值,当达到阈值时,则进行重哈希rehash(),将当前数组数据迁移到更大的数组。

    (5)上面内容主要从以下博文学习得到,建议感兴趣的同学可以细看下面的文章。

    3.2.2 HashTable 对应的标准库

    C++新标准中有2个STL容器是用hashtable作为底层实现的:

    (1)unodered_set,能够存储单类型的容器。例如建立一个字符串类型的hashtable。

    (2)unodered_map,能够存储键值对的容器。例如建立一个 <姓名,年龄>的hashtable。

    3.2.3 unodered_set使用示例

    关于标准库的使用,如果是int,string,float这些基本类型,那么STL自带的hash函数能够处理,那么建立时只需传递数据类型。如:

    unodered_set<int> age_set;//建立1个int类型的hashtable
    unodered_map<string, age> person_map;//建立1个<string, age>类型的hashtable
    

    若是其他类型,则还需要传递hash函数以及比较函数。而这2个函数一般通过函数对象的形式的传递。

    下面代码使用了函数对象。若暂时不知道的,可以先看下一小节。

    /*
    unordered_set的样例代码。
    */
    # include<iostream>
    # include<unordered_set>
    using namespace std;
    
    //定义1个类
    class Point{
    public:
        int x;
        int y;
        Point(int x, int y){
            this->x = x;
            this->y = y;
        }
    };
    //定义Point的hash类
    //由于其重载了(),故其实例化后的对象,类似于函数指针。
    class PointHash{
        public:
        size_t operator()(const Point& p)const{
            //这里调用STL的hash为我们计算中间值
            return hash<int>()(p.x) + hash<int>()(p.y);
        }
    };
    //定义Point的equal类
    //由于其重载了(),故其实例化后的对象,类似于函数指针。
    class PointEqual{
        public:
        bool operator()(const Point& a, const Point& b)const{
            return a.x == b.x;
        }
    };
    
    int main(){
    
        unordered_set<Point, PointHash, PointEqual> my_set;
        my_set.insert(Point(11, 22));
    
        for(auto it = my_set.begin(); it != my_set.end(); ++it){
            cout << it->x << ","<< it->y << endl;
        }
        /*
        输出:11, 22
        */
    
        auto result = my_set.find(Point(11, 22));
        if(result != my_set.end()){
            cout << result->x << ","<< result->y << endl;
        }
        /*
        输出:11, 22
        author's blog == http://www.cnblogs.com/toulanboy/
        */
        return 0;
    }
    

    3.3.4 参考文章

    该部分参考文章如下,作者写得太好了,感谢。

    3.3 函数对象

    3.3.1 基本概念

    函数对象,也被称为伪函数,在STL容器中经常被使用。

    本质是一个类,该类重载了(),其实例化的对象可以实现函数调用的效果。

    举个例子:

    class My_Lovely_Add{
        public:
        //重载()
        int operator()(int a, int b){
            return a+b;
        } 
    };
    
    int main(){
    
        My_Lovely_Add f;
        cout << f(11, 22) << endl;
        //输出:33
        return 0;
    }
    

    上述My_Lovely_Add类由于重载了(),故其实例化的对象可以实现函数调用的效果。

    3.3.2 与函数指针的异同

    他们两者都能实现具体函数的传递,从目前的学习来看,函数对象具备以下优点:

    • 可以使用inline。
    • 可以通过类成员记录调用情况。

    3.3.3 参考文章

    4 代码

    然后,就可以使用hashtable的STL之一 unodered_map来解题了!

    class Solution {
    public:
        //学习了官方题解:https://leetcode-cn.com/problems/two-sum/solution/liang-shu-zhi-he-by-leetcode-solution/
        vector<int> twoSum(vector<int>& nums, int target) {
            //创建unordered_map,利用其O(1)的插入和查找进行快匹配
            unordered_map<int, int> u_map;
            //创建unordered_map的迭代器
            unordered_map<int, int>::iterator it;
    
            for(int i=0; i<nums.size(); ++i){
                //看看前面是否出现有匹配的数字
                it = u_map.find(target-nums[i]);
                //有则输出
                if(it != u_map.end())
                    return {it->second, i};
                //否则,把当前数字放进Map,继续往下
                u_map.insert(pair(nums[i], i));
            }
            return {};
        }
    };
    

    写到最后:

    (1)整理这个简短的内容,不知不觉已经过去2小时,午饭时间都过了。。can~

    (2)这个只是简单的概述,没有特别具体深入,但希望对你有帮助~

  • 相关阅读:
    tyvj1463 智商问题
    P1070 道路游戏
    P1862 输油管道问题
    P1875 佳佳的魔法药水
    P1498 南蛮图腾
    P1489 猫狗大战
    P1395 会议(求树的重心)
    P2285 [HNOI2004]打鼹鼠
    P3819 松江1843路(洛谷月赛)
    P3818 小A和uim之大逃离 II(洛谷月赛)
  • 原文地址:https://www.cnblogs.com/toulanboy/p/13766990.html
Copyright © 2020-2023  润新知