字典:如何最小化散列表和空间中未使用的char数组元素的空间?

时间:2017-02-27 20:49:41

标签: c++ algorithm dictionary data-structures hash

我正在编写一个程序,读取一个包含200,000个字典单词的文本文档。我会将它与2,000,000个单词的另一个文档进行比较,以计算字典中没有的行。我只会存储字母字符a-z(26个字符)。因此,我只需要5位来表示每一个。角色的最大长度为29 (30 including the null character)。如果字典中只包含小于7 characters的单词,我还需要考虑。

此系统的条件是它有自己的必须使用的自定义分配器。所以每当我使用" new" (或任何其他类型的堆内存分配)动态分配内存,每次使用32 bytes关键字时,它将耗尽至少 new内存。为了节省内存,我需要初始化更大的char arrays

我也不允许直接读取文件中的字典单词。它们将在一个名为dictionaryWords的数组中提供给我,并在构建完哈希表后将被销毁。所以指向给我的话并不合理。

该程序必须以低于1 second的速度运行。

Dictionary:
abc
lkas
ddjjsa
dada

Word Doc:
abc
lkas
weee
dada
jajaja

Wrong Numbers List: 3, 5

它会将数字3和5放入Word Doc中不在字典中的行号的数组中。

我将200,000字典单词保留在哈希表中,因此从单词文档到哈希表的查找O(n)n个单词。问题是我的哈希表需要使用0.5的加载因子进行重新散列,所以它必须重新散列并且只要半满时就加倍。这给我留下了浪费的200,000空哈希条目,占用了内存。由于最大字长为30,而字符值为1 byte,因此我的哈希条目中每个字的费用为30 bytes。程序运行时,我在堆中浪费30 characters * 1 byte * 200,000 empty entries = 6000000 bytes / 6MB个内存空间。我想尽可能地减少空间使用,但能够在O(n)时间内运行。

这是我的哈希表的哈希条目。你可以在这里看到,对于200,000字的表大小,我需要400,000个这样的条目以保持0.5的负载系数

struct HashElement
{
    char element[30];

    HashElement(char * word) 
      {
        memset(element, '\0', 30); //Set all bits to NULL for collision checking
        if (e != NULL)
        {
            strcpy(element, word);
        }
      }
};

如果我只用5位代表每个角色,我就可以节省3/8的浪费空间。

我已经考虑过我的哈希条目的指针方法:

struct HashElement
{
    char * element;

    HashElement(char * word) 
      {
        element = NULL;
        if (e != NULL)
        {
            element = word;
        }
      }
};

但是使用new关键字将耗尽AT LEAST 32 bytes用于此系统。因此,每次初始化哈希条目的元素时,无论字长大小是多少,都将花费32 bytes。如果我想将此问题分散到word文档中每行只包含7个字符,我会遇到麻烦,因为我只需要8 bytes每个单词,而我使用的是32 bytes

2 个答案:

答案 0 :(得分:2)

当您需要首先解决许多简单问题时,您正在解决一个难题。解决所有容易出现的问题可能就足够了,因此您无需处理将位压缩为更少字节的难题。

  1. 为什么分配30个字节只是因为您可能需要多达30个字节?

  2. 为什么要为空哈希桶分配元素?

答案 1 :(得分:1)

这是一个简单的字符串压缩器类,它将在'a'和'z'之间包含一串字符,并将每个8位表示压缩为5位并将得到的二进制分解为7位表示。由于每个字符仍由唯一编号表示,因此该字的散列仍应与预期一样唯一:

class StringCompressor
{
public:
    static string Compress( string );
    static string ToBinary( long long input , int length );
    static int ToInt( string input );
    static string Decompress( string );
};

string StringCompressor::Compress( string input )
{
    stringstream ss;
    for ( char c : input )
    {
        string temp = ToBinary( ( c - 'a' ) , 5 );
        ss << temp;
    }
    ss << string( ( 7 - ( ss.str().length() % 7 ) ) , '0' );
    string temp = ss.str();
    ss.str( "" );
    for ( int i = 0; i < temp.length(); i += 7 )
    {
        string temp2 = temp.substr( i , 7 );
        ss << (char)ToInt( temp2 );
    }
    return ss.str();
}
string StringCompressor::Decompress( string input )
{
    stringstream ss;
    for ( char c : input )
    {
        string temp = ToBinary( c , 7 );
        ss << temp;
    }
    string temp = ss.str().substr( 0 , ss.str().length() - ( ss.str().length() % 5 ) );
    ss.str( "" );
    for ( int i = 0; i < temp.length(); i += 5 )
    {
        ss << (char)( ( ToInt( temp.substr( i , 5 ) ) ) + 'a' );
    }
    return ss.str();
}
string StringCompressor::ToBinary( long long input , int length )
{
    string output( length , '0' );
    for ( int i = length - 1; i >= 0; i-- )
    {
        long long test = pow( 2.0 , i );
        if ( input >= test )
        {
            output[( length - 1 ) - i] = '1';
            input -= test;
        }

    }
    return output;
}
//Take a string representation of a binary number and return the base10 representation of it.  
//There's no validation of the string
int StringCompressor::ToInt( string input )
{
    int length = input.length();
    int output = 0;
    double temp = 0;
    for ( int i = 0; i < length; i++ )
    {
        temp = ( input[( length - 1 ) - i] - '0' );
        output += pow( 2.0 , i ) * temp;
    }
    return output;
}