706设计哈希映射

不使用任何内建的哈希表库设计一个哈希映射
具体地说,你的设计应该包含以下的功能
put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。
get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。
remove(key):如果映射中存在这个键,删除这个数值对。
示例:

MyHashMap hashMap = new MyHashMap();
hashMap.put(1, 1);
hashMap.put(2, 2);
hashMap.get(1); // 返回 1
hashMap.get(3); // 返回 -1 (未找到)
hashMap.put(2, 1); // 更新已有的值
hashMap.get(2); // 返回 1
hashMap.remove(2); // 删除键为2的数据
hashMap.get(2); // 返回 -1 (未找到)

注意:

所有的值都在 [0, 1000000]的范围内。
操作的总数目在[1, 10000]范围内。
不要使用内建的哈希库。

1、暴力求解法

#include<iostream>
#include<vector>
#include <time.h>
using namespace std;
class Hashmap1 {
public:
	Hashmap1() :data(vector<int>(N, -1))
	{}

	void put(int key, int value) {
		data[key] = value;
	}
	int get(int key) {
		if (data[key] != -1)//-1也就是这个键没有,初始的值就是-1
		{
			return data[key];
		}
		return -1;
	}

	void remove(int key) {
		data[key] = -1;
	}


private:
	static const int N = 1000001;
	vector<int> data;
};
int main()
{
	clock_t start = clock();
	Hashmap1 hash;
	hash.put(1, 10);
	hash.put(10000, 20);
	cout << hash.get(10000) << endl;
	hash.remove(10000);
	cout << hash.get(10000) << endl;
	clock_t ends = clock();
	cout << "Running Time : " << (double)(ends - start) / CLOCKS_PER_SEC << endl;//运行时间为0.193秒
	return 0;


}

分析一下:暴力求解法,我们的哈希函数就是数组存储位置下标=key,为了存储100000以内的key,我们创建了一个1000000长度的int数组,int数据范围足够存100000以内的数字。
符合题目要求。空间复杂度就是1000000,数组长度。时间复杂度就是o(1)

提交之后,发现空间消耗203MB,时间消耗193毫秒。

显然这是不够的,我们需要更加高效的解法。

2.二维数组,稀疏数组节省空间
先看代码:

#include<iostream>
#include<vector>
#include <time.h>
#include <cstring>

using namespace std;
class Hashmap1 {
public:
	Hashmap1()  
	{
		for(int i=1;i<1001;i++)
			for (int j = 1; j < 1001; j++)
			{
				data[i][j] = -1;
			}
	}
	auto getHashKey1(int key) {
		return key % N;
	}

	auto getHashKey2(int key) {
		return (key / N)+1;
	}

	void put(int key, int value) {
		auto hashKey1 = getHashKey1(key);
		auto hashKey2 = getHashKey2(key);
		data[hashKey2][hashKey1] = value;
	}
	int get(int key) {
		auto hashKey1 = getHashKey1(key);
		auto hashKey2 = getHashKey2(key);

		if (data[hashKey2][hashKey1] != -1)
		{
			return data[hashKey2][hashKey1];
		}
		return -1;
	}

	void remove(int key) {
		auto hashKey1 = getHashKey1(key);
		auto hashKey2 = getHashKey2(key);
		data[hashKey2][hashKey1] = -1;
	}


private:
	static const int N = 1001;
	int data[N][N];
};
int main()
{
	 
	Hashmap1* hash=new  Hashmap1();
	hash->put(1, 10);
	hash->put(1001, 20);
	cout << hash->get(1001) << endl;
	hash->remove(1001);
	cout << hash->get(1001) << endl;
	delete(hash);
	 
	return 0;


}

我们使用10011001的二维数组存储,注意的一点是,
如果我们用Hashmap1 hash来创建一个新对象,会出现警告,因为这个是从栈中分配空间,深度是有限的适用于存放简单的临时变量,一旦存储空间过长,就会出现溢出。
我们可以用Hashmap1
hash=new Hashmap1();new一个对象,这是从堆中分配空间的,重要的是我们要手动释放空间delete(hash),并且调用函数时要用指针引用->.
不过好像并没有节省空间,和我的预期有差距,在leetcode没有通过,应该是空间太大。想了一下,一开始就定义了1001*1001并且初始化了,而且判断位置时更复杂了,应该在Put时才把那一行初始化为1,这样的话,有很多行就不用初始化,节省空间。

3、 开放定址法:线性探测,如果有冲突,就找下一个位置存储。发现空间优化了,只用了53MB,但是用时336ms,比暴力法增加了近一倍。

class MyHashMap {
public:
    MyHashMap() {
        hashTable = vector<pair<int, int>>(N, {-1, -1});
    }
    
    int find(int key) {
        int k = key % N;
        while (hashTable[k].first != key && hashTable[k].first != -1) {
            k = (k + 1) % N;
        }

        return k;
    }

    void put(int key, int value) {
        auto k = find(key);
        hashTable[k] = {key, value};
    }
    
    int get(int key) {
        auto k = find(key);
        if (hashTable[k].first == -1) {
            return -1;
        }

        return hashTable[k].second;
    } 
    
    void remove(int key) {
        auto k = find(key);
        if (hashTable[k].first != -1) {
            hashTable[k].first = -2; // Mark as deleted (use a different value with -1)
        }
    }

private:
    const static int N = 20011;
    vector<pair<int, int>> hashTable;
};

4、拉链法

#include<iostream>
#include<vector>
#include <time.h>
#include <cstring>
using namespace std;
struct MyListNode {
	int key;
	int val;
	MyListNode* next;
	MyListNode() : key(-1), val(-1), next(nullptr) {}
	MyListNode(int _key, int _val) : key(_key), val(_val), next(nullptr) {}
};



class MyHashMap {
public:
	MyHashMap() {
		nums.resize(N, new MyListNode());//vv.resize(int n,element)表示调整容器vv的大小为1009,扩容后的每个元素的值为element,默认为0,这里是一个空的头结点
	}

	void put(int key, int value) {
		auto hashKey = getHashKey(key);
		auto& head = nums[hashKey];
		auto p = head;
		auto tail = p;
		while (p != nullptr) {
			if (p->key == key) {
				p->val = value;
				return;
			}
			tail = p;
			p = p->next;
		}

		tail->next = new MyListNode(key, value);
	}

	int getHashKey(int key) {
		return key % N;
	}

	int get(int key) {
		auto hashKey = getHashKey(key);
		auto& head = nums[hashKey];
		auto p = head;
		while (p != nullptr) {
			if (p->key == key) {
				return p->val;
			}
			p = p->next;
		}

		return -1;
	}

	void remove(int key) {
		auto hashKey = getHashKey(key);
		// Note: if use auto head will cause crash,
		// we want to set head to nullptr if last element deleted
		auto& head = nums[hashKey];
		MyListNode* prev = head;
		auto p = head->next;
		while (p != nullptr) {
			if (p->key == key) {
				prev->next = p->next;
				p->next = nullptr;
				delete p;
				return;
			}
			prev = p;
			p = p->next;
		}
	}

private:
	// The closest prime number around 1000 is 997 and 1009
	const static int N = 1009;
	vector<MyListNode*> nums;
};

这里面也是定义了一个vector数组,只不过数组的元素是链表。我们需要定义一个链表结构体,里面存放键、值、指向下一个节点的指针。
其时间和空间效率和开放定址法差不多,时间效率稍好。

个人觉得,在存储查找方面拉链法是有优势的。比如我们如果在位置1发生了哈希冲突,拉链法只需要增加一个节点,查找也只需要多查一次,而开放定址法在1发生冲突后,存储查找次数还要受到2,3,4,5等位置的影响,如果2,3,4,5都有元素,那么要一直到6才能存储并找到,效率低了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈希表的函数映射思想是通过一个哈希函数将关键码映射哈希表的地址上。哈希函数的设定很灵活,只要使任何关键码的哈希函数值都落在表长允许的范围之内即可。哈希函数的作用是将关键码转化为一个整数,然后根据这个整数来确定关键码在哈希表中的位置。哈希函数的设计要尽量减少冲突,即不同的关键码映射到同一个地址上的情况。冲突是不可避免的,所以哈希方法的目标是尽量减少冲突的发生。常见的解决冲突的方法有线性探测法和链地址法。线性探测法是当发生冲突时,去寻找下一个空的哈希地址,直到找到一个空的地址将数据元素存入。\[1\]\[3\]哈希表的函数映射思想在实际应用中被广泛使用,特别是在哈希表和分布式缓存等领域。\[2\] #### 引用[.reference_title] - *1* *3* [哈希表详解](https://blog.csdn.net/fengyuyeguirenenen/article/details/126363148)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [什么是哈希哈希表哈希函数,哈希碰撞?](https://blog.csdn.net/xiantianga6883/article/details/116997684)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值