题目概述
请用哈希表存储客户的账户和密码信息,然后检查用户输入的账户和密码是否正确。
输入:第一行是一个正整数n(<=100,000),表示客户数目,然后是每个客户的信息。
每个客户信息的第一行是账号,第二行是密码。
账户信息之后是用户登录信息。第一行是登录用户数目m(<=10,000), 然后是m个用户输入的账户(第一行)和密码(第二行)。假设用户名只含大写字母。
输出:检查每个用户(共m个登录用户)的登录信息,如果正确,则输出账户及“OK”字样,否则输出账户及“Invalid user name or password”字样。
要求:
时间限制: 3000 ms
空间限制: 32 MB
(原题还增加了密码md5加密,由于只是简单地调用函数,故此处省略)
方法一 字符权重 + 开放寻址法
哈希函数的选用—字符权重
由于名字只含大写字母,很直接地想到将A~Z对应成26进制,然后计算不同字符串的哈希值。但是这样写,当名字越长,哈希出来的值越来越大。
一位:26
二位:702
三位:18278
四位:475254
五位:12356630
在第四位时,就已经超过了我们要存放的数据大小,很容易超过限定内存。
int myhash(string s) {
int len = s.size();
int id = 0;
int index = 1;
for (int i = 0; i < len; i++) {
id += index *(s[i] - 'A' + 1);
index *= 26;
}
return id;
}
但是我们也能稍微改进一下,一是限定计算的位数,二是更改不同位置的权重~~(说白了就是卡时间和卡空间的暴力做法qwq)~~
当限制计算前5位数,每位权重为10^i,此时下标最高为288886,不会超出太多(以下为更改后)
int myhash(string s) {
int len = s.size();
int id = 0;
int index = 1;
for (int i = 0; i < len && i < 5; i++) {
id += index *(s[i] - 'A' + 1);
index *= 10;
}
return id;
}
冲突的设计—开放寻址法
像这种数据分散很开,并且最高下标远高于实际需要存储的散列表,在发生冲突时,可以考虑使用开放寻址法,使空余空间得到充分利用。
为了方便(懒 ),这里直接使用 线性探测
当存放数据时,发现位置已被占用,则从该位置开始,往后一个个挪去探测是否有空位。
完整代码如下(还需增加上面的自定义的哈希函数)
#include<iostream>
#include<string>
using namespace std;
string name[288886];
string passw[288886];
int check[288886] = {0};
//判断用户名是否存在,或者密码是否正确
bool checkpass(string each_name, string password, int id) {
while (check[id]) {
if (name[id] == each_name){
if (password == passw[id]) return true;
return false;
}
id++;
if (id == 288886) id = 0;
}
return false;
}
int main() {
int user;
int test;
int id;
cin >> user;
string each_name;
string password;
//录入的用户数
for (int i = 0; i < user; i++) {
cin >> each_name;
cin >> password;
int id = myhash(each_name);
while (check[id]) {
id++;
if (id == 288886) id = 0;
}
check[id] = 1;
name[id] = each_name;
passw[id] = password;
}
//尝试登陆的测试数
cin >> test;
while (test--) {
cin >> each_name;
cin >> password;
id = myhash(each_name);
if (checkpass(each_name, password, id) ) cout << each_name << ":OK" << endl;
else cout << each_name << ":Invalid user name or password" << endl;
}
return 0;
}
方法二 字符相加 + 拉链法
哈希函数的选用—字符相加
还能想到一种方法是,将字符串中每个字母A ~ Z对应1 ~ 26,然后把各个位置的字符串值直接相加得到哈希值。在客户名字不长的情况下,下标500就绰绰有余了。
但是这种方法面临的问题是:发生冲突的可能性大大增加,要多考虑时间成本。
int myhash(string s) {
int len = s.size();
int id = 0;
for (int i = 0; i < len; i++) {
id += s[i] - 'A' + 1;
}
return id;
}
冲突的设计—拉链法
而这种数据密集的散列表,更多使用 拉链法 解决冲突。
当发生冲突时,拉出一个动态链表代替静态顺序存储结构,可以避免哈希函数的冲突。我们也完全可以使用vector容器代替自己写链表,减少出错。
#include<iostream>
#include<string>
#include<vector>
using namespace std;
vector<bool> check(500,false);
vector<vector<string> > r_name(500);
vector<vector<string> > r_pass(500);
bool checkpass(string each_name,string password ,int id) {
if (!check[id]) return false;
int len = r_name[id].size();
for (int i = 0; i < len; i++) {
if (r_name[id][i] == each_name) {
if (r_pass[id][i] == password) return true;
return false;
}
}
return false;
}
int main() {
int user;
int test;
int id;
string each_name;
string password;
//录入的用户数
cin >> user;
for (int i = 0; i < user; i++) {
cin >> each_name;
cin >> password;
id = myhash(each_name);
if (!check[id]) check[id] = true;
r_name[id].push_back(each_name);
r_pass[id].push_back(password);
}
//尝试登陆的测试数
cin >> test;
while (test--) {
cin >> each_name;
cin >> password;
id = myhash(each_name);
if (checkpass(each_name,password,id) ) cout << each_name << ":OK" << endl;
else cout << each_name << ":Invalid user name or password" << endl;
}
return 0;
}
写得很烂的小菜鸡wwwwww
如有问题的地方,欢迎和我交流qwq