我需要使用哈希表创建MultiMap,但出现了超过时间限制的错误(C ++)

时间:2018-11-14 22:32:29

标签: c++ algorithm hashtable multimap

我正在尝试解决算法任务:我需要使用哈希表创建MultiMap(key,(values))。 我无法使用设置和地图库。我将代码发送到测试系统,但是在测试20上遇到了超过时间限制的错误。我不知道该测试包含的内容。该代码必须执行以下任务:

输入x y-添加对(x,y)。如果存在对,则不执行任何操作。

删除x y-删除对(x,y)。如果不存在配对,则什么也不做。

deleteall x-删除所有具有第一个元素x的对。

get x-显示具有第一个元素x和第二个元素的对的数量。

操作量<= 100000

时间限制-2秒

示例:

multimap.in:

输入一个

输入b

输入c

获取

删除a b

获取

删除所有

获取

multimap.out:

3 b c a

2 c a

0

#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

inline long long h1(const string& key) {
    long long number = 0;
    const int p = 31;
    int pow = 1;
    for(auto& x : key){
        number += (x - 'a' + 1 ) * pow;
        pow *= p;
    }
    return abs(number) % 1000003;
}

 inline void Put(vector<vector<pair<string,string>>>& Hash_table,const long long& hash, const string& key, const string& value) {
    int checker = 0;
    for(int i = 0; i < Hash_table[hash].size();i++) {
        if(Hash_table[hash][i].first == key && Hash_table[hash][i].second == value) {
                checker = 1;
            break;
        }
    }
    if(checker == 0){
        pair <string,string> key_value = make_pair(key,value);
        Hash_table[hash].push_back(key_value);
    }
}
 inline void Delete(vector<vector<pair<string,string>>>& Hash_table,const long long& hash, const string& key, const string& value) {
    for(int i = 0; i < Hash_table[hash].size();i++) {
        if(Hash_table[hash][i].first == key && Hash_table[hash][i].second == value) {
            Hash_table[hash].erase(Hash_table[hash].begin() + i);
            break;
        }
    }
}

  inline void Delete_All(vector<vector<pair<string,string>>>& Hash_table,const long long& hash,const string& key) {
    for(int i = Hash_table[hash].size() - 1;i >= 0;i--){
        if(Hash_table[hash][i].first == key){
            Hash_table[hash].erase(Hash_table[hash].begin() + i);
        }
    }
}
inline string Get(const vector<vector<pair<string,string>>>& Hash_table,const long long& hash, const string& key) {
    string result="";
    int counter = 0;
    for(int i = 0; i < Hash_table[hash].size();i++){
        if(Hash_table[hash][i].first == key){
            counter++;
            result += Hash_table[hash][i].second + " ";
        }
    }
    if(counter != 0)
        return to_string(counter) + " " + result + "\n";
    else
        return "0\n";

}

int main() {
    vector<vector<pair<string,string>>> Hash_table;
    Hash_table.resize(1000003);
    ifstream input("multimap.in");
    ofstream output("multimap.out");
    string command;
    string key;
    int k = 0;
    string value;
     while(true) {
        input >> command;
        if(input.eof())
            break;
        if(command == "put") {
            input >> key;
            long long hash = h1(key);
            input >> value;
            Put(Hash_table,hash,key,value);
        }
        if(command == "delete") {
            input >> key;
            input >> value;
            long long  hash = h1(key);
            Delete(Hash_table,hash,key,value);
        } 
        if(command == "get") {
            input >> key;
            long long  hash = h1(key);
            output << Get(Hash_table,hash,key);
        }
        if(command == "deleteall"){
            input >> key;
            long long hash = h1(key);
            Delete_All(Hash_table,hash,key);
        } 
    }  
}

如何使我的代码工作更快?

1 个答案:

答案 0 :(得分:0)

最初,这是一个设计问题:通常,通常只将键传递给函数并计算其中的哈希值。您的变体允许用户将元素放置在哈希表中的任何位置(使用错误的哈希值),以便用户可以轻松地将其破坏。

所以e。 G。 put

using HashTable = std::vector<std::vector<std::pair<std::string, std::string>>>;

void put(HashTable& table, std::string& key, std::string const& value)
{
    auto hash = h1(key);
    // ...
}

如果可以的话,可以对哈希函数进行参数化,但是您需要为(包装向量的向量)编写一个单独的类,并在构造函数中提供哈希函数,以使用户无法任意交换它(并再次破坏哈希表)。一个类将带来额外的好处,最重要的是:更好的封装(将向量隐藏起来,因此用户无法使用向量自己的界面对其进行更改):

