C++自定义哈希函数与比较器

C++自定义哈希函数与比较器

unordered_set与unordered_map

unordered_set与unordered_map的声明为

template<class Key,class Hash=hash<Key>,class Pred=equal_to<Key>,class Alloc=allocate<Key>>
class unordered_set;
template<class Key,class T,class Hash=hash<Key>,class Pred=equal_to<Key>,class Alloc=allocate<pair<const Key,T>>>
class unordered_map;

通常指定第一个模版参数与第二个模版参数即可使用,无需显示指定模版参数HashPredAlloc(空间配置器)

unordered_set<int> hashset;
unordered_map<string,string> hashmap;

因为在std下有针对int类型和string类型特化的hashhash结构,在该结构中由系统提供了将int类型和string类型转化为哈希值的仿函数。

如果哈希表中存放的key值类型在std::hash中没有完成特化,那么在使用unordered_set与unordered_map时会出现错误,例如

unordered_set<array<int, 26>> hashset;//error

此时就需要自定义哈希函数与比较器,因为系统不知道如何将类似array这样的类型转化为哈希值,同时系统也不知道如何比较2个array类型的对象是否相等。

特化std::hash与std::equal_to

将std::hash和std::equal_to针对特定类型进行特化可以避免上述错误

namespace std{
    template<>
    struct hash<array<int,26>>{
        size_t operator()(const array<int,26>& arr)const{
            size_t hashValue=0;
            for(int i=0;i<26;i++){
                hashValue=hashValue*31+std::hash<int>()(arr[i]);
            }
            return hashValue;
        }
    };
    template<>
    struct equal_to<array<int,26>>{
        bool operator()(const array<int,26>& arr1,const array<int,26>& arr2)const{
            for(int i=0;i<26;i++){
                if(arr1[i]!=arr2[i]){
                    return false;
                }
            }
            return true;
        }
    };
}
unordered_set<array<int, 26>> hashset;//success

在对std::hash和std::equal_to进行特化时,其结构内部的仿函数应该使用const进行修饰,否则在使用时可能发生错误。

除此之外,还可以使用下面的方式进行特化,起到的效果一致

namespace std{
    template<typename T,size_t N>
    struct hash<array<T,N>>{
        size_t operator()(const array<T,N>& arr)const{
            size_t hashValue=0;
            for(int i=0;i<N;i++){
                hashValue=hashValue*31+std::hash<T>()(arr[i]);
            }
            return hashValue;
        }
    };
    template<typename T,size_t N>
    struct equal_to<array<T,N>>{
        bool operator()(const array<T,N>& arr1,const array<T,N>& arr2)const{
            for(int i=0;i<N;i++){
                if(arr1[i]!=arr2[i]){
                    return false;
                }
            }
            return true;
        }
    };
}

自定义比较结构

自定义比较结构时,需要显示指出模版参数的类型

struct ArrayHash {
	size_t operator()(const array<int, 26>& arr)const {
		size_t hashValue = 0;
		for (int i = 0; i < 26; i++) {
			hashValue = hashValue * 31 + std::hash<int>()(arr[i]);
		}
		return hashValue;
	}
};
struct ArrayPred {
	bool operator()(const array<int, 26>& arr1, const array<int, 26>& arr2)const {
		for (int i = 0; i < 26; i++) {
			if (arr1[i] != arr2[i]) {
				return false;
			}
		}
		return true;
	}
};
//第二个模版参数为ArrayHash,第三个模版参数为ArrayPred
unordered_set<array<int,26>,ArrayHash,ArrayPred> hashset;

使用lambda表达式

可以使用lambda表达式配合decltype完成自定义哈希函数与比较器

auto ArrayHash = [](const array<int, 26>& arr) {
    size_t hashValue = 0;
    for (int i = 0; i < 26; i++) {
        hashValue = hashValue * 31 + std::hash<int>()(arr[i]);
    }
    return hashValue;
};
auto ArrayPred = [](const array<int, 26>& arr1, const array<int, 26>& arr2) {
    for (int i = 0; i < 26; i++) {
        if (arr1[i] != arr2[i]) {
            return false;
        }
    }
    return true;
};

由于lambda表达式闭包的特点,其对应的类型无法生成默认对象(例如上述decltype(ArrayPred) obj是错误的),因此需要在构造函数中将lambda对象传入,unordered_set存在如下构造函数:

explicit unordered_set(size_t n=/*依据平台与库的实现*/,const hasher& hf=hasher(),const key_equal& eql=key_equal(),const allocator_type& alloc=allocator_type());

其中n为初始时哈希表的大小,与平台和库的实现有关,由于lambda闭包,第二个参数与第三个参数必须传入指定lambda对象,这就需要我们对第一个参数进行设置,因此,使用lambda表达式完成自定义哈希函数与比较器,在进行实例化对象时,需要采取如下写法

unordered_set<array<int,26>,decltype(ArrayHash),decltype(ArrayPred)> hashset(20,ArrayHash,ArrayPred);

第一个参数为哈希表的初始大小(必须指定)。

map与set

map与set的声明

template<class Key,class T,class Compare=less<Key>,class Alloc=allocator<pair<const Ket,T>>>
class map;
template<class Key,class Compare=less<Key>,class Alloc=allocator<T>>
class set;

在定义对象时,需要保证模版参数对应的类型可以进行比较操作,若不能,则需要自定义比较操作。

namespace std {
	template<class T,size_t N>
	struct less<array<T,N>> {
		bool operator()(const array<T, N>& left, const array<T, N>& right)const {
			return left[0] < right[0];
		}
	};
}
set<array<int, 26>> s;

自定义比较操作可以针对std::less进行特化,可以自定义比较结构,也可以使用lambda表达式,若使用lambda表达式,需要在构造函数中传入lambda对象

struct ArrayComp {
	bool operator()(const array<int, 26>& left, const array<int, 26>& right)const {
		return left[0] < right[0];
	}
};
set<array<int, 26>,ArrayComp> s;

lambda:

auto ArrayComp = [](const array<int, 26>& left, const array<int, 26>& right) {
    return left[0] < right[0];
};
set<array<int, 26>, decltype(ArrayComp)> s(ArrayComp);

应用

leetcode字母异位词分组

在此题中,可以使用哈希表,并将key设置为array,此时需要自定义哈希函数与比较规则

namespace std{
    template<typename T,size_t N>
    struct hash<array<T,N>>{
        size_t operator()(const array<T,N>& arr)const{
            size_t hashValue=0;
            for(const auto& i:arr){
                hashValue=hashValue*31+std::hash<T>()(i);
            }
            return hashValue;
        }
    };
    template<typename T,size_t N>
    struct equal_to<array<T,N>>{
        size_t operator()(const array<T,N>& arr1,const array<T,N>& arr2)const{
            for(int i=0;i<26;i++){
                if(arr1[i]!=arr2[i]){
                    return false;
                }
            }
            return true;
        }
    };
}
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>> ans;
        unordered_map<array<int,26>,vector<string>> hashmap;
        for(string& s:strs){
            array<int,26> charcnts{};
            for(char c:s){
                charcnts[c-'a']++;
            }
            hashmap[charcnts].push_back(move(s));
        }
        for(auto&[k,vecstr]:hashmap){
            ans.push_back(move(vecstr));
        }
        return ans;
    }
};

你可能感兴趣的:(哈希算法,c++,散列表)