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