《算法笔记》学习笔记
第四章 入门篇(2)–算法初步
4.2散列
4.2.1 散列的定义与整数散列
散列(hash):将元素(
k
e
y
key
key)通过一个函数(
H
H
H)转换为整数(
H
(
k
e
y
)
H(key)
H(key)),使得该整数可以尽量唯一地代表整个元素
常用散列函数:
- 直接定址法:恒等变换( H ( k e y ) = k e y H(key) = key H(key)=key)或线性变换( H ( k e y ) = a ∗ k e y + b H(key) = a * key + b H(key)=a∗key+b)
- 平方取中法:将 k e y key key的平方的中间若干位作为hash值(很少用)
- 除留余数法:将key除以一个数mod得到的余数作为hash值得方法,即
H
(
k
e
y
)
=
k
e
y
%
m
o
d
H(key) = key \% mod
H(key)=key%mod但这种方法会导致“冲突”的发生。以下三种方法可以解决冲突:
- 线性探查法(Linear Probing)
当得到 k e y key key的hash值 H ( k e y ) H(key) H(key),但是表中下标为 H ( k e y ) H(key) H(key)的位置已经被某个其他元素使用了,那么就检查下一个位置 H ( k e y ) + 1 H(key)+1 H(key)+1是否被占,若没有就使用这个位置;否则就继续检查下一个位置。如果检查过程中超过了表长,那么就回到表的首位继续循环,直到找到一个可以使用的位置,或是发现表中所有位置否已被使用。不过这种方法容易导致扎堆,在一定程度上会降低效率。 - 平方探查法(Quadratic probing)
为了避免扎堆现象,当表中下标为 H ( k e y ) H(key) H(key)的位置被占时,将按下面的顺序检查表中的位置: H ( k e y ) + 1 2 、 H ( k e y ) − 1 2 、 H ( k e y ) + 2 2 、 H ( k e y ) − 2 2 、 . . . H(key)+1^2、H(key)-1^2、H(key)+2^2、H(key)-2^2、... H(key)+12、H(key)−12、H(key)+22、H(key)−22、...。若检查过程中 H ( k e y ) + k 2 H(key)+k^2 H(key)+k2超过了表长TSize,那么就把 H ( k e y ) + k 2 H(key)+k^2 H(key)+k2对表长TSize取模;若 H ( k e y ) − k 2 < 0 H(key)-k^2<0 H(key)−k2<0,那么将 ( ( H ( k e y ) − k 2 ) % T S i z e + T S i z e ) % T S i z e ((H(key)-k^2)\%TSize+TSize)\%TSize ((H(key)−k2)%TSize+TSize)%TSize作为结果 - 链地址法(拉链法)
将所有 H ( k e y ) H(key) H(key)相同的key连接成一条单链表
- 线性探查法(Linear Probing)
4.2.2 字符串hash初步
字符串hash:将一个字符串S映射为一个整数,使该整数尽可能唯一地代表字符串S
关于如何解决字符串hash,可以将A~Z看成0 ~25,也就是将字母对应成二十六进制,所以只需要进行进制转换。在这里我们通常的进制转换一般是从字符串末尾逐次向前遍历进行转换:
//假设字符串S只由A~Z的字符组成
int f1(string s) {
int l = s.size();
int result = 0;
for(int i = l - 1; i >= 0; i--) {
result += (s[i] - 'A') * pow(26, l - 1 - i);
}
return result;
}
而书中给出了另一种方法:
int f2(string s) {
int result = 0;
for(int i = 0; i < s.size(); i++) {
result = result * 26 + s[i] - 'A';
}
return result;
}
例题
问题:给出N个字符串(由恰好三位大写字母组成),再给出M个查询字符串,问每个查询字符串在N个字符串中出现的次数。
#include <iostream>
#include <string>
using namespace std;
int hashTable(string s) {
int result = 0;
for(int i = 0; i < s.size(); i++) {
result = result * 26 + s[i] - 'A';
}
return result;
}
int main() {
int n, m;
cin >> n >> m;
int a[26 * 26 * 26];
for(int i = 0; i < n; i++) {
string s;
cin >> s;
a[hashTable(s)]++;
}
for(int i = 0; i < m; i++) {
string s;
cin >> s;
cout << a[hashTable(s)] << endl;
}
return 0;
}
习题
Codeup墓地
1.问题 A: 谁是你的潜在朋友
//谁是你的潜在朋友
#include <iostream>
using namespace std;
int main() {
int n, m;
while(cin >> n >> m) {
int person_like[n] = {0}, book[m] = {0};
for(int i = 0; i < n; i++) {
cin >> person_like[i];
book[person_like[i]]++;
}
for(int i = 0; i < n; i++) {
if(book[person_like[i]] == 1) {
cout << "BeiJu" << endl;
} else {
cout << book[person_like[i]] - 1 << endl;
}
}
}
return 0;
}
注意数组初始化为零
2.问题 B: 分组统计
//分组统计
#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main() {
int m;
cin >> m;
for(int i = 0; i < m; i++) {
int n;
cin >> n;
int a[n];
set<int> number;
set<int> group;
vector<pair<int, int> > array;
for(int j = 0; j < n; j++) {
cin >> a[j];
number.insert(a[j]);
}
for(int j = 0; j < n; j++) {
int temp;
cin >> temp;
pair<int, int> p;
p.first = temp;
p.second = a[j];
array.push_back(p);
group.insert(temp);
}
for(set<int>::iterator iterator = group.begin(); iterator != group.end(); iterator++) {
cout << *iterator << "={";
bool flag = true;
for(set<int>::iterator iter = number.begin(); iter != number.end(); iter++) {
if(flag) flag = false;
else cout << ",";
cout << *iter << "=";
int count = 0;
for(int j = 0; j < array.size(); j++) {
if (array[j].first == *iterator && array[j].second == *iter)
count++;
}
cout << count;
}
cout << "}" << endl;
}
}
return 0;
}
n不超过100是指 数的个数不超过100,不是数的范围
3.问题C: Be Unique (20)
//Be Unique
#include <iostream>
using namespace std;
int main() {
int n;
while(cin >> n) {
int number[10000] = {0};
int a[n];
for(int i = 0; i < n; i++) {
cin >> a[i];
number[a[i] - 1]++;
}
bool flag = true;
for(int i = 0; i < n; i++) {
if(number[a[i] - 1] == 1) {
flag = false;
cout << a[i] << endl;
break;
}
}
if(flag) cout << "None" << endl;
}
return 0;
}
4.问题 D: String Subtraction (20)
//String Subtraction
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1, s2;
while(getline(cin, s1) && getline(cin, s2)) {
for(int i = 0; i < s1.length(); i++) {
if(s2.find(s1[i]) == string::npos) cout << s1[i];
}
cout << endl;
}
return 0;
}
总结体会
本节的将字符串hash转化为进制转换很有趣。在实战中可以利用一些像vetcor、map、set等数据结构提升效率