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)這個只是簡單的概述,沒有特別具體深入,但希望對你有幫助~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM