A. DS哈希查找--链地址法
题目描述
给出一个数据序列,建立哈希表,采用求余法作为哈希函数,模数为11,哈希冲突用链地址法和表头插入
如果首次查找失败,就把数据插入到相应的位置中
实现哈希查找功能
输入
第一行输入n,表示有n个数据
第二行输入n个数据,都是自然数且互不相同,数据之间用空格隔开
第三行输入t,表示要查找t个数据
从第四行起,每行输入一个要查找的数据,都是正整数或0
6
11 23 39 48 75 62
6
39
52
52
63
63
52
输出
每行输出对应数据的查找结果
6 1
error
8 1
error
8 1
8 2
提示
注意,当两次输入要相同的查找数据,如果第一次查找不成功就会执行插入,那么第二次查找必然成功,且查找次数为1次(因为做表头插入)
例如示例数据中输入两次52,第一次查找失败就把52插入到位置8,第二次查找就成功了,所以第一次输出error,第二次就输出8 1
为什么第三次输入52会输出8 2
需要考虑多轮数据输入的情况,即第一个cin或者scanf需要用到循环,例如:while(cin>>...)。
#include <iostream>
#include <cstdio>
using namespace std;
struct node
{
int data;//数据值
node* next;//指针
};
node* s[11];
void hash_head(int key)
{
node* p = new node;
p->data = key;
p->next = s[key % 11];
s[key % 11] = p;
}
int main()
{
int n, key;
while (scanf("%d", &n) != EOF)
{
for (int i = 0; i < 11; i++)s[i] = NULL; //由于多组样例需要初始化哈希表
while (n--)
{
cin >> key;
hash_head(key);
}
int t;
cin >> t;
while (t--)
{
cin >> key;
node* p = s[key % 11];
int num = 1;
while (p)
{
if (p->data == key)
{
break;
}
p = p->next;
num++;
}
if (p) cout << key % 11 << " " << num << endl;
else
{
cout << "error" << endl;
hash_head(key);
}
}
}
}
B. DS哈希查找与增补
题目描述
给出一个数据序列,建立哈希表,采用求余法作为哈希函数,模数为11,哈希冲突用链地址法和表尾插入
如果首次查找失败,就把数据插入到相应的位置中
实现哈希查找与增补功能
输入
第一行输入n,表示有n个数据
第二行输入n个数据,都是自然数且互不相同,数据之间用空格隔开
第三行输入t,表示要查找t个数据
从第四行起,每行输入一个要查找的数据,都是正整数
6
11 23 39 48 75 62
6
39
52
52
63
63
52
输出
每行输出对应数据的查找结果,每个结果表示为数据所在位置[0,11)和查找次数,中间用空格分开
6 1
error
8 1
error
8 2
8 1
#include <iostream>
#include <cstdio>
using namespace std;
struct node
{
int data;//数据值
node* next;//指针
};
node* s[11];
void hash_head(int key) {
node* p = new node;
p->data = key;
p->next = nullptr; // 新节点作为链表的末尾节点,因此 next 指针应该指向 nullptr
int index = key % 11;
if (s[index] == nullptr) { // 如果对应索引处没有节点,则直接将新节点作为第一个节点
s[index] = p;
}
else {
node* tail = s[index];
while (tail->next != nullptr) { // 遍历到链表的末尾
tail = tail->next;
}
tail->next = p; // 在末尾添加新节点
}
}
int main()
{
int n, key;
while (scanf("%d", &n) != EOF)
{
for (int i = 0; i < 11; i++)s[i] = NULL; //由于多组样例需要初始化哈希表
while (n--)
{
cin >> key;
hash_head(key);
}
int t;
cin >> t;
while (t--)
{
cin >> key;
node* p = s[key % 11];
int num = 1;
while (p)
{
if (p->data == key)
{
break;
}
p = p->next;
num++;
}
if (p) cout << key % 11 << " " << num << endl;
else
{
cout << "error" << endl;
hash_head(key);
}
}
}
}
C. DS哈希查找—线性探测再散列
题目描述
定义哈希函数为H(key) = key%11。输入表长(大于、等于11),输入关键字集合,用线性探测再散列构建哈希表,并查找给定关键字。
–程序要求–
若使用C++只能include一个头文件iostream;若使用C语言只能include一个头文件stdio 程序中若include多过一个头文件,不看代码,作0分处理 不允许使用第三方对象或函数实现本题的要求
输入
测试次数t
每组测试数据为:
哈希表长m、关键字个数n
n个关键字
查找次数k
k个待查关键字
输出
对每组测试数据,输出以下信息:
构造的哈希表信息,数组中没有关键字的位置输出NULL
对k个待查关键字,分别输出:0或1(0—不成功,1—成功)、比较次数、查找成功的位置(从1开始)
样例输入
1
12 10
22 19 21 8 9 30 33 4 15 14
4
22
56
30
17
样例输出
22 30 33 14 4 15 NULL NULL 19 8 21 9
1 1 1
0 6
1 6 2
0 1
线性探测再散列的意思是
假设要放入4,16,7
4就直接放到4的位置,然后16%9 = 7,所以16放到7的位置,然后当7又要再放的时候,就找后面的一个位置,要是有人了就继续找后面的位置放。
#include <iostream>
using namespace std;
class HashTable {//构造hash列表
private:
int* data;
int size;
public:
HashTable(int n) {
size = n;
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = -1;//先都赋值为-1
}
}
int Hash(int n) {//定义哈希函数
return n % 11;
}
void insert(int n, int hash) {//线性探测再散列
hash %= size; //循环判断直到找到NULL
if (data[hash] == -1) {
data[hash] = n;
}
else {
if (data[hash + 1] == -1) {
data[hash + 1] = n;
}
else {
insert(n, hash + 1);
}
}
}
void search(int find) {
int cnt = 0, index = 0;
int hash = Hash(find);
while (1) {
hash %= size;
cnt++;
if (data[hash] == find) {
cout << "1 " << cnt << " " << hash + 1 << "\n";
return;
}
if (data[hash] == -1) {
cout << "0 " << cnt << "\n";
return;
}
hash++;
}
}
void print() {
for (int i = 0; i < size; i++) {
if (data[i] > 0) {
cout << data[i];
}
else {
cout << "NULL";
}
if (i!=size - 1)
{
cout << " ";
}
}
cout << endl;
}
~HashTable() {
delete[] data;
}
};
int main() {
int t;
cin >> t;
while (t--) {
int size, n;
cin >> size >> n;
HashTable h(size);
while (n--) {
int data;
cin >> data;
h.insert(data, h.Hash(data));
}
h.print();
int k;
cin >> k;
while (k--) {
int find;
cin >> find;
h.search(find);
}
}
return 0;
}
D. DS哈希查找—二次探测再散列
题目描述
定义哈希函数为H(key) = key%11。输入表长(大于、等于11),输入关键字集合,用二次探测再散列构建哈希表,并查找给定关键字。探测时如遇到已存在于哈希表的相同数字,则新数字不存入。
输入
测试次数t
每组测试数据格式如下:
哈希表长m、关键字个数n
n个关键字
查找次数k
k个待查关键字
输出
对每组测试数据,输出以下信息:
构造的哈希表信息,数组中没有关键字的位置输出NULL
对k个待查关键字,分别输出:
0或1(0—不成功,1—成功)、比较次数、查找成功的位置(从1开始)
什么是二次探测再散列?
举例如下:
如图 [7]的位置已经存放了16, 那么7 存放的位置 就在7+1², 7-1²,7+2²,7-2²,7+3²,7-3²找到NULL为止
因为7+1² = 8。[8] = NULL, 所以7 放在位置[8]
所以思路是:
取key的方式:先取余,若不冲突则直接存,若冲突则加上偏移量(1²,-1²,2²,-2²......),然后在长为m的hash表中循环滚动,最后确定key
key第一次取value%11
如果位置冲突,key取:value % 11 + 1²,如果key超过hash表的长度m,key取key-m,如果key的值为负,key取key+m
如果位置冲突,key取:value % 11 + (-1²),如果key超过hash表的长度m,key取key-m,如果key的值为负,key取key+m
如果位置冲突,key取:value % 11 + (2²),如果key超过hash表的长度m,key取key-m,如果key的值为负,key取key+m
如果位置冲突,key取:value % 11 + (-2²),如果key超过hash表的长度m,key取key-m,如果key的值为负,key取key+m
那么这样的话代码就不用大刀阔斧的改了
但是需要注意的是,有时候你偏移量会加的很大,然后就会数组越界了,这在我下面的代码中会讲解决方法,就是你用那个数字先减掉size先,就减少了越界的可能性
#include <iostream>
using namespace std;
#define INITIALIZE -1
class HashTable
{
private:
int size; // 表长
int* hashTable; // 哈希表,用于存值
public:
HashTable();
~HashTable();
int hash(int num);
void outPut();
void searchValue(int t);
};
HashTable::HashTable()
{
int n; // 关键字个数
cin >> size >> n;
hashTable = new int[size];
// 初始化哈希表
for (int i = 0; i < size; i++)
{
hashTable[i] = INITIALIZE; // 初始化哈希表,表示为空
}
// 构建哈希表
int val;
int key;
while (n--)
{
cin >> val;
key = hash(val); // 据哈希函数求键值
int index = key % size;
if (hashTable[index] == INITIALIZE || hashTable[index] == val)
{
hashTable[index] = val;
}
else
{
int oper = 0;
for (int i = 1; ; i++) // 二次探测
{
oper = i * i;
while (oper > size) //**注意;这里如果没有这样操作,在输入案例很多的情况下,会导致后面数组访问越界
{
oper -= size;
}
if (hashTable[(index + oper) % size] == INITIALIZE || hashTable[(index + oper) % size] == val) // **注意这里需要对重复值进行判断,即当表已经出现这个值的时候,不要再往下找了
{
hashTable[(index + oper) % size] = val;
break;
}
else
{
if ((index - oper) >= 0)
{
if (hashTable[index - oper] == INITIALIZE || hashTable[index - oper] == val)
{
hashTable[index - oper] = val;
break;
}
}
else
{
if (hashTable[size + (index - oper)] == INITIALIZE || hashTable[size + (index - oper)] == val)//
{
hashTable[size + (index - oper)] = val;
break;
}
}
}
}
}
}
}
HashTable::~HashTable()
{
delete[] hashTable;
}
// 哈希函数
int HashTable::hash(int num)
{
return num % 11;
}
void HashTable::outPut()
{
for (int i = 0; i < size; i++)
{
if (hashTable[i] != INITIALIZE)
{
cout << hashTable[i];
}
else
{
cout << "NULL";
}
// 用这种方式输出空格,可以避免输出最后一个值的时候多输出额外空格的问题发生
if (i != size - 1)
{
cout << ' ';
}
}
cout << endl;
}
void HashTable::searchValue(int t)
{
int searchTime; // 比较次数
int val; // 需要被查找的值
int key; // 值对应的键
int flag; // 标记是否成功查找:0 或 1(0—不成功,1—成功)
while (t--)
{
searchTime = 0;
flag = 0;
cin >> val;
key = hash(val); // 据哈希函数求键值
int index = key;
int symbol = 1; // 1 代表 “正号”,0代表“负号”
// 查找关键代码
int oper = 0;
for (int i = 1;;) // 反复二次探测查找
{
searchTime++;
if (hashTable[index] == val) // 查找成功
{
flag = 1;
cout << 1 << ' ' << searchTime << ' ' << (index)+1 << endl; // 查找成功的位置:从1开始,需要index + 1
break;
}
else if (hashTable[index] == INITIALIZE)
{
break;
}
oper = i * i;
while (oper > size)
{
oper -= size;
}
if (symbol)
{
symbol = 0; // 下次变成“负号”
index = (key + oper) % size;
}
else
{
symbol = 1;
index = key - oper;
if (index < 0)
{
index += size;
}
i++; // 注意i增加放置的位置
}
}
if (flag == 0)
{
// 不能将下面语句该输出放在①处;原因:如果hashTable是慢的,且没有需要查找的元素,
// 此时将输出放置①处会导致没有结果输出,所以将输出独立出来。
cout << 0 << ' ' << searchTime << endl;
}
}
}
int main()
{
int t;
cin >> t;
while (t--)
{
HashTable table;
table.outPut();
int times;
cin >> times;
table.searchValue(times);
}
return 0;
}
感谢感谢!
E. DS哈希查找--Trie树
题目描述
Trie树又称单词查找树,是一种树形结构,如下图所示。
它是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。
输入的一组单词,创建Trie树。输入字符串,计算以该字符串为公共前缀的单词数。
(提示:树结点有26个指针,指向单词的下一字母结点。兄弟节点按词典序排序。)
输入
测试数据有多组
每组测试数据格式为:
第一行:一行单词,单词全小写字母,且单词不会重复,单词的长度不超过10
第二行:测试公共前缀字符串数量t
后跟t行,每行一个字符串
abcd abd bcd efg hig
3
ab
bc
abcde
输出
每组测试数据输出格式为:
第一行:创建的Trie树的层次遍历结果
第2~t+1行:对每行字符串,输出树中以该字符串为公共前缀的单词数。
abehbcficddggd
2
1
0
具体思路就是结合字典树,深度优先搜索和广度优先搜索的知识进行解答,字典树的建立其实与二叉树的建立相似,只是左右孩子变成了26个孩子,对这棵字典树进行深度优先搜索就是每一个单词,而这道题的第一个要求其实就是进行一次广度优先搜索,然后寻找前缀其实就是对树进行深度优先搜索,搜索到这个前缀后,以前缀的最后一个字母为根节点,深度优先搜索有多少子树,也就是有多少单词以这个词组为前缀,最后实现代码如下:
gpt帮我注释了一下别人的代码,嘻嘻
#include<iostream> // 引入输入输出流库
#include<string> // 引入字符串操作库
#include<queue> // 引入队列库
using namespace std;
int sum; // 搜索结果计数器
class Trie { // 定义Trie树类
public:
bool flag = false; // 判断标志位,默认为false
Trie* next[26] = { nullptr }; // 子节点指针数组,用于指向下一层的节点
friend void BFS(); // 声明友元函数BFS,用于遍历Trie树
void insert(string str) { // 插入一个新的单词
Trie* node = this; // 定义节点指针node,初始化为this指针,即当前对象的指针
for (int i = 0; i < str.length(); i++) { // 遍历字符串中的每个字符
char c = str[i]; // 取出当前字符
if (node->next[c - 'a'] == nullptr) { // 如果该字符对应的子节点为空指针,则为其创建一个新的节点
node->next[c - 'a'] = new Trie();
}
node = node->next[c - 'a']; // 将节点指针指向下一个节点
}
node->flag = true; // 标记最终节点
}
int searchStart(string pre) { // 查找以给定前缀pre开头的单词数量
Trie* node = this; // 初始化节点指针
sum = 0; // 计数器清零
for (int i = 0; i < pre.length(); i++) { // 遍历前缀字符串中的每个字符
char c = pre[i]; // 取出当前字符
if (node->next[c - 'a'] == nullptr) return 0; // 如果存在字符不在Trie树中,则直接返回0
node = node->next[c - 'a']; // 将节点指针指向下一个节点
}
DFS(node); // 对以pre为前缀的单词子树进行深度优先遍历,统计数量
return sum; // 返回结果
}
void DFS(Trie* Node) { // 深度优先遍历
Trie* tr = new Trie(); // 定义新的Trie节点
tr = Node; // 初始化新节点
int temp = 0; // 标记是否存在子节点
for (int i = 0; i < 26; i++) { // 遍历当前节点的所有子节点
if (tr->next[i]) // 如果该子节点不为空
{
DFS(tr->next[i]); // 递归遍历该子节点的子树
temp = 1; // 存在子节点
}
}
if (temp == 0) { // 如果不存在子节点
sum++; // 计数器加一
return;
}
}
};
void BFS(Trie* Tree) { // 广度优先遍历Trie树
queue<Trie*>q; // 定义Trie节点指针队列
q.push(Tree); // 将根节点压入队列
while (!q.empty()) { // 当队列不为空时循环
Trie* t = q.front(); // 取出队首元素
q.pop(); // 弹出队首元素
for (int i = 0; i < 26; i++) { // 遍历该节点的所有子节点
if (t->next[i]) { // 如果该子节点不为空
cout << char('a' + i); // 输出该子节点对应的字符
q.push(t->next[i]); // 将该子节点压入队列
}
}
}
}
int main() { // 主函数
string arr; // 定义一个字符串变量arr,用于存储输入的单词
Trie* tree = new Trie(); // 动态创建一个Trie树对象,并将其地址存储在指针tree中
int t; // 定义一个整型变量t,用于存储后续查询的次数
while (1) { // 无限循环,直到读入到数字为止
cin >> arr; // 从标准输入流中读入一个字符串
if (arr[0] >= '0' && arr[0] <= '9') { // 如果该字符串的第一个字符是数字,说明后面的字符串是查询字符串
t = arr[0] - '0'; // 将数字字符转换为整型
break; // 跳出循环
}
tree->insert(arr); // 否则将该字符串插入Trie树中
}
BFS(tree); // 遍历Trie树并输出所有单词
cout << endl;
while (t--) { // 进行t次查询
string test;
cin >> test; // 读入查询字符串
cout << tree->searchStart(test) << endl; // 输出以该字符串为前缀的单词数量
}
}