【哈希表】数据结构与算法——代码随想录

学习目的
1.学习了解哈希表基础理论和应用场景
2.掌握哈希表的常用

参考资料
红黑树
C++中set的用法
auto关键字
哈希表(Hash Table)/散列表(Key-Value)
散列算法
(一)哈希表基础知识
1.基础概念
(1)相关背景
数组特点:寻址容易、插入和删除困难;
链表特点:寻址困难、插入和删除容易;
哈希表:集查找、插入和删除一身的数据结构;
(哈希法是牺牲了空间换取了时间,用额外的数据结构存放数据实现快速查找)
应用:
用来快速判断一个元素是否出现集合里。
eg:日常学生表单中查询学生是否在名单上,就把学校里学生的名字都存在哈希表里,通过索引名字就可以查询。类比数组,将索引从下标变成其他任意你希望的样子,通过哈希函数完成这种对应关系。

(2)基础术语
Hash Table:根据Key-vaule直接进行访问的数据结构,常用的map;
Hash Function:Hash Table的映射函数,可以把任意长度的输入变成固定长度的输入,输出就是哈希值
由关键字k可以有HashFuntion很快得知存储的位置,以关键值为自变量,以实际函数计算结果为存储结构;
(3)哈希碰撞
多对一的映射

解决办法 详细内容 其他备注
线性探测法 哈希表的长度比数据长度适当的大一点 如:同学1与同学2的名字在表中映射到了相同的地方,那就将碰撞的两个同学名字依次放入哈希表中。
再哈希法 当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。 计算时间增加。
链地址法(拉链法) 所有关键字为同义词的记录存储在同一线性链表中。 处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;但是指针需要额外的空间。

拉链法也可以视为一个链表数组,每一个指针指向一个链表的头结点
2.代码实现
(1)利用set读取字符串中的相关信息

unordered_set<int> nums_set(nums.begin(),nums.end());//用set记录数组中的数据信息

3.常用操作
(二)链表例题
1.有效的字母异位词
有效的字母异位词
字母的种类数目有限,所以可以考虑用数组来存储,定义一个长度为26的字符数组。
求两个字符串出现的元素个数是否相等,
一个是定义两个这样的数组再进行比较,另一个是直接在同一个数组上进行操作。
扩展到其他类型的字符统计,包括但不局限于字母。
步骤:
a.定义一个数组记录字符串
b.遍历字符串统计字符中各个字母出现的次数;
c.两个字符串一个进行正向记录,一个反向消除
d.判定数组是不是全为0元素,为0,则是异位词
重点在遍历两个数组

class Solution {
public:
    bool isAnagram(string s, string t) {
        int record[26] ={0};
        for(int i = 0; i<s.size();i++){//分别统计两个字符串字母出现的情况
            record[s[i]-'a']++;
        }
        for(int j = 0; j<t.size();j++){
            record[t[j]-'a']--;
        }
        for(int k = 0;k<26;k++){
            if(record[k]!=0){
                return false;
            }
            else continue;
        }
        return true;
    }
};

2.两个数组的交集
两个数组的交集
基本原理
与上一题的区别在于这个是具体的数值,数值元素的值分散,再用数组会造成空间的极大浪费;
std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。
unorder_set自动包含了排序和去重
步骤:
a.定义一个存放结果的unorder_set
b.将两个数组放入set中去重
c.遍历一个数组,看数组中的元素是否在另一个数组中出现过。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result;
        unordered_set<int> nums_1(nums1.begin(),nums1.end());//对nums1进行去重和排序
        for(int num:nums2){ //遍历nums2中的元素
            if(nums_1.find(num)!=nums_1.end()){     //如果有相同的元素就不会指向末尾了
            result.insert(num);//集合用insert的方式,这就一起把nums2进行了去重
            }
        }
        return vector<int>(result.begin(),result.end());//把unordered_set转换成vector形式          
    }
};

(2)哈希表达相关概念

三种访问方式:向量循秩访问Call by rank 、列表循位置访问Call by position、二叉搜索树循关键码访问Call by key、哈希循值访问Call by value。
哈希表常用的地址方法
将数据尽可能分散到哈希表的每一个项中;
定址的方法:哈希函数、处理冲突的方法

定制方法 定址公式
直接定址法 key=Value+C
除法取余 key=value%C
数字分析法 分析数据的特点,根据数据的特点进行映射
折叠法 尽可能让每一位key与每一位的value都有关
平方取中法
根据关键值直接进行访问的数据结构。

4.常见的三种哈希结构

用哈希解决问题的三种数据结构:数组、集合、映射

3.快乐数
将数据按位数拆解求和的过程;
如果是快乐数则最终循环会变到1,不是快乐数则会无限循环。
能用哈希表解决这个问题的关键在于,不是快乐数求和的结果会重复出现
步骤:
a.写一个计算数据各个位数上数据平方和的函数
b.将每一次记录的结果存入一个set,如果数据已经出现过,return false

class Solution {
public:
    int getSum(int n){
        int sum=0;
        while(n){
            int k = n%10;
            sum += k*k;
            n = n/10;
            
        }
        return sum;
    }
    bool isHappy(int n) {
        set<int> set;
        while(1){//这个while(1)的用法可以由题目中提到的无限循环想到,循环体内用return打破循环
            int sum = getSum(n);
            //最重要的就是对sum进行判断
            if(sum == 1){
                return true;
            }
            if(set.find(sum)!=set.end()){
                return false;
            }else{
                set.insert(sum);
            }
            n = sum;
        }
    }
};

4.两数之和
分析题目选择合适的存储方式:
数组:在记录具体数据出现次数时一般不用数组,因为哈希值太大会造成存储资源的浪费
set:只能存放一个key值既要判断符合两数之和的数是否存在,又要返回数值对应的下标位置。
map:题目中不需要key值有序,选择unordered_map更合适
步骤:
a.用map存储数组元素和下标
b.同一个元素不能重复,所以先要将目标元素移除
c.遍历数组看对应的target-nums[i]是否在容器里

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        //定义一个map存储元素值和对应下标
        unordered_map<int,int> map;
        //遍历整个数组找答案
        for(int i=0;i<nums.size();i++){
        //map.find()会返回一个迭代器,用auto比较万能
            auto iter = map.find(target-nums[i]);
            if(iter!=map.end()){
                return{iter->second,i};
            }
            //将元素与已经插入到map中的元素进行搜索,对应题目要求中的同一个元素在答案里不重复出现
            //map插入方式要用pair的方式
            map.insert(pair<int,int>(nums[i],i));
        }
        return{};
    }
};

5.赎金信
和之前的两个字母异位词类似的解法
之前的要求两个字符串之间每个字符出现的次数一致,
这个只需要杂志上的字母个数大于赎金信需要的个数,所以将return条件修改一下就可以。
步骤:
a.用一个数组存储magazine中出现字符的情况,出现对应字符++
b.遍历赎金信字符串,出现对应字符–
c.对整个数组中的元素进行是否小于0的判断

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[26] = {0};
        for (int i = 0; i < magazine.length(); i++) {
            // 通过recode数据记录 magazine里各个字符出现次数
            record[magazine[i]-'a'] ++;
        }
        for (int j = 0; j < ransomNote.length(); j++) {
            // 遍历ransomNote,在record里对应的字符个数做--操作
            record[ransomNote[j]-'a']--;
            // 如果小于零说明ransomNote里出现的字符,magazine没有
            if(record[ransomNote[j]-'a'] < 0) {
                return false;
            }
        }
        return true;
    }
};

你可能感兴趣的:(数据结构与算法,数据结构,散列表,算法)