我正在尝试解决算法任务:我需要使用哈希表创建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);
}
}
}
如何使我的代码工作更快?
答案 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 library的std::remove
和std::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!
}