class HashTable
{
public:
    // IF you want to provide hash function:
    template <typename Hash>
    HashTable(Hash hash) : hash(hash) { }

    void put(std::string const& key, std::string const& value);
    void remove(std::string const& key, std::string const& value); //(delete is keyword!)
    // ... 
private:
    std::vector<std::vector<std::pair<std::string, std::string>>> data;

    // if hash function parametrized:
    std::function<size_t(std::string)> hash; // #include <functional> for
};

我不确定100%std::function到底有多有效,因此对于高性能代码,最好直接使用哈希函数h1(不要像上面说明的那样构造)。

进行优化:

对于散列键,我希望使用无符号值:负索引无论如何都是没有意义的,那么为什么要允许它们呢?如果测试系统是32位系统(可能不太可能,但仍然...),则long long(有符号或无符号)可能是一个不好的选择。 size_t一次涵盖了两个问题:它是未签名的,并且已为给定系统选择了适当的大小(如果对详细信息感兴趣:实际上已针对地址总线大小进行了调整,但是在现代系统上,这也等于寄存器大小,这是我们需要的)。选择pow的类型相同。

deleteAll的实现效率低下:擦除每个元素后,会将所有后续元素向前移一个位置。如果删除多个元素,则会重复执行此操作,因此一个元素可以多次移动。更好:

auto pos = vector.begin();
for(auto& pair : vector)
{
    if(pair.first != keyToDelete)
        *pos++ = std::move(s); // move semantics: faster than copying!
}
vector.erase(pos, vector.end());

这将最多移动一次每个元素,一次擦除所有剩余元素。从最后一次擦除开始,Appart(然后您必须明确地做)是或多或少地来自algorithm librarystd::removestd::remove_if。您可以使用它吗?然后您的代码可能如下所示:

auto condition = [&keyToDelete](std::pair<std::string, std::string> const& p)
                 { return p.first == keyToDelete; };
vector.erase(std::remove_if(vector.begin(), vector.end(), condition), vector.end());

您将从已经高度优化的算法中受益。

仅会获得很小的性能提升,但仍然:如果您找到了一个元素就简单地返回,则可以在put中保留变量初始化,赋值和条件分支(在某些系统上,后者可能是相对昂贵的操作)。 :

//int checker = 0;
for(auto& pair : hashTable[hash]) // just a little more comfortable to write...
{
    if(pair.first == key && pair.second == value)
        return;
}
auto key_value = std::make_pair(key, value);
hashTable[hash].push_back(key_value);

再次,使用算法库:

auto key_value = std::make_pair(key, value);
// same condition as above!
if(std::find_if(vector.begin(), vector.end(), condition) == vector.end();
{
    vector.push_back(key_value);
}

然后少于100000的操作并不表示每个操作都需要一个单独的键/值对。我们可能希望添加,删除,重新添加,...等键,因此您很可能不必应对100000个不同的值。我以为您的地图太大了(请注意,它也需要初始化100000个向量)。我以为已经足够小了(可能是1009或10007?您可能需要尝试一下...)。

保持内部向量的排序也可能会给您带来一些性能提升:

  • put:您可以使用二进制搜索找到要插入的两个元素之间的两个元素(如果这两个元素中的一个等于给定的元素,那么当然不插入)
  • delete:使用二进制搜索找到要删除的元素。
  • deleteAll:找到要删除的元素的上限和下限,并立即擦除整个范围。
  • get:找到deleteAll的上下限,之间的距离(元素数)是一个简单的减法,您可以直接打印出文本(而不是先构建一个长字符串) )。不过,可以发现直接输出或创建字符串实际上更有效的方式,因为直接输出涉及多个系统调用,最终可能会再次损失以前获得的性能...

考虑您的输入循环:

检查eof()(仅)为critical!如果文件中有错误,您将陷入无休止的循环,因为设置了失败位,operator>>实际上将不再读取任何内容,并且您将永远不会结束文件。这甚至可能是您的第20个测试失败的原因。

另外:您具有基于行的输入(每个命令在单独的行上),因此一次读取一整行,然后再进行解析,这样可以节省一些系统调用。如果缺少某些参数,您将正确地检测到它,而不是(非法)将下一个命令(例如put)读取为参数,类似地,您也不会将多余的参数解释为下一命令。如果某行由于某种原因(上面的参数数量太少或命令未知)无效,则可以单独决定要执行的操作(只需忽略该行或完全中止处理)。所以:

std::string line;
while(std::getline(std::cin, line))
{
    // parse the string; if line is invalid, appropriate error handling
    // (ignoring the line, exiting from loop, ...)
}
if(!std::cin.eof())
{
    // some error occured, print error message!
}