数据结构实验15-哈希查找

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;
}

这里借鉴了(90条消息) DS哈希查找—二次探测再散列_编程小白小吴的博客-CSDN博客

感谢感谢!

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; // 输出以该字符串为前缀的单词数量
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值