zoukankan      html  css  js  c++  java
  • 字符串Hash

    Hash,我们又称散列,指的是我们通过一个散列算法,把输入值变换成另一个输出值,通常来说,是变得更易于我们处理的一个值,输出值的值域通常小于输入值的值域,这个过程也叫映射。

    在之前的学习中,我用的比较多的主要还是整数的映射,今天碰到一道字符串的题目,所以尝试了一下字符串映射的操作,特此记录。

    题目:

    输入一个N和M,其中中间以空格隔开,接下来是N个由三个小字字母组成的字符串和M个同样格式的字符串
    每个字符串占一行,输出M个字符串的内容以及分别在前面N个字符串中出现的次数,中间以空格隔开,每个
    输出占一行。
    示例输入:
    6 2
    doc
    txt
    exe
    doc
    txt
    txt
    doc
    txt
    示例输出:
    doc 1
    txt 3

    首先我们来想想,用一般的方法如何做?
    既然需要查询M个字符串在前N个字符串中出现了多少次,那么我们直接双重循环即可实现,拿每一个字符串分别与前N个做比较,如果相同则累加次数,最终输出即可。不难算出,此算法的时间复杂度为O(N*M),当数据量小的时候,这也不失为一种好办法,毕竟简单易懂,但是如果数据量上来了,M和N达到了104 或者更多的时候,我想这可能就行不太通了。

    我们分析一下这里的时间复杂度主要是什么操作贡献的:首先M个字符串要输出,那么外层循环M肯定少不了的(我们暂且不看输入的哈~),那么内层的N是不是有这个必要呢?我们找到了一个“doc”,累加了1;然后找到了一个“txt”,忽略它;找到“exe”,忽略;找到“doc”,累加…然后我们来找txt,找到“doc”,忽略;找到“txt”,累加…然后我们得出结论“txt”出现了3次。我们在找doc的时候,并没有为我们找txt提供任何帮助,导致我们在找txt又从第一个开始找,M个字符串,每一个都需要从第一个开始找,查询复杂度为O(N),最终时间复杂度为O(N*M)。
    那我们有办法避免吗?有!既然外层M不能少,而内层主要是查询导致复杂度偏高的,那我们用Hash空间换时间降低查询的复杂度,查询的过程直接像查字典一样复杂度为O(1).

    我们对数字用Hash函数的时候,通常是采取直接寻址法、除留余数法等等,而字符串怎么处理呢?字符串每个位的字符都是26个字母,有多个字符,当字符位数不够多时,我们可以把它当成一个26进制数字处理,而一个其它进制的数转成10进制是唯一的,且10进制可以直接作为下标(类似直接寻址法).
    我们来计算一下3位的字符串需要的Hash表要多长:

    ((26*0+25)*26+25)*26+25 = 25*26*27+25 = 17575

    emmmmm…勉强在接受范围内吧,好啦下面奉上本题采用字符串Hash的代码:

     1 #include<stdio.h>
     2 #define MAX 100
     3 char str[MAX][4];
     4 int table[25*26*27+26] = {0};//化成26进制处理   假设((26*0+25)*26+25)*26+25 = 25*26*27+25   
     5 int hash(char *s,int len);
     6 int main(void)
     7 {
     8     int n,m;
     9     char s[4];
    10     //freopen("data.txt","r",stdin);
    11     scanf("%d%d",&n,&m);
    12     for(int i = 0;i < n;++i)
    13     {
    14         scanf("%s",s);//原始的n个不需要储存,只需要计算映射后的值并累加即可。 
    15         ++table[hash(s,3)];
    16     }
    17     for(int i = 0;i < m;++i)
    18         scanf("%s",str[i]);
    19     for(int i = 0;i < m;++i)
    20     {
    21         printf("%s %d",str[i],table[hash(str[i],3)]);
    22         if(i != m-1)
    23             putchar('
    ');
    24     }  
    25     return 0;
    26 }
    27 int hash(char *s,int len)
    28 {
    29     int num = 0;
    30     for(int i = 0;i < len;++i)
    31     {
    32         num = num*26 + (s[i] - 'a');//把对应的字符当成26进制数字来处理,化成10进制 
    33     }
    34     return num; 
    35 } 

    接下来是朴素的代码:

    #include<stdio.h>
    #include<string.h>
    #define MAX 200
    char str[MAX][4];
    int main(void)
    {
        int n,m;
        char s[4];
        //freopen("data.txt","r",stdin);
        scanf("%d%d",&n,&m);
        for(int i = 0;i < n;++i)
            scanf("%s",str[i]);
        for(int i = 0;i < m;++i)
            scanf("%s",str[n+i]);
        for(int i = n;i < n+m;++i)
        {
            int count = 0;
            for(int j = 0;j < n;++j)
            {
                if(strcmp(str[i],str[j]) == 0)
                    ++count;
            }
            printf("%s %d",str[i],count);
            if(i != m-1)
                putchar('
    ');
        }  
        return 0;
    }

    我用文件重定向方式去掉了输入的时间,分别看了一下两种方法的时间:

    上面的是使用了Hash算法的,下面的是采用了朴素查找算法的,可以发现使用Hash算法速度略快,在字符串较多的时候效果应该更明显。

  • 相关阅读:
    把Linq查询返回的var类型的数据 转换为DataTable EF连接查询
    无法更新 EntitySet 因为它有一个 DefiningQuery
    MVC上传文件
    MySql删除表、数据
    LINQ to Entities 不支持 LINQ 表达式节点类型“ArrayIndex”。
    MVC仓储使用join
    MVC仓储执行存储过程报错“未提供该参数”
    Newtonsoft.Json自动升级版本号,导致dll冲突
    MVC中构建Linq条件、排序、Selector字段过滤
    AutoMapper
  • 原文地址:https://www.cnblogs.com/YaLi/p/10502871.html
Copyright © 2011-2022 走看看