C++实现hashtable(线性探测法)

2,540 阅读3分钟
#include<iostream>
using namespace std;


/*
一、散列表由来

散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一组扩展,由数组演化而来。

二、散列思想

我们用“键值”或者“关键字”来标识一个对象。把“键值”或者“关键字”转化为数组下标的映射方法就叫做“散列函数”,经过散列函数计算的值称为“散列值”。

三、散列函数设计的基本要求

①散列值为一个非负整数值,对应数组下标
②if key1=key2 then hash(key1)=hash(key2)
③if key1≠key2 then hash(key2)≠hash(key2)

如果无法满足③,就是发生了散列冲突(ps:实际上要找一个满足③的散列函数几乎是不可能的)。

四、解决散列冲突方法

1. 开放寻址法(opeing addressing)
①线性探测(Linear Probing):当我们往散列表中插入数据时,如果某个数据经过散列函数散列之后,
                              存储位置已经被占用,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。

ps:采用线性探测方法解决冲突时,删除操作不能单存地把删除地元素设置为空,否则查找操作将可能失败。

②二次探测(Quadratic probing)和双重散列(double hashing)

2. 链表法(chaining)


*/


class HashNode {
public:
	int value;
	int key;
	HashNode(int key, int value) : value(value), key(key) {}
};


class HashMap {
	HashNode** arr;
	int capacity;
	int size;
	HashNode* dummy;  // 虚拟节点代表被删除的节点

public:
	HashMap() {
		capacity = 20;
		size = 0;
		arr = new HashNode * [capacity];
		for (int i = 0; i < capacity; i++) {
			arr[i] = nullptr;
		}
		dummy = new HashNode(-1, -1);
	}

	~HashMap() {
		for (int i = 0; i < capacity; i++) {
			if (arr[i] != nullptr) {
				delete arr[i];
			}
		}
		delete[] arr;
	}

	int hashCode(int key) {
		return key % capacity;
	}

	void insertNode(int key, int value) {
		HashNode* temp = new HashNode(key, value);
		int hashIndex = hashCode(key);
		// 线性法解决碰撞
		while (arr[hashIndex] != nullptr && arr[hashIndex]->key != key && arr[hashIndex]->key != -1) {
			// 如果容量已满会造成死循环
			hashIndex++;
			hashIndex %= capacity;
		}

		// 找到了存有相同键值的元素,先将该元素delete
		if (arr[hashIndex] != nullptr) {
			delete arr[hashIndex];
			size--;
		}

		arr[hashIndex] = temp;
		size++;
	}

	
	int deleteNode(int key) {
		int hashIndex = hashCode(key);
		while (arr[hashIndex] != nullptr && arr[hashIndex]->key != key) {
			hashIndex++;
			hashIndex %= capacity;
		}

		if (arr[hashIndex] == nullptr) {
			return -1;
		}

		int temp = arr[hashIndex]->value;
		delete arr[hashIndex];
		arr[hashIndex] = dummy;
		size--;
		return temp;


		return -1;

	}

	int search(int key) {
		int hashIndex = hashCode(key);
		while (arr[hashIndex] != nullptr && arr[hashIndex]->key != key) {
			hashIndex++;
			hashIndex %= capacity;
		}
		if (arr[hashIndex] == nullptr) {
			return -1;
		}

		return arr[hashIndex]->value;
	}

	int sizeOfMap() {
		return size;
	}

	bool isEmpty() {
		return size == 0;
	}

	void display() {
		for (int i = 0; i < capacity; i++) {
			if (arr[i] != NULL) {
				cout << "key = " << arr[i]->key
					<< " value = " << arr[i]->value << endl;
			}
		}
	}
};

int main() {
	HashMap* h = new HashMap();
	cout << h->isEmpty() << endl;
	h->insertNode(1, 1);
	h->insertNode(2, 2);
	h->insertNode(2, 3);
	h->insertNode(5, 8);
	h->insertNode(6, 9);
	h->display();
	cout << h->isEmpty() << endl;
	cout << h->sizeOfMap() << endl;
	cout << h->search(2) << endl;
	h->display();
	cout << h->deleteNode(7) << endl;
	cout << h->deleteNode(5) << endl;
	h->display();
	delete h;
	return 0;
}

ps:本人C++小白,暂时无法实现泛型的hashtable,在这里使用int作为键和值,其次代码通过了基本的测试,但内存有无泄露也不太清楚,查阅网上的实现发现都有问题,但难以改正。

主要参考

Implementing own Hash Table with Open Addressing Linear Probing in C++ (感觉这个人写的代码,指针好像没有释放,也没有析构函数,感觉代码很多问题,不知道是我自己的理解问题还是确实有问题)

C++ Program to Implement Hash Tables(这位老哥写的还行,但也觉得有问题,特别是删除操作,他直接删除了没有使用标记,后面查找好像会出问题)